Skip to content

Commit 0aaeeee

Browse files
committed
controllers: RFC-0005 fmt for HelmRepository rev
This includes changes to the `ChartRepository`, to allow calculating the revision and digest and tidy things. In addition, the responsibility of caching the `IndexFile` has been moved to the reconcilers. As this allowed to remove a lot of complexities within the `ChartRepository`, and prevented passing on the cache in general. Change `HelmRepository`'s Revision to digest Signed-off-by: Hidde Beydals <[email protected]>
1 parent f00aeae commit 0aaeeee

File tree

7 files changed

+856
-675
lines changed

7 files changed

+856
-675
lines changed

controllers/helmchart_controller.go

Lines changed: 67 additions & 53 deletions
Original file line numberDiff line numberDiff line change
@@ -21,21 +21,19 @@ import (
2121
"crypto/tls"
2222
"errors"
2323
"fmt"
24-
"github.com/fluxcd/pkg/git"
25-
"github.com/opencontainers/go-digest"
2624
"net/url"
2725
"os"
2826
"path/filepath"
2927
"strconv"
3028
"strings"
3129
"time"
3230

33-
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
34-
soci "github.com/fluxcd/source-controller/internal/oci"
3531
"github.com/google/go-containerregistry/pkg/authn"
3632
"github.com/google/go-containerregistry/pkg/v1/remote"
33+
"github.com/opencontainers/go-digest"
3734
helmgetter "helm.sh/helm/v3/pkg/getter"
3835
helmreg "helm.sh/helm/v3/pkg/registry"
36+
helmrepo "helm.sh/helm/v3/pkg/repo"
3937
corev1 "k8s.io/api/core/v1"
4038
apierrs "k8s.io/apimachinery/pkg/api/errors"
4139
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
@@ -54,7 +52,9 @@ import (
5452
"sigs.k8s.io/controller-runtime/pkg/reconcile"
5553
"sigs.k8s.io/controller-runtime/pkg/source"
5654

55+
eventv1 "github.com/fluxcd/pkg/apis/event/v1beta1"
5756
"github.com/fluxcd/pkg/apis/meta"
57+
"github.com/fluxcd/pkg/git"
5858
"github.com/fluxcd/pkg/oci"
5959
"github.com/fluxcd/pkg/runtime/conditions"
6060
helper "github.com/fluxcd/pkg/runtime/controller"
@@ -70,6 +70,7 @@ import (
7070
"github.com/fluxcd/source-controller/internal/helm/getter"
7171
"github.com/fluxcd/source-controller/internal/helm/registry"
7272
"github.com/fluxcd/source-controller/internal/helm/repository"
73+
soci "github.com/fluxcd/source-controller/internal/oci"
7374
sreconcile "github.com/fluxcd/source-controller/internal/reconcile"
7475
"github.com/fluxcd/source-controller/internal/reconcile/summarize"
7576
"github.com/fluxcd/source-controller/internal/util"
@@ -527,7 +528,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
527528
}
528529

529530
// Build client options from secret
530-
opts, tls, err := r.clientOptionsFromSecret(secret, normalizedURL)
531+
opts, tlsCfg, err := r.clientOptionsFromSecret(secret, normalizedURL)
531532
if err != nil {
532533
e := &serror.Event{
533534
Err: err,
@@ -538,7 +539,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
538539
return sreconcile.ResultEmpty, e
539540
}
540541
clientOpts = append(clientOpts, opts...)
541-
tlsConfig = tls
542+
tlsConfig = tlsCfg
542543

543544
// Build registryClient options from secret
544545
keychain, err = registry.LoginOptionFromSecret(normalizedURL, *secret)
@@ -651,35 +652,38 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
651652
}
652653
}
653654
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...)
658656
if err != nil {
659657
return chartRepoConfigErrorReturn(err, obj)
660658
}
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.
662662
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")
676665
}
666+
}()
677667

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+
}()
681684
}
682-
}()
685+
}
686+
chartRepo = httpChartRepo
683687
}
684688

