Merge pull request #1447 from isometry/feature/ignore-missing-values-files
Add `.spec.ignoreMissingValuesFiles` to HelmChart API
This commit is contained in:
commit
5fcae5c475
2
Makefile
2
Makefile
|
@ -188,7 +188,7 @@ TMP_DIR=$$(mktemp -d) ;\
|
|||
cd $$TMP_DIR ;\
|
||||
go mod init tmp ;\
|
||||
echo "Downloading $(2)" ;\
|
||||
env -i bash -c "GOBIN=$(GOBIN) PATH=$(PATH) GOPATH=$(shell go env GOPATH) GOCACHE=$(shell go env GOCACHE) go install $(2)" ;\
|
||||
env -i bash -c "GOBIN=$(GOBIN) PATH=\"$(PATH)\" GOPATH=$(shell go env GOPATH) GOCACHE=$(shell go env GOCACHE) go install $(2)" ;\
|
||||
rm -rf $$TMP_DIR ;\
|
||||
}
|
||||
endef
|
||||
|
|
|
@ -79,6 +79,11 @@ type HelmChartSpec struct {
|
|||
// +deprecated
|
||||
ValuesFile string `json:"valuesFile,omitempty"`
|
||||
|
||||
// IgnoreMissingValuesFiles controls whether to silently ignore missing values
|
||||
// files rather than failing.
|
||||
// +optional
|
||||
IgnoreMissingValuesFiles bool `json:"ignoreMissingValuesFiles,omitempty"`
|
||||
|
||||
// Suspend tells the controller to suspend the reconciliation of this
|
||||
// source.
|
||||
// +optional
|
||||
|
@ -142,6 +147,12 @@ type HelmChartStatus struct {
|
|||
// +optional
|
||||
ObservedChartName string `json:"observedChartName,omitempty"`
|
||||
|
||||
// ObservedValuesFiles are the observed value files of the last successful
|
||||
// reconciliation.
|
||||
// It matches the chart in the last successfully reconciled artifact.
|
||||
// +optional
|
||||
ObservedValuesFiles []string `json:"observedValuesFiles,omitempty"`
|
||||
|
||||
// Conditions holds the conditions for the HelmChart.
|
||||
// +optional
|
||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
||||
|
|
|
@ -484,6 +484,11 @@ func (in *HelmChartSpec) DeepCopy() *HelmChartSpec {
|
|||
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||
func (in *HelmChartStatus) DeepCopyInto(out *HelmChartStatus) {
|
||||
*out = *in
|
||||
if in.ObservedValuesFiles != nil {
|
||||
in, out := &in.ObservedValuesFiles, &out.ObservedValuesFiles
|
||||
*out = make([]string, len(*in))
|
||||
copy(*out, *in)
|
||||
}
|
||||
if in.Conditions != nil {
|
||||
in, out := &in.Conditions, &out.Conditions
|
||||
*out = make([]v1.Condition, len(*in))
|
||||
|
|
|
@ -363,6 +363,11 @@ spec:
|
|||
Chart is the name or path the Helm chart is available at in the
|
||||
SourceRef.
|
||||
type: string
|
||||
ignoreMissingValuesFiles:
|
||||
description: |-
|
||||
IgnoreMissingValuesFiles controls whether to silently ignore missing values
|
||||
files rather than failing.
|
||||
type: boolean
|
||||
interval:
|
||||
description: |-
|
||||
Interval at which the HelmChart SourceRef is checked for updates.
|
||||
|
@ -638,6 +643,14 @@ spec:
|
|||
ObservedSourceArtifactRevision is the last observed Artifact.Revision
|
||||
of the HelmChartSpec.SourceRef.
|
||||
type: string
|
||||
observedValuesFiles:
|
||||
description: |-
|
||||
ObservedValuesFiles are the observed value files of the last successful
|
||||
reconciliation.
|
||||
It matches the chart in the last successfully reconciled artifact.
|
||||
items:
|
||||
type: string
|
||||
type: array
|
||||
url:
|
||||
description: |-
|
||||
URL is the dynamic fetch link for the latest Artifact.
|
||||
|
|
|
@ -660,6 +660,19 @@ is merged before the ValuesFiles items. Ignored when omitted.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ignoreMissingValuesFiles</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>IgnoreMissingValuesFiles controls whether to silently ignore missing values
|
||||
files rather than failing.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
|
@ -2329,6 +2342,19 @@ is merged before the ValuesFiles items. Ignored when omitted.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>ignoreMissingValuesFiles</code><br>
|
||||
<em>
|
||||
bool
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>IgnoreMissingValuesFiles controls whether to silently ignore missing values
|
||||
files rather than failing.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>suspend</code><br>
|
||||
<em>
|
||||
bool
|
||||
|
@ -2436,6 +2462,20 @@ resolved chart reference.</p>
|
|||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>observedValuesFiles</code><br>
|
||||
<em>
|
||||
[]string
|
||||
</em>
|
||||
</td>
|
||||
<td>
|
||||
<em>(Optional)</em>
|
||||
<p>ObservedValuesFiles are the observed value files of the last successful
|
||||
reconciliation.
|
||||
It matches the chart in the last successfully reconciled artifact.</p>
|
||||
</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>
|
||||
<code>conditions</code><br>
|
||||
<em>
|
||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition">
|
||||
|
|
|
@ -202,6 +202,16 @@ spec:
|
|||
Values files also affect the generated artifact revision, see
|
||||
[artifact](#artifact).
|
||||
|
||||
### Ignore missing values files
|
||||
|
||||
`.spec.ignoreMissingValuesFiles` is an optional field to specify whether missing
|
||||
values files should be ignored rather than be considered errors. It defaults to
|
||||
`false`.
|
||||
|
||||
When `.spec.valuesFiles` and `.spec.ignoreMissingValuesFiles` are specified,
|
||||
the `.status.observedValuesFiles` field is populated with the list of values
|
||||
files that were found and actually contributed to the packaged chart.
|
||||
|
||||
### Reconcile strategy
|
||||
|
||||
`.spec.reconcileStrategy` is an optional field to specify what enables the
|
||||
|
|
|
@ -666,6 +666,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
|
|||
cb := chart.NewRemoteBuilder(chartRepo)
|
||||
opts := chart.BuildOptions{
|
||||
ValuesFiles: obj.GetValuesFiles(),
|
||||
IgnoreMissingValuesFiles: obj.Spec.IgnoreMissingValuesFiles,
|
||||
Force: obj.Generation != obj.Status.ObservedGeneration,
|
||||
// The remote builder will not attempt to download the chart if
|
||||
// an artifact exists with the same name and version and `Force` is false.
|
||||
|
@ -674,6 +675,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
|
|||
}
|
||||
if artifact := obj.GetArtifact(); artifact != nil {
|
||||
opts.CachedChart = r.Storage.LocalPath(*artifact)
|
||||
opts.CachedChartValuesFiles = obj.Status.ObservedValuesFiles
|
||||
}
|
||||
|
||||
// Set the VersionMetadata to the object's Generation if ValuesFiles is defined
|
||||
|
@ -761,10 +763,12 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
|
|||
// Configure builder options, including any previously cached chart
|
||||
opts := chart.BuildOptions{
|
||||
ValuesFiles: obj.GetValuesFiles(),
|
||||
IgnoreMissingValuesFiles: obj.Spec.IgnoreMissingValuesFiles,
|
||||
Force: obj.Generation != obj.Status.ObservedGeneration,
|
||||
}
|
||||
if artifact := obj.Status.Artifact; artifact != nil {
|
||||
if artifact := obj.GetArtifact(); artifact != nil {
|
||||
opts.CachedChart = r.Storage.LocalPath(*artifact)
|
||||
opts.CachedChartValuesFiles = obj.Status.ObservedValuesFiles
|
||||
}
|
||||
|
||||
// Configure revision metadata for chart build if we should react to revision changes
|
||||
|
@ -884,6 +888,11 @@ func (r *HelmChartReconciler) reconcileArtifact(ctx context.Context, _ *patch.Se
|
|||
// Record it on the object
|
||||
obj.Status.Artifact = artifact.DeepCopy()
|
||||
obj.Status.ObservedChartName = b.Name
|
||||
if obj.Spec.IgnoreMissingValuesFiles {
|
||||
obj.Status.ObservedValuesFiles = b.ValuesFiles
|
||||
} else {
|
||||
obj.Status.ObservedValuesFiles = nil
|
||||
}
|
||||
|
||||
// Update symlink on a "best effort" basis
|
||||
symURL, err := r.Storage.Symlink(artifact, "latest.tar.gz")
|
||||
|
|
|
@ -927,6 +927,23 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
|
|||
g.Expect(build.Path).To(BeARegularFile())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Uses artifact as build cache with observedValuesFiles",
|
||||
beforeFunc: func(obj *helmv1.HelmChart, repository *helmv1.HelmRepository) {
|
||||
obj.Spec.Chart = chartName
|
||||
obj.Spec.Version = chartVersion
|
||||
obj.Status.Artifact = &sourcev1.Artifact{Path: chartName + "-" + chartVersion + ".tgz"}
|
||||
obj.Status.ObservedValuesFiles = []string{"values.yaml", "override.yaml"}
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertFunc: func(g *WithT, obj *helmv1.HelmChart, build chart.Build) {
|
||||
g.Expect(build.Name).To(Equal(chartName))
|
||||
g.Expect(build.Version).To(Equal(chartVersion))
|
||||
g.Expect(build.Path).To(Equal(filepath.Join(serverFactory.Root(), obj.Status.Artifact.Path)))
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(Equal([]string{"values.yaml", "override.yaml"}))
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Sets Generation as VersionMetadata with values files",
|
||||
beforeFunc: func(obj *helmv1.HelmChart, repository *helmv1.HelmRepository) {
|
||||
|
@ -940,6 +957,51 @@ func TestHelmChartReconciler_buildFromHelmRepository(t *testing.T) {
|
|||
g.Expect(build.Version).To(Equal(higherChartVersion + "+3"))
|
||||
g.Expect(build.Path).ToNot(BeEmpty())
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(Equal([]string{"values.yaml", "override.yaml"}))
|
||||
},
|
||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Missing values files are an error",
|
||||
beforeFunc: func(obj *helmv1.HelmChart, repository *helmv1.HelmRepository) {
|
||||
obj.Spec.Chart = chartName
|
||||
obj.Spec.ValuesFiles = []string{"missing.yaml"}
|
||||
},
|
||||
wantErr: &chart.BuildError{Err: errors.New("values files merge error: failed to merge chart values: no values file found at path 'missing.yaml'")},
|
||||
},
|
||||
{
|
||||
name: "All missing values files ignored",
|
||||
beforeFunc: func(obj *helmv1.HelmChart, repository *helmv1.HelmRepository) {
|
||||
obj.Spec.Chart = chartName
|
||||
obj.Spec.Version = chartVersion
|
||||
obj.Spec.ValuesFiles = []string{"missing.yaml"}
|
||||
obj.Spec.IgnoreMissingValuesFiles = true
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertFunc: func(g *WithT, obj *helmv1.HelmChart, build chart.Build) {
|
||||
g.Expect(build.Name).To(Equal(chartName))
|
||||
g.Expect(build.Version).To(Equal(chartVersion + "+0"))
|
||||
g.Expect(build.ValuesFiles).To(BeEmpty())
|
||||
},
|
||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Partial missing values files ignored",
|
||||
beforeFunc: func(obj *helmv1.HelmChart, repository *helmv1.HelmRepository) {
|
||||
obj.Spec.Chart = chartName
|
||||
obj.Spec.Version = chartVersion
|
||||
obj.Spec.ValuesFiles = []string{"values.yaml", "override.yaml", "invalid.yaml"}
|
||||
obj.Spec.IgnoreMissingValuesFiles = true
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertFunc: func(g *WithT, obj *helmv1.HelmChart, build chart.Build) {
|
||||
g.Expect(build.Name).To(Equal(chartName))
|
||||
g.Expect(build.Version).To(Equal(chartVersion + "+0"))
|
||||
g.Expect(build.ValuesFiles).To(Equal([]string{"values.yaml", "override.yaml"}))
|
||||
},
|
||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
||||
|
@ -1211,6 +1273,7 @@ func TestHelmChartReconciler_buildFromOCIHelmRepository(t *testing.T) {
|
|||
g.Expect(build.Version).To(Equal(metadata.Version))
|
||||
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(BeEmpty())
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1433,6 +1496,10 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
|
|||
g.Expect(build.Version).To(Equal("0.1.0+3"))
|
||||
g.Expect(build.ResolvedDependencies).To(Equal(0))
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(Equal([]string{
|
||||
"testdata/charts/helmchart/values.yaml",
|
||||
"testdata/charts/helmchart/override.yaml",
|
||||
}))
|
||||
},
|
||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
||||
|
@ -1451,6 +1518,24 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
|
|||
g.Expect(build.Version).To(Equal("0.1.0"))
|
||||
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(BeEmpty())
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Chart from storage cache with ObservedValuesFiles",
|
||||
source: *chartsArtifact.DeepCopy(),
|
||||
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||
obj.Spec.Chart = "testdata/charts/helmchart-0.1.0.tgz"
|
||||
obj.Status.Artifact = cachedArtifact.DeepCopy()
|
||||
obj.Status.ObservedValuesFiles = []string{"values.yaml", "override.yaml"}
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertFunc: func(g *WithT, build chart.Build) {
|
||||
g.Expect(build.Name).To(Equal("helmchart"))
|
||||
g.Expect(build.Version).To(Equal("0.1.0"))
|
||||
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(Equal([]string{"values.yaml", "override.yaml"}))
|
||||
},
|
||||
},
|
||||
{
|
||||
|
@ -1468,6 +1553,7 @@ func TestHelmChartReconciler_buildFromTarballArtifact(t *testing.T) {
|
|||
g.Expect(build.Version).To(Equal("0.1.0"))
|
||||
g.Expect(build.Path).ToNot(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||
g.Expect(build.Path).To(BeARegularFile())
|
||||
g.Expect(build.ValuesFiles).To(BeEmpty())
|
||||
},
|
||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
||||
|
@ -1565,7 +1651,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Copying artifact to storage from build makes ArtifactInStorage=True",
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", nil),
|
||||
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
|
||||
},
|
||||
|
@ -1575,6 +1661,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
|
||||
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
||||
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
||||
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertConditions: []metav1.Condition{
|
||||
|
@ -1597,6 +1684,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
afterFunc: func(t *WithT, obj *helmv1.HelmChart) {
|
||||
t.Expect(obj.Status.Artifact.Path).To(Equal("testdata/charts/helmchart-0.1.0.tgz"))
|
||||
t.Expect(obj.Status.ObservedChartName).To(BeEmpty())
|
||||
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||
t.Expect(obj.Status.URL).To(BeEmpty())
|
||||
},
|
||||
},
|
||||
|
@ -1626,7 +1714,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Removes ArtifactOutdatedCondition after creating new artifact",
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", nil),
|
||||
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
|
||||
},
|
||||
|
@ -1636,6 +1724,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
|
||||
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
||||
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
||||
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertConditions: []metav1.Condition{
|
||||
|
@ -1644,7 +1733,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
},
|
||||
{
|
||||
name: "Creates latest symlink to the created artifact",
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz"),
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", nil),
|
||||
afterFunc: func(t *WithT, obj *helmv1.HelmChart) {
|
||||
t.Expect(obj.GetArtifact()).ToNot(BeNil())
|
||||
|
||||
|
@ -1659,6 +1748,46 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
|||
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, helmv1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Updates ObservedValuesFiles after creating new artifact",
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", []string{"values.yaml", "override.yaml"}),
|
||||
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
|
||||
},
|
||||
afterFunc: func(t *WithT, obj *helmv1.HelmChart) {
|
||||
t.Expect(obj.GetArtifact()).ToNot(BeNil())
|
||||
t.Expect(obj.GetArtifact().Digest).To(Equal("sha256:bbdf96023c912c393b49d5238e227576ed0d20d1bb145d7476d817b80e20c11a"))
|
||||
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
|
||||
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
||||
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
||||
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, helmv1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
|
||||
},
|
||||
},
|
||||
{
|
||||
name: "Updates ObservedValuesFiles with IgnoreMissingValuesFiles after creating new artifact",
|
||||
build: mockChartBuild("helmchart", "0.1.0", "testdata/charts/helmchart-0.1.0.tgz", []string{"values.yaml", "override.yaml"}),
|
||||
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
|
||||
obj.Spec.ValuesFiles = []string{"values.yaml", "missing.yaml", "override.yaml"}
|
||||
obj.Spec.IgnoreMissingValuesFiles = true
|
||||
},
|
||||
afterFunc: func(t *WithT, obj *helmv1.HelmChart) {
|
||||
t.Expect(obj.GetArtifact()).ToNot(BeNil())
|
||||
t.Expect(obj.GetArtifact().Digest).To(Equal("sha256:bbdf96023c912c393b49d5238e227576ed0d20d1bb145d7476d817b80e20c11a"))
|
||||
t.Expect(obj.GetArtifact().Revision).To(Equal("0.1.0"))
|
||||
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
||||
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
||||
t.Expect(obj.Status.ObservedValuesFiles).To(Equal([]string{"values.yaml", "override.yaml"}))
|
||||
},
|
||||
want: sreconcile.ResultSuccess,
|
||||
assertConditions: []metav1.Condition{
|
||||
*conditions.TrueCondition(sourcev1.ArtifactInStorageCondition, helmv1.ChartPullSucceededReason, "pulled 'helmchart' chart with version '0.1.0'"),
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
|
@ -2016,7 +2145,7 @@ func TestHelmChartReconciler_reconcileSubRecs(t *testing.T) {
|
|||
}
|
||||
}
|
||||
|
||||
func mockChartBuild(name, version, path string) *chart.Build {
|
||||
func mockChartBuild(name, version, path string, valuesFiles []string) *chart.Build {
|
||||
var copyP string
|
||||
if path != "" {
|
||||
f, err := os.Open(path)
|
||||
|
@ -2035,6 +2164,7 @@ func mockChartBuild(name, version, path string) *chart.Build {
|
|||
Name: name,
|
||||
Version: version,
|
||||
Path: copyP,
|
||||
ValuesFiles: valuesFiles,
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -107,6 +107,12 @@ type BuildOptions struct {
|
|||
// ValuesFiles can be set to a list of relative paths, used to compose
|
||||
// and overwrite an alternative default "values.yaml" for the chart.
|
||||
ValuesFiles []string
|
||||
// CachedChartValuesFiles is a list of relative paths that were used to
|
||||
// build the cached chart.
|
||||
CachedChartValuesFiles []string
|
||||
// IgnoreMissingValuesFiles controls whether to silently ignore missing
|
||||
// values files rather than failing.
|
||||
IgnoreMissingValuesFiles bool
|
||||
// CachedChart can be set to the absolute path of a chart stored on
|
||||
// the local filesystem, and is used for simple validation by metadata
|
||||
// comparisons.
|
||||
|
|
|
@ -121,6 +121,11 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
|||
if result.Name == curMeta.Name && result.Version == curMeta.Version {
|
||||
result.Path = opts.CachedChart
|
||||
result.ValuesFiles = opts.GetValuesFiles()
|
||||
if opts.CachedChartValuesFiles != nil {
|
||||
// If the cached chart values files are set, we should use them
|
||||
// instead of reporting the values files.
|
||||
result.ValuesFiles = opts.CachedChartValuesFiles
|
||||
}
|
||||
result.Packaged = requiresPackaging
|
||||
|
||||
return result, nil
|
||||
|
@ -140,9 +145,12 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
|||
}
|
||||
|
||||
// Merge chart values, if instructed
|
||||
var mergedValues map[string]interface{}
|
||||
var (
|
||||
mergedValues map[string]interface{}
|
||||
valuesFiles []string
|
||||
)
|
||||
if len(opts.GetValuesFiles()) > 0 {
|
||||
if mergedValues, err = mergeFileValues(localRef.WorkDir, opts.ValuesFiles); err != nil {
|
||||
if mergedValues, valuesFiles, err = mergeFileValues(localRef.WorkDir, opts.ValuesFiles, opts.IgnoreMissingValuesFiles); err != nil {
|
||||
return result, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
||||
}
|
||||
}
|
||||
|
@ -163,7 +171,7 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
|||
if err != nil {
|
||||
return result, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
||||
}
|
||||
result.ValuesFiles = opts.GetValuesFiles()
|
||||
result.ValuesFiles = valuesFiles
|
||||
}
|
||||
|
||||
// Ensure dependencies are fetched if building from a directory
|
||||
|
@ -187,31 +195,42 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
|||
}
|
||||
|
||||
// mergeFileValues merges the given value file paths into a single "values.yaml" map.
|
||||
// The provided (relative) paths may not traverse outside baseDir. It returns the merge
|
||||
// result, or an error.
|
||||
func mergeFileValues(baseDir string, paths []string) (map[string]interface{}, error) {
|
||||
// The provided (relative) paths may not traverse outside baseDir. By default, a missing
|
||||
// file is considered an error. If ignoreMissing is true, missing files are ignored.
|
||||
// It returns the merge result and the list of files that contributed to that result,
|
||||
// or an error.
|
||||
func mergeFileValues(baseDir string, paths []string, ignoreMissing bool) (map[string]interface{}, []string, error) {
|
||||
mergedValues := make(map[string]interface{})
|
||||
valuesFiles := make([]string, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
secureP, err := securejoin.SecureJoin(baseDir, p)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
return nil, nil, err
|
||||
}
|
||||
if f, err := os.Stat(secureP); err != nil || !f.Mode().IsRegular() {
|
||||
return nil, fmt.Errorf("no values file found at path '%s' (reference '%s')",
|
||||
f, err := os.Stat(secureP)
|
||||
switch {
|
||||
case err != nil:
|
||||
if ignoreMissing && os.IsNotExist(err) {
|
||||
continue
|
||||
}
|
||||
fallthrough
|
||||
case !f.Mode().IsRegular():
|
||||
return nil, nil, fmt.Errorf("no values file found at path '%s' (reference '%s')",
|
||||
strings.TrimPrefix(secureP, baseDir), p)
|
||||
}
|
||||
b, err := os.ReadFile(secureP)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("could not read values from file '%s': %w", p, err)
|
||||
return nil, nil, fmt.Errorf("could not read values from file '%s': %w", p, err)
|
||||
}
|
||||
values := make(map[string]interface{})
|
||||
err = yaml.Unmarshal(b, &values)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling values from '%s' failed: %w", p, err)
|
||||
return nil, nil, fmt.Errorf("unmarshaling values from '%s' failed: %w", p, err)
|
||||
}
|
||||
mergedValues = transform.MergeMaps(mergedValues, values)
|
||||
valuesFiles = append(valuesFiles, p)
|
||||
}
|
||||
return mergedValues, nil
|
||||
return mergedValues, valuesFiles, nil
|
||||
}
|
||||
|
||||
// copyFileToPath attempts to copy in to out. It returns an error if out already exists.
|
||||
|
|
|
@ -284,7 +284,9 @@ func Test_mergeFileValues(t *testing.T) {
|
|||
name string
|
||||
files []*helmchart.File
|
||||
paths []string
|
||||
want map[string]interface{}
|
||||
ignoreMissing bool
|
||||
wantValues map[string]interface{}
|
||||
wantFiles []string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
|
@ -295,10 +297,11 @@ func Test_mergeFileValues(t *testing.T) {
|
|||
{Name: "c.yaml", Data: []byte("b: d")},
|
||||
},
|
||||
paths: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||
want: map[string]interface{}{
|
||||
wantValues: map[string]interface{}{
|
||||
"a": "b",
|
||||
"b": "d",
|
||||
},
|
||||
wantFiles: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||
},
|
||||
{
|
||||
name: "illegal traverse",
|
||||
|
@ -318,6 +321,25 @@ func Test_mergeFileValues(t *testing.T) {
|
|||
paths: []string{"a.yaml"},
|
||||
wantErr: "no values file found at path '/a.yaml'",
|
||||
},
|
||||
{
|
||||
name: "ignore missing files",
|
||||
files: []*helmchart.File{
|
||||
{Name: "a.yaml", Data: []byte("a: b")},
|
||||
},
|
||||
paths: []string{"a.yaml", "b.yaml"},
|
||||
ignoreMissing: true,
|
||||
wantValues: map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
wantFiles: []string{"a.yaml"},
|
||||
},
|
||||
{
|
||||
name: "all files missing",
|
||||
paths: []string{"a.yaml"},
|
||||
ignoreMissing: true,
|
||||
wantValues: map[string]interface{}{},
|
||||
wantFiles: []string{},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
|
@ -329,16 +351,18 @@ func Test_mergeFileValues(t *testing.T) {
|
|||
g.Expect(os.WriteFile(filepath.Join(baseDir, f.Name), f.Data, 0o640)).To(Succeed())
|
||||
}
|
||||
|
||||
got, err := mergeFileValues(baseDir, tt.paths)
|
||||
gotValues, gotFiles, err := mergeFileValues(baseDir, tt.paths, tt.ignoreMissing)
|
||||
if tt.wantErr != "" {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(gotValues).To(BeNil())
|
||||
g.Expect(gotFiles).To(BeNil())
|
||||
return
|
||||
}
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
g.Expect(gotValues).To(Equal(tt.wantValues))
|
||||
g.Expect(gotFiles).To(Equal(tt.wantFiles))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
|
@ -103,7 +103,7 @@ func (b *remoteChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
|||
}
|
||||
chart.Metadata.Version = result.Version
|
||||
|
||||
mergedValues, err := mergeChartValues(chart, opts.ValuesFiles)
|
||||
mergedValues, valuesFiles, err := mergeChartValues(chart, opts.ValuesFiles, opts.IgnoreMissingValuesFiles)
|
||||
if err != nil {
|
||||
err = fmt.Errorf("failed to merge chart values: %w", err)
|
||||
return result, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
||||
|
@ -113,7 +113,7 @@ func (b *remoteChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
|||
if err != nil {
|
||||
return nil, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
||||
}
|
||||
result.ValuesFiles = opts.GetValuesFiles()
|
||||
result.ValuesFiles = valuesFiles
|
||||
}
|
||||
|
||||
// Package the chart with the custom values
|
||||
|
@ -203,6 +203,11 @@ func generateBuildResult(cv *repo.ChartVersion, opts BuildOptions) (*Build, bool
|
|||
if result.Name == curMeta.Name && result.Version == curMeta.Version {
|
||||
result.Path = opts.CachedChart
|
||||
result.ValuesFiles = opts.GetValuesFiles()
|
||||
if opts.CachedChartValuesFiles != nil {
|
||||
// If the cached chart values files are set, we should use them
|
||||
// instead of reporting the values files.
|
||||
result.ValuesFiles = opts.CachedChartValuesFiles
|
||||
}
|
||||
result.Packaged = requiresPackaging
|
||||
return result, true, nil
|
||||
}
|
||||
|
@ -226,13 +231,18 @@ func setBuildMetaData(version, versionMetadata string) (*semver.Version, error)
|
|||
}
|
||||
|
||||
// mergeChartValues merges the given chart.Chart Files paths into a single "values.yaml" map.
|
||||
// It returns the merge result, or an error.
|
||||
func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interface{}, error) {
|
||||
// By default, a missing file is considered an error. If ignoreMissing is set true,
|
||||
// missing files are ignored.
|
||||
// It returns the merge result and the list of files that contributed to that result,
|
||||
// or an error.
|
||||
func mergeChartValues(chart *helmchart.Chart, paths []string, ignoreMissing bool) (map[string]interface{}, []string, error) {
|
||||
mergedValues := make(map[string]interface{})
|
||||
valuesFiles := make([]string, 0, len(paths))
|
||||
for _, p := range paths {
|
||||
cfn := filepath.Clean(p)
|
||||
if cfn == chartutil.ValuesfileName {
|
||||
mergedValues = transform.MergeMaps(mergedValues, chart.Values)
|
||||
valuesFiles = append(valuesFiles, p)
|
||||
continue
|
||||
}
|
||||
var b []byte
|
||||
|
@ -243,15 +253,19 @@ func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interf
|
|||
}
|
||||
}
|
||||
if b == nil {
|
||||
return nil, fmt.Errorf("no values file found at path '%s'", p)
|
||||
if ignoreMissing {
|
||||
continue
|
||||
}
|
||||
return nil, nil, fmt.Errorf("no values file found at path '%s'", p)
|
||||
}
|
||||
values := make(map[string]interface{})
|
||||
if err := yaml.Unmarshal(b, &values); err != nil {
|
||||
return nil, fmt.Errorf("unmarshaling values from '%s' failed: %w", p, err)
|
||||
return nil, nil, fmt.Errorf("unmarshaling values from '%s' failed: %w", p, err)
|
||||
}
|
||||
mergedValues = transform.MergeMaps(mergedValues, values)
|
||||
valuesFiles = append(valuesFiles, p)
|
||||
}
|
||||
return mergedValues, nil
|
||||
return mergedValues, valuesFiles, nil
|
||||
}
|
||||
|
||||
// validatePackageAndWriteToPath atomically writes the packaged chart from reader
|
||||
|
|
|
@ -446,7 +446,9 @@ func Test_mergeChartValues(t *testing.T) {
|
|||
name string
|
||||
chart *helmchart.Chart
|
||||
paths []string
|
||||
want map[string]interface{}
|
||||
ignoreMissing bool
|
||||
wantValues map[string]interface{}
|
||||
wantFiles []string
|
||||
wantErr string
|
||||
}{
|
||||
{
|
||||
|
@ -459,10 +461,11 @@ func Test_mergeChartValues(t *testing.T) {
|
|||
},
|
||||
},
|
||||
paths: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||
want: map[string]interface{}{
|
||||
wantValues: map[string]interface{}{
|
||||
"a": "b",
|
||||
"b": "d",
|
||||
},
|
||||
wantFiles: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||
},
|
||||
{
|
||||
name: "uses chart values",
|
||||
|
@ -475,10 +478,11 @@ func Test_mergeChartValues(t *testing.T) {
|
|||
},
|
||||
},
|
||||
paths: []string{chartutil.ValuesfileName, "c.yaml"},
|
||||
want: map[string]interface{}{
|
||||
wantValues: map[string]interface{}{
|
||||
"a": "b",
|
||||
"b": "d",
|
||||
},
|
||||
wantFiles: []string{chartutil.ValuesfileName, "c.yaml"},
|
||||
},
|
||||
{
|
||||
name: "unmarshal error",
|
||||
|
@ -496,21 +500,59 @@ func Test_mergeChartValues(t *testing.T) {
|
|||
paths: []string{"a.yaml"},
|
||||
wantErr: "no values file found at path 'a.yaml'",
|
||||
},
|
||||
{
|
||||
name: "merges values ignoring file missing",
|
||||
chart: &helmchart.Chart{
|
||||
Files: []*helmchart.File{
|
||||
{Name: "a.yaml", Data: []byte("a: b")},
|
||||
},
|
||||
},
|
||||
paths: []string{"a.yaml", "b.yaml"},
|
||||
ignoreMissing: true,
|
||||
wantValues: map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
wantFiles: []string{"a.yaml"},
|
||||
},
|
||||
{
|
||||
name: "merges values ignoring all missing",
|
||||
chart: &helmchart.Chart{},
|
||||
paths: []string{"a.yaml"},
|
||||
ignoreMissing: true,
|
||||
wantValues: map[string]interface{}{},
|
||||
wantFiles: []string{},
|
||||
},
|
||||
{
|
||||
name: "uses chart values ignoring missing file",
|
||||
chart: &helmchart.Chart{
|
||||
Values: map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
},
|
||||
paths: []string{chartutil.ValuesfileName, "c.yaml"},
|
||||
ignoreMissing: true,
|
||||
wantValues: map[string]interface{}{
|
||||
"a": "b",
|
||||
},
|
||||
wantFiles: []string{chartutil.ValuesfileName},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
g := NewWithT(t)
|
||||
|
||||
got, err := mergeChartValues(tt.chart, tt.paths)
|
||||
gotValues, gotFiles, err := mergeChartValues(tt.chart, tt.paths, tt.ignoreMissing)
|
||||
if tt.wantErr != "" {
|
||||
g.Expect(err).To(HaveOccurred())
|
||||
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
||||
g.Expect(got).To(BeNil())
|
||||
g.Expect(gotValues).To(BeNil())
|
||||
g.Expect(gotFiles).To(BeNil())
|
||||
return
|
||||
}
|
||||
|
||||
g.Expect(err).ToNot(HaveOccurred())
|
||||
g.Expect(got).To(Equal(tt.want))
|
||||
g.Expect(gotValues).To(Equal(tt.wantValues))
|
||||
g.Expect(gotFiles).To(Equal(tt.wantFiles))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue