Revert "Merge pull request #770 from souleb/oci-for-deps-manager"

This reverts commit 0219905036, reversing
changes made to f7006e91dd.
This commit is contained in:
Paulo Gomes 2022-07-15 14:58:44 +01:00
parent 8cff49f2c9
commit cb119befe6
No known key found for this signature in database
GPG Key ID: 9995233870E99BEE
16 changed files with 242 additions and 585 deletions

View File

@ -35,7 +35,6 @@ 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"
@ -463,10 +462,9 @@ 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(normalizedURL),
helmgetter.WithURL(repo.Spec.URL),
helmgetter.WithTimeout(repo.Spec.Timeout.Duration),
helmgetter.WithPassCredentialsAll(repo.Spec.PassCredentials),
}
@ -494,7 +492,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
}
clientOpts = append(clientOpts, opts...)
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, normalizedURL)
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, repo.Spec.URL)
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to create TLS client config with secret data: %w", err),
@ -506,7 +504,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
}
// Build registryClient options from secret
loginOpt, err := registry.LoginOptionFromSecret(normalizedURL, *secret)
loginOpt, err := registry.LoginOptionFromSecret(repo.Spec.URL, *secret)
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to configure Helm client with secret data: %w", err),
@ -521,11 +519,11 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
}
// Initialize the chart repository
var chartRepo repository.Downloader
var chartRepo chart.Remote
switch repo.Spec.Type {
case sourcev1.HelmRepositoryTypeOCI:
if !helmreg.IsOCI(normalizedURL) {
err := fmt.Errorf("invalid OCI registry URL: %s", normalizedURL)
if !helmreg.IsOCI(repo.Spec.URL) {
err := fmt.Errorf("invalid OCI registry URL: %s", repo.Spec.URL)
return chartRepoConfigErrorReturn(err, obj)
}
@ -533,7 +531,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
// 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, credentialsFile, err := r.RegistryClientGenerator(loginOpts != nil)
registryClient, file, err := r.RegistryClientGenerator(loginOpts != nil)
if err != nil {
e := &serror.Event{
Err: fmt.Errorf("failed to construct Helm client: %w", err),
@ -543,9 +541,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
return sreconcile.ResultEmpty, e
}
if credentialsFile != "" {
if file != "" {
defer func() {
if err := os.Remove(credentialsFile); err != nil {
if err := os.Remove(file); err != nil {
r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason,
"failed to delete temporary credentials file: %s", err)
}
@ -554,7 +552,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(normalizedURL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(clientOpts), repository.WithOCIRegistryClient(registryClient))
ociChartRepo, err := repository.NewOCIChartRepository(repo.Spec.URL, repository.WithOCIGetter(r.Getters), repository.WithOCIGetterOptions(clientOpts), repository.WithOCIRegistryClient(registryClient))
if err != nil {
return chartRepoConfigErrorReturn(err, obj)
}
@ -574,7 +572,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
}
}
default:
httpChartRepo, err := repository.NewChartRepository(normalizedURL, r.Storage.LocalPath(*repo.GetArtifact()), r.Getters, tlsConfig, clientOpts,
httpChartRepo, err := repository.NewChartRepository(repo.Spec.URL, 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)
}))
@ -687,15 +685,9 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
// Setup dependency manager
dm := chart.NewDependencyManager(
chart.WithDownloaderCallback(r.namespacedChartRepositoryCallback(ctx, obj.GetName(), obj.GetNamespace())),
chart.WithRepositoryCallback(r.namespacedChartRepositoryCallback(ctx, obj.GetName(), obj.GetNamespace())),
)
defer func() {
err := dm.Clear()
if err != nil {
r.eventLogf(ctx, obj, corev1.EventTypeWarning, meta.FailedReason,
"dependency manager cleanup error: %s", err)
}
}()
defer dm.Clear()
// Configure builder options, including any previously cached chart
opts := chart.BuildOptions{
@ -922,17 +914,12 @@ func (r *HelmChartReconciler) garbageCollect(ctx context.Context, obj *sourcev1.
return nil
}
// namespacedChartRepositoryCallback returns a chart.GetChartDownloaderCallback scoped to the given namespace.
// The returned callback returns a repository.Downloader configured with the retrieved v1beta1.HelmRepository,
// namespacedChartRepositoryCallback returns a chart.GetChartRepositoryCallback scoped to the given namespace.
// The returned callback returns a repository.ChartRepository configured with the retrieved v1beta1.HelmRepository,
// or a shim with defaults if no object could be found.
// 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)
func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Context, name, namespace string) chart.GetChartRepositoryCallback {
return func(url string) (*repository.ChartRepository, error) {
var tlsConfig *tls.Config
repo, err := r.resolveDependencyRepository(ctx, url, namespace)
if err != nil {
// Return Kubernetes client errors, but ignore others
@ -947,7 +934,7 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
}
}
clientOpts := []helmgetter.Option{
helmgetter.WithURL(normalizedURL),
helmgetter.WithURL(repo.Spec.URL),
helmgetter.WithTimeout(repo.Spec.Timeout.Duration),
helmgetter.WithPassCredentialsAll(repo.Spec.PassCredentials),
}
@ -961,60 +948,13 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
}
clientOpts = append(clientOpts, opts...)
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, normalizedURL)
tlsConfig, err = getter.TLSClientConfigFromSecret(*secret, repo.Spec.URL)
if err != nil {
return nil, fmt.Errorf("failed to create TLS client config for HelmRepository '%s': %w", repo.Name, 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)
}
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)
chartRepo, err := repository.NewChartRepository(repo.Spec.URL, "", r.Getters, tlsConfig, clientOpts)
if err != nil {
return nil, err
}
@ -1023,15 +963,11 @@ func (r *HelmChartReconciler) namespacedChartRepositoryCallback(ctx context.Cont
// 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) {
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)
})
}
chartRepo = httpChartRepo
}
return chartRepo, nil
}
}