685689
// Construct the chart builder with scoped configuration
@@ -845,7 +849,7 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
845849
// early.
846850
// On a successful archive, the Artifact in the Status of the object is set,
847851
// 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) {
849853
// Without a complete chart build, there is little to reconcile
850854
if !b.Complete() {
851855
return sreconcile.ResultRequeue, nil
@@ -1016,14 +1020,15 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10161020
authenticator authn.Authenticator
10171021
keychain authn.Keychain
10181022
)
1023+
10191024
normalizedURL := repository.NormalizeURL(url)
1020-
repo, err := r.resolveDependencyRepository(ctx, url, namespace)
1025+
obj, err := r.resolveDependencyRepository(ctx, url, namespace)
10211026
if err != nil {
10221027
// Return Kubernetes client errors, but ignore others
10231028
if apierrs.ReasonForError(err) != metav1.StatusReasonUnknown {
10241029
return nil, err
10251030
}
1026-
repo = &sourcev1.HelmRepository{
1031+
obj = &sourcev1.HelmRepository{
10271032
Spec: sourcev1.HelmRepositorySpec{
10281033
URL: url,
10291034
Timeout: &metav1.Duration{Duration: 60 * time.Second},
@@ -1032,37 +1037,37 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10321037
}
10331038

10341039
// 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)
10361041
defer cancel()
10371042

10381043
clientOpts := []helmgetter.Option{
10391044
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),
10421047
}
1043-
if secret, err := r.getHelmRepositorySecret(ctx, repo); secret != nil || err != nil {
1048+
if secret, err := r.getHelmRepositorySecret(ctx, obj); secret != nil || err != nil {
10441049
if err != nil {
10451050
return nil, err
10461051
}
10471052

10481053
// Build client options from secret
1049-
opts, tls, err := r.clientOptionsFromSecret(secret, normalizedURL)
1054+
opts, tlsCfg, err := r.clientOptionsFromSecret(secret, normalizedURL)
10501055
if err != nil {
10511056
return nil, err
10521057
}
10531058
clientOpts = append(clientOpts, opts...)
1054-
tlsConfig = tls
1059+
tlsConfig = tlsCfg
10551060

10561061
// Build registryClient options from secret
10571062
keychain, err = registry.LoginOptionFromSecret(normalizedURL, *secret)
10581063
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)
10601065
}
10611066

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)
10641069
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)
10661071
}
10671072
if auth != nil {
10681073
authenticator = auth
@@ -1078,7 +1083,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10781083
if helmreg.IsOCI(normalizedURL) {
10791084
registryClient, credentialsFile, err := r.RegistryClientGenerator(loginOpt != nil)
10801085
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)
10821087
}
10831088

10841089
var errs []error
@@ -1089,7 +1094,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
10891094
repository.WithOCIRegistryClient(registryClient),
10901095
repository.WithCredentialsFile(credentialsFile))
10911096
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))
10931098
// clean up the credentialsFile
10941099
if credentialsFile != "" {
10951100
if err := os.Remove(credentialsFile); err != nil {
@@ -1104,7 +1109,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
11041109
if loginOpt != nil {
11051110
err = ociChartRepo.Login(loginOpt)
11061111
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))
11081113
// clean up the credentialsFile
11091114
errs = append(errs, ociChartRepo.Clear())
11101115
return nil, kerrors.NewAggregate(errs)
@@ -1113,19 +1118,28 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
11131118

11141119
chartRepo = ociChartRepo
11151120
} else {
1116-
httpChartRepo, err := repository.NewChartRepository(normalizedURL, "", r.Getters, tlsConfig, clientOpts)
1121+
httpChartRepo, err := repository.NewChartRepository(normalizedURL, "", r.Getters, tlsConfig, clientOpts...)
11171122
if err != nil {
11181123
return nil, err
11191124
}
11201125

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+
}
11291143
}
11301144

11311145
chartRepo = httpChartRepo

0 commit comments

Comments
 (0)