Merge pull request #305 from arbourd/values-files

This commit is contained in:
Hidde Beydals 2021-04-19 15:31:53 +02:00 committed by GitHub
commit f56c96fff6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
9 changed files with 477 additions and 41 deletions

View File

@ -45,9 +45,19 @@ type HelmChartSpec struct {
// +required // +required
Interval metav1.Duration `json:"interval"` Interval metav1.Duration `json:"interval"`
// Alternative values file to use as the default chart values, expected to be a // Alternative list of values files to use as the chart values (values.yaml
// relative path in the SourceRef. Ignored when omitted. // is not included by default), expected to be a relative path in the SourceRef.
// Values files are merged in the order of this list with the last file overriding
// the first. Ignored when omitted.
// +optional // +optional
ValuesFiles []string `json:"valuesFiles,omitempty"`
// Alternative values file to use as the default chart values, expected to
// be a relative path in the SourceRef. Deprecated in favor of ValuesFiles,
// for backwards compatibility the file defined here is merged before the
// ValuesFiles items. Ignored when omitted.
// +optional
// +deprecated
ValuesFile string `json:"valuesFile,omitempty"` ValuesFile string `json:"valuesFile,omitempty"`
// This flag tells the controller to suspend the reconciliation of this source. // This flag tells the controller to suspend the reconciliation of this source.
@ -168,6 +178,17 @@ func (in *HelmChart) GetInterval() metav1.Duration {
return in.Spec.Interval return in.Spec.Interval
} }
// GetValuesFiles returns a merged list of ValuesFiles.
func (in *HelmChart) GetValuesFiles() []string {
valuesFiles := in.Spec.ValuesFiles
// Prepend the deprecated ValuesFile to the list
if in.Spec.ValuesFile != "" {
valuesFiles = append([]string{in.Spec.ValuesFile}, valuesFiles...)
}
return valuesFiles
}
// +genclient // +genclient
// +genclient:Namespaced // +genclient:Namespaced
// +kubebuilder:object:root=true // +kubebuilder:object:root=true

View File

@ -324,7 +324,7 @@ func (in *HelmChart) DeepCopyInto(out *HelmChart) {
*out = *in *out = *in
out.TypeMeta = in.TypeMeta out.TypeMeta = in.TypeMeta
in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) in.ObjectMeta.DeepCopyInto(&out.ObjectMeta)
out.Spec = in.Spec in.Spec.DeepCopyInto(&out.Spec)
in.Status.DeepCopyInto(&out.Status) in.Status.DeepCopyInto(&out.Status)
} }
@ -383,6 +383,11 @@ func (in *HelmChartSpec) DeepCopyInto(out *HelmChartSpec) {
*out = *in *out = *in
out.SourceRef = in.SourceRef out.SourceRef = in.SourceRef
out.Interval = in.Interval out.Interval = in.Interval
if in.ValuesFiles != nil {
in, out := &in.ValuesFiles, &out.ValuesFiles
*out = make([]string, len(*in))
copy(*out, *in)
}
} }
// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartSpec. // DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new HelmChartSpec.

View File

@ -94,8 +94,19 @@ spec:
type: boolean type: boolean
valuesFile: valuesFile:
description: Alternative values file to use as the default chart values, description: Alternative values file to use as the default chart values,
expected to be a relative path in the SourceRef. Ignored when omitted. expected to be a relative path in the SourceRef. Deprecated in favor
of ValuesFiles, for backwards compatibility the file defined here
is merged before the ValuesFiles items. Ignored when omitted.
type: string type: string
valuesFiles:
description: Alternative list of values files to use as the chart
values (values.yaml is not included by default), expected to be
a relative path in the SourceRef. Values files are merged in the
order of this list with the last file overriding the first. Ignored
when omitted.
items:
type: string
type: array
version: version:
default: '*' default: '*'
description: The chart version semver expression, ignored for charts description: The chart version semver expression, ignored for charts

View File

@ -8,4 +8,6 @@ spec:
kind: GitRepository kind: GitRepository
name: podinfo name: podinfo
chart: charts/podinfo chart: charts/podinfo
valuesFile: charts/podinfo/values-prod.yaml valuesFile: charts/podinfo/values.yaml
valuesFiles:
- charts/podinfo/values-prod.yaml

