Skip to content

Commit a454bc3

Browse files
committed
fixup! Enable remote dependencies from OCI repositories
1 parent 9b8740e commit a454bc3

13 files changed

+224
-239
lines changed

controllers/helmchart_controller.go

+27-12
Original file line numberDiff line numberDiff line change
@@ -461,7 +461,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
461461
loginOpts []helmreg.LoginOption
462462
)
463463

464-
normalizedURL := strings.TrimSuffix(repo.Spec.URL, "/")
464+
normalizedURL := repository.NormalizeURL(repo.Spec.URL)
465465
// Construct the Getter options from the HelmRepository data
466466
clientOpts := []helmgetter.Option{
467467
helmgetter.WithURL(normalizedURL),
@@ -519,7 +519,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
519519
}
520520

521521
// Initialize the chart repository
522-
var chartRepo chart.Downloader
522+
var chartRepo repository.Downloader
523523
switch repo.Spec.Type {
524524
case sourcev1.HelmRepositoryTypeOCI:
525525
if !helmreg.IsOCI(normalizedURL) {
@@ -687,7 +687,13 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
687687
dm := chart.NewDependencyManager(
688688
chart.WithDownloaderCallback(r.namespacedChartRepositoryCallback(ctx, obj.GetName(), obj.GetNamespace())),
689689
)
690-
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+
}()
691697

692698
// Configure builder options, including any previously cached chart
693699
opts := chart.BuildOptions{
@@ -915,16 +921,16 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
915921
}
916922

917923
// namespacedChartRepositoryCallback returns a chart.GetChartDownloaderCallback scoped to the given namespace.
918-
// The returned callback returns a repository.ChartRepository configured with the retrieved v1beta1.HelmRepository,
924+
// The returned callback returns a repository.Downloader configured with the retrieved v1beta1.HelmRepository,
919925
// or a shim with defaults if no object could be found.
920926
// The callback returns an object with a state, so the caller has to do the necessary cleanup.
921927
func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, name, namespace string) chart.GetChartDownloaderCallback {
922-
return func(url string) (chart.CleanableDownloader, error) {
928+
return func(url string) (repository.Downloader, error) {
923929
var (
924930
tlsConfig *tls.Config
925931
loginOpts []helmreg.LoginOption
926932
)
927-
normalizedURL := strings.TrimSuffix(url, "/")
933+
normalizedURL := repository.NormalizeURL(url)
928934
repo, err := r.resolveDependencyRepository(ctx, url, namespace)
929935
if err != nil {
930936
// Return Kubernetes client errors, but ignore others
@@ -967,7 +973,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
967973
loginOpts = append([]helmreg.LoginOption{}, loginOpt)
968974
}
969975

970-
var chartRepo chart.CleanableDownloader
976+
var chartRepo repository.Downloader
971977
if helmreg.IsOCI(normalizedURL) {
972978
registryClient, file, err := r.RegistryClientGenerator(loginOpts != nil)
973979
if err != nil {
@@ -980,13 +986,15 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
980986
ociChartRepo, err := repository.NewOCIChartRepository(normalizedURL, repository.WithOCIGetter(r.Getters),
981987
repository.WithOCIGetterOptions(clientOpts),
982988
repository.WithOCIRegistryClient(registryClient),
983-
repository.WithCredentialFile(file))
989+
repository.WithCredentialsFile(file))
984990
if err != nil {
991+
errs = append(errs, fmt.Errorf("failed to create OCI chart repository for HelmRepository '%s': %w", repo.Name, err))
985992
// clean up the file
986-
if err := os.Remove(file); err != nil {
987-
errs = append(errs, err)
993+
if file != "" {
994+
if err := os.Remove(file); err != nil {
995+
errs = append(errs, err)
996+
}
988997
}
989-
errs = append(errs, fmt.Errorf("failed to create OCI chart repository for HelmRepository '%s': %w", repo.Name, err))
990998
return nil, kerrors.NewAggregate(errs)
991999
}
9921000

@@ -995,7 +1003,14 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
9951003
if loginOpts != nil {
9961004
err = ociChartRepo.Login(loginOpts...)
9971005
if err != nil {
998-
return nil, fmt.Errorf("failed to login to OCI chart repository for HelmRepository '%s': %w", repo.Name, err)
1006+
errs = append(errs, fmt.Errorf("failed to login to OCI chart repository for HelmRepository '%s': %w", repo.Name, err))
1007+
// clean up the file
1008+
if file != "" {
1009+
if err := os.Remove(file); err != nil {
1010+
errs = append(errs, err)
1011+
}
1012+
}
1013+
return nil, kerrors.NewAggregate(errs)
9991014
}
10001015
}
10011016

controllers/helmchart_controller_test.go

-3
Original file line numberDiff line numberDiff line change
@@ -411,9 +411,6 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
411411
}))
412412
},
413413
},
414-
//{
415-
// name: "Error on transient build error",
416-
//},
417414
{
418415
name: "Stalling on persistent build error",
419416
source: &sourcev1.GitRepository{

controllers/helmrepository_controller_oci.go

+1-1
Original file line numberDiff line numberDiff line change
@@ -326,7 +326,7 @@ func (r *HelmRepositoryOCIReconciler) reconcile(ctx context.Context, obj *v1beta
326326
if loginOpts != nil {
327327
err = chartRepo.Login(loginOpts...)
328328
if err != nil {
329-
e := fmt.Errorf("failed to log into registry '%s': %w", obj.Spec.URL, err)
329+
e := fmt.Errorf("failed to login to registry '%s': %w", obj.Spec.URL, err)
330330
conditions.MarkFalse(obj, meta.ReadyCondition, sourcev1.AuthenticationFailedReason, e.Error())
331331
result, retErr = ctrl.Result{}, e
332332
return

internal/helm/chart/builder_local_test.go

+3-3
Original file line numberDiff line numberDiff line change
@@ -67,7 +67,7 @@ func TestLocalBuilder_Build(t *testing.T) {
6767
reference Reference
6868
buildOpts BuildOptions
6969
valuesFiles []helmchart.File
70-
repositories map[string]CleanableDownloader
70+
repositories map[string]repository.Downloader
7171
dependentChartPaths []string
7272
wantValues chartutil.Values
7373
wantVersion string
@@ -146,7 +146,7 @@ fullnameOverride: "full-foo-name-override"`),
146146
{
147147
name: "chart with dependencies",
148148
reference: LocalReference{Path: "../testdata/charts/helmchartwithdeps"},
149-
repositories: map[string]CleanableDownloader{
149+
repositories: map[string]repository.Downloader{
150150
"https://grafana.github.io/helm-charts/": mockRepo(),
151151
},
152152
dependentChartPaths: []string{"./../testdata/charts/helmchart"},
@@ -165,7 +165,7 @@ fullnameOverride: "full-foo-name-override"`),
165165
{
166166
name: "v1 chart with dependencies",
167167
reference: LocalReference{Path: "../testdata/charts/helmchartwithdeps-v1"},
168-
repositories: map[string]CleanableDownloader{
168+
repositories: map[string]repository.Downloader{
169169
"https://grafana.github.io/helm-charts/": mockRepo(),
170170
},
171171
dependentChartPaths: []string{"../testdata/charts/helmchart-v1"},

internal/helm/chart/builder_remote.go

+5-13
Original file line numberDiff line numberDiff line change
@@ -34,24 +34,16 @@ import (
3434

3535
"github.com/fluxcd/source-controller/internal/fs"
3636
"github.com/fluxcd/source-controller/internal/helm/chart/secureloader"
37+
"github.com/fluxcd/source-controller/internal/helm/repository"
3738
)
3839

39-
// Downloader is used to download a chart from a remote Helm repository or OCI registry.
40-
type Downloader interface {
41-
// GetChartVersion returns the repo.ChartVersion for the given name and version
42-
// from the remote repository.ChartRepository.
43-
GetChartVersion(name, version string) (*repo.ChartVersion, error)
44-
// DownloadChart downloads a chart from the remote Helm repository or OCI registry.
45-
DownloadChart(chart *repo.ChartVersion) (*bytes.Buffer, error)
46-
}
47-
4840
type remoteChartBuilder struct {
49-
remote Downloader
41+
remote repository.Downloader
5042
}
5143

5244
// NewRemoteBuilder returns a Builder capable of building a Helm
53-
// chart with a RemoteReference in the given Downloader.
54-
func NewRemoteBuilder(repository Downloader) Builder {
45+
// chart with a RemoteReference in the given repository.Downloader.
46+
func NewRemoteBuilder(repository repository.Downloader) Builder {
5547
return &remoteChartBuilder{
5648
remote: repository,
5749
}
@@ -132,7 +124,7 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
132124
return result, nil
133125
}
134126

135-
func (b *remoteChartBuilder) downloadFromRepository(remote Downloader, remoteRef RemoteReference, opts BuildOptions) (*bytes.Buffer, *Build, error) {
127+
func (b *remoteChartBuilder) downloadFromRepository(remote repository.Downloader, remoteRef RemoteReference, opts BuildOptions) (*bytes.Buffer, *Build, error) {
136128
// Get the current version for the RemoteReference
137129
cv, err := remote.GetChartVersion(remoteRef.Name, remoteRef.Version)
138130
if err != nil {

internal/helm/chart/dependency_manager.go

+9-16
Original file line numberDiff line numberDiff line change
@@ -30,30 +30,23 @@ import (
3030
"golang.org/x/sync/errgroup"
3131
"golang.org/x/sync/semaphore"
3232
helmchart "helm.sh/helm/v3/pkg/chart"
33+
"k8s.io/apimachinery/pkg/util/errors"
3334

3435
"github.com/fluxcd/source-controller/internal/helm/chart/secureloader"
3536
"github.com/fluxcd/source-controller/internal/helm/repository"
3637
)
3738

38-
// CleanableDownloader is a Downloader that cleans temporary files created by the downloader,
39-
// caching the files if the cache is configured, and calling garbage collector to remove
40-
// unused files.
41-
type CleanableDownloader interface {
42-
Clean() []error
43-
Downloader
44-
}
45-
4639
// GetChartDownloaderCallback must return a Downloader for the
4740
// URL or an error describing why it could not be returned.
48-
type GetChartDownloaderCallback func(url string) (CleanableDownloader, error)
41+
type GetChartDownloaderCallback func(url string) (repository.Downloader, error)
4942

5043
// DependencyManager manages dependencies for a Helm chart.
5144
type DependencyManager struct {
5245
// downloaders contains a map of Downloader objects
5346
// indexed by their repository.NormalizeURL.
5447
// It is consulted as a lookup table for missing dependencies, based on
5548
// the (repository) URL the dependency refers to.
56-
downloaders map[string]CleanableDownloader
49+
downloaders map[string]repository.Downloader
5750

5851
// getChartDownloaderCallback can be set to an on-demand GetChartDownloaderCallback
5952
// whose returned result is cached to downloaders.
@@ -72,7 +65,7 @@ type DependencyManagerOption interface {
7265
applyToDependencyManager(dm *DependencyManager)
7366
}
7467

75-
type WithRepositories map[string]CleanableDownloader
68+
type WithRepositories map[string]repository.Downloader
7669

7770
func (o WithRepositories) applyToDependencyManager(dm *DependencyManager) {
7871
dm.downloaders = o
@@ -103,12 +96,12 @@ func NewDependencyManager(opts ...DependencyManagerOption) *DependencyManager {
10396
// Clear iterates over the downloaders, calling Clean on all
10497
// items.
10598
// It returns a collection of errors.
106-
func (dm *DependencyManager) Clear() []error {
99+
func (dm *DependencyManager) Clear() error {
107100
var errs []error
108101
for _, v := range dm.downloaders {
109102
errs = append(errs, v.Clean()...)
110103
}
111-
return errs
104+
return errors.NewAggregate(errs)
112105
}
113106

114107
// Build compiles a set of missing dependencies from chart.Chart, and attempts to
@@ -241,7 +234,7 @@ func (dm *DependencyManager) addRemoteDependency(chart *chartWithLock, dep *helm
241234

242235
ver, err := repo.GetChartVersion(dep.Name, dep.Version)
243236
if err != nil {
244-
return fmt.Errorf("failed to get chart version '%s' from '%s': %w", dep.Version, dep.Repository, err)
237+
return fmt.Errorf("failed to get chart '%s' version '%s' from '%s': %w", dep.Name, dep.Version, dep.Repository, err)
245238
}
246239
res, err := repo.DownloadChart(ver)
247240
if err != nil {
@@ -260,7 +253,7 @@ func (dm *DependencyManager) addRemoteDependency(chart *chartWithLock, dep *helm
260253

261254
// resolveRepository first attempts to resolve the url from the downloaders, falling back
262255
// to getDownloaderCallback if set. It returns the resolved Index, or an error.
263-
func (dm *DependencyManager) resolveRepository(url string) (_ Downloader, err error) {
256+
func (dm *DependencyManager) resolveRepository(url string) (repo repository.Downloader, err error) {
264257
dm.mu.Lock()
265258
defer dm.mu.Unlock()
266259

@@ -272,7 +265,7 @@ func (dm *DependencyManager) resolveRepository(url string) (_ Downloader, err er
272265
}
273266

274267
if dm.downloaders == nil {
275-
dm.downloaders = map[string]CleanableDownloader{}
268+
dm.downloaders = map[string]repository.Downloader{}
276269
}
277270

278271
if dm.downloaders[nUrl], err = dm.getChartDownloaderCallback(nUrl); err != nil {

0 commit comments

Comments
 (0)