1
16
17 package registry
18
19 import (
20 "bytes"
21 "context"
22 "fmt"
23 "io"
24 "net/http"
25 "strings"
26 "time"
27
28 helmtime "helm.sh/helm/v3/pkg/time"
29
30 "github.com/Masterminds/semver/v3"
31 ocispec "github.com/opencontainers/image-spec/specs-go/v1"
32 "github.com/pkg/errors"
33 "github.com/sirupsen/logrus"
34 orascontext "oras.land/oras-go/pkg/context"
35 "oras.land/oras-go/pkg/registry"
36
37 "helm.sh/helm/v3/internal/tlsutil"
38 "helm.sh/helm/v3/pkg/chart"
39 "helm.sh/helm/v3/pkg/chart/loader"
40 )
41
42 var immutableOciAnnotations = []string{
43 ocispec.AnnotationVersion,
44 ocispec.AnnotationTitle,
45 }
46
47
48 func IsOCI(url string) bool {
49 return strings.HasPrefix(url, fmt.Sprintf("%s://", OCIScheme))
50 }
51
52
53 func ContainsTag(tags []string, tag string) bool {
54 for _, t := range tags {
55 if tag == t {
56 return true
57 }
58 }
59 return false
60 }
61
62 func GetTagMatchingVersionOrConstraint(tags []string, versionString string) (string, error) {
63 var constraint *semver.Constraints
64 if versionString == "" {
65
66 constraint, _ = semver.NewConstraint("*")
67 } else {
68
69
70 for _, v := range tags {
71 if versionString == v {
72 return v, nil
73 }
74 }
75
76
77 var err error
78 constraint, err = semver.NewConstraint(versionString)
79 if err != nil {
80 return "", err
81 }
82 }
83
84
85
86 for _, v := range tags {
87 test, err := semver.NewVersion(v)
88 if err != nil {
89 continue
90 }
91 if constraint.Check(test) {
92 return v, nil
93 }
94 }
95
96 return "", errors.Errorf("Could not locate a version matching provided version string %s", versionString)
97 }
98
99
100 func extractChartMeta(chartData []byte) (*chart.Metadata, error) {
101 ch, err := loader.LoadArchive(bytes.NewReader(chartData))
102 if err != nil {
103 return nil, err
104 }
105 return ch.Metadata, nil
106 }
107
108
109
110 func ctx(out io.Writer, debug bool) context.Context {
111 if !debug {
112 return orascontext.Background()
113 }
114 ctx := orascontext.WithLoggerFromWriter(context.Background(), out)
115 orascontext.GetLogger(ctx).Logger.SetLevel(logrus.DebugLevel)
116 return ctx
117 }
118
119
120
121
122
123 func parseReference(raw string) (registry.Reference, error) {
124
125
126
127
128
129
130 parts := strings.Split(raw, ":")
131 if len(parts) > 1 && !strings.Contains(parts[len(parts)-1], "/") {
132 tag := parts[len(parts)-1]
133
134 if tag != "" {
135
136 newTag := strings.ReplaceAll(tag, "+", "_")
137 raw = strings.ReplaceAll(raw, tag, newTag)
138 }
139 }
140
141 return registry.ParseReference(raw)
142 }
143
144
145 func NewRegistryClientWithTLS(out io.Writer, certFile, keyFile, caFile string, insecureSkipTLSverify bool, registryConfig string, debug bool) (*Client, error) {
146 tlsConf, err := tlsutil.NewClientTLS(certFile, keyFile, caFile, insecureSkipTLSverify)
147 if err != nil {
148 return nil, fmt.Errorf("can't create TLS config for client: %s", err)
149 }
150
151 registryClient, err := NewClient(
152 ClientOptDebug(debug),
153 ClientOptEnableCache(true),
154 ClientOptWriter(out),
155 ClientOptCredentialsFile(registryConfig),
156 ClientOptHTTPClient(&http.Client{
157 Transport: &http.Transport{
158 TLSClientConfig: tlsConf,
159 },
160 }),
161 )
162 if err != nil {
163 return nil, err
164 }
165 return registryClient, nil
166 }
167
168
169 func generateOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string {
170
171
172 ociAnnotations := generateChartOCIAnnotations(meta, creationTime)
173
174
175 annotations:
176 for chartAnnotationKey, chartAnnotationValue := range meta.Annotations {
177
178
179 for _, immutableOciKey := range immutableOciAnnotations {
180 if immutableOciKey == chartAnnotationKey {
181 continue annotations
182 }
183 }
184
185
186 ociAnnotations[chartAnnotationKey] = chartAnnotationValue
187 }
188
189 return ociAnnotations
190 }
191
192
193 func generateChartOCIAnnotations(meta *chart.Metadata, creationTime string) map[string]string {
194 chartOCIAnnotations := map[string]string{}
195
196 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationDescription, meta.Description)
197 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationTitle, meta.Name)
198 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationVersion, meta.Version)
199 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationURL, meta.Home)
200
201 if len(creationTime) == 0 {
202 creationTime = helmtime.Now().UTC().Format(time.RFC3339)
203 }
204
205 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationCreated, creationTime)
206
207 if len(meta.Sources) > 0 {
208 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationSource, meta.Sources[0])
209 }
210
211 if meta.Maintainers != nil && len(meta.Maintainers) > 0 {
212 var maintainerSb strings.Builder
213
214 for maintainerIdx, maintainer := range meta.Maintainers {
215
216 if len(maintainer.Name) > 0 {
217 maintainerSb.WriteString(maintainer.Name)
218 }
219
220 if len(maintainer.Email) > 0 {
221 maintainerSb.WriteString(" (")
222 maintainerSb.WriteString(maintainer.Email)
223 maintainerSb.WriteString(")")
224 }
225
226 if maintainerIdx < len(meta.Maintainers)-1 {
227 maintainerSb.WriteString(", ")
228 }
229
230 }
231
232 chartOCIAnnotations = addToMap(chartOCIAnnotations, ocispec.AnnotationAuthors, maintainerSb.String())
233
234 }
235
236 return chartOCIAnnotations
237 }
238
239
240 func addToMap(inputMap map[string]string, newKey string, newValue string) map[string]string {
241
242
243 if len(strings.TrimSpace(newValue)) > 0 {
244 inputMap[newKey] = newValue
245 }
246
247 return inputMap
248
249 }
250
View as plain text