View File

@ -411,6 +411,9 @@ func TestHelmChartReconciler_reconcileSource(t *testing.T) {
}))
},
},
//{
// name: "Error on transient build error",
//},
{
name: "Stalling on persistent build error",
source: &sourcev1.GitRepository{
@ -1217,7 +1220,7 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
assertFunc: func(g *WithT, build chart.Build) {
g.Expect(build.Name).To(Equal("helmchartwithdeps"))
g.Expect(build.Version).To(Equal("0.1.0"))
g.Expect(build.ResolvedDependencies).To(Equal(4))
g.Expect(build.ResolvedDependencies).To(Equal(3))
g.Expect(build.Path).To(BeARegularFile())
},
cleanFunc: func(g *WithT, build *chart.Build) {
@ -1329,7 +1332,6 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
EventRecorder: record.NewFakeRecorder(32),
Storage: storage,
Getters: testGetters,
RegistryClientGenerator: registry.ClientGenerator,
}
obj := &sourcev1.HelmChart{

View File

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

View File

@ -31,6 +31,3 @@ dependencies:
- name: grafana
version: ">=5.7.0"
repository: "https://grafana.github.io/helm-charts"
- name: podinfo
version: ">=6.1.*"
repository: "oci://ghcr.io/stefanprodan/charts"

View File

@ -67,7 +67,7 @@ func TestLocalBuilder_Build(t *testing.T) {
reference Reference
buildOpts BuildOptions
valuesFiles []helmchart.File
repositories map[string]repository.Downloader
repositories map[string]*repository.ChartRepository
dependentChartPaths []string
wantValues chartutil.Values
wantVersion string
@ -146,7 +146,7 @@ fullnameOverride: "full-foo-name-override"`),
{
name: "chart with dependencies",
reference: LocalReference{Path: "../testdata/charts/helmchartwithdeps"},
repositories: map[string]repository.Downloader{
repositories: map[string]*repository.ChartRepository{
"https://grafana.github.io/helm-charts/": mockRepo(),
},
dependentChartPaths: []string{"./../testdata/charts/helmchart"},
@ -165,7 +165,7 @@ fullnameOverride: "full-foo-name-override"`),
{
name: "v1 chart with dependencies",
reference: LocalReference{Path: "../testdata/charts/helmchartwithdeps-v1"},
repositories: map[string]repository.Downloader{
repositories: map[string]*repository.ChartRepository{
"https://grafana.github.io/helm-charts/": mockRepo(),
},
dependentChartPaths: []string{"../testdata/charts/helmchart-v1"},

View File

@ -25,6 +25,7 @@ import (
"path/filepath"
"github.com/Masterminds/semver/v3"
"github.com/fluxcd/source-controller/internal/helm/repository"
helmchart "helm.sh/helm/v3/pkg/chart"
"helm.sh/helm/v3/pkg/chartutil"
"helm.sh/helm/v3/pkg/repo"
@ -34,16 +35,24 @@ import (
"github.com/fluxcd/source-controller/internal/fs"
"github.com/fluxcd/source-controller/internal/helm/chart/secureloader"
"github.com/fluxcd/source-controller/internal/helm/repository"
)
// Remote is a repository.ChartRepository or a repository.OCIChartRepository.
// It is used to download a chart from a remote Helm repository or OCI registry.
type Remote interface {
// GetChart returns a chart.Chart from the remote repository.
Get(name, version string) (*repo.ChartVersion, error)
// GetChartVersion returns a chart.ChartVersion from the remote repository.
DownloadChart(chart *repo.ChartVersion) (*bytes.Buffer, error)
}
type remoteChartBuilder struct {
remote repository.Downloader
remote Remote
}
// NewRemoteBuilder returns a Builder capable of building a Helm
// chart with a RemoteReference in the given repository.Downloader.
func NewRemoteBuilder(repository repository.Downloader) Builder {
// chart with a RemoteReference in the given repository.ChartRepository.
func NewRemoteBuilder(repository Remote) Builder {
return &remoteChartBuilder{
remote: repository,
}
@ -74,13 +83,32 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
return nil, &BuildError{Reason: ErrChartReference, Err: err}
}
res, result, err := b.downloadFromRepository(b.remote, remoteRef, opts)
var (
res *bytes.Buffer
err error
)
result := &Build{}
switch b.remote.(type) {
case *repository.ChartRepository:
res, err = b.downloadFromRepository(b.remote.(*repository.ChartRepository), remoteRef, result, opts)
if err != nil {
return nil, &BuildError{Reason: ErrChartPull, Err: err}
}
if res == nil {
return result, nil
}
case *repository.OCIChartRepository:
res, err = b.downloadFromOCIRepository(b.remote.(*repository.OCIChartRepository), remoteRef, result, opts)
if err != nil {
return nil, &BuildError{Reason: ErrChartPull, Err: err}
}
if res == nil {
return result, nil
}
default:
return nil, &BuildError{Reason: ErrChartReference, Err: fmt.Errorf("unsupported remote type %T", b.remote)}
}
requiresPackaging := len(opts.GetValuesFiles()) != 0 || opts.VersionMetadata != ""
@ -124,31 +152,66 @@ func (b *remoteChartBuilder) Build(_ context.Context, ref Reference, p string, o
return result, nil
}
func (b *remoteChartBuilder) downloadFromRepository(remote repository.Downloader, remoteRef RemoteReference, opts BuildOptions) (*bytes.Buffer, *Build, error) {
// Get the current version for the RemoteReference
cv, err := remote.GetChartVersion(remoteRef.Name, remoteRef.Version)
func (b *remoteChartBuilder) downloadFromOCIRepository(remote *repository.OCIChartRepository, remoteRef RemoteReference, buildResult *Build, opts BuildOptions) (*bytes.Buffer, error) {
cv, err := remote.Get(remoteRef.Name, remoteRef.Version)
if err != nil {
err = fmt.Errorf("failed to get chart version for remote reference: %w", err)
return nil, nil, &BuildError{Reason: ErrChartReference, Err: err}
return nil, &BuildError{Reason: ErrChartPull, Err: err}
}
result, shouldReturn, err := generateBuildResult(cv, opts)
if err != nil {
return nil, nil, err
return nil, err
}
if shouldReturn {
return nil, result, nil
*buildResult = *result
return nil, nil
}
// Download the package for the resolved version
res, err := remote.DownloadChart(cv)
if err != nil {
err = fmt.Errorf("failed to download chart for remote reference: %w", err)
return nil, nil, &BuildError{Reason: ErrChartPull, Err: err}
return nil, &BuildError{Reason: ErrChartPull, Err: err}
}
return res, result, nil
*buildResult = *result
return res, nil
}
func (b *remoteChartBuilder) downloadFromRepository(remote *repository.ChartRepository, remoteRef RemoteReference, buildResult *Build, opts BuildOptions) (*bytes.Buffer, error) {
if err := remote.StrategicallyLoadIndex(); err != nil {
err = fmt.Errorf("could not load repository index for remote chart reference: %w", err)
return nil, &BuildError{Reason: ErrChartPull, Err: err}
}
// Get the current version for the RemoteReference
cv, err := remote.Get(remoteRef.Name, remoteRef.Version)
if err != nil {
err = fmt.Errorf("failed to get chart version for remote reference: %w", err)
return nil, &BuildError{Reason: ErrChartReference, Err: err}
}
result, shouldReturn, err := generateBuildResult(cv, opts)
if err != nil {
return nil, err
}
*buildResult = *result
if shouldReturn {
return nil, nil
}
// Download the package for the resolved version
res, err := remote.DownloadChart(cv)
if err != nil {
err = fmt.Errorf("failed to download chart for remote reference: %w", err)
return nil, &BuildError{Reason: ErrChartPull, Err: err}
}
return res, nil
}
// generateBuildResult returns a Build object generated from the given chart version and build options. It also returns

View File

@ -30,27 +30,26 @@ import (
"golang.org/x/sync/errgroup"
"golang.org/x/sync/semaphore"
helmchart "helm.sh/helm/v3/pkg/chart"
"k8s.io/apimachinery/pkg/util/errors"
"github.com/fluxcd/source-controller/internal/helm/chart/secureloader"
"github.com/fluxcd/source-controller/internal/helm/repository"
)
// GetChartDownloaderCallback must return a Downloader for the
// URL or an error describing why it could not be returned.
type GetChartDownloaderCallback func(url string) (repository.Downloader, error)
// GetChartRepositoryCallback must return a repository.ChartRepository for the
// URL, or an error describing why it could not be returned.
type GetChartRepositoryCallback func(url string) (*repository.ChartRepository, error)
// DependencyManager manages dependencies for a Helm chart.
type DependencyManager struct {
// downloaders contains a map of Downloader objects
// repositories contains a map of repository.ChartRepository objects
// indexed by their repository.NormalizeURL.
// It is consulted as a lookup table for missing dependencies, based on
// the (repository) URL the dependency refers to.
downloaders map[string]repository.Downloader
repositories map[string]*repository.ChartRepository
// getChartDownloaderCallback can be set to an on-demand GetChartDownloaderCallback
// whose returned result is cached to downloaders.
getChartDownloaderCallback GetChartDownloaderCallback
// getRepositoryCallback can be set to an on-demand GetChartRepositoryCallback
// whose returned result is cached to repositories.
getRepositoryCallback GetChartRepositoryCallback
// concurrent is the number of concurrent chart-add operations during
// Build. Defaults to 1 (non-concurrent).
@ -65,16 +64,16 @@ type DependencyManagerOption interface {
applyToDependencyManager(dm *DependencyManager)
}
type WithRepositories map[string]repository.Downloader
type WithRepositories map[string]*repository.ChartRepository
func (o WithRepositories) applyToDependencyManager(dm *DependencyManager) {
dm.downloaders = o
dm.repositories = o
}
type WithDownloaderCallback GetChartDownloaderCallback
type WithRepositoryCallback GetChartRepositoryCallback
func (o WithDownloaderCallback) applyToDependencyManager(dm *DependencyManager) {
dm.getChartDownloaderCallback = GetChartDownloaderCallback(o)
func (o WithRepositoryCallback) applyToDependencyManager(dm *DependencyManager) {
dm.getRepositoryCallback = GetChartRepositoryCallback(o)
}
type WithConcurrent int64
@ -93,16 +92,20 @@ func NewDependencyManager(opts ...DependencyManagerOption) *DependencyManager {
return dm
}
// Clear iterates over the downloaders, calling Clear on all
// items. It returns an aggregate error of all Clear errors.
func (dm *DependencyManager) Clear() error {
// Clear iterates over the repositories, calling Unload and RemoveCache on all
// items. It returns a collection of (cache removal) errors.
func (dm *DependencyManager) Clear() []error {
var errs []error
for _, v := range dm.downloaders {
if v != nil {
errs = append(errs, v.Clear())
for _, v := range dm.repositories {
if err := v.CacheIndexInMemory(); err != nil {
errs = append(errs, err)
}
v.Unload()
if err := v.RemoveCache(); err != nil {
errs = append(errs, err)
}
}
return errors.NewAggregate(errs)
return errs
}
// Build compiles a set of missing dependencies from chart.Chart, and attempts to
@ -233,9 +236,13 @@ func (dm *DependencyManager) addRemoteDependency(chart *chartWithLock, dep *helm
return err
}
ver, err := repo.GetChartVersion(dep.Name, dep.Version)
if err = repo.StrategicallyLoadIndex(); err != nil {
return fmt.Errorf("failed to load index for '%s': %w", dep.Name, err)
}
ver, err := repo.Get(dep.Name, dep.Version)
if err != nil {
return fmt.Errorf("failed to get chart '%s' version '%s' from '%s': %w", dep.Name, dep.Version, dep.Repository, err)
return err
}
res, err := repo.DownloadChart(ver)
if err != nil {
@ -252,9 +259,9 @@ func (dm *DependencyManager) addRemoteDependency(chart *chartWithLock, dep *helm
return nil
}
// resolveRepository first attempts to resolve the url from the downloaders, falling back
// to getDownloaderCallback if set. It returns the resolved Index, or an error.
func (dm *DependencyManager) resolveRepository(url string) (repo repository.Downloader, err error) {
// resolveRepository first attempts to resolve the url from the repositories, falling back
// to getRepositoryCallback if set. It returns the resolved Index, or an error.
func (dm *DependencyManager) resolveRepository(url string) (_ *repository.ChartRepository, err error) {
dm.mu.Lock()
defer dm.mu.Unlock()
@ -263,22 +270,20 @@ func (dm *DependencyManager) resolveRepository(url string) (repo repository.Down
if err != nil {
return
}
if _, ok := dm.downloaders[nUrl]; !ok {
if dm.getChartDownloaderCallback == nil {
if _, ok := dm.repositories[nUrl]; !ok {
if dm.getRepositoryCallback == nil {
err = fmt.Errorf("no chart repository for URL '%s'", nUrl)
return
}
if dm.downloaders == nil {
dm.downloaders = map[string]repository.Downloader{}
if dm.repositories == nil {
dm.repositories = map[string]*repository.ChartRepository{}
}
if dm.downloaders[nUrl], err = dm.getChartDownloaderCallback(nUrl); err != nil {
if dm.repositories[nUrl], err = dm.getRepositoryCallback(nUrl); err != nil {
err = fmt.Errorf("failed to get chart repository for URL '%s': %w", nUrl, err)
return
}
}
return dm.downloaders[nUrl], nil
return dm.repositories[nUrl], nil
}
// secureLocalChartPath returns the secure absolute path of a local dependency.

View File

@ -21,7 +21,6 @@ import (
"context"
"errors"
"fmt"
"net/url"
"os"
"path/filepath"
"sync"
@ -30,38 +29,12 @@ import (
. "github.com/onsi/gomega"
helmchart "helm.sh/helm/v3/pkg/chart"
helmgetter "helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/registry"
"helm.sh/helm/v3/pkg/repo"
"github.com/fluxcd/source-controller/internal/helm/chart/secureloader"
"github.com/fluxcd/source-controller/internal/helm/repository"
)
type mockTagsGetter struct {
tags map[string][]string
}
func (m *mockTagsGetter) Tags(requestURL string) ([]string, error) {
u, err := url.Parse(requestURL)
if err != nil {
return nil, err
}
name := filepath.Base(u.Path)
if tags, ok := m.tags[name]; ok {
return tags, nil
}
return nil, fmt.Errorf("no tags found for %s with requestURL %s", name, requestURL)
}
func (m *mockTagsGetter) Login(_ string, _ ...registry.LoginOption) error {
return nil
}
func (m *mockTagsGetter) Logout(_ string, _ ...registry.LogoutOption) error {
return nil
}
// mockGetter is a simple mocking getter.Getter implementation, returning
// a byte response to any provided URL.
type mockGetter struct {
@ -76,43 +49,25 @@ func (g *mockGetter) Get(_ string, _ ...helmgetter.Option) (*bytes.Buffer, error
func TestDependencyManager_Clear(t *testing.T) {
g := NewWithT(t)
file, err := os.CreateTemp("", "")
g.Expect(err).ToNot(HaveOccurred())
ociRepoWithCreds, err := repository.NewOCIChartRepository("oci://example.com", repository.WithCredentialsFile(file.Name()))
g.Expect(err).ToNot(HaveOccurred())
downloaders := map[string]repository.Downloader{
"with index": &repository.ChartRepository{
repos := map[string]*repository.ChartRepository{
"with index": {
Index: repo.NewIndexFile(),
RWMutex: &sync.RWMutex{},
},
"cached cache path": &repository.ChartRepository{
"cached cache path": {
CachePath: "/invalid/path/resets",
Cached: true,
RWMutex: &sync.RWMutex{},
},
"with credentials": ociRepoWithCreds,
"without credentials": &repository.OCIChartRepository{},
"nil downloader": nil,
}
dm := NewDependencyManager(WithRepositories(downloaders))
dm := NewDependencyManager(WithRepositories(repos))
g.Expect(dm.Clear()).To(BeNil())
g.Expect(dm.downloaders).To(HaveLen(len(downloaders)))
for _, v := range downloaders {
switch v := v.(type) {
case *repository.ChartRepository:
g.Expect(dm.repositories).To(HaveLen(len(repos)))
for _, v := range repos {
g.Expect(v.Index).To(BeNil())
g.Expect(v.CachePath).To(BeEmpty())
g.Expect(v.Cached).To(BeFalse())
case *repository.OCIChartRepository:
g.Expect(v.HasCredentials()).To(BeFalse())
}
}
if _, err := os.Stat(file.Name()); !errors.Is(err, os.ErrNotExist) {
err = os.Remove(file.Name())
g.Expect(err).ToNot(HaveOccurred())
}
}
@ -125,22 +80,8 @@ func TestDependencyManager_Build(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(chartGrafana).ToNot(BeEmpty())
mockrepos := []repository.Downloader{
&repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
Client: &mockGetter{
Response: chartGrafana,
},
RegistryClient: &mockTagsGetter{
tags: map[string][]string{
"grafana": {"6.17.4"},
},
},
},
&repository.ChartRepository{
mockRepo := func() *repository.ChartRepository {
return &repository.ChartRepository{
Client: &mockGetter{
Response: chartGrafana,
},
@ -155,24 +96,19 @@ func TestDependencyManager_Build(t *testing.T) {
URLs: []string{"https://example.com/grafana.tgz"},
},
},
"nil downloader": nil,
},
},
RWMutex: &sync.RWMutex{},
},
}
}
for _, repo := range mockrepos {
build(t, repo)
}
}
func build(t *testing.T, mockRepo repository.Downloader) {
tests := []struct {
name string
baseDir string
path string
downloaders map[string]repository.Downloader
getChartDownloaderCallback GetChartDownloaderCallback
repositories map[string]*repository.ChartRepository
getChartRepositoryCallback GetChartRepositoryCallback
want int
wantChartFunc func(g *WithT, c *helmchart.Chart)
wantErr string
@ -205,10 +141,10 @@ func build(t *testing.T, mockRepo repository.Downloader) {
name: "build with dependencies using lock file",
baseDir: "./../testdata/charts",
path: "helmchartwithdeps",
downloaders: map[string]repository.Downloader{
"https://grafana.github.io/helm-charts/": mockRepo,
repositories: map[string]*repository.ChartRepository{
"https://grafana.github.io/helm-charts/": mockRepo(),
},
getChartDownloaderCallback: func(url string) (repository.Downloader, error) {
getChartRepositoryCallback: func(url string) (*repository.ChartRepository, error) {
return &repository.ChartRepository{URL: "https://grafana.github.io/helm-charts/"}, nil
},
wantChartFunc: func(g *WithT, c *helmchart.Chart) {
@ -235,8 +171,8 @@ func build(t *testing.T, mockRepo repository.Downloader) {
g.Expect(err).ToNot(HaveOccurred())
dm := NewDependencyManager(
WithRepositories(tt.downloaders),
WithDownloaderCallback(tt.getChartDownloaderCallback),
WithRepositories(tt.repositories),
WithRepositoryCallback(tt.getChartRepositoryCallback),
)
absBaseDir, err := filepath.Abs(tt.baseDir)
g.Expect(err).ToNot(HaveOccurred())
@ -385,15 +321,15 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
tests := []struct {
name string
downloaders map[string]repository.Downloader
repositories map[string]*repository.ChartRepository
dep *helmchart.Dependency
wantFunc func(g *WithT, c *helmchart.Chart)
wantErr string
}{
{
name: "adds remote dependency",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {
Client: &mockGetter{
Response: chartB,
},
@ -423,24 +359,25 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
},
{
name: "resolve repository error",
downloaders: map[string]repository.Downloader{},
repositories: map[string]*repository.ChartRepository{},
dep: &helmchart.Dependency{
Repository: "https://example.com",
},
wantErr: "no chart repository for URL",
},
{
name: "resolve aliased repository error",
downloaders: map[string]repository.Downloader{},
dep: &helmchart.Dependency{
Repository: "@fantastic-charts",
},
wantErr: "aliased repository dependency is not supported",
},
// Commented due to 770 being reverted
// {
// name: "resolve aliased repository error",
// downloaders: map[string]repository.Downloader{},
// dep: &helmchart.Dependency{
// Repository: "@fantastic-charts",
// },
// wantErr: "aliased repository dependency is not supported",
// },
{
name: "strategic load error",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {
CachePath: "/invalid/cache/path/foo",
RWMutex: &sync.RWMutex{},
},
@ -452,8 +389,8 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
},
{
name: "repository get error",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {
Index: &repo.IndexFile{},
RWMutex: &sync.RWMutex{},
},
@ -465,8 +402,8 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
},
{
name: "repository version constraint error",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {
Index: &repo.IndexFile{
Entries: map[string]repo.ChartVersions{
chartName: {
@ -491,8 +428,8 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
},
{
name: "repository chart download error",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {
Index: &repo.IndexFile{
Entries: map[string]repo.ChartVersions{
chartName: {
@ -517,8 +454,8 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
},
{
name: "chart load error",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {
Client: &mockGetter{},
Index: &repo.IndexFile{
Entries: map[string]repo.ChartVersions{
@ -549,137 +486,7 @@ func TestDependencyManager_addRemoteDependency(t *testing.T) {
g := NewWithT(t)
dm := &DependencyManager{
downloaders: tt.downloaders,
}
chart := &helmchart.Chart{}
err := dm.addRemoteDependency(&chartWithLock{Chart: chart}, tt.dep)
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
return
}
g.Expect(err).ToNot(HaveOccurred())
if tt.wantFunc != nil {
tt.wantFunc(g, chart)
}
})
}
}
func TestDependencyManager_addRemoteOCIDependency(t *testing.T) {
g := NewWithT(t)
chartB, err := os.ReadFile("../testdata/charts/helmchart-0.1.0.tgz")
g.Expect(err).ToNot(HaveOccurred())
g.Expect(chartB).ToNot(BeEmpty())
tests := []struct {
name string
downloaders map[string]repository.Downloader
dep *helmchart.Dependency
wantFunc func(g *WithT, c *helmchart.Chart)
wantErr string
}{
{
name: "adds remote oci dependency",
downloaders: map[string]repository.Downloader{
"oci://example.com": &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
Client: &mockGetter{
Response: chartB,
},
RegistryClient: &mockTagsGetter{
tags: map[string][]string{
"helmchart": {"0.1.0"},
},
},
},
},
dep: &helmchart.Dependency{
Name: chartName,
Repository: "oci://example.com",
},
wantFunc: func(g *WithT, c *helmchart.Chart) {
g.Expect(c.Dependencies()).To(HaveLen(1))
},
},
{
name: "remote oci repository fetch tags error",
downloaders: map[string]repository.Downloader{
"oci://example.com": &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
RegistryClient: &mockTagsGetter{
tags: map[string][]string{},
},
},
},
dep: &helmchart.Dependency{
Name: chartName,
Repository: "oci://example.com",
},
wantErr: fmt.Sprintf("no tags found for %s", chartName),
},
{
name: "remote oci repository version constraint error",
downloaders: map[string]repository.Downloader{
"oci://example.com": &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
Client: &mockGetter{
Response: chartB,
},
RegistryClient: &mockTagsGetter{
tags: map[string][]string{
"helmchart": {"0.1.0"},
},
},
},
},
dep: &helmchart.Dependency{
Name: chartName,
Version: "0.2.0",
Repository: "oci://example.com",
},
wantErr: "could not locate a version matching provided version string 0.2.0",
},
{
name: "chart load error",
downloaders: map[string]repository.Downloader{
"oci://example.com": &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
Client: &mockGetter{},
RegistryClient: &mockTagsGetter{
tags: map[string][]string{
"helmchart": {"0.1.0"},
},
},
},
},
dep: &helmchart.Dependency{
Name: chartName,
Version: chartVersion,
Repository: "oci://example.com",
},
wantErr: "failed to load downloaded archive of version '0.1.0'",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
dm := &DependencyManager{
downloaders: tt.downloaders,
repositories: tt.repositories,
}
chart := &helmchart.Chart{}
err := dm.addRemoteDependency(&chartWithLock{Chart: chart}, tt.dep)
@ -699,98 +506,54 @@ func TestDependencyManager_addRemoteOCIDependency(t *testing.T) {
func TestDependencyManager_resolveRepository(t *testing.T) {
tests := []struct {
name string
downloaders map[string]repository.Downloader
getChartDownloaderCallback GetChartDownloaderCallback
repositories map[string]*repository.ChartRepository
getChartRepositoryCallback GetChartRepositoryCallback
url string
want repository.Downloader
wantDownloaders map[string]repository.Downloader
want *repository.ChartRepository
wantRepositories map[string]*repository.ChartRepository
wantErr string
}{
{
name: "resolves from downloaders index",
name: "resolves from repositories index",
url: "https://example.com",
downloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{URL: "https://example.com"},
repositories: map[string]*repository.ChartRepository{
"https://example.com/": {URL: "https://example.com"},
},
want: &repository.ChartRepository{URL: "https://example.com"},
},
{
name: "resolves from callback",
url: "https://example.com",
getChartDownloaderCallback: func(_ string) (repository.Downloader, error) {
getChartRepositoryCallback: func(url string) (*repository.ChartRepository, error) {
return &repository.ChartRepository{URL: "https://example.com"}, nil
},
want: &repository.ChartRepository{URL: "https://example.com"},
wantDownloaders: map[string]repository.Downloader{
"https://example.com/": &repository.ChartRepository{URL: "https://example.com"},
wantRepositories: map[string]*repository.ChartRepository{
"https://example.com/": {URL: "https://example.com"},
},
},
{
name: "error from callback",
url: "https://example.com",
getChartDownloaderCallback: func(_ string) (repository.Downloader, error) {
getChartRepositoryCallback: func(url string) (*repository.ChartRepository, error) {
return nil, errors.New("a very unique error")
},
wantErr: "a very unique error",
wantDownloaders: map[string]repository.Downloader{},
wantRepositories: map[string]*repository.ChartRepository{},
},
{
name: "error on not found",
url: "https://example.com",
wantErr: "no chart repository for URL",
},
{
name: "resolves from oci repository",
url: "oci://example.com",
downloaders: map[string]repository.Downloader{
"oci://example.com": &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
},
},
want: &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
},
},
{
name: "resolves oci repository from callback",
url: "oci://example.com",
getChartDownloaderCallback: func(_ string) (repository.Downloader, error) {
return &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com"},
}, nil
},
want: &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
},
wantDownloaders: map[string]repository.Downloader{
"oci://example.com": &repository.OCIChartRepository{
URL: url.URL{
Scheme: "oci",
Host: "example.com",
},
},
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
dm := &DependencyManager{
downloaders: tt.downloaders,
getChartDownloaderCallback: tt.getChartDownloaderCallback,
repositories: tt.repositories,
getRepositoryCallback: tt.getChartRepositoryCallback,
}
got, err := dm.resolveRepository(tt.url)
@ -803,8 +566,8 @@ func TestDependencyManager_resolveRepository(t *testing.T) {
g.Expect(err).ToNot(HaveOccurred())
g.Expect(got).To(Equal(tt.want))
if tt.wantDownloaders != nil {
g.Expect(dm.downloaders).To(Equal(tt.wantDownloaders))
if tt.wantRepositories != nil {
g.Expect(dm.repositories).To(Equal(tt.wantRepositories))
}
})
}

View File

@ -21,7 +21,6 @@ import (
"os"
"helm.sh/helm/v3/pkg/registry"
"k8s.io/apimachinery/pkg/util/errors"
)
// ClientGenerator generates a registry client and a temporary credential file.
@ -31,25 +30,16 @@ func ClientGenerator(isLogin bool) (*registry.Client, string, error) {
if isLogin {
// create a temporary file to store the credentials
// this is needed because otherwise the credentials are stored in ~/.docker/config.json.
credentialsFile, err := os.CreateTemp("", "credentials")
credentialFile, err := os.CreateTemp("", "credentials")
if err != nil {
return nil, "", err
}
var errs []error
rClient, err := registry.NewClient(registry.ClientOptWriter(io.Discard), registry.ClientOptCredentialsFile(credentialsFile.Name()))
rClient, err := registry.NewClient(registry.ClientOptWriter(io.Discard), registry.ClientOptCredentialsFile(credentialFile.Name()))
if err != nil {
errs = append(errs, err)
// attempt to delete the temporary file
if credentialsFile != nil {
err := os.Remove(credentialsFile.Name())
if err != nil {
errs = append(errs, err)
return nil, "", err
}
}
return nil, "", errors.NewAggregate(errs)
}
return rClient, credentialsFile.Name(), nil
return rClient, credentialFile.Name(), nil
}
rClient, err := registry.NewClient(registry.ClientOptWriter(io.Discard))

View File

@ -35,7 +35,6 @@ import (
"github.com/Masterminds/semver/v3"
"helm.sh/helm/v3/pkg/getter"
"helm.sh/helm/v3/pkg/repo"
kerrors "k8s.io/apimachinery/pkg/util/errors"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/version"
@ -151,15 +150,10 @@ func newChartRepository() *ChartRepository {
}
}
// GetChartVersion returns the repo.ChartVersion for the given name, the version is expected
// Get returns the repo.ChartVersion for the given name, the version is expected
// to be a semver.Constraints compatible string. If version is empty, the latest
// stable version will be returned and prerelease versions will be ignored.
func (r *ChartRepository) GetChartVersion(name, ver string) (*repo.ChartVersion, error) {
// See if we already have the index in cache or try to load it.
if err := r.StrategicallyLoadIndex(); err != nil {
return nil, err
}
func (r *ChartRepository) Get(name, ver string) (*repo.ChartVersion, error) {
r.RLock()
defer r.RUnlock()
@ -477,23 +471,6 @@ func (r *ChartRepository) Unload() {
r.Index = nil
}
// Clear caches the index in memory before unloading it.
// It cleans up temporary files and directories created by the repository.
func (r *ChartRepository) Clear() error {
var errs []error
if err := r.CacheIndexInMemory(); err != nil {
errs = append(errs, err)
}
r.Unload()
if err := r.RemoveCache(); err != nil {
errs = append(errs, err)
}
return kerrors.NewAggregate(errs)
}
// SetMemCache sets the cache to use for this repository.
func (r *ChartRepository) SetMemCache(key string, c *cache.Cache, ttl time.Duration, rec RecordMetricsFunc) {
r.IndexKey = key

View File

@ -181,7 +181,7 @@ func TestChartRepository_Get(t *testing.T) {
t.Run(tt.name, func(t *testing.T) {
g := NewWithT(t)
cv, err := r.GetChartVersion(tt.chartName, tt.chartVersion)
cv, err := r.Get(tt.chartName, tt.chartVersion)
if tt.wantErr != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))

View File

@ -21,7 +21,6 @@ import (
"crypto/tls"
"fmt"
"net/url"
"os"
"path"
"sort"
"strings"
@ -61,8 +60,6 @@ type OCIChartRepository struct {
// RegistryClient is a client to use while downloading tags or charts from a registry.
RegistryClient RegistryClient
// credentialsFile is a temporary credentials file to use while downloading tags or charts from a registry.
credentialsFile string
}
// OCIChartRepositoryOption is a function that can be passed to NewOCIChartRepository
@ -97,14 +94,6 @@ func WithOCIGetterOptions(getterOpts []getter.Option) OCIChartRepositoryOption {
}
}
// WithCredentialsFile returns a ChartRepositoryOption that will set the credentials file
func WithCredentialsFile(credentialsFile string) OCIChartRepositoryOption {
return func(r *OCIChartRepository) error {
r.credentialsFile = credentialsFile
return nil
}
}
// NewOCIChartRepository constructs and returns a new ChartRepository with
// the ChartRepository.Client configured to the getter.Getter for the
// repository URL scheme. It returns an error on URL parsing failures.
@ -126,18 +115,18 @@ func NewOCIChartRepository(repositoryURL string, chartRepoOpts ...OCIChartReposi
return r, nil
}
// GetChartVersion returns the repo.ChartVersion for the given name, the version is expected
// Get returns the repo.ChartVersion for the given name, the version is expected
// to be a semver.Constraints compatible string. If version is empty, the latest
// stable version will be returned and prerelease versions will be ignored.
// adapted from https://github.com/helm/helm/blob/49819b4ef782e80b0c7f78c30bd76b51ebb56dc8/pkg/downloader/chart_downloader.go#L162
func (r *OCIChartRepository) GetChartVersion(name, ver string) (*repo.ChartVersion, error) {
func (r *OCIChartRepository) Get(name, ver string) (*repo.ChartVersion, error) {
// Find chart versions matching the given name.
// Either in an index file or from a registry.
cpURL := r.URL
cpURL.Path = path.Join(cpURL.Path, name)
cvs, err := r.getTags(cpURL.String())
if err != nil {
return nil, fmt.Errorf("could not get tags for %q: %s", name, err)
return nil, err
}
if len(cvs) == 0 {
@ -164,7 +153,7 @@ func (r *OCIChartRepository) getTags(ref string) ([]string, error) {
// Retrieve list of repository tags
tags, err := r.RegistryClient.Tags(strings.TrimPrefix(ref, fmt.Sprintf("%s://", registry.OCIScheme)))
if err != nil {
return nil, fmt.Errorf("could not fetch tags for %q: %s", ref, err)
return nil, err
}
if len(tags) == 0 {
return nil, fmt.Errorf("unable to locate any tags in provided repository: %s", ref)
@ -217,23 +206,6 @@ func (r *OCIChartRepository) Logout() error {
return nil
}
// HasCredentials returns true if the OCIChartRepository has credentials.
func (r *OCIChartRepository) HasCredentials() bool {
return r.credentialsFile != ""
}
// Clear deletes the OCI registry credentials file.
func (r *OCIChartRepository) Clear() error {
// clean the credentials file if it exists
if r.credentialsFile != "" {
if err := os.Remove(r.credentialsFile); err != nil {
return err
}
}
r.credentialsFile = ""
return nil
}
// getLastMatchingVersionOrConstraint returns the last version that matches the given version string.
// If the version string is empty, the highest available version is returned.
func getLastMatchingVersionOrConstraint(cvs []string, ver string) (string, error) {

View File

@ -183,7 +183,7 @@ func TestOCIChartRepository_Get(t *testing.T) {
g.Expect(r).ToNot(BeNil())
chart := "podinfo"
cv, err := r.GetChartVersion(chart, tc.version)
cv, err := r.Get(chart, tc.version)
if tc.expectedErr != "" {
g.Expect(err).To(HaveOccurred())
g.Expect(err.Error()).To(Equal(tc.expectedErr))

View File

@ -1,35 +0,0 @@
/*
Copyright 2022 The Flux authors
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package repository
import (
"bytes"
"helm.sh/helm/v3/pkg/repo"
)
// Downloader is used to download a chart from a remote Helm repository or OCI Helm repository.
type Downloader interface {
// GetChartVersion returns the repo.ChartVersion for the given name and version
// from the remote Helm repository or OCI Helm repository.
GetChartVersion(name, version string) (*repo.ChartVersion, error)
// DownloadChart downloads a chart from the remote Helm repository or OCI Helm repository.
DownloadChart(chart *repo.ChartVersion) (*bytes.Buffer, error)
// Clear removes all temporary files created by the downloader, caching the files if the cache is configured,
// and calling garbage collector to remove unused files.
Clear() error
}

View File

@ -39,13 +39,10 @@ func NormalizeURL(repositoryURL string) string {
if repositoryURL == "" {
return ""
}
if strings.Contains(repositoryURL, helmreg.OCIScheme) {
return strings.TrimRight(repositoryURL, "/")
}
return strings.TrimRight(repositoryURL, "/") + "/"
}
// ValidateDepURL returns an error if the given depended repository URL declaration is not supported

View File

@ -48,16 +48,6 @@ func TestNormalizeURL(t *testing.T) {
url: "",
want: "",
},
{
name: "oci with slash",
url: "oci://example.com/",
want: "oci://example.com",
},
{
name: "oci double slash",
url: "oci://example.com//",
want: "oci://example.com",
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {