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