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 ;\
|
cd $$TMP_DIR ;\
|
||||||
go mod init tmp ;\
|
go mod init tmp ;\
|
||||||
echo "Downloading $(2)" ;\
|
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 ;\
|
rm -rf $$TMP_DIR ;\
|
||||||
}
|
}
|
||||||
endef
|
endef
|
||||||
|
|
|
@ -79,6 +79,11 @@ type HelmChartSpec struct {
|
||||||
// +deprecated
|
// +deprecated
|
||||||
ValuesFile string `json:"valuesFile,omitempty"`
|
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
|
// Suspend tells the controller to suspend the reconciliation of this
|
||||||
// source.
|
// source.
|
||||||
// +optional
|
// +optional
|
||||||
|
@ -142,6 +147,12 @@ type HelmChartStatus struct {
|
||||||
// +optional
|
// +optional
|
||||||
ObservedChartName string `json:"observedChartName,omitempty"`
|
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.
|
// Conditions holds the conditions for the HelmChart.
|
||||||
// +optional
|
// +optional
|
||||||
Conditions []metav1.Condition `json:"conditions,omitempty"`
|
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.
|
// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil.
|
||||||
func (in *HelmChartStatus) DeepCopyInto(out *HelmChartStatus) {
|
func (in *HelmChartStatus) DeepCopyInto(out *HelmChartStatus) {
|
||||||
*out = *in
|
*out = *in
|
||||||
|
if in.ObservedValuesFiles != nil {
|
||||||
|
in, out := &in.ObservedValuesFiles, &out.ObservedValuesFiles
|
||||||
|
*out = make([]string, len(*in))
|
||||||
|
copy(*out, *in)
|
||||||
|
}
|
||||||
if in.Conditions != nil {
|
if in.Conditions != nil {
|
||||||
in, out := &in.Conditions, &out.Conditions
|
in, out := &in.Conditions, &out.Conditions
|
||||||
*out = make([]v1.Condition, len(*in))
|
*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
|
Chart is the name or path the Helm chart is available at in the
|
||||||
SourceRef.
|
SourceRef.
|
||||||
type: string
|
type: string
|
||||||
|
ignoreMissingValuesFiles:
|
||||||
|
description: |-
|
||||||
|
IgnoreMissingValuesFiles controls whether to silently ignore missing values
|
||||||
|
files rather than failing.
|
||||||
|
type: boolean
|
||||||
interval:
|
interval:
|
||||||
description: |-
|
description: |-
|
||||||
Interval at which the HelmChart SourceRef is checked for updates.
|
Interval at which the HelmChart SourceRef is checked for updates.
|
||||||
|
@ -638,6 +643,14 @@ spec:
|
||||||
ObservedSourceArtifactRevision is the last observed Artifact.Revision
|
ObservedSourceArtifactRevision is the last observed Artifact.Revision
|
||||||
of the HelmChartSpec.SourceRef.
|
of the HelmChartSpec.SourceRef.
|
||||||
type: string
|
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:
|
url:
|
||||||
description: |-
|
description: |-
|
||||||
URL is the dynamic fetch link for the latest Artifact.
|
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>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<code>suspend</code><br>
|
||||||
<em>
|
<em>
|
||||||
bool
|
bool
|
||||||
|
@ -2329,6 +2342,19 @@ is merged before the ValuesFiles items. Ignored when omitted.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<code>suspend</code><br>
|
||||||
<em>
|
<em>
|
||||||
bool
|
bool
|
||||||
|
@ -2436,6 +2462,20 @@ resolved chart reference.</p>
|
||||||
</tr>
|
</tr>
|
||||||
<tr>
|
<tr>
|
||||||
<td>
|
<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>
|
<code>conditions</code><br>
|
||||||
<em>
|
<em>
|
||||||
<a href="https://pkg.go.dev/k8s.io/apimachinery/pkg/apis/meta/v1#Condition">
|
<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
|
Values files also affect the generated artifact revision, see
|
||||||
[artifact](#artifact).
|
[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
|
### Reconcile strategy
|
||||||
|
|
||||||
`.spec.reconcileStrategy` is an optional field to specify what enables the
|
`.spec.reconcileStrategy` is an optional field to specify what enables the
|
||||||
|
|
|
@ -665,8 +665,9 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
|
||||||
// Construct the chart builder with scoped configuration
|
// Construct the chart builder with scoped configuration
|
||||||
cb := chart.NewRemoteBuilder(chartRepo)
|
cb := chart.NewRemoteBuilder(chartRepo)
|
||||||
opts := chart.BuildOptions{
|
opts := chart.BuildOptions{
|
||||||
ValuesFiles: obj.GetValuesFiles(),
|
ValuesFiles: obj.GetValuesFiles(),
|
||||||
Force: obj.Generation != obj.Status.ObservedGeneration,
|
IgnoreMissingValuesFiles: obj.Spec.IgnoreMissingValuesFiles,
|
||||||
|
Force: obj.Generation != obj.Status.ObservedGeneration,
|
||||||
// The remote builder will not attempt to download the chart if
|
// The remote builder will not attempt to download the chart if
|
||||||
// an artifact exists with the same name and version and `Force` is false.
|
// an artifact exists with the same name and version and `Force` is false.
|
||||||
// It will however try to verify the chart if `obj.Spec.Verify` is set, at every reconciliation.
|
// It will however try to verify the chart if `obj.Spec.Verify` is set, at every reconciliation.
|
||||||
|
@ -674,6 +675,7 @@ func (r *HelmChartReconciler) buildFromHelmRepository(ctx context.Context, obj *
|
||||||
}
|
}
|
||||||
if artifact := obj.GetArtifact(); artifact != nil {
|
if artifact := obj.GetArtifact(); artifact != nil {
|
||||||
opts.CachedChart = r.Storage.LocalPath(*artifact)
|
opts.CachedChart = r.Storage.LocalPath(*artifact)
|
||||||
|
opts.CachedChartValuesFiles = obj.Status.ObservedValuesFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Set the VersionMetadata to the object's Generation if ValuesFiles is defined
|
// Set the VersionMetadata to the object's Generation if ValuesFiles is defined
|
||||||
|
@ -760,11 +762,13 @@ func (r *HelmChartReconciler) buildFromTarballArtifact(ctx context.Context, obj
|
||||||
|
|
||||||
// Configure builder options, including any previously cached chart
|
// Configure builder options, including any previously cached chart
|
||||||
opts := chart.BuildOptions{
|
opts := chart.BuildOptions{
|
||||||
ValuesFiles: obj.GetValuesFiles(),
|
ValuesFiles: obj.GetValuesFiles(),
|
||||||
Force: obj.Generation != obj.Status.ObservedGeneration,
|
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.CachedChart = r.Storage.LocalPath(*artifact)
|
||||||
|
opts.CachedChartValuesFiles = obj.Status.ObservedValuesFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Configure revision metadata for chart build if we should react to revision changes
|
// 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
|
// Record it on the object
|
||||||
obj.Status.Artifact = artifact.DeepCopy()
|
obj.Status.Artifact = artifact.DeepCopy()
|
||||||
obj.Status.ObservedChartName = b.Name
|
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
|
// Update symlink on a "best effort" basis
|
||||||
symURL, err := r.Storage.Symlink(artifact, "latest.tar.gz")
|
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())
|
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",
|
name: "Sets Generation as VersionMetadata with values files",
|
||||||
beforeFunc: func(obj *helmv1.HelmChart, repository *helmv1.HelmRepository) {
|
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.Version).To(Equal(higherChartVersion + "+3"))
|
||||||
g.Expect(build.Path).ToNot(BeEmpty())
|
g.Expect(build.Path).ToNot(BeEmpty())
|
||||||
g.Expect(build.Path).To(BeARegularFile())
|
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) {
|
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
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.Version).To(Equal(metadata.Version))
|
||||||
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||||
g.Expect(build.Path).To(BeARegularFile())
|
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.Version).To(Equal("0.1.0+3"))
|
||||||
g.Expect(build.ResolvedDependencies).To(Equal(0))
|
g.Expect(build.ResolvedDependencies).To(Equal(0))
|
||||||
g.Expect(build.Path).To(BeARegularFile())
|
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) {
|
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
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.Version).To(Equal("0.1.0"))
|
||||||
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
g.Expect(build.Path).To(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||||
g.Expect(build.Path).To(BeARegularFile())
|
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.Version).To(Equal("0.1.0"))
|
||||||
g.Expect(build.Path).ToNot(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
g.Expect(build.Path).ToNot(Equal(storage.LocalPath(*cachedArtifact.DeepCopy())))
|
||||||
g.Expect(build.Path).To(BeARegularFile())
|
g.Expect(build.Path).To(BeARegularFile())
|
||||||
|
g.Expect(build.ValuesFiles).To(BeEmpty())
|
||||||
},
|
},
|
||||||
cleanFunc: func(g *WithT, build *chart.Build) {
|
cleanFunc: func(g *WithT, build *chart.Build) {
|
||||||
g.Expect(os.Remove(build.Path)).To(Succeed())
|
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",
|
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) {
|
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
|
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.GetArtifact().Revision).To(Equal("0.1.0"))
|
||||||
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
||||||
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
||||||
|
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
|
@ -1597,6 +1684,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
||||||
afterFunc: func(t *WithT, obj *helmv1.HelmChart) {
|
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.Artifact.Path).To(Equal("testdata/charts/helmchart-0.1.0.tgz"))
|
||||||
t.Expect(obj.Status.ObservedChartName).To(BeEmpty())
|
t.Expect(obj.Status.ObservedChartName).To(BeEmpty())
|
||||||
|
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||||
t.Expect(obj.Status.URL).To(BeEmpty())
|
t.Expect(obj.Status.URL).To(BeEmpty())
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
@ -1626,7 +1714,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Removes ArtifactOutdatedCondition after creating new artifact",
|
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) {
|
beforeFunc: func(obj *helmv1.HelmChart) {
|
||||||
conditions.MarkTrue(obj, sourcev1.ArtifactOutdatedCondition, "Foo", "")
|
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.GetArtifact().Revision).To(Equal("0.1.0"))
|
||||||
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
t.Expect(obj.Status.URL).ToNot(BeEmpty())
|
||||||
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
t.Expect(obj.Status.ObservedChartName).To(Equal("helmchart"))
|
||||||
|
t.Expect(obj.Status.ObservedValuesFiles).To(BeNil())
|
||||||
},
|
},
|
||||||
want: sreconcile.ResultSuccess,
|
want: sreconcile.ResultSuccess,
|
||||||
assertConditions: []metav1.Condition{
|
assertConditions: []metav1.Condition{
|
||||||
|
@ -1644,7 +1733,7 @@ func TestHelmChartReconciler_reconcileArtifact(t *testing.T) {
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Creates latest symlink to the created artifact",
|
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) {
|
afterFunc: func(t *WithT, obj *helmv1.HelmChart) {
|
||||||
t.Expect(obj.GetArtifact()).ToNot(BeNil())
|
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'"),
|
*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 {
|
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
|
var copyP string
|
||||||
if path != "" {
|
if path != "" {
|
||||||
f, err := os.Open(path)
|
f, err := os.Open(path)
|
||||||
|
@ -2032,9 +2161,10 @@ func mockChartBuild(name, version, path string) *chart.Build {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return &chart.Build{
|
return &chart.Build{
|
||||||
Name: name,
|
Name: name,
|
||||||
Version: version,
|
Version: version,
|
||||||
Path: copyP,
|
Path: copyP,
|
||||||
|
ValuesFiles: valuesFiles,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -107,6 +107,12 @@ type BuildOptions struct {
|
||||||
// ValuesFiles can be set to a list of relative paths, used to compose
|
// ValuesFiles can be set to a list of relative paths, used to compose
|
||||||
// and overwrite an alternative default "values.yaml" for the chart.
|
// and overwrite an alternative default "values.yaml" for the chart.
|
||||||
ValuesFiles []string
|
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
|
// CachedChart can be set to the absolute path of a chart stored on
|
||||||
// the local filesystem, and is used for simple validation by metadata
|
// the local filesystem, and is used for simple validation by metadata
|
||||||
// comparisons.
|
// 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 {
|
if result.Name == curMeta.Name && result.Version == curMeta.Version {
|
||||||
result.Path = opts.CachedChart
|
result.Path = opts.CachedChart
|
||||||
result.ValuesFiles = opts.GetValuesFiles()
|
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
|
result.Packaged = requiresPackaging
|
||||||
|
|
||||||
return result, nil
|
return result, nil
|
||||||
|
@ -140,9 +145,12 @@ func (b *localChartBuilder) Build(ctx context.Context, ref Reference, p string,
|
||||||
}
|
}
|
||||||
|
|
||||||
// Merge chart values, if instructed
|
// Merge chart values, if instructed
|
||||||
var mergedValues map[string]interface{}
|
var (
|
||||||
|
mergedValues map[string]interface{}
|
||||||
|
valuesFiles []string
|
||||||
|
)
|
||||||
if len(opts.GetValuesFiles()) > 0 {
|
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}
|
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 {
|
if err != nil {
|
||||||
return result, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
return result, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
||||||
}
|
}
|
||||||
result.ValuesFiles = opts.GetValuesFiles()
|
result.ValuesFiles = valuesFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Ensure dependencies are fetched if building from a directory
|
// 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.
|
// 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
|
// The provided (relative) paths may not traverse outside baseDir. By default, a missing
|
||||||
// result, or an error.
|
// file is considered an error. If ignoreMissing is true, missing files are ignored.
|
||||||
func mergeFileValues(baseDir string, paths []string) (map[string]interface{}, error) {
|
// 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{})
|
mergedValues := make(map[string]interface{})
|
||||||
|
valuesFiles := make([]string, 0, len(paths))
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
secureP, err := securejoin.SecureJoin(baseDir, p)
|
secureP, err := securejoin.SecureJoin(baseDir, p)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, nil, err
|
||||||
}
|
}
|
||||||
if f, err := os.Stat(secureP); err != nil || !f.Mode().IsRegular() {
|
f, err := os.Stat(secureP)
|
||||||
return nil, fmt.Errorf("no values file found at path '%s' (reference '%s')",
|
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)
|
strings.TrimPrefix(secureP, baseDir), p)
|
||||||
}
|
}
|
||||||
b, err := os.ReadFile(secureP)
|
b, err := os.ReadFile(secureP)
|
||||||
if err != nil {
|
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{})
|
values := make(map[string]interface{})
|
||||||
err = yaml.Unmarshal(b, &values)
|
err = yaml.Unmarshal(b, &values)
|
||||||
if err != nil {
|
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)
|
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.
|
// copyFileToPath attempts to copy in to out. It returns an error if out already exists.
|
||||||
|
|
|
@ -281,11 +281,13 @@ func TestLocalBuilder_Build_CachedChart(t *testing.T) {
|
||||||
|
|
||||||
func Test_mergeFileValues(t *testing.T) {
|
func Test_mergeFileValues(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
files []*helmchart.File
|
files []*helmchart.File
|
||||||
paths []string
|
paths []string
|
||||||
want map[string]interface{}
|
ignoreMissing bool
|
||||||
wantErr string
|
wantValues map[string]interface{}
|
||||||
|
wantFiles []string
|
||||||
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "merges values from files",
|
name: "merges values from files",
|
||||||
|
@ -295,10 +297,11 @@ func Test_mergeFileValues(t *testing.T) {
|
||||||
{Name: "c.yaml", Data: []byte("b: d")},
|
{Name: "c.yaml", Data: []byte("b: d")},
|
||||||
},
|
},
|
||||||
paths: []string{"a.yaml", "b.yaml", "c.yaml"},
|
paths: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||||
want: map[string]interface{}{
|
wantValues: map[string]interface{}{
|
||||||
"a": "b",
|
"a": "b",
|
||||||
"b": "d",
|
"b": "d",
|
||||||
},
|
},
|
||||||
|
wantFiles: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "illegal traverse",
|
name: "illegal traverse",
|
||||||
|
@ -318,6 +321,25 @@ func Test_mergeFileValues(t *testing.T) {
|
||||||
paths: []string{"a.yaml"},
|
paths: []string{"a.yaml"},
|
||||||
wantErr: "no values file found at path '/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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
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())
|
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 != "" {
|
if tt.wantErr != "" {
|
||||||
g.Expect(err).To(HaveOccurred())
|
g.Expect(err).To(HaveOccurred())
|
||||||
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
||||||
g.Expect(got).To(BeNil())
|
g.Expect(gotValues).To(BeNil())
|
||||||
|
g.Expect(gotFiles).To(BeNil())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
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
|
chart.Metadata.Version = result.Version
|
||||||
|
|
||||||
mergedValues, err := mergeChartValues(chart, opts.ValuesFiles)
|
mergedValues, valuesFiles, err := mergeChartValues(chart, opts.ValuesFiles, opts.IgnoreMissingValuesFiles)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
err = fmt.Errorf("failed to merge chart values: %w", err)
|
err = fmt.Errorf("failed to merge chart values: %w", err)
|
||||||
return result, &BuildError{Reason: ErrValuesFilesMerge, Err: 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 {
|
if err != nil {
|
||||||
return nil, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
return nil, &BuildError{Reason: ErrValuesFilesMerge, Err: err}
|
||||||
}
|
}
|
||||||
result.ValuesFiles = opts.GetValuesFiles()
|
result.ValuesFiles = valuesFiles
|
||||||
}
|
}
|
||||||
|
|
||||||
// Package the chart with the custom values
|
// 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 {
|
if result.Name == curMeta.Name && result.Version == curMeta.Version {
|
||||||
result.Path = opts.CachedChart
|
result.Path = opts.CachedChart
|
||||||
result.ValuesFiles = opts.GetValuesFiles()
|
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
|
result.Packaged = requiresPackaging
|
||||||
return result, true, nil
|
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.
|
// mergeChartValues merges the given chart.Chart Files paths into a single "values.yaml" map.
|
||||||
// It returns the merge result, or an error.
|
// By default, a missing file is considered an error. If ignoreMissing is set true,
|
||||||
func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interface{}, error) {
|
// 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{})
|
mergedValues := make(map[string]interface{})
|
||||||
|
valuesFiles := make([]string, 0, len(paths))
|
||||||
for _, p := range paths {
|
for _, p := range paths {
|
||||||
cfn := filepath.Clean(p)
|
cfn := filepath.Clean(p)
|
||||||
if cfn == chartutil.ValuesfileName {
|
if cfn == chartutil.ValuesfileName {
|
||||||
mergedValues = transform.MergeMaps(mergedValues, chart.Values)
|
mergedValues = transform.MergeMaps(mergedValues, chart.Values)
|
||||||
|
valuesFiles = append(valuesFiles, p)
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
var b []byte
|
var b []byte
|
||||||
|
@ -243,15 +253,19 @@ func mergeChartValues(chart *helmchart.Chart, paths []string) (map[string]interf
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if b == nil {
|
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{})
|
values := make(map[string]interface{})
|
||||||
if err := yaml.Unmarshal(b, &values); err != nil {
|
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)
|
mergedValues = transform.MergeMaps(mergedValues, values)
|
||||||
|
valuesFiles = append(valuesFiles, p)
|
||||||
}
|
}
|
||||||
return mergedValues, nil
|
return mergedValues, valuesFiles, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// validatePackageAndWriteToPath atomically writes the packaged chart from reader
|
// validatePackageAndWriteToPath atomically writes the packaged chart from reader
|
||||||
|
|
|
@ -443,11 +443,13 @@ entries:
|
||||||
|
|
||||||
func Test_mergeChartValues(t *testing.T) {
|
func Test_mergeChartValues(t *testing.T) {
|
||||||
tests := []struct {
|
tests := []struct {
|
||||||
name string
|
name string
|
||||||
chart *helmchart.Chart
|
chart *helmchart.Chart
|
||||||
paths []string
|
paths []string
|
||||||
want map[string]interface{}
|
ignoreMissing bool
|
||||||
wantErr string
|
wantValues map[string]interface{}
|
||||||
|
wantFiles []string
|
||||||
|
wantErr string
|
||||||
}{
|
}{
|
||||||
{
|
{
|
||||||
name: "merges values",
|
name: "merges values",
|
||||||
|
@ -459,10 +461,11 @@ func Test_mergeChartValues(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paths: []string{"a.yaml", "b.yaml", "c.yaml"},
|
paths: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||||
want: map[string]interface{}{
|
wantValues: map[string]interface{}{
|
||||||
"a": "b",
|
"a": "b",
|
||||||
"b": "d",
|
"b": "d",
|
||||||
},
|
},
|
||||||
|
wantFiles: []string{"a.yaml", "b.yaml", "c.yaml"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "uses chart values",
|
name: "uses chart values",
|
||||||
|
@ -475,10 +478,11 @@ func Test_mergeChartValues(t *testing.T) {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
paths: []string{chartutil.ValuesfileName, "c.yaml"},
|
paths: []string{chartutil.ValuesfileName, "c.yaml"},
|
||||||
want: map[string]interface{}{
|
wantValues: map[string]interface{}{
|
||||||
"a": "b",
|
"a": "b",
|
||||||
"b": "d",
|
"b": "d",
|
||||||
},
|
},
|
||||||
|
wantFiles: []string{chartutil.ValuesfileName, "c.yaml"},
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "unmarshal error",
|
name: "unmarshal error",
|
||||||
|
@ -496,21 +500,59 @@ func Test_mergeChartValues(t *testing.T) {
|
||||||
paths: []string{"a.yaml"},
|
paths: []string{"a.yaml"},
|
||||||
wantErr: "no values file found at path '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 {
|
for _, tt := range tests {
|
||||||
t.Run(tt.name, func(t *testing.T) {
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
g := NewWithT(t)
|
g := NewWithT(t)
|
||||||
|
|
||||||
got, err := mergeChartValues(tt.chart, tt.paths)
|
gotValues, gotFiles, err := mergeChartValues(tt.chart, tt.paths, tt.ignoreMissing)
|
||||||
if tt.wantErr != "" {
|
if tt.wantErr != "" {
|
||||||
g.Expect(err).To(HaveOccurred())
|
g.Expect(err).To(HaveOccurred())
|
||||||
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
g.Expect(err.Error()).To(ContainSubstring(tt.wantErr))
|
||||||
g.Expect(got).To(BeNil())
|
g.Expect(gotValues).To(BeNil())
|
||||||
|
g.Expect(gotFiles).To(BeNil())
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
g.Expect(err).ToNot(HaveOccurred())
|
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