View File

@ -8,4 +8,6 @@ spec:
kind: HelmRepository kind: HelmRepository
name: podinfo name: podinfo
chart: podinfo chart: podinfo
valuesFile: values-prod.yaml valuesFile: values.yaml
valuesFiles:
- values-prod.yaml

View File

@ -49,11 +49,13 @@ import (
"sigs.k8s.io/controller-runtime/pkg/predicate" "sigs.k8s.io/controller-runtime/pkg/predicate"
"sigs.k8s.io/controller-runtime/pkg/reconcile" "sigs.k8s.io/controller-runtime/pkg/reconcile"
"sigs.k8s.io/controller-runtime/pkg/source" "sigs.k8s.io/controller-runtime/pkg/source"
"sigs.k8s.io/yaml"
"github.com/fluxcd/pkg/apis/meta" "github.com/fluxcd/pkg/apis/meta"
"github.com/fluxcd/pkg/runtime/events" "github.com/fluxcd/pkg/runtime/events"
"github.com/fluxcd/pkg/runtime/metrics" "github.com/fluxcd/pkg/runtime/metrics"
"github.com/fluxcd/pkg/runtime/predicates" "github.com/fluxcd/pkg/runtime/predicates"
"github.com/fluxcd/pkg/runtime/transform"
"github.com/fluxcd/pkg/untar" "github.com/fluxcd/pkg/untar"
sourcev1 "github.com/fluxcd/source-controller/api/v1beta1" sourcev1 "github.com/fluxcd/source-controller/api/v1beta1"
@ -380,11 +382,13 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
readyMessage = fmt.Sprintf("Fetched revision: %s", newArtifact.Revision) readyMessage = fmt.Sprintf("Fetched revision: %s", newArtifact.Revision)
) )
switch { switch {
case chart.Spec.ValuesFile != "" && chart.Spec.ValuesFile != chartutil.ValuesfileName: case len(chart.GetValuesFiles()) > 0:
var ( var (
tmpDir string tmpDir string
pkgPath string pkgPath string
) )
valuesMap := make(map[string]interface{})
// Load the chart // Load the chart
helmChart, err := loader.LoadArchive(res) helmChart, err := loader.LoadArchive(res)
if err != nil { if err != nil {
@ -392,18 +396,43 @@ func (r *HelmChartReconciler) reconcileFromHelmRepository(ctx context.Context,
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
} }
// Find override file and retrieve contents for _, v := range chart.GetValuesFiles() {
var valuesData []byte if v == "values.yaml" {
cfn := filepath.Clean(chart.Spec.ValuesFile) valuesMap = transform.MergeMaps(valuesMap, helmChart.Values)
for _, f := range helmChart.Files { continue
if f.Name == cfn {
valuesData = f.Data
break
} }
var valuesData []byte
cfn := filepath.Clean(v)
for _, f := range helmChart.Files {
if f.Name == cfn {
valuesData = f.Data
break
}
}
if valuesData == nil {
err = fmt.Errorf("invalid values file path: %s", v)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
yamlMap := make(map[string]interface{})
err = yaml.Unmarshal(valuesData, &yamlMap)
if err != nil {
err = fmt.Errorf("unmarshaling values from %s failed: %w", v, err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
valuesMap = transform.MergeMaps(valuesMap, yamlMap)
}
yamlBytes, err := yaml.Marshal(valuesMap)
if err != nil {
err = fmt.Errorf("marshaling values failed: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err
} }
// Overwrite values file // Overwrite values file
if changed, err := helm.OverwriteChartDefaultValues(helmChart, valuesData); err != nil { if changed, err := helm.OverwriteChartDefaultValues(helmChart, yamlBytes); err != nil {
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err
} else if !changed { } else if !changed {
// No changes, skip to write original package to storage // No changes, skip to write original package to storage
@ -508,22 +537,41 @@ func (r *HelmChartReconciler) reconcileFromTarballArtifact(ctx context.Context,
// or write the chart directly to storage. // or write the chart directly to storage.
pkgPath := chartPath pkgPath := chartPath
isValuesFileOverriden := false isValuesFileOverriden := false
if chart.Spec.ValuesFile != "" { if len(chart.GetValuesFiles()) > 0 {
srcPath, err := securejoin.SecureJoin(tmpDir, chart.Spec.ValuesFile) valuesMap := make(map[string]interface{})
if err != nil { for _, v := range chart.GetValuesFiles() {
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err srcPath, err := securejoin.SecureJoin(tmpDir, v)
} if err != nil {
if f, err := os.Stat(srcPath); os.IsNotExist(err) || !f.Mode().IsRegular() { return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
err = fmt.Errorf("invalid values file path: %s", chart.Spec.ValuesFile) }
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err if f, err := os.Stat(srcPath); os.IsNotExist(err) || !f.Mode().IsRegular() {
err = fmt.Errorf("invalid values file path: %s", v)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
valuesData, err := ioutil.ReadFile(srcPath)
if err != nil {
err = fmt.Errorf("failed to read from values file '%s': %w", v, err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
yamlMap := make(map[string]interface{})
err = yaml.Unmarshal(valuesData, &yamlMap)
if err != nil {
err = fmt.Errorf("unmarshaling values from %s failed: %w", v, err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err
}
valuesMap = transform.MergeMaps(valuesMap, yamlMap)
} }
valuesData, err := ioutil.ReadFile(srcPath) yamlBytes, err := yaml.Marshal(valuesMap)
if err != nil { if err != nil {
err = fmt.Errorf("failed to read from values file '%s': %w", chart.Spec.ValuesFile, err) err = fmt.Errorf("marshaling values failed: %w", err)
return sourcev1.HelmChartNotReady(chart, sourcev1.StorageOperationFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err
} }
isValuesFileOverriden, err = helm.OverwriteChartDefaultValues(helmChart, valuesData)
isValuesFileOverriden, err = helm.OverwriteChartDefaultValues(helmChart, yamlBytes)
if err != nil { if err != nil {
return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err return sourcev1.HelmChartNotReady(chart, sourcev1.ChartPackageFailedReason, err.Error()), err
} }

View File

@ -151,10 +151,125 @@ var _ = Describe("HelmChartReconciler", func() {
!storage.ArtifactExist(*got.Status.Artifact) !storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue()) }, timeout, interval).Should(BeTrue())
When("Setting valid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFiles = []string{
"values.yaml",
"override.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting invalid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFiles = []string{
"values.yaml",
"invalid.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.ObservedGeneration > updated.Status.ObservedGeneration &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting valid valuesFiles and valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "values.yaml"
updated.Spec.ValuesFiles = []string{
"override.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting valid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "override.yaml"
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
_, exists := helmChart.Values["testDefault"]
Expect(exists).To(BeFalse())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting invalid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "invalid.yaml"
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.ObservedGeneration > updated.Status.ObservedGeneration &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
_, exists := helmChart.Values["testDefault"]
Expect(exists).To(BeFalse())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
By("Expecting missing HelmRepository error") By("Expecting missing HelmRepository error")
updated := &sourcev1.HelmChart{} updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).Should(Succeed()) Expect(k8sClient.Get(context.Background(), key, updated)).Should(Succeed())
updated.Spec.SourceRef.Name = "invalid" updated.Spec.SourceRef.Name = "invalid"
updated.Spec.ValuesFile = ""
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).Should(Succeed()) Expect(k8sClient.Update(context.Background(), updated)).Should(Succeed())
Eventually(func() bool { Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, updated) _ = k8sClient.Get(context.Background(), key, updated)
@ -601,10 +716,13 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(helmChart.Values["testDefault"]).To(BeTrue()) Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeFalse()) Expect(helmChart.Values["testOverride"]).To(BeFalse())
When("Setting valid valuesFile attribute", func() { When("Setting valid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{} updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed()) Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchart/override.yaml" updated.Spec.ValuesFiles = []string{
"./testdata/charts/helmchart/values.yaml",
"./testdata/charts/helmchart/override.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed()) Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{} got := &sourcev1.HelmChart{}
Eventually(func() bool { Eventually(func() bool {
@ -617,13 +735,17 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(f.Size()).To(BeNumerically(">", 0)) Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact)) helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue()) Expect(helmChart.Values["testOverride"]).To(BeTrue())
}) })
When("Setting invalid valuesFile attribute", func() { When("Setting invalid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{} updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed()) Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchart/invalid.yaml" updated.Spec.ValuesFiles = []string{
"./testdata/charts/helmchart/values.yaml",
"./testdata/charts/helmchart/invalid.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed()) Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{} got := &sourcev1.HelmChart{}
Eventually(func() bool { Eventually(func() bool {
@ -636,6 +758,74 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(f.Size()).To(BeNumerically(">", 0)) Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact)) helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting valid valuesFiles and valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchart/values.yaml"
updated.Spec.ValuesFiles = []string{
"./testdata/charts/helmchart/override.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting valid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchart/override.yaml"
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
_, exists := helmChart.Values["testDefault"]
Expect(exists).To(BeFalse())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting invalid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchart/invalid.yaml"
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.ObservedGeneration > updated.Status.ObservedGeneration &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
_, exists := helmChart.Values["testDefault"]
Expect(exists).To(BeFalse())
Expect(helmChart.Values["testOverride"]).To(BeTrue()) Expect(helmChart.Values["testOverride"]).To(BeTrue())
}) })
}) })
@ -987,10 +1177,13 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(helmChart.Values["testDefault"]).To(BeTrue()) Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeFalse()) Expect(helmChart.Values["testOverride"]).To(BeFalse())
When("Setting valid valuesFile attribute", func() { When("Setting valid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{} updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed()) Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchartwithdeps/override.yaml" updated.Spec.ValuesFiles = []string{
"./testdata/charts/helmchartwithdeps/values.yaml",
"./testdata/charts/helmchartwithdeps/override.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed()) Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{} got := &sourcev1.HelmChart{}
Eventually(func() bool { Eventually(func() bool {
@ -1003,13 +1196,17 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(f.Size()).To(BeNumerically(">", 0)) Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact)) helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue()) Expect(helmChart.Values["testOverride"]).To(BeTrue())
}) })
When("Setting invalid valuesFile attribute", func() { When("Setting invalid valuesFiles attribute", func() {
updated := &sourcev1.HelmChart{} updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed()) Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchartwithdeps/invalid.yaml" updated.Spec.ValuesFiles = []string{
"./testdata/charts/helmchartwithdeps/values.yaml",
"./testdata/charts/helmchartwithdeps/invalid.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed()) Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{} got := &sourcev1.HelmChart{}
Eventually(func() bool { Eventually(func() bool {
@ -1022,6 +1219,74 @@ var _ = Describe("HelmChartReconciler", func() {
Expect(f.Size()).To(BeNumerically(">", 0)) Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact)) helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred()) Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting valid valuesFiles and valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchartwithdeps/values.yaml"
updated.Spec.ValuesFiles = []string{
"./testdata/charts/helmchartwithdeps/override.yaml",
}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(helmChart.Values["testDefault"]).To(BeTrue())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting valid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchartwithdeps/override.yaml"
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.Artifact.Checksum != updated.Status.Artifact.Checksum &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
_, exists := helmChart.Values["testDefault"]
Expect(exists).To(BeFalse())
Expect(helmChart.Values["testOverride"]).To(BeTrue())
})
When("Setting invalid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
updated.Spec.ValuesFile = "./testdata/charts/helmchartwithdeps/invalid.yaml"
updated.Spec.ValuesFiles = []string{}
Expect(k8sClient.Update(context.Background(), updated)).To(Succeed())
got := &sourcev1.HelmChart{}
Eventually(func() bool {
_ = k8sClient.Get(context.Background(), key, got)
return got.Status.ObservedGeneration > updated.Status.ObservedGeneration &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
f, err := os.Stat(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
Expect(f.Size()).To(BeNumerically(">", 0))
helmChart, err := loader.Load(storage.LocalPath(*got.Status.Artifact))
Expect(err).NotTo(HaveOccurred())
_, exists := helmChart.Values["testDefault"]
Expect(exists).To(BeFalse())
Expect(helmChart.Values["testOverride"]).To(BeTrue()) Expect(helmChart.Values["testOverride"]).To(BeTrue())
}) })
}) })

View File

@ -542,6 +542,21 @@ Kubernetes meta/v1.Duration
</tr> </tr>
<tr> <tr>
<td> <td>
<code>valuesFiles</code><br>
<em>
[]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Alternative list of values files to use as the chart values (values.yaml
is not included by default), expected to be a relative path in the SourceRef.
Values files are merged in the order of this list with the last file overriding
the first. Ignored when omitted.</p>
</td>
</tr>
<tr>
<td>
<code>valuesFile</code><br> <code>valuesFile</code><br>
<em> <em>
string string
@ -549,8 +564,10 @@ string
</td> </td>
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>Alternative values file to use as the default chart values, expected to be a <p>Alternative values file to use as the default chart values, expected to
relative path in the SourceRef. Ignored when omitted.</p> be a relative path in the SourceRef. Deprecated in favor of ValuesFiles,
for backwards compatibility the file defined here is merged before the
ValuesFiles items. Ignored when omitted.</p>
</td> </td>
</tr> </tr>
<tr> <tr>
@ -1481,6 +1498,21 @@ Kubernetes meta/v1.Duration
</tr> </tr>
<tr> <tr>
<td> <td>
<code>valuesFiles</code><br>
<em>
[]string
</em>
</td>
<td>
<em>(Optional)</em>
<p>Alternative list of values files to use as the chart values (values.yaml
is not included by default), expected to be a relative path in the SourceRef.
Values files are merged in the order of this list with the last file overriding
the first. Ignored when omitted.</p>
</td>
</tr>
<tr>
<td>
<code>valuesFile</code><br> <code>valuesFile</code><br>
<em> <em>
string string
@ -1488,8 +1520,10 @@ string
</td> </td>
<td> <td>
<em>(Optional)</em> <em>(Optional)</em>
<p>Alternative values file to use as the default chart values, expected to be a <p>Alternative values file to use as the default chart values, expected to
relative path in the SourceRef. Ignored when omitted.</p> be a relative path in the SourceRef. Deprecated in favor of ValuesFiles,
for backwards compatibility the file defined here is merged before the
ValuesFiles items. Ignored when omitted.</p>
</td> </td>
</tr> </tr>
<tr> <tr>

View File

@ -28,9 +28,19 @@ type HelmChartSpec struct {
// +required // +required
Interval metav1.Duration `json:"interval"` Interval metav1.Duration `json:"interval"`
// Alternative values file to use as the default chart values, expected to be a // Alternative list of values files to use as the chart values (values.yaml
// relative path in the SourceRef. Ignored when omitted. // is not included by default), expected to be a relative path in the SourceRef.
// Values files are merged in the order of this list with the last file overriding
// the first. Ignored when omitted.
// +optional // +optional
ValuesFiles []string `json:"valuesFiles,omitempty"`
// Alternative values file to use as the default chart values, expected to
// be a relative path in the SourceRef. Deprecated in favor of ValuesFiles,
// for backwards compatibility the file defined here is merged before the
// ValuesFiles items. Ignored when omitted.
// +optional
// +deprecated
ValuesFile string `json:"valuesFile,omitempty"` ValuesFile string `json:"valuesFile,omitempty"`
// This flag tells the controller to suspend the reconciliation of this source. // This flag tells the controller to suspend the reconciliation of this source.
@ -182,6 +192,44 @@ spec:
interval: 10m interval: 10m
``` ```
Override default values with alternative values files relative to the
path in the SourceRef:
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmChart
metadata:
name: redis
namespace: default
spec:
chart: redis
version: 10.5.7
sourceRef:
name: stable
kind: HelmRepository
interval: 5m
valuesFiles:
- values.yaml
- values-production.yaml
```
```yaml
apiVersion: source.toolkit.fluxcd.io/v1beta1
kind: HelmChart
metadata:
name: podinfo
namespace: default
spec:
chart: ./charts/podinfo
sourceRef:
name: podinfo
kind: GitRepository
interval: 10m
valuesFiles:
- ./charts/podinfo/values.yaml
- ./charts/podinfo/values-production.yaml
```
## Status examples ## Status examples
Successful chart pull: Successful chart pull: