-
Notifications
You must be signed in to change notification settings - Fork 209
Enable Umbrella Chart with dependencies from OCI repositories #770
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -35,6 +35,7 @@ import ( | |
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" | ||
"k8s.io/apimachinery/pkg/runtime" | ||
"k8s.io/apimachinery/pkg/types" | ||
kerrors "k8s.io/apimachinery/pkg/util/errors" | ||
"k8s.io/apimachinery/pkg/util/uuid" | ||
kuberecorder "k8s.io/client-go/tools/record" | ||
ctrl "sigs.k8s.io/controller-runtime" | ||
|
@@ -461,9 +462,10 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * | |
loginOpts []helmreg.LoginOption | ||
) | ||
|
||
normalizedURL := repository.NormalizeURL(repo.Spec.URL) | ||
// Construct the Getter options from the HelmRepository data | ||
clientOpts := []helmgetter.Option{ | ||
helmgetter.WithURL(repo.Spec.URL), | ||
helmgetter.WithURL(normalizedURL), | ||
helmgetter.WithTimeout(repo.Spec.Timeout.Duration), | ||
helmgetter.WithPassCredentialsAll(repo.Spec.PassCredentials), | ||
} | ||
|
@@ -491,7 +493,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * | |
} | ||
clientOpts = append(clientOpts, opts...) | ||
|
||
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, repo.Spec.URL) | ||
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, normalizedURL) | ||
if err != nil { | ||
e := &serror.Event{ | ||
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 * | |
} | ||
|
||
// Build registryClient options from secret | ||
loginOpt, err := registry.LoginOptionFromSecret(repo.Spec.URL, *secret) | ||
loginOpt, err := registry.LoginOptionFromSecret(normalizedURL, *secret) | ||
if err != nil { | ||
e := &serror.Event{ | ||
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 * | |
} | ||
|
||
// Initialize the chart repository | ||
var chartRepo chart.Remote | ||
var chartRepo repository.Downloader | ||
switch repo.Spec.Type { | ||
case sourcev1.HelmRepositoryTypeOCI: | ||
if !helmreg.IsOCI(repo.Spec.URL) { | ||
err := fmt.Errorf("invalid OCI registry URL: %s", repo.Spec.URL) | ||
if !helmreg.IsOCI(normalizedURL) { | ||
err := fmt.Errorf("invalid OCI registry URL: %s", normalizedURL) | ||
return chartRepoConfigErrorReturn(err, obj) | ||
} | ||
|
||
// with this function call, we create a temporary file to store the credentials if needed. | ||
// this is needed because otherwise the credentials are stored in ~/.docker/config.json. | ||
// TODO@souleb: remove this once the registry move to Oras v2 | ||
// or rework to enable reusing credentials to avoid the unneccessary handshake operations | ||
registryClient, file, err := r.RegistryClientGenerator(loginOpts != nil) | ||
registryClient, credentialsFile, err := r.RegistryClientGenerator(loginOpts != nil) | ||
if err != nil { | ||
e := &serror.Event{ | ||
Err: fmt.Errorf("failed to construct Helm client: %w", err), | ||
|
@@ -540,9 +542,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * | |
return sreconcile.ResultEmpty, e | ||
} | ||
|
||
if file != "" { | ||
if credentialsFile != "" { | ||
defer func() { | ||
if err := os.Remove(file); err != nil { | ||
if err := os.Remove(credentialsFile); err != nil { | ||
r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason, | ||
"failed to delete temporary credentials file: %s", err) | ||
} | ||
|
@@ -551,7 +553,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * | |
|
||
// Tell the chart repository to use the OCI client with the configured getter | ||
clientOpts = append(clientOpts, helmgetter.WithRegistryClient(registryClient)) | ||
ociChartRepo, err := repository.NewOCIChartRepository(repo.Spec.URL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(clientOpts), repository.WithOCIRegistryClient(registryClient)) | ||
ociChartRepo, err := repository.NewOCIChartRepository(normalizedURL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(clientOpts), repository.WithOCIRegistryClient(registryClient)) | ||
if err != nil { | ||
return chartRepoConfigErrorReturn(err, obj) | ||
} | ||
|
@@ -571,7 +573,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj * | |
} | ||
} | ||
default: | ||
httpChartRepo, err := repository.NewChartRepository(repo.Spec.URL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, tlsConfig, clientOpts, | ||
httpChartRepo, err := repository.NewChartRepository(normalizedURL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, tlsConfig, clientOpts, | ||
repository.WithMemoryCache(r.Storage.LocalPath(*repo.GetArtifact()), r.Cache, r.TTL, func(event string) { | ||
r.IncCacheEvents(event, obj.Name, obj.Namespace) | ||
})) | ||
|
@@ -684,9 +686,15 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj | |
|
||
// Setup dependency manager | ||
dm := chart.NewDependencyManager( | ||
chart.WithRepositoryCallback(r.namespacedChartRepositoryCallback(ctx, obj.GetName(), obj.GetNamespace())), | ||
chart.WithDownloaderCallback(r.namespacedChartRepositoryCallback(ctx, obj.GetName(), obj.GetNamespace())), | ||
) | ||
defer dm.Clear() | ||
defer func() { | ||
err := dm.Clear() | ||
if err != nil { | ||
r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason, | ||
"dependency manager cleanup error: %s", err) | ||
} | ||
}() | ||
|
||
// Configure builder options, including any previously cached chart | ||
opts := chart.BuildOptions{ | ||
|
@@ -913,12 +921,17 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1. | |
return nil | ||
} | ||
|
||
// namespacedChartRepositoryCallback returns a chart.GetChartRepositoryCallback scoped to the given namespace. | ||
// The returned callback returns a repository.ChartRepository configured with the retrieved v1beta1.HelmRepository, | ||
// namespacedChartRepositoryCallback returns a chart.GetChartDownloaderCallback scoped to the given namespace. | ||
// The returned callback returns a repository.Downloader configured with the retrieved v1beta1.HelmRepository, | ||
// or a shim with defaults if no object could be found. | ||
func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, name, namespace string) chart.GetChartRepositoryCallback { | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This func has very much overlap with There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'm not sure we can reuse that much with all the |
||
return func(url string) (*repository.ChartRepository, error) { | ||
var tlsConfig *tls.Config | ||
// The callback returns an object with a state, so the caller has to do the necessary cleanup. | ||
func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, name, namespace string) chart.GetChartDownloaderCallback { | ||
return func(url string) (repository.Downloader, error) { | ||
var ( | ||
tlsConfig *tls.Config | ||
loginOpts []helmreg.LoginOption | ||
) | ||
normalizedURL := repository.NormalizeURL(url) | ||
repo, err := r.resolveDependencyRepository(ctx, url, namespace) | ||
if err != nil { | ||
// Return Kubernetes client errors, but ignore others | ||
|
@@ -933,7 +946,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont | |
} | ||
} | ||
clientOpts := []helmgetter.Option{ | ||
helmgetter.WithURL(repo.Spec.URL), | ||
helmgetter.WithURL(normalizedURL), | ||
helmgetter.WithTimeout(repo.Spec.Timeout.Duration), | ||
helmgetter.WithPassCredentialsAll(repo.Spec.PassCredentials), | ||
} | ||
|
@@ -947,26 +960,77 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont | |
} | ||
clientOpts = append(clientOpts, opts...) | ||
|
||
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, repo.Spec.URL) | ||
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, normalizedURL) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create TLS client config for HelmRepository '%s': %w", repo.Name, err) | ||
} | ||
} | ||
|
||
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, "", r.Getters, tlsConfig, clientOpts) | ||
if err != nil { | ||
return nil, err | ||
// Build registryClient options from secret | ||
loginOpt, err := registry.LoginOptionFromSecret(normalizedURL, *secret) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create login options for HelmRepository '%s': %w", repo.Name, err) | ||
} | ||
|
||
loginOpts = append([]helmreg.LoginOption{}, loginOpt) | ||
} | ||
|
||
// Ensure that the cache key is the same as the artifact path | ||
// otherwise don't enable caching. We don't want to cache indexes | ||
// for repositories that are not reconciled by the source controller. | ||
if repo.Status.Artifact != nil { | ||
chartRepo.CachePath = r.Storage.LocalPath(*repo.GetArtifact()) | ||
chartRepo.SetMemCache(r.Storage.LocalPath(*repo.GetArtifact()), r.Cache, r.TTL, func(event string) { | ||
r.IncCacheEvents(event, name, namespace) | ||
}) | ||
var chartRepo repository.Downloader | ||
if helmreg.IsOCI(normalizedURL) { | ||
registryClient, credentialsFile, err := r.RegistryClientGenerator(loginOpts != nil) | ||
if err != nil { | ||
return nil, fmt.Errorf("failed to create registry client for HelmRepository '%s': %w", repo.Name, err) | ||
} | ||
|
||
var errs []error | ||
// Tell the chart repository to use the OCI client with the configured getter | ||
clientOpts = append(clientOpts, helmgetter.WithRegistryClient(registryClient)) | ||
ociChartRepo, err := repository.NewOCIChartRepository(normalizedURL, repository.WithOCIGetter(r.Getters), | ||
repository.WithOCIGetterOptions(clientOpts), | ||
repository.WithOCIRegistryClient(registryClient), | ||
repository.WithCredentialsFile(credentialsFile)) | ||
if err != nil { | ||
errs = append(errs, fmt.Errorf("failed to create OCI chart repository for HelmRepository '%s': %w", repo.Name, err)) | ||
// clean up the credentialsFile | ||
if credentialsFile != "" { | ||
if err := os.Remove(credentialsFile); err != nil { | ||
errs = append(errs, err) | ||
} | ||
} | ||
return nil, kerrors.NewAggregate(errs) | ||
} | ||
|
||
// If login options are configured, use them to login to the registry | ||
// The OCIGetter will later retrieve the stored credentials to pull the chart | ||
if loginOpts != nil { | ||
err = ociChartRepo.Login(loginOpts...) | ||
if err != nil { | ||
errs = append(errs, fmt.Errorf("failed to login to OCI chart repository for HelmRepository '%s': %w", repo.Name, err)) | ||
// clean up the credentialsFile | ||
errs = append(errs, ociChartRepo.Clear()) | ||
return nil, kerrors.NewAggregate(errs) | ||
} | ||
} | ||
|
||
chartRepo = ociChartRepo | ||
} else { | ||
httpChartRepo, err := repository.NewChartRepository(normalizedURL, "", r.Getters, tlsConfig, clientOpts) | ||
if err != nil { | ||
return nil, err | ||
} | ||
|
||
// Ensure that the cache key is the same as the artifact path | ||
// otherwise don't enable caching. We don't want to cache indexes | ||
// for repositories that are not reconciled by the source controller. | ||
if repo.Status.Artifact != nil { | ||
httpChartRepo.CachePath = r.Storage.LocalPath(*repo.GetArtifact()) | ||
httpChartRepo.SetMemCache(r.Storage.LocalPath(*repo.GetArtifact()), r.Cache, r.TTL, func(event string) { | ||
r.IncCacheEvents(event, name, namespace) | ||
}) | ||
} | ||
|
||
chartRepo = httpChartRepo | ||
} | ||
|
||
return chartRepo, nil | ||
} | ||
} | ||
|
Uh oh!
There was an error while loading. Please reload this page.