Skip to content

Commit 5d616b6

Browse files
committed
Add OCI Helm Repositories section to the doc
This make sure to provide a new registryClient for every reconciliation. Various other comments addressed. Signed-off-by: Soule BA <soule@weave.works>
1 parent aacbe0f commit 5d616b6

9 files changed

+281
-107
lines changed

api/v1beta1/helmrepository_types.go

-11
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,6 @@ const (
3030
// HelmRepositoryURLIndexKey is the key to use for indexing HelmRepository
3131
// resources by their HelmRepositorySpec.URL.
3232
HelmRepositoryURLIndexKey = ".metadata.helmRepositoryURL"
33-
// HelmRepositoryTypeDefault is the default HelmRepository type.
34-
// It is used when no type is specified and corresponds to a Helm repository.
35-
HelmRepositoryTypeDefault = "default"
36-
// HelmRepositoryTypeOCI is the type for an OCI repository.
37-
HelmRepositoryTypeOCI = "oci"
3833
)
3934

4035
// HelmRepositorySpec defines the reference to a Helm repository.
@@ -77,12 +72,6 @@ type HelmRepositorySpec struct {
7772
// AccessFrom defines an Access Control List for allowing cross-namespace references to this object.
7873
// +optional
7974
AccessFrom *acl.AccessFrom `json:"accessFrom,omitempty"`
80-
81-
// Type of the HelmRepository.
82-
// When this field is set to "oci", the URL field value must be prefixed with "oci://".
83-
// +kubebuilder:validation:Enum=default;oci
84-
// +optional
85-
Type string `json:"type,omitempty"`
8675
}
8776

8877
// HelmRepositoryStatus defines the observed state of the HelmRepository.

api/v1beta2/condition_types.go

