From f1362bd3a99dd556cd23626b1ca196d7bb51b6bd Mon Sep 17 00:00:00 2001 From: Aurel Canciu Date: Wed, 28 Oct 2020 00:19:29 +0200 Subject: [PATCH] Implement DepdendencyManager for non-packaged Non-packaged charts that don't have their dependencies present in charts/ will now have these dependencies built using the DependencyManager. The idea behind it is to replicate the logic implemeneted in Helm's downloader.Manager with the support for already existing HelmRepository resources and their chart retrieval capabilities. Signed-off-by: Aurel Canciu --- controllers/dependency_manager.go | 142 ++++++++++ controllers/dependency_manager_test.go | 203 +++++++++++++++ controllers/helmchart_controller.go | 153 ++++++++++- controllers/helmchart_controller_test.go | 243 ++++++++++++++++++ .../testdata/charts/helmchart-0.1.0.tgz | Bin 0 -> 3277 bytes go.mod | 1 + 6 files changed, 733 insertions(+), 9 deletions(-) create mode 100644 controllers/dependency_manager.go create mode 100644 controllers/dependency_manager_test.go create mode 100644 controllers/testdata/charts/helmchart-0.1.0.tgz diff --git a/controllers/dependency_manager.go b/controllers/dependency_manager.go new file mode 100644 index 00000000..22a3dd8e --- /dev/null +++ b/controllers/dependency_manager.go @@ -0,0 +1,142 @@ +/* +Copyright 2020 The Flux CD contributors. + +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 controllers + +import ( + "context" + "fmt" + "os" + "path" + "path/filepath" + "strings" + + "github.com/Masterminds/semver/v3" + "github.com/fluxcd/source-controller/internal/helm" + "golang.org/x/sync/errgroup" + helmchart "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/chart/loader" +) + +// DependencyWithRepository is a container for a dependency and its respective +// repository +type DependencyWithRepository struct { + Dependency *helmchart.Dependency + Repo *helm.ChartRepository +} + +// DependencyManager manages dependencies for helm charts +type DependencyManager struct { + Chart *helmchart.Chart + ChartPath string + Dependencies []*DependencyWithRepository +} + +// Build compiles and builds the chart dependencies +func (dm *DependencyManager) Build() error { + if dm.Dependencies == nil { + return nil + } + + ctx := context.Background() + errs, ctx := errgroup.WithContext(ctx) + + for _, item := range dm.Dependencies { + dep := item.Dependency + chartRepo := item.Repo + errs.Go(func() error { + var ( + ch *helmchart.Chart + err error + ) + if strings.HasPrefix(dep.Repository, "file://") { + ch, err = chartForLocalDependency(dep, dm.ChartPath) + } else { + ch, err = chartForRemoteDependency(dep, chartRepo) + } + if err != nil { + return err + } + dm.Chart.AddDependency(ch) + return nil + }) + } + + return errs.Wait() +} + +func chartForLocalDependency(dep *helmchart.Dependency, cp string) (*helmchart.Chart, error) { + origPath, err := filepath.Abs(path.Join(cp, strings.TrimPrefix(dep.Repository, "file://"))) + if err != nil { + return nil, err + } + + if _, err := os.Stat(origPath); os.IsNotExist(err) { + err := fmt.Errorf("chart path %s not found: %w", origPath, err) + return nil, err + } else if err != nil { + return nil, err + } + + ch, err := loader.Load(origPath) + if err != nil { + return nil, err + } + + constraint, err := semver.NewConstraint(dep.Version) + if err != nil { + err := fmt.Errorf("dependency %s has an invalid version/constraint format: %w", dep.Name, err) + return nil, err + } + + v, err := semver.NewVersion(ch.Metadata.Version) + if err != nil { + return nil, err + } + + if !constraint.Check(v) { + err = fmt.Errorf("can't get a valid version for dependency %s", dep.Name) + return nil, err + } + + return ch, nil +} + +func chartForRemoteDependency(dep *helmchart.Dependency, chartrepo *helm.ChartRepository) (*helmchart.Chart, error) { + if chartrepo == nil { + err := fmt.Errorf("chartrepo should not be nil") + return nil, err + } + + // Lookup the chart version in the chart repository index + chartVer, err := chartrepo.Get(dep.Name, dep.Version) + if err != nil { + return nil, err + } + + // Download chart + res, err := chartrepo.DownloadChart(chartVer) + if err != nil { + return nil, err + } + + ch, err := loader.LoadArchive(res) + if err != nil { + return nil, err + } + + return ch, nil +} diff --git a/controllers/dependency_manager_test.go b/controllers/dependency_manager_test.go new file mode 100644 index 00000000..80b1df91 --- /dev/null +++ b/controllers/dependency_manager_test.go @@ -0,0 +1,203 @@ +/* +Copyright 2020 The Flux CD contributors. + +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 controllers + +import ( + "bytes" + "io/ioutil" + "strings" + "testing" + + "github.com/fluxcd/source-controller/internal/helm" + helmchart "helm.sh/helm/v3/pkg/chart" + "helm.sh/helm/v3/pkg/getter" + "helm.sh/helm/v3/pkg/repo" +) + +var ( + helmPackageFile = "testdata/charts/helmchart-0.1.0.tgz" + + localDepFixture helmchart.Dependency = helmchart.Dependency{ + Name: "helmchart", + Version: "0.1.0", + Repository: "file://../helmchart", + } + remoteDepFixture helmchart.Dependency = helmchart.Dependency{ + Name: "helmchart", + Version: "0.1.0", + Repository: "https://example.com/charts", + } + chartFixture helmchart.Chart = helmchart.Chart{ + Metadata: &helmchart.Metadata{ + Name: "test", + }, + } +) + +func TestBuild_WithEmptyDependencies(t *testing.T) { + dm := DependencyManager{ + Dependencies: nil, + } + if err := dm.Build(); err != nil { + t.Errorf("Build() should return nil") + } +} + +func TestBuild_WithLocalChart(t *testing.T) { + loc := localDepFixture + chart := chartFixture + dm := DependencyManager{ + Chart: &chart, + ChartPath: "testdata/charts/helmchart", + Dependencies: []*DependencyWithRepository{ + { + Dependency: &loc, + Repo: nil, + }, + }, + } + + if err := dm.Build(); err != nil { + t.Errorf("Build() expected to not return error: %s", err) + } + + deps := dm.Chart.Dependencies() + if len(deps) != 1 { + t.Fatalf("chart expected to have one dependency registered") + } + if deps[0].Metadata.Name != localDepFixture.Name { + t.Errorf("chart dependency has incorrect name, expected: %s, got: %s", localDepFixture.Name, deps[0].Metadata.Name) + } + if deps[0].Metadata.Version != localDepFixture.Version { + t.Errorf("chart dependency has incorrect version, expected: %s, got: %s", localDepFixture.Version, deps[0].Metadata.Version) + } + + tests := []struct { + name string + dep helmchart.Dependency + expectError string + }{ + { + name: "invalid path", + dep: helmchart.Dependency{ + Name: "helmchart", + Version: "0.1.0", + Repository: "file://../invalid", + }, + expectError: "no such file or directory", + }, + { + name: "invalid version constraint format", + dep: helmchart.Dependency{ + Name: "helmchart", + Version: "!2.0", + Repository: "file://../helmchart", + }, + expectError: "has an invalid version/constraint format", + }, + { + name: "invalid version", + dep: helmchart.Dependency{ + Name: "helmchart", + Version: "1.0.0", + Repository: "file://../helmchart", + }, + expectError: "can't get a valid version for dependency", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + c := chartFixture + dm = DependencyManager{ + Chart: &c, + ChartPath: "testdata/charts/helmchart", + Dependencies: []*DependencyWithRepository{ + { + Dependency: &tt.dep, + Repo: nil, + }, + }, + } + + if err := dm.Build(); err == nil { + t.Errorf("Build() expected to return error") + } else if !strings.Contains(err.Error(), tt.expectError) { + t.Errorf("Build() expected to return error: %s, got: %s", tt.expectError, err) + } + if len(dm.Chart.Dependencies()) > 0 { + t.Fatalf("chart expected to have no dependencies registered") + } + }) + } +} + +func TestBuild_WithRemoteChart(t *testing.T) { + chart := chartFixture + b, err := ioutil.ReadFile(helmPackageFile) + if err != nil { + t.Fatal(err) + } + i := repo.NewIndexFile() + i.Add(&helmchart.Metadata{Name: "helmchart", Version: "0.1.0"}, "helmchart-0.1.0.tgz", "http://example.com/charts", "sha256:1234567890") + mg := mockGetter{response: b} + cr := &helm.ChartRepository{ + URL: remoteDepFixture.Repository, + Index: i, + Client: &mg, + } + dm := DependencyManager{ + Chart: &chart, + Dependencies: []*DependencyWithRepository{ + { + Dependency: &remoteDepFixture, + Repo: cr, + }, + }, + } + + if err := dm.Build(); err != nil { + t.Errorf("Build() expected to not return error: %s", err) + } + + deps := dm.Chart.Dependencies() + if len(deps) != 1 { + t.Fatalf("chart expected to have one dependency registered") + } + if deps[0].Metadata.Name != remoteDepFixture.Name { + t.Errorf("chart dependency has incorrect name, expected: %s, got: %s", remoteDepFixture.Name, deps[0].Metadata.Name) + } + if deps[0].Metadata.Version != remoteDepFixture.Version { + t.Errorf("chart dependency has incorrect version, expected: %s, got: %s", remoteDepFixture.Version, deps[0].Metadata.Version) + } + + // When repo is not set + dm.Dependencies[0].Repo = nil + if err := dm.Build(); err == nil { + t.Errorf("Build() expected to return error") + } else if !strings.Contains(err.Error(), "chartrepo should not be nil") { + t.Errorf("Build() expected to return different error, got: %s", err) + } +} + +type mockGetter struct { + response []byte +} + +func (g *mockGetter) Get(url string, options ...getter.Option) (*bytes.Buffer, error) { + return bytes.NewBuffer(g.response), nil +} diff --git a/controllers/helmchart_controller.go b/controllers/helmchart_controller.go index 3567f5de..398a8860 100644 --- a/controllers/helmchart_controller.go +++ b/controllers/helmchart_controller.go @@ -28,6 +28,7 @@ import ( "github.com/fluxcd/pkg/apis/meta" "github.com/go-logr/logr" + helmchart "helm.sh/helm/v3/pkg/chart" "helm.sh/helm/v3/pkg/chart/loader" "helm.sh/helm/v3/pkg/chartutil" "helm.sh/helm/v3/pkg/getter" @@ -433,18 +434,130 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context, // Either (re)package the chart with the declared default values file, // or write the chart directly to storage. pkgPath := chartPath - isDir := chartFileInfo.IsDir() - if isDir || (chart.Spec.ValuesFile != "" && chart.Spec.ValuesFile != chartutil.ValuesfileName) { + isValuesFileOverriden := false + if chart.Spec.ValuesFile != "" && chart.Spec.ValuesFile != chartutil.ValuesfileName { // Overwrite default values if configured - if changed, err := helm.OverwriteChartDefaultValues(helmChart, chart.Spec.ValuesFile); err != nil { + isValuesFileOverriden, err = helm.OverwriteChartDefaultValues(helmChart, chart.Spec.ValuesFile) + if err != nil { return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err - } else if isDir || changed { - // Package the chart - pkgPath, err = chartutil.Save(helmChart, tmpDir) - if err != nil { - err = fmt.Errorf("chart package error: %w", err) - return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err + } + } + + isDir := chartFileInfo.IsDir() + switch { + case isDir: + // Load dependencies + if err = chartutil.ProcessDependencies(helmChart, helmChart.Values); err != nil { + err = fmt.Errorf("failed to process chart dependencies: %w", err) + return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err + } + + // Determine chart dependencies + deps := helmChart.Dependencies() + reqs := helmChart.Metadata.Dependencies + lock := helmChart.Lock + if lock != nil { + // Load from lockfile if exists + reqs = lock.Dependencies + } + var dwr []*DependencyWithRepository + for _, dep := range reqs { + // Exclude existing dependencies + for _, existing := range deps { + if existing.Name() == dep.Name { + continue + } } + + // Continue loop if file scheme detected + if strings.HasPrefix(dep.Repository, "file://") { + dwr = append(dwr, &DependencyWithRepository{ + Dependency: dep, + Repo: nil, + }) + continue + } + + // Discover existing HelmRepository by URL + repository, err := r.resolveDependencyRepository(ctx, dep, chart.Namespace) + if err != nil { + repository = &sourcev1.HelmRepository{ + Spec: sourcev1.HelmRepositorySpec{ + URL: dep.Repository, + }, + } + } + + // Configure ChartRepository getter options + var clientOpts []getter.Option + if secret, err := r.getHelmRepositorySecret(ctx, repository); err != nil { + return sourcev1.HelmChartNotReady(chart, sourcev1.AuthenticationFailedReason, err.Error()), err + } else if secret != nil { + opts, cleanup, err := helm.ClientOptionsFromSecret(*secret) + if err != nil { + err = fmt.Errorf("auth options error: %w", err) + return sourcev1.HelmChartNotReady(chart, sourcev1.AuthenticationFailedReason, err.Error()), err + } + defer cleanup() + + clientOpts = opts + } + + // Initialize the chart repository and load the index file + chartRepo, err := helm.NewChartRepository(repository.Spec.URL, r.Getters, clientOpts) + if err != nil { + switch err.(type) { + case *url.Error: + return sourcev1.HelmChartNotReady(chart, sourcev1.URLInvalidReason, err.Error()), err + default: + return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err + } + } + if repository.Status.Artifact != nil { + indexFile, err := os.Open(r.Storage.LocalPath(*repository.GetArtifact())) + if err != nil { + return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err + } + b, err := ioutil.ReadAll(indexFile) + if err != nil { + return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err + } + if err = chartRepo.LoadIndex(b); err != nil { + return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err + } + } else { + // Download index + err = chartRepo.DownloadIndex() + if err != nil { + return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPullFailedReason, err.Error()), err + } + } + + dwr = append(dwr, &DependencyWithRepository{ + Dependency: dep, + Repo: chartRepo, + }) + } + + // Construct dependencies for chart if any + if len(dwr) > 0 { + dm := &DependencyManager{ + Chart: helmChart, + ChartPath: chartPath, + Dependencies: dwr, + } + err = dm.Build() + if err != nil { + return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err + } + } + + fallthrough + case isValuesFileOverriden: + pkgPath, err = chartutil.Save(helmChart, tmpDir) + if err != nil { + err = fmt.Errorf("chart package error: %w", err) + return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err } } @@ -590,6 +703,28 @@ func (r *HelmChartReconciler) indexHelmRepositoryByURL(o runtime.Object) []strin return nil } +func (r *HelmChartReconciler) resolveDependencyRepository(ctx context.Context, dep *helmchart.Dependency, namespace string) (*sourcev1.HelmRepository, error) { + url := helm.NormalizeChartRepositoryURL(dep.Repository) + if url == "" { + return nil, fmt.Errorf("invalid repository URL") + } + + listOpts := []client.ListOption{ + client.InNamespace(namespace), + client.MatchingField(sourcev1.HelmRepositoryURLIndexKey, url), + } + var list sourcev1.HelmRepositoryList + err := r.Client.List(ctx, &list, listOpts...) + if err != nil { + return nil, fmt.Errorf("unable to retrieve HelmRepositoryList: %w", err) + } + if len(list.Items) > 0 { + return &list.Items[0], nil + } + + return nil, fmt.Errorf("no HelmRepository found") +} + func (r *HelmChartReconciler) getHelmRepositorySecret(ctx context.Context, repository *sourcev1.HelmRepository) (*corev1.Secret, error) { if repository.Spec.SecretRef != nil { name := types.NamespacedName{ diff --git a/controllers/helmchart_controller_test.go b/controllers/helmchart_controller_test.go index 24824ccf..d5e5c500 100644 --- a/controllers/helmchart_controller_test.go +++ b/controllers/helmchart_controller_test.go @@ -693,4 +693,247 @@ var _ = Describe("HelmChartReconciler", func() { }, timeout, interval).Should(BeTrue()) }) }) + + Context("HelmChart from GitRepository with HelmRepository dependency", func() { + var ( + namespace *corev1.Namespace + gitServer *gittestserver.GitServer + helmServer *helmtestserver.HelmServer + err error + ) + + BeforeEach(func() { + namespace = &corev1.Namespace{ + ObjectMeta: metav1.ObjectMeta{Name: "test-git-repository-" + randStringRunes(5)}, + } + err = k8sClient.Create(context.Background(), namespace) + Expect(err).NotTo(HaveOccurred(), "failed to create test namespace") + + gitServer, err = gittestserver.NewTempGitServer() + Expect(err).NotTo(HaveOccurred()) + gitServer.AutoCreate() + Expect(gitServer.StartHTTP()).To(Succeed()) + + helmServer, err = helmtestserver.NewTempHelmServer() + Expect(err).To(Succeed()) + helmServer.Start() + }) + + AfterEach(func() { + gitServer.StopHTTP() + os.RemoveAll(gitServer.Root()) + + os.RemoveAll(helmServer.Root()) + helmServer.Stop() + + err = k8sClient.Delete(context.Background(), namespace) + Expect(err).NotTo(HaveOccurred(), "failed to delete test namespace") + }) + + It("Creates artifacts for", func() { + helmServer.Stop() + var username, password = "john", "doe" + helmServer.WithMiddleware(func(handler http.Handler) http.Handler { + return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + u, p, ok := r.BasicAuth() + if !ok || username != u || password != p { + w.WriteHeader(401) + return + } + handler.ServeHTTP(w, r) + }) + }) + helmServer.Start() + + Expect(helmServer.PackageChart(path.Join("testdata/charts/helmchart"))).Should(Succeed()) + Expect(helmServer.GenerateIndex()).Should(Succeed()) + + secretKey := types.NamespacedName{ + Name: "helmrepository-auth-" + randStringRunes(5), + Namespace: namespace.Name, + } + secret := &corev1.Secret{ + ObjectMeta: metav1.ObjectMeta{ + Name: secretKey.Name, + Namespace: secretKey.Namespace, + }, + Data: map[string][]byte{ + "username": []byte(username), + "password": []byte(password), + }, + } + Expect(k8sClient.Create(context.Background(), secret)).Should(Succeed()) + + By("Creating repository and waiting for artifact") + helmRepositoryKey := types.NamespacedName{ + Name: "helmrepository-sample-" + randStringRunes(5), + Namespace: namespace.Name, + } + helmRepository := &sourcev1.HelmRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: helmRepositoryKey.Name, + Namespace: helmRepositoryKey.Namespace, + }, + Spec: sourcev1.HelmRepositorySpec{ + URL: helmServer.URL(), + SecretRef: &corev1.LocalObjectReference{ + Name: secretKey.Name, + }, + Interval: metav1.Duration{Duration: pullInterval}, + }, + } + Expect(k8sClient.Create(context.Background(), helmRepository)).Should(Succeed()) + defer k8sClient.Delete(context.Background(), helmRepository) + + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), helmRepositoryKey, helmRepository) + return helmRepository.Status.Artifact != nil + }, timeout, interval).Should(BeTrue()) + + fs := memfs.New() + gitrepo, err := git.Init(memory.NewStorage(), fs) + Expect(err).NotTo(HaveOccurred()) + + wt, err := gitrepo.Worktree() + Expect(err).NotTo(HaveOccurred()) + + u, err := url.Parse(gitServer.HTTPAddress()) + Expect(err).NotTo(HaveOccurred()) + u.Path = path.Join(u.Path, fmt.Sprintf("repository-%s.git", randStringRunes(5))) + + _, err = gitrepo.CreateRemote(&config.RemoteConfig{ + Name: "origin", + URLs: []string{u.String()}, + }) + Expect(err).NotTo(HaveOccurred()) + + chartDir := "testdata/charts/helmchartwithdeps" + Expect(filepath.Walk(chartDir, func(p string, fi os.FileInfo, err error) error { + if err != nil { + return err + } + + switch { + case fi.Mode().IsDir(): + return fs.MkdirAll(p, os.ModeDir) + case !fi.Mode().IsRegular(): + return nil + } + + b, err := ioutil.ReadFile(p) + if err != nil { + return err + } + + ff, err := fs.Create(p) + if err != nil { + return err + } + if _, err := ff.Write(b); err != nil { + return err + } + _ = ff.Close() + _, err = wt.Add(p) + + return err + })).To(Succeed()) + + By("Configuring the chart dependency") + filePath := fs.Join(chartDir, chartutil.ChartfileName) + f, err := fs.OpenFile(filePath, os.O_RDWR, os.FileMode(0600)) + Expect(err).NotTo(HaveOccurred()) + + b := make([]byte, 2048) + n, err := f.Read(b) + Expect(err).NotTo(HaveOccurred()) + b = b[0:n] + + err = f.Close() + Expect(err).NotTo(HaveOccurred()) + + y := new(helmchart.Metadata) + err = yaml.Unmarshal(b, y) + Expect(err).NotTo(HaveOccurred()) + + y.Dependencies = []*helmchart.Dependency{ + { + Name: "helmchart", + Version: ">=0.1.0", + Repository: helmRepository.Spec.URL, + }, + } + + b, err = yaml.Marshal(y) + Expect(err).NotTo(HaveOccurred()) + + ff, err := fs.Create(filePath) + Expect(err).NotTo(HaveOccurred()) + + _, err = ff.Write(b) + Expect(err).NotTo(HaveOccurred()) + + err = ff.Close() + Expect(err).NotTo(HaveOccurred()) + + _, err = wt.Commit("Helm charts", &git.CommitOptions{ + Author: &object.Signature{ + Name: "John Doe", + Email: "john@example.com", + When: time.Now(), + }, + All: true, + }) + Expect(err).NotTo(HaveOccurred()) + + err = gitrepo.Push(&git.PushOptions{}) + Expect(err).NotTo(HaveOccurred()) + + repositoryKey := types.NamespacedName{ + Name: fmt.Sprintf("git-repository-sample-%s", randStringRunes(5)), + Namespace: namespace.Name, + } + repository := &sourcev1.GitRepository{ + ObjectMeta: metav1.ObjectMeta{ + Name: repositoryKey.Name, + Namespace: repositoryKey.Namespace, + }, + Spec: sourcev1.GitRepositorySpec{ + URL: u.String(), + Interval: metav1.Duration{Duration: indexInterval}, + }, + } + Expect(k8sClient.Create(context.Background(), repository)).Should(Succeed()) + defer k8sClient.Delete(context.Background(), repository) + + key := types.NamespacedName{ + Name: "helmchart-sample-" + randStringRunes(5), + Namespace: namespace.Name, + } + chart := &sourcev1.HelmChart{ + ObjectMeta: metav1.ObjectMeta{ + Name: key.Name, + Namespace: key.Namespace, + }, + Spec: sourcev1.HelmChartSpec{ + Chart: "testdata/charts/helmchartwithdeps", + Version: "*", + SourceRef: sourcev1.LocalHelmChartSourceReference{ + Kind: sourcev1.GitRepositoryKind, + Name: repositoryKey.Name, + }, + Interval: metav1.Duration{Duration: pullInterval}, + }, + } + Expect(k8sClient.Create(context.Background(), chart)).Should(Succeed()) + defer k8sClient.Delete(context.Background(), chart) + + By("Expecting artifact") + got := &sourcev1.HelmChart{} + Eventually(func() bool { + _ = k8sClient.Get(context.Background(), key, got) + return got.Status.Artifact != nil && + storage.ArtifactExist(*got.Status.Artifact) + }, timeout, interval).Should(BeTrue()) + }) + }) }) diff --git a/controllers/testdata/charts/helmchart-0.1.0.tgz b/controllers/testdata/charts/helmchart-0.1.0.tgz new file mode 100644 index 0000000000000000000000000000000000000000..f64a32eeeb54fc24a44390478a3adf5bcb5cf754 GIT binary patch literal 3277 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 literal 0 HcmV?d00001 diff --git a/go.mod b/go.mod index 2f60de8e..7cc9fa3c 100644 --- a/go.mod +++ b/go.mod @@ -21,6 +21,7 @@ require ( github.com/minio/minio-go/v7 v7.0.5 github.com/onsi/ginkgo v1.12.1 github.com/onsi/gomega v1.10.1 + golang.org/x/sync v0.0.0-20190911185100-cd5d95a43a6e helm.sh/helm/v3 v3.3.4 k8s.io/api v0.18.9 k8s.io/apimachinery v0.18.9