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 <aurelcanciu@gmail.com>
This commit is contained in:
parent
38317ab7c0
commit
f1362bd3a9
|
@ -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
|
||||
}
|
|
@ -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
|
||||
}
|
|
@ -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{
|
||||
|
|
|
@ -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())
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
Binary file not shown.
Loading…
Reference in New Issue