-4
Original file line numberDiff line numberDiff line change
@@ -39,10 +39,6 @@ const (
3939
// is enabled.
4040
SourceVerifiedCondition string = "SourceVerified"
4141

42-
//SourceValidCondition indicates the validity of the Source.
43-
// If True, the Source is valid. If False, it is not valid.
44-
SourceValidCondition string = "SourceValid"
45-
4642
// FetchFailedCondition indicates a transient or persistent fetch failure
4743
// of an upstream Source.
4844
// If True, observations on the upstream Source revision may be impossible,

config/crd/bases/source.toolkit.fluxcd.io_helmrepositories.yaml

-7
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,6 @@ spec:
109109
default: 60s
110110
description: The timeout of index downloading, defaults to 60s.
111111
type: string
112-
type:
113-
description: Type of the HelmRepository. When this field is set to "oci",
114-
the URL field value must be prefixed with "oci://".
115-
enum:
116-
- default
117-
- oci
118-
type: string
119112
url:
120113
description: The Helm repository URL, a valid URL contains at least
121114
a protocol and host.

controllers/helmchart_controller.go

+25-20
Original file line numberDiff line numberDiff line change
@@ -117,10 +117,10 @@ type HelmChartReconciler struct {
117117
kuberecorder.EventRecorder
118118
helper.Metrics
119119

120-
RegistryClient *registry.Client
121-
Storage *Storage
122-
Getters helmgetter.Providers
123-
ControllerName string
120+
RegistryClientGenerator RegistryClientGeneratorFunc
121+
Storage *Storage
122+
Getters helmgetter.Providers
123+
ControllerName string
124124

125125
Cache *cache.Cache
126126
TTL time.Duration
@@ -508,14 +508,31 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
508508

509509
// Initialize the chart repository
510510
var chartRepo chart.Remote
511-
if repo.Spec.Type == sourcev1.HelmRepositoryTypeOCI {
511+
switch repo.Spec.Type {
512+
case sourcev1.HelmRepositoryTypeOCI:
512513
if !registry.IsOCI(repo.Spec.URL) {
513514
err := fmt.Errorf("invalid OCI registry URL: %s", repo.Spec.URL)
514515
return chartRepoErrorReturn(err, obj)
515516
}
517+
518+
// with this function call, we create a temporary file to store the credentials if needed.
519+
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
520+
// TODO@souleb: remove this once the registry move to Oras v2
521+
// or rework to enable reusing credentials to avoid the unneccessary handshake operations
522+
registryClient, file, err := r.RegistryClientGenerator(logOpts != nil)
523+
if err != nil {
524+
return chartRepoErrorReturn(err, obj)
525+
}
526+
527+
defer func() {
528+
if file != "" {
529+
os.Remove(file)
530+
}
531+
}()
532+
516533
// Tell the chart repository to use the OCI client with the configured getter
517-
clientOpts = append(clientOpts, helmgetter.WithRegistryClient(r.RegistryClient))
518-
ociChartRepo, err := repository.NewOCIChartRepository(repo.Spec.URL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(clientOpts), repository.WithOCIRegistryClient(r.RegistryClient))
534+
clientOpts = append(clientOpts, helmgetter.WithRegistryClient(registryClient))
535+
ociChartRepo, err := repository.NewOCIChartRepository(repo.Spec.URL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(clientOpts), repository.WithOCIRegistryClient(registryClient))
519536
if err != nil {
520537
return chartRepoErrorReturn(err, obj)
521538
}
@@ -524,24 +541,12 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
524541
// If login options are configured, use them to login to the registry
525542
// The OCIGetter will later retrieve the stored credentials to pull the chart
526543
if logOpts != nil {
527-
// create a temporary file to store the credentials
528-
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
529-
// TODO@souleb: remove this once the registry move to Oras v2
530-
// or rework to enable reusing credentials to avoid the unneccessary handshake operations
531-
credentialFile, err := os.CreateTemp("", "credentials")
532-
if err != nil {
533-
return chartRepoErrorReturn(err, obj)
534-
}
535-
defer os.Remove(credentialFile.Name())
536-
537-
// set the credentials file to the registry client
538-
registry.ClientOptCredentialsFile(credentialFile.Name())(r.RegistryClient)
539544
err = ociChartRepo.Login(logOpts...)
540545
if err != nil {
541546
return chartRepoErrorReturn(err, obj)
542547
}
543548
}
544-
} else {
549+
default:
545550
var httpChartRepo *repository.ChartRepository
546551
httpChartRepo, err := repository.NewChartRepository(repo.Spec.URL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, tlsConfig, clientOpts,
547552
repository.WithMemoryCache(r.Storage.LocalPath(*repo.GetArtifact()), r.Cache, r.TTL, func(event string) {

controllers/helmchart_controller_test.go

+9-5
Original file line numberDiff line numberDiff line change
@@ -942,12 +942,16 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
942942
clientBuilder.WithObjects(tt.secret.DeepCopy())
943943
}
944944

945+
registryClientGenerator := func(_ bool) (*registry.Client, string, error) {
946+
return testRegistryClient, "", nil
947+
}
948+
945949
r := &HelmChartReconciler{
946-
Client: clientBuilder.Build(),
947-
EventRecorder: record.NewFakeRecorder(32),
948-
Getters: testGetters,
949-
Storage: storage,
950-
RegistryClient: testRegistryClient,
950+
Client: clientBuilder.Build(),
951+
EventRecorder: record.NewFakeRecorder(32),
952+
Getters: testGetters,
953+
Storage: storage,
954+
RegistryClientGenerator: registryClientGenerator,
951955
}
952956

953957
repository := &sourcev1.HelmRepository{

controllers/helmrepository_controller_oci.go

+37-32
Original file line numberDiff line numberDiff line change
@@ -49,14 +49,12 @@ var helmRepositoryOCIReadyCondition = summarize.Conditions{
4949
Target: meta.ReadyCondition,
5050
Owned: []string{
5151
sourcev1.FetchFailedCondition,
52-
sourcev1.SourceValidCondition,
5352
meta.ReadyCondition,
5453
meta.ReconcilingCondition,
5554
meta.StalledCondition,
5655
},
5756
Summarize: []string{
5857
sourcev1.FetchFailedCondition,
59-
sourcev1.SourceValidCondition,
6058
meta.StalledCondition,
6159
meta.ReconcilingCondition,
6260
},
@@ -83,11 +81,17 @@ type HelmRepositoryOCIReconciler struct {
8381
client.Client
8482
kuberecorder.EventRecorder
8583
helper.Metrics
86-
Getters helmgetter.Providers
87-
ControllerName string
88-
RegistryClient *registry.Client
84+
Getters helmgetter.Providers
85+
ControllerName string
86+
RegistryClientGenerator RegistryClientGeneratorFunc
8987
}
9088

89+
// RegistryClientGeneratorFunc is a function that returns a registry client
90+
// and an optional file name.
91+
// The file is used to store the registry client credentials.
92+
// The caller is responsible for deleting the file.
93+
type RegistryClientGeneratorFunc func(isLogin bool) (*registry.Client, string, error)
94+
9195
// helmRepositoryOCIReconcileFunc is the function type for all the
9296
// v1beta2.HelmRepository (sub)reconcile functions for OCI type. The type implementations
9397
// are grouped and executed serially to perform the complete reconcile of the
@@ -283,55 +287,56 @@ func (r *HelmRepositoryOCIReconciler) reconcileSource(ctx context.Context, obj *
283287

284288
// validateSource the HelmRepository object by checking the url and connecting to the underlying registry
285289
// with he provided credentials.
286-
func (r *HelmRepositoryOCIReconciler) validateSource(ctx context.Context, obj *sourcev1.HelmRepository, loginOpts ...registry.LoginOption) (sreconcile.Result, error) {
287-
chartRepo, err := repository.NewOCIChartRepository(obj.Spec.URL, repository.WithOCIRegistryClient(r.RegistryClient))
290+
func (r *HelmRepositoryOCIReconciler) validateSource(ctx context.Context, obj *sourcev1.HelmRepository, logOpts ...registry.LoginOption) (sreconcile.Result, error) {
291+
registryClient, file, err := r.RegistryClientGenerator(logOpts != nil)
292+
if err != nil {
293+
e := &serror.Stalling{
294+
Err: fmt.Errorf("failed to create registry client:: %w", err),
295+
Reason: meta.FailedReason,
296+
}
297+
conditions.MarkFalse(obj, meta.ReadyCondition, e.Reason, e.Err.Error())
298+
return sreconcile.ResultEmpty, e
299+
}
300+
301+
defer func() {
302+
if file != "" {
303+
os.Remove(file)
304+
}
305+
}()
306+
307+
chartRepo, err := repository.NewOCIChartRepository(obj.Spec.URL, repository.WithOCIRegistryClient(registryClient))
288308
if err != nil {
289309
if strings.Contains(err.Error(), "parse") {
290-
e := &serror.Event{
310+
e := &serror.Stalling{
291311
Err: fmt.Errorf("failed to parse URL '%s': %w", obj.Spec.URL, err),
292-
Reason: "ValidationError",
312+
Reason: sourcev1.URLInvalidReason,
293313
}
294-
conditions.MarkFalse(obj, sourcev1.SourceValidCondition, e.Reason, e.Err.Error())
314+
conditions.MarkFalse(obj, meta.ReadyCondition, e.Reason, e.Err.Error())
295315
return sreconcile.ResultEmpty, e
296316
} else if strings.Contains(err.Error(), "the url scheme is not supported") {
297317
e := &serror.Event{
298318
Err: err,
299-
Reason: "ValidationError",
319+
Reason: sourcev1.URLInvalidReason,
300320
}
301-
conditions.MarkFalse(obj, sourcev1.SourceValidCondition, e.Reason, e.Err.Error())
321+
conditions.MarkFalse(obj, meta.ReadyCondition, e.Reason, e.Err.Error())
302322
return sreconcile.ResultEmpty, e
303323
}
304324
}
305325

306326
// Attempt to login to the registry if credentials are provided.
307-
if loginOpts != nil {
308-
// create a temporary file to store the credentials
309-
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
310-
credentialFile, err := os.CreateTemp("", "credentials")
327+
if logOpts != nil {
328+
err = chartRepo.Login(logOpts...)
311329
if err != nil {
312330
e := &serror.Event{
313331
Err: fmt.Errorf("failed to create temporary file: %w", err),
314-
Reason: "ValidationError",
315-
}
316-
conditions.MarkFalse(obj, sourcev1.SourceValidCondition, e.Reason, e.Err.Error())
317-
return sreconcile.ResultEmpty, e
318-
}
319-
defer os.Remove(credentialFile.Name())
320-
321-
// set the credentials file to the registry client
322-
registry.ClientOptCredentialsFile(credentialFile.Name())(r.RegistryClient)
323-
err = chartRepo.Login(loginOpts...)
324-
if err != nil {
325-
e := &serror.Event{
326-
Err: fmt.Errorf("failed to login to registry: %w", err),
327-
Reason: "ValidationError",
332+
Reason: meta.FailedReason,
328333
}
329-
conditions.MarkFalse(obj, sourcev1.SourceValidCondition, e.Reason, e.Err.Error())
334+
conditions.MarkFalse(obj, meta.ReadyCondition, e.Reason, e.Err.Error())
330335
return sreconcile.ResultEmpty, e
331336
}
332337
}
333338

334-
conditions.MarkTrue(obj, sourcev1.SourceValidCondition, meta.SucceededReason, "Helm repository %q is valid", obj.Name)
339+
conditions.MarkTrue(obj, meta.ReadyCondition, meta.SucceededReason, "Helm repository %q is valid", obj.Name)
335340

336341
return sreconcile.ResultSuccess, nil
337342
}

controllers/suite_test.go

+8-5
Original file line numberDiff line numberDiff line change
@@ -233,12 +233,15 @@ func TestMain(m *testing.M) {
233233
panic(fmt.Sprintf("Failed to start HelmRepositoryReconciler: %v", err))
234234
}
235235

236+
registryClientGenerator := func(_ bool) (*registry.Client, string, error) {
237+
return testRegistryClient, "", nil
238+
}
236239
if err = (&HelmRepositoryOCIReconciler{
237-
Client: testEnv,
238-
EventRecorder: record.NewFakeRecorder(32),
239-
Metrics: testMetricsH,
240-
Getters: testGetters,
241-
RegistryClient: testRegistryClient,
240+
Client: testEnv,
241+
EventRecorder: record.NewFakeRecorder(32),
242+
Metrics: testMetricsH,
243+
Getters: testGetters,
244+
RegistryClientGenerator: registryClientGenerator,
242245
}).SetupWithManager(testEnv); err != nil {
243246
panic(fmt.Sprintf("Failed to start HelmRepositoryOCIReconciler: %v", err))
244247
}

0 commit comments

Comments
 (0)