@@ -35,6 +35,7 @@ import (
35
35
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
36
36
"k8s.io/apimachinery/pkg/runtime"
37
37
"k8s.io/apimachinery/pkg/types"
38
+ kerrors "k8s.io/apimachinery/pkg/util/errors"
38
39
"k8s.io/apimachinery/pkg/util/uuid"
39
40
kuberecorder "k8s.io/client-go/tools/record"
40
41
ctrl "sigs.k8s.io/controller-runtime"
@@ -460,9 +461,10 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
460
461
loginOpts []helmreg.LoginOption
461
462
)
462
463
464
+ normalizedURL := repository .NormalizeURL (repo .Spec .URL )
463
465
// Construct the Getter options from the HelmRepository data
464
466
clientOpts := []helmgetter.Option {
465
- helmgetter .WithURL (repo . Spec . URL ),
467
+ helmgetter .WithURL (normalizedURL ),
466
468
helmgetter .WithTimeout (repo .Spec .Timeout .Duration ),
467
469
helmgetter .WithPassCredentialsAll (repo .Spec .PassCredentials ),
468
470
}
@@ -490,7 +492,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
490
492
}
491
493
clientOpts = append (clientOpts , opts ... )
492
494
493
- tlsConfig , err = getter .TLSClientConfigFromSecret (* secret , repo . Spec . URL )
495
+ tlsConfig , err = getter .TLSClientConfigFromSecret (* secret , normalizedURL )
494
496
if err != nil {
495
497
e := & serror.Event {
496
498
Err : fmt .Errorf ("failed to create TLS client config with secret data: %w" , err ),
@@ -502,7 +504,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
502
504
}
503
505
504
506
// Build registryClient options from secret
505
- loginOpt , err := registry .LoginOptionFromSecret (repo . Spec . URL , * secret )
507
+ loginOpt , err := registry .LoginOptionFromSecret (normalizedURL , * secret )
506
508
if err != nil {
507
509
e := & serror.Event {
508
510
Err : fmt .Errorf ("failed to configure Helm client with secret data: %w" , err ),
@@ -517,19 +519,19 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
517
519
}
518
520
519
521
// Initialize the chart repository
520
- var chartRepo chart. Repository
522
+ var chartRepo repository. Downloader
521
523
switch repo .Spec .Type {
522
524
case sourcev1 .HelmRepositoryTypeOCI :
523
- if ! helmreg .IsOCI (repo . Spec . URL ) {
524
- err := fmt .Errorf ("invalid OCI registry URL: %s" , repo . Spec . URL )
525
+ if ! helmreg .IsOCI (normalizedURL ) {
526
+ err := fmt .Errorf ("invalid OCI registry URL: %s" , normalizedURL )
525
527
return chartRepoConfigErrorReturn (err , obj )
526
528
}
527
529
528
530
// with this function call, we create a temporary file to store the credentials if needed.
529
531
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
530
532
// TODO@souleb: remove this once the registry move to Oras v2
531
533
// or rework to enable reusing credentials to avoid the unneccessary handshake operations
532
- registryClient , file , err := r .RegistryClientGenerator (loginOpts != nil )
534
+ registryClient , credentialsFile , err := r .RegistryClientGenerator (loginOpts != nil )
533
535
if err != nil {
534
536
e := & serror.Event {
535
537
Err : fmt .Errorf ("failed to construct Helm client: %w" , err ),
@@ -539,9 +541,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
539
541
return sreconcile .ResultEmpty , e
540
542
}
541
543
542
- if file != "" {
544
+ if credentialsFile != "" {
543
545
defer func () {
544
- if err := os .Remove (file ); err != nil {
546
+ if err := os .Remove (credentialsFile ); err != nil {
545
547
r .eventLogf (ctx , obj , corev1 .EventTypeWarning , meta .FailedReason ,
546
548
"failed to delete temporary credentials file: %s" , err )
547
549
}
@@ -550,7 +552,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
550
552
551
553
// Tell the chart repository to use the OCI client with the configured getter
552
554
clientOpts = append (clientOpts , helmgetter .WithRegistryClient (registryClient ))
553
- ociChartRepo , err := repository .NewOCIChartRepository (repo . Spec . URL , repository .WithOCIGetter (r .Getters ), repository .WithOCIGetterOptions (clientOpts ), repository .WithOCIRegistryClient (registryClient ))
555
+ ociChartRepo , err := repository .NewOCIChartRepository (normalizedURL , repository .WithOCIGetter (r .Getters ), repository .WithOCIGetterOptions (clientOpts ), repository .WithOCIRegistryClient (registryClient ))
554
556
if err != nil {
555
557
return chartRepoConfigErrorReturn (err , obj )
556
558
}
@@ -570,7 +572,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
570
572
}
571
573
}
572
574
default :
573
- httpChartRepo , err := repository .NewChartRepository (repo . Spec . URL , r .Storage .LocalPath (* repo .GetArtifact ()), r .Getters , tlsConfig , clientOpts ,
575
+ httpChartRepo , err := repository .NewChartRepository (normalizedURL , r .Storage .LocalPath (* repo .GetArtifact ()), r .Getters , tlsConfig , clientOpts ,
574
576
repository .WithMemoryCache (r .Storage .LocalPath (* repo .GetArtifact ()), r .Cache , r .TTL , func (event string ) {
575
577
r .IncCacheEvents (event , obj .Name , obj .Namespace )
576
578
}))
@@ -683,9 +685,15 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
683
685
684
686
// Setup dependency manager
685
687
dm := chart .NewDependencyManager (
686
- chart .WithRepositoryCallback (r .namespacedChartRepositoryCallback (ctx , obj .GetName (), obj .GetNamespace ())),
688
+ chart .WithDownloaderCallback (r .namespacedChartRepositoryCallback (ctx , obj .GetName (), obj .GetNamespace ())),
687
689
)
688
- defer dm .Clear ()
690
+ defer func () {
691
+ err := dm .Clear ()
692
+ if err != nil {
693
+ r .eventLogf (ctx , obj , corev1 .EventTypeWarning , meta .FailedReason ,
694
+ "dependency manager cleanup error: %s" , err )
695
+ }
696
+ }()
689
697
690
698
// Configure builder options, including any previously cached chart
691
699
opts := chart.BuildOptions {
@@ -912,12 +920,17 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
912
920
return nil
913
921
}
914
922
915
- // namespacedChartRepositoryCallback returns a chart.GetChartRepositoryCallback scoped to the given namespace.
916
- // The returned callback returns a repository.ChartRepository configured with the retrieved v1beta1.HelmRepository,
923
+ // namespacedChartRepositoryCallback returns a chart.GetChartDownloaderCallback scoped to the given namespace.
924
+ // The returned callback returns a repository.Downloader configured with the retrieved v1beta1.HelmRepository,
917
925
// or a shim with defaults if no object could be found.
918
- func (r * HelmChartReconciler ) namespacedChartRepositoryCallback (ctx context.Context , name , namespace string ) chart.GetChartRepositoryCallback {
919
- return func (url string ) (* repository.ChartRepository , error ) {
920
- var tlsConfig * tls.Config
926
+ // The callback returns an object with a state, so the caller has to do the necessary cleanup.
927
+ func (r * HelmChartReconciler ) namespacedChartRepositoryCallback (ctx context.Context , name , namespace string ) chart.GetChartDownloaderCallback {
928
+ return func (url string ) (repository.Downloader , error ) {
929
+ var (
930
+ tlsConfig * tls.Config
931
+ loginOpts []helmreg.LoginOption
932
+ )
933
+ normalizedURL := repository .NormalizeURL (url )
921
934
repo , err := r .resolveDependencyRepository (ctx , url , namespace )
922
935
if err != nil {
923
936
// Return Kubernetes client errors, but ignore others
@@ -932,7 +945,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
932
945
}
933
946
}
934
947
clientOpts := []helmgetter.Option {
935
- helmgetter .WithURL (repo . Spec . URL ),
948
+ helmgetter .WithURL (normalizedURL ),
936
949
helmgetter .WithTimeout (repo .Spec .Timeout .Duration ),
937
950
helmgetter .WithPassCredentialsAll (repo .Spec .PassCredentials ),
938
951
}
@@ -946,26 +959,77 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
946
959
}
947
960
clientOpts = append (clientOpts , opts ... )
948
961
949
- tlsConfig , err = getter .TLSClientConfigFromSecret (* secret , repo . Spec . URL )
962
+ tlsConfig , err = getter .TLSClientConfigFromSecret (* secret , normalizedURL )
950
963
if err != nil {
951
964
return nil , fmt .Errorf ("failed to create TLS client config for HelmRepository '%s': %w" , repo .Name , err )
952
965
}
953
- }
954
966
955
- chartRepo , err := repository .NewChartRepository (repo .Spec .URL , "" , r .Getters , tlsConfig , clientOpts )
956
- if err != nil {
957
- return nil , err
967
+ // Build registryClient options from secret
968
+ loginOpt , err := registry .LoginOptionFromSecret (normalizedURL , * secret )
969
+ if err != nil {
970
+ return nil , fmt .Errorf ("failed to create login options for HelmRepository '%s': %w" , repo .Name , err )
971
+ }
972
+
973
+ loginOpts = append ([]helmreg.LoginOption {}, loginOpt )
958
974
}
959
975
960
- // Ensure that the cache key is the same as the artifact path
961
- // otherwise don't enable caching. We don't want to cache indexes
962
- // for repositories that are not reconciled by the source controller.
963
- if repo .Status .Artifact != nil {
964
- chartRepo .CachePath = r .Storage .LocalPath (* repo .GetArtifact ())
965
- chartRepo .SetMemCache (r .Storage .LocalPath (* repo .GetArtifact ()), r .Cache , r .TTL , func (event string ) {
966
- r .IncCacheEvents (event , name , namespace )
967
- })
976
+ var chartRepo repository.Downloader
977
+ if helmreg .IsOCI (normalizedURL ) {
978
+ registryClient , credentialsFile , err := r .RegistryClientGenerator (loginOpts != nil )
979
+ if err != nil {
980
+ return nil , fmt .Errorf ("failed to create registry client for HelmRepository '%s': %w" , repo .Name , err )
981
+ }
982
+
983
+ var errs []error
984
+ // Tell the chart repository to use the OCI client with the configured getter
985
+ clientOpts = append (clientOpts , helmgetter .WithRegistryClient (registryClient ))
986
+ ociChartRepo , err := repository .NewOCIChartRepository (normalizedURL , repository .WithOCIGetter (r .Getters ),
987
+ repository .WithOCIGetterOptions (clientOpts ),
988
+ repository .WithOCIRegistryClient (registryClient ),
989
+ repository .WithCredentialsFile (credentialsFile ))
990
+ if err != nil {
991
+ errs = append (errs , fmt .Errorf ("failed to create OCI chart repository for HelmRepository '%s': %w" , repo .Name , err ))
992
+ // clean up the credentialsFile
993
+ if credentialsFile != "" {
994
+ if err := os .Remove (credentialsFile ); err != nil {
995
+ errs = append (errs , err )
996
+ }
997
+ }
998
+ return nil , kerrors .NewAggregate (errs )
999
+ }
1000
+
1001
+ // If login options are configured, use them to login to the registry
1002
+ // The OCIGetter will later retrieve the stored credentials to pull the chart
1003
+ if loginOpts != nil {
1004
+ err = ociChartRepo .Login (loginOpts ... )
1005
+ if err != nil {
1006
+ errs = append (errs , fmt .Errorf ("failed to login to OCI chart repository for HelmRepository '%s': %w" , repo .Name , err ))
1007
+ // clean up the credentialsFile
1008
+ errs = append (errs , ociChartRepo .Clear ())
1009
+ return nil , kerrors .NewAggregate (errs )
1010
+ }
1011
+ }
1012
+
1013
+ chartRepo = ociChartRepo
1014
+ } else {
1015
+ httpChartRepo , err := repository .NewChartRepository (normalizedURL , "" , r .Getters , tlsConfig , clientOpts )
1016
+ if err != nil {
1017
+ return nil , err
1018
+ }
1019
+
1020
+ // Ensure that the cache key is the same as the artifact path
1021
+ // otherwise don't enable caching. We don't want to cache indexes
1022
+ // for repositories that are not reconciled by the source controller.
1023
+ if repo .Status .Artifact != nil {
1024
+ httpChartRepo .CachePath = r .Storage .LocalPath (* repo .GetArtifact ())
1025
+ httpChartRepo .SetMemCache (r .Storage .LocalPath (* repo .GetArtifact ()), r .Cache , r .TTL , func (event string ) {
1026
+ r .IncCacheEvents (event , name , namespace )
1027
+ })
1028
+ }
1029
+
1030
+ chartRepo = httpChartRepo
968
1031
}
1032
+
969
1033
return chartRepo , nil
970
1034
}
971
1035
}
0 commit comments