@@ -21,21 +21,19 @@ import (
21
21
"crypto/tls"
22
22
"errors"
23
23
"fmt"
24
- "github.com/fluxcd/pkg/git"
25
- "github.com/opencontainers/go-digest"
26
24
"net/url"
27
25
"os"
28
26
"path/filepath"
29
27
"strconv"
30
28
"strings"
31
29
"time"
32
30
33
- eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
34
- soci "github.com/fluxcd/source-controller/internal/oci"
35
31
"github.com/google/go-containerregistry/pkg/authn"
36
32
"github.com/google/go-containerregistry/pkg/v1/remote"
33
+ "github.com/opencontainers/go-digest"
37
34
helmgetter "helm.sh/helm/v3/pkg/getter"
38
35
helmreg "helm.sh/helm/v3/pkg/registry"
36
+ helmrepo "helm.sh/helm/v3/pkg/repo"
39
37
corev1 "k8s.io/api/core/v1"
40
38
apierrs "k8s.io/apimachinery/pkg/api/errors"
41
39
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -54,7 +52,9 @@ import (
54
52
"sigs.k8s.io/controller-runtime/pkg/reconcile"
55
53
"sigs.k8s.io/controller-runtime/pkg/source"
56
54
55
+ eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
57
56
"github.com/fluxcd/pkg/apis/meta"
57
+ "github.com/fluxcd/pkg/git"
58
58
"github.com/fluxcd/pkg/oci"
59
59
"github.com/fluxcd/pkg/runtime/conditions"
60
60
helper "github.com/fluxcd/pkg/runtime/controller"
@@ -70,6 +70,7 @@ import (
70
70
"github.com/fluxcd/source-controller/internal/helm/getter"
71
71
"github.com/fluxcd/source-controller/internal/helm/registry"
72
72
"github.com/fluxcd/source-controller/internal/helm/repository"
73
+ soci "github.com/fluxcd/source-controller/internal/oci"
73
74
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
74
75
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
75
76
"github.com/fluxcd/source-controller/internal/util"
@@ -527,7 +528,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
527
528
}
528
529
529
530
// Build client options from secret
530
- opts , tls , err := r .clientOptionsFromSecret (secret , normalizedURL )
531
+ opts , tlsCfg , err := r .clientOptionsFromSecret (secret , normalizedURL )
531
532
if err != nil {
532
533
e := & serror.Event {
533
534
Err : err ,
@@ -538,7 +539,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
538
539
return sreconcile .ResultEmpty , e
539
540
}
540
541
clientOpts = append (clientOpts , opts ... )
541
- tlsConfig = tls
542
+ tlsConfig = tlsCfg
542
543
543
544
// Build registryClient options from secret
544
545
keychain , err = registry .LoginOptionFromSecret (normalizedURL , * secret )
@@ -651,35 +652,38 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
651
652
}
652
653
}
653
654
default :
654
- httpChartRepo , err := repository .NewChartRepository (normalizedURL , r .Storage .LocalPath (* repo .GetArtifact ()), r .Getters , tlsConfig , clientOpts ,
655
- repository .WithMemoryCache (r .Storage .LocalPath (* repo .GetArtifact ()), r .Cache , r .TTL , func (event string ) {
656
- r .IncCacheEvents (event , obj .Name , obj .Namespace )
657
- }))
655
+ httpChartRepo , err := repository .NewChartRepository (normalizedURL , r .Storage .LocalPath (* repo .GetArtifact ()), r .Getters , tlsConfig , clientOpts ... )
658
656
if err != nil {
659
657
return chartRepoConfigErrorReturn (err , obj )
660
658
}
661
- chartRepo = httpChartRepo
659
+
660
+ // NB: this needs to be deferred first, as otherwise the Index will disappear
661
+ // before we had a chance to cache it.
662
662
defer func () {
663
- if httpChartRepo == nil {
664
- return
665
- }
666
- // Cache the index if it was successfully retrieved
667
- // and the chart was successfully built
668
- if r .Cache != nil && httpChartRepo .Index != nil {
669
- // The cache key have to be safe in multi-tenancy environments,
670
- // as otherwise it could be used as a vector to bypass the helm repository's authentication.
671
- // Using r.Storage.LocalPath(*repo.GetArtifact() is safe as the path is in the format /<helm-repository-name>/<chart-name>/<filename>.
672
- err := httpChartRepo .CacheIndexInMemory ()
673
- if err != nil {
674
- r .eventLogf (ctx , obj , eventv1 .EventTypeTrace , sourcev1 .CacheOperationFailedReason , "failed to cache index: %s" , err )
675
- }
663
+ if err := httpChartRepo .Clear (); err != nil {
664
+ ctrl .LoggerFrom (ctx ).Error (err , "failed to clear Helm repository index" )
676
665
}
666
+ }()
677
667
678
- // Delete the index reference
679
- if httpChartRepo .Index != nil {
680
- httpChartRepo .Unload ()
668
+ // Attempt to load the index from the cache.
669
+ if r .Cache != nil {
670
+ if index , ok := r .Cache .Get (httpChartRepo .Path ); ok {
671
+ r .IncCacheEvents (cache .CacheEventTypeHit , repo .Name , repo .Namespace )
672
+ r .Cache .SetExpiration (httpChartRepo .Path , r .TTL )
673
+ httpChartRepo .Index = index .(* helmrepo.IndexFile )
674
+ } else {
675
+ r .IncCacheEvents (cache .CacheEventTypeMiss , repo .Name , repo .Namespace )
676
+ defer func () {
677
+ // If we succeed in loading the index, cache it.
678
+ if httpChartRepo .Index != nil {
679
+ if err = r .Cache .Set (httpChartRepo .Path , httpChartRepo .Index , r .TTL ); err != nil {
680
+ r .eventLogf (ctx , obj , eventv1 .EventTypeTrace , sourcev1 .CacheOperationFailedReason , "failed to cache index: %s" , err )
681
+ }
682
+ }
683
+ }()
681
684
}
682
- }()
685
+ }
686
+ chartRepo = httpChartRepo
683
687
}
684
688
685
689
// Construct the chart builder with scoped configuration
@@ -845,7 +849,7 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
845
849
// early.
846
850
// On a successful archive, the Artifact in the Status of the object is set,
847
851
// and the symlink in the Storage is updated to its path.
848
- func (r * HelmChartReconciler ) reconcileArtifact (ctx context.Context , sp * patch.SerialPatcher , obj * sourcev1.HelmChart , b * chart.Build ) (sreconcile.Result , error ) {
852
+ func (r * HelmChartReconciler ) reconcileArtifact (ctx context.Context , _ * patch.SerialPatcher , obj * sourcev1.HelmChart , b * chart.Build ) (sreconcile.Result , error ) {
849
853
// Without a complete chart build, there is little to reconcile
850
854
if ! b .Complete () {
851
855
return sreconcile .ResultRequeue , nil
@@ -1016,14 +1020,15 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1016
1020
authenticator authn.Authenticator
1017
1021
keychain authn.Keychain
1018
1022
)
1023
+
1019
1024
normalizedURL := repository .NormalizeURL (url )
1020
- repo , err := r .resolveDependencyRepository (ctx , url , namespace )
1025
+ obj , err := r .resolveDependencyRepository (ctx , url , namespace )
1021
1026
if err != nil {
1022
1027
// Return Kubernetes client errors, but ignore others
1023
1028
if apierrs .ReasonForError (err ) != metav1 .StatusReasonUnknown {
1024
1029
return nil , err
1025
1030
}
1026
- repo = & sourcev1.HelmRepository {
1031
+ obj = & sourcev1.HelmRepository {
1027
1032
Spec : sourcev1.HelmRepositorySpec {
1028
1033
URL : url ,
1029
1034
Timeout : & metav1.Duration {Duration : 60 * time .Second },
@@ -1032,37 +1037,37 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1032
1037
}
1033
1038
1034
1039
// Used to login with the repository declared provider
1035
- ctxTimeout , cancel := context .WithTimeout (ctx , repo .Spec .Timeout .Duration )
1040
+ ctxTimeout , cancel := context .WithTimeout (ctx , obj .Spec .Timeout .Duration )
1036
1041
defer cancel ()
1037
1042
1038
1043
clientOpts := []helmgetter.Option {
1039
1044
helmgetter .WithURL (normalizedURL ),
1040
- helmgetter .WithTimeout (repo .Spec .Timeout .Duration ),
1041
- helmgetter .WithPassCredentialsAll (repo .Spec .PassCredentials ),
1045
+ helmgetter .WithTimeout (obj .Spec .Timeout .Duration ),
1046
+ helmgetter .WithPassCredentialsAll (obj .Spec .PassCredentials ),
1042
1047
}
1043
- if secret , err := r .getHelmRepositorySecret (ctx , repo ); secret != nil || err != nil {
1048
+ if secret , err := r .getHelmRepositorySecret (ctx , obj ); secret != nil || err != nil {
1044
1049
if err != nil {
1045
1050
return nil , err
1046
1051
}
1047
1052
1048
1053
// Build client options from secret
1049
- opts , tls , err := r .clientOptionsFromSecret (secret , normalizedURL )
1054
+ opts , tlsCfg , err := r .clientOptionsFromSecret (secret , normalizedURL )
1050
1055
if err != nil {
1051
1056
return nil , err
1052
1057
}
1053
1058
clientOpts = append (clientOpts , opts ... )
1054
- tlsConfig = tls
1059
+ tlsConfig = tlsCfg
1055
1060
1056
1061
// Build registryClient options from secret
1057
1062
keychain , err = registry .LoginOptionFromSecret (normalizedURL , * secret )
1058
1063
if err != nil {
1059
- return nil , fmt .Errorf ("failed to create login options for HelmRepository '%s': %w" , repo .Name , err )
1064
+ return nil , fmt .Errorf ("failed to create login options for HelmRepository '%s': %w" , obj .Name , err )
1060
1065
}
1061
1066
1062
- } else if repo .Spec .Provider != sourcev1 .GenericOCIProvider && repo .Spec .Type == sourcev1 .HelmRepositoryTypeOCI {
1063
- auth , authErr := oidcAuth (ctxTimeout , repo .Spec .URL , repo .Spec .Provider )
1067
+ } else if obj .Spec .Provider != sourcev1 .GenericOCIProvider && obj .Spec .Type == sourcev1 .HelmRepositoryTypeOCI {
1068
+ auth , authErr := oidcAuth (ctxTimeout , obj .Spec .URL , obj .Spec .Provider )
1064
1069
if authErr != nil && ! errors .Is (authErr , oci .ErrUnconfiguredProvider ) {
1065
- return nil , fmt .Errorf ("failed to get credential from %s: %w" , repo .Spec .Provider , authErr )
1070
+ return nil , fmt .Errorf ("failed to get credential from %s: %w" , obj .Spec .Provider , authErr )
1066
1071
}
1067
1072
if auth != nil {
1068
1073
authenticator = auth
@@ -1078,7 +1083,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1078
1083
if helmreg .IsOCI (normalizedURL ) {
1079
1084
registryClient , credentialsFile , err := r .RegistryClientGenerator (loginOpt != nil )
1080
1085
if err != nil {
1081
- return nil , fmt .Errorf ("failed to create registry client for HelmRepository '%s': %w" , repo .Name , err )
1086
+ return nil , fmt .Errorf ("failed to create registry client for HelmRepository '%s': %w" , obj .Name , err )
1082
1087
}
1083
1088
1084
1089
var errs []error
@@ -1089,7 +1094,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1089
1094
repository .WithOCIRegistryClient (registryClient ),
1090
1095
repository .WithCredentialsFile (credentialsFile ))
1091
1096
if err != nil {
1092
- errs = append (errs , fmt .Errorf ("failed to create OCI chart repository for HelmRepository '%s': %w" , repo .Name , err ))
1097
+ errs = append (errs , fmt .Errorf ("failed to create OCI chart repository for HelmRepository '%s': %w" , obj .Name , err ))
1093
1098
// clean up the credentialsFile
1094
1099
if credentialsFile != "" {
1095
1100
if err := os .Remove (credentialsFile ); err != nil {
@@ -1104,7 +1109,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1104
1109
if loginOpt != nil {
1105
1110
err = ociChartRepo .Login (loginOpt )
1106
1111
if err != nil {
1107
- errs = append (errs , fmt .Errorf ("failed to login to OCI chart repository for HelmRepository '%s': %w" , repo .Name , err ))
1112
+ errs = append (errs , fmt .Errorf ("failed to login to OCI chart repository for HelmRepository '%s': %w" , obj .Name , err ))
1108
1113
// clean up the credentialsFile
1109
1114
errs = append (errs , ociChartRepo .Clear ())
1110
1115
return nil , kerrors .NewAggregate (errs )
@@ -1113,19 +1118,28 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
1113
1118
1114
1119
chartRepo = ociChartRepo
1115
1120
} else {
1116
- httpChartRepo , err := repository .NewChartRepository (normalizedURL , "" , r .Getters , tlsConfig , clientOpts )
1121
+ httpChartRepo , err := repository .NewChartRepository (normalizedURL , "" , r .Getters , tlsConfig , clientOpts ... )
1117
1122
if err != nil {
1118
1123
return nil , err
1119
1124
}
1120
1125
1121
- // Ensure that the cache key is the same as the artifact path
1122
- // otherwise don't enable caching. We don't want to cache indexes
1123
- // for repositories that are not reconciled by the source controller.
1124
- if repo .Status .Artifact != nil {
1125
- httpChartRepo .CachePath = r .Storage .LocalPath (* repo .GetArtifact ())
1126
- httpChartRepo .SetMemCache (r .Storage .LocalPath (* repo .GetArtifact ()), r .Cache , r .TTL , func (event string ) {
1127
- r .IncCacheEvents (event , name , namespace )
1128
- })
1126
+ if obj .Status .Artifact != nil {
1127
+ // Attempt to load the index from the cache.
1128
+ httpChartRepo .Path = r .Storage .LocalPath (* obj .GetArtifact ())
1129
+ if r .Cache != nil {
1130
+ if index , ok := r .Cache .Get (httpChartRepo .Path ); ok {
1131
+ r .IncCacheEvents (cache .CacheEventTypeHit , name , namespace )
1132
+ r .Cache .SetExpiration (httpChartRepo .Path , r .TTL )
1133
+
1134
+ httpChartRepo .Index = index .(* helmrepo.IndexFile )
1135
+ } else {
1136
+ r .IncCacheEvents (cache .CacheEventTypeMiss , name , namespace )
1137
+ if err := httpChartRepo .LoadFromPath (); err != nil {
1138
+ return nil , err
1139
+ }
1140
+ r .Cache .Set (httpChartRepo .Path , httpChartRepo .Index , r .TTL )
1141
+ }
1142
+ }
1129
1143
}
1130
1144
1131
1145
chartRepo = httpChartRepo
0 commit comments