@@ -21,41 +21,49 @@ import (
21
21
"crypto/tls"
22
22
"fmt"
23
23
"net/url"
24
+ "sort"
24
25
"strings"
25
26
26
27
"helm.sh/helm/v3/pkg/chart"
27
28
"helm.sh/helm/v3/pkg/getter"
28
29
"helm.sh/helm/v3/pkg/registry"
29
30
"helm.sh/helm/v3/pkg/repo"
30
31
32
+ "github.com/Masterminds/semver/v3"
33
+ "github.com/fluxcd/pkg/version"
31
34
"github.com/fluxcd/source-controller/internal/transport"
32
35
)
33
36
37
+ type RegistryClient interface {
38
+ Login (host string , opts ... registry.LoginOption ) error
39
+ Logout (host string , opts ... registry.LogoutOption ) error
40
+ Tags (url string ) ([]string , error )
41
+ }
42
+
34
43
// OCIChartRepository represents a Helm chart repository, and the configuration
35
44
// required to download the repository tags and charts from the repository.
36
45
// All methods are thread safe unless defined otherwise.
37
46
type OCIChartRepository struct {
38
- // URL the ChartRepository's index.yaml can be found at,
39
- // without the index.yaml suffix.
47
+ // URL is the location of the repository.
40
48
URL url.URL
41
- // Client to use while downloading the Index or a chart from the URL .
49
+ // Client to use while accessing the repository's contents .
42
50
Client getter.Getter
43
- // Options to configure the Client with while downloading the Index
51
+ // Options to configure the Client with while downloading tags
44
52
// or a chart from the URL.
45
53
Options []getter.Option
46
54
47
55
tlsConfig * tls.Config
48
56
49
57
// RegistryClient is a client to use while downloading tags or charts from a registry.
50
- RegistryClient * registry. Client
58
+ RegistryClient RegistryClient
51
59
}
52
60
53
61
// OCIChartRepositoryOption is a function that can be passed to NewOCIChartRepository
54
62
// to configure an OCIChartRepository.
55
63
type OCIChartRepositoryOption func (* OCIChartRepository ) error
56
64
57
65
// WithOCIRegistryClient returns a ChartRepositoryOption that will set the registry client
58
- func WithOCIRegistryClient (client * registry. Client ) OCIChartRepositoryOption {
66
+ func WithOCIRegistryClient (client RegistryClient ) OCIChartRepositoryOption {
59
67
return func (r * OCIChartRepository ) error {
60
68
r .RegistryClient = client
61
69
return nil
@@ -139,7 +147,7 @@ func (r *OCIChartRepository) Get(name, ver string) (*repo.ChartVersion, error) {
139
147
// If empty, try to get the highest available tag
140
148
// If exact version, try to find it
141
149
// If semver constraint string, try to find a match
142
- tag , err := registry . GetTagMatchingVersionOrConstraint (cvs , ver )
150
+ tag , err := getLastMatchingVersionOrConstraint (cvs , ver )
143
151
return & repo.ChartVersion {
144
152
URLs : []string {fmt .Sprintf ("%s/%s:%s" , r .URL .String (), name , tag )},
145
153
Metadata : & chart.Metadata {
@@ -174,6 +182,10 @@ func (r *OCIChartRepository) DownloadChart(chart *repo.ChartVersion) (*bytes.Buf
174
182
}
175
183
176
184
ref := chart .URLs [0 ]
185
+ if ! registry .IsOCI (ref ) {
186
+ return nil , fmt .Errorf ("chart '%s' is not an OCI chart" , chart .Name )
187
+ }
188
+
177
189
u , err := url .Parse (ref )
178
190
if err != nil {
179
191
err = fmt .Errorf ("invalid chart URL format '%s': %w" , ref , err )
@@ -187,3 +199,67 @@ func (r *OCIChartRepository) DownloadChart(chart *repo.ChartVersion) (*bytes.Buf
187
199
// trim the oci scheme prefix if needed
188
200
return r .Client .Get (strings .TrimPrefix (u .String (), fmt .Sprintf ("%s://" , registry .OCIScheme )), clientOpts ... )
189
201
}
202
+
203
+ // Login attempts to login to the OCI registry.
204
+ func (r * OCIChartRepository ) Login (opts ... registry.LoginOption ) error {
205
+ err := r .RegistryClient .Login (r .URL .Host , opts ... )
206
+ if err != nil {
207
+ return err
208
+ }
209
+ return nil
210
+ }
211
+
212
+ // Logout attempts to logout from the OCI registry.
213
+ func (r * OCIChartRepository ) Logout () error {
214
+ err := r .RegistryClient .Logout (r .URL .Host )
215
+ if err != nil {
216
+ return err
217
+ }
218
+ return nil
219
+ }
220
+
221
+ func getLastMatchingVersionOrConstraint (cvs []string , ver string ) (string , error ) {
222
+ // Check for exact matches first
223
+ if ver != "" {
224
+ for _ , cv := range cvs {
225
+ if ver == cv {
226
+ return cv , nil
227
+ }
228
+ }
229
+ }
230
+
231
+ // Continue to look for a (semantic) version match
232
+ verConstraint , err := semver .NewConstraint ("*" )
233
+ if err != nil {
234
+ return "" , err
235
+ }
236
+ latestStable := ver == "" || ver == "*"
237
+ if ! latestStable {
238
+ verConstraint , err = semver .NewConstraint (ver )
239
+ if err != nil {
240
+ return "" , err
241
+ }
242
+ }
243
+
244
+ matchingVersions := make ([]string , 0 , len (cvs ))
245
+ for _ , cv := range cvs {
246
+ v , err := version .ParseVersion (cv )
247
+ if err != nil {
248
+ continue
249
+ }
250
+
251
+ if ! verConstraint .Check (v ) {
252
+ continue
253
+ }
254
+
255
+ matchingVersions = append (matchingVersions , cv )
256
+ }
257
+ if len (matchingVersions ) == 0 {
258
+ return "" , fmt .Errorf ("could not locate a version matching provided version string %s" , ver )
259
+ }
260
+
261
+ // Sort versions
262
+ sort .Sort (sort .Reverse (sort .StringSlice (matchingVersions )))
263
+
264
+ return matchingVersions [0 ], nil
265
+ }
0 commit comments