Merge pull request #244 from fluxcd/fix-values-file-path-support

Fix HelmChart valuesFile chart path restriction
This commit is contained in:
Aurel Canciu 2020-12-17 13:12:09 +02:00 committed by GitHub
commit c3f6ee74ca
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
6 changed files with 214 additions and 132 deletions

View File

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

View File

@ -22,6 +22,7 @@ import (
"io/ioutil" "io/ioutil"
"net/url" "net/url"
"os" "os"
"path/filepath"
"regexp" "regexp"
"strings" "strings"
"time" "time"
@ -378,8 +379,18 @@ 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
var valuesData []byte
cfn := filepath.Clean(chart.Spec.ValuesFile)
for _, f := range helmChart.Files {
if f.Name == cfn {
valuesData = f.Data
break
}
}
// Overwrite values file // Overwrite values file
if changed, err := helm.OverwriteChartDefaultValues(helmChart, chart.Spec.ValuesFile); err != nil { if changed, err := helm.OverwriteChartDefaultValues(helmChart, valuesData); 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
@ -483,9 +494,27 @@ 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 != "" && chart.Spec.ValuesFile != chartutil.ValuesfileName { if chart.Spec.ValuesFile != "" {
// Overwrite default values if configured srcPath, err := securejoin.SecureJoin(tmpDir, chart.Spec.ValuesFile)
isValuesFileOverriden, err = helm.OverwriteChartDefaultValues(helmChart, chart.Spec.ValuesFile) if err != nil {
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", chart.Spec.ValuesFile)
return chart, err
}
src, err := os.Open(srcPath)
if err != nil {
err = fmt.Errorf("failed to open values file '%s': %w", chart.Spec.ValuesFile, err)
return chart, err
}
defer src.Close()
var valuesData []byte
if _, err := src.Read(valuesData); err == nil {
isValuesFileOverriden, err = helm.OverwriteChartDefaultValues(helmChart, valuesData)
}
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

@ -693,6 +693,31 @@ var _ = Describe("HelmChartReconciler", func() {
return got.Status.Artifact != nil && return got.Status.Artifact != nil &&
storage.ArtifactExist(*got.Status.Artifact) storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue()) }, timeout, interval).Should(BeTrue())
When("Setting valid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
chart.Spec.ValuesFile = "./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 != nil &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
})
When("Setting invalid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
chart.Spec.ValuesFile = "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.Artifact != nil && got.Status.Artifact.Revision == updated.Status.Artifact.Revision
}, timeout, interval).Should(BeTrue())
})
}) })
}) })
@ -936,6 +961,31 @@ var _ = Describe("HelmChartReconciler", func() {
return got.Status.Artifact != nil && return got.Status.Artifact != nil &&
storage.ArtifactExist(*got.Status.Artifact) storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue()) }, timeout, interval).Should(BeTrue())
When("Setting valid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
chart.Spec.ValuesFile = "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 != nil &&
storage.ArtifactExist(*got.Status.Artifact)
}, timeout, interval).Should(BeTrue())
})
When("Setting invalid valuesFile attribute", func() {
updated := &sourcev1.HelmChart{}
Expect(k8sClient.Get(context.Background(), key, updated)).To(Succeed())
chart.Spec.ValuesFile = "./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 != nil && got.Status.Artifact.Revision == updated.Status.Artifact.Revision
}, timeout, interval).Should(BeTrue())
})
}) })
}) })
}) })

View File

@ -0,0 +1,66 @@
# Override values for helmchart.
# This is a YAML-formatted file.
# Declare variables to be passed into your templates.
replicaCount: 3
image:
repository: nginx
pullPolicy: IfNotPresent
imagePullSecrets: []
nameOverride: ""
fullnameOverride: ""
serviceAccount:
# Specifies whether a service account should be created
create: true
# The name of the service account to use.
# If not set and create is true, a name is generated using the fullname template
name:
podSecurityContext: {}
# fsGroup: 2000
securityContext: {}
# capabilities:
# drop:
# - ALL
# readOnlyRootFilesystem: true
# runAsNonRoot: true
# runAsUser: 1000
service:
type: ClusterIP
port: 80
ingress:
enabled: false
annotations: {}
# kubernetes.io/ingress.class: nginx
# kubernetes.io/tls-acme: "true"
hosts:
- host: chart-example.local
paths: []
tls: []
# - secretName: chart-example-tls
# hosts:
# - chart-example.local
resources: {}
# We usually recommend not to specify default resources and to leave this as a conscious
# choice for the user. This also increases chances charts run on environments with little
# resources, such as Minikube. If you do want to specify resources, uncomment the following
# lines, adjust them as necessary, and remove the curly braces after 'resources:'.
# limits:
# cpu: 100m
# memory: 128Mi
# requests:
# cpu: 100m
# memory: 128Mi
nodeSelector: {}
tolerations: []
affinity: {}

View File

@ -25,46 +25,30 @@ import (
) )
// OverwriteChartDefaultValues overwrites the chart default values file with the // OverwriteChartDefaultValues overwrites the chart default values file with the
// contents of the given valuesFile. // given data.
func OverwriteChartDefaultValues(chart *helmchart.Chart, valuesFile string) (bool, error) { func OverwriteChartDefaultValues(chart *helmchart.Chart, data []byte) (bool, error) {
if valuesFile == "" || valuesFile == chartutil.ValuesfileName {
return false, nil
}
// Find override file and retrieve contents
var valuesData []byte
for _, f := range chart.Files {
if f.Name == valuesFile {
valuesData = f.Data
break
}
}
if valuesData == nil {
return false, fmt.Errorf("failed to locate override values file: %s", valuesFile)
}
// Read override values file data // Read override values file data
values, err := chartutil.ReadValues(valuesData) values, err := chartutil.ReadValues(data)
if err != nil { if err != nil {
return false, fmt.Errorf("failed to parse override values file: %s", valuesFile) return false, fmt.Errorf("failed to parse provided override values file data")
} }
// Replace current values file in Raw field // Replace current values file in Raw field
for _, f := range chart.Raw { for _, f := range chart.Raw {
if f.Name == chartutil.ValuesfileName { if f.Name == chartutil.ValuesfileName {
// Do nothing if contents are equal // Do nothing if contents are equal
if reflect.DeepEqual(f.Data, valuesData) { if reflect.DeepEqual(f.Data, data) {
return false, nil return false, nil
} }
// Replace in Files field // Replace in Files field
for _, f := range chart.Files { for _, f := range chart.Files {
if f.Name == chartutil.ValuesfileName { if f.Name == chartutil.ValuesfileName {
f.Data = valuesData f.Data = data
} }
} }
f.Data = valuesData f.Data = data
chart.Values = values chart.Values = values
return true, nil return true, nil
} }

View File

@ -18,7 +18,6 @@ package helm
import ( import (
"reflect" "reflect"
"strings"
"testing" "testing"
helmchart "helm.sh/helm/v3/pkg/chart" helmchart "helm.sh/helm/v3/pkg/chart"
@ -27,24 +26,11 @@ import (
var ( var (
originalValuesFixture []byte = []byte("override: original") originalValuesFixture []byte = []byte("override: original")
overrideValuesFixture []byte = []byte("override: test")
chartFilesFixture []*helmchart.File = []*helmchart.File{ chartFilesFixture []*helmchart.File = []*helmchart.File{
{ {
Name: "values.yaml", Name: "values.yaml",
Data: originalValuesFixture, Data: originalValuesFixture,
}, },
{
Name: "values-identical.yaml",
Data: originalValuesFixture,
},
{
Name: "values-override.yaml",
Data: overrideValuesFixture,
},
{
Name: "values-invalid.yaml",
Data: []byte(":fail!"),
},
} }
chartFixture helmchart.Chart = helmchart.Chart{ chartFixture helmchart.Chart = helmchart.Chart{
Metadata: &helmchart.Metadata{ Metadata: &helmchart.Metadata{
@ -57,104 +43,71 @@ var (
) )
func TestOverwriteChartDefaultValues(t *testing.T) { func TestOverwriteChartDefaultValues(t *testing.T) {
for _, tt := range []string{"", "values.yaml", "values-identical.yaml"} { invalidChartFixture := chartFixture
t.Run(tt, func(t *testing.T) { invalidChartFixture.Raw = []*helmchart.File{}
fixture := chartFixture invalidChartFixture.Files = []*helmchart.File{}
ok, err := OverwriteChartDefaultValues(&fixture, tt)
if ok { testCases := []struct {
t.Error("OverwriteChartDefaultValues() should return false") desc string
return chart helmchart.Chart
data []byte
ok bool
expectErr bool
}{
{
desc: "invalid chart",
chart: invalidChartFixture,
data: originalValuesFixture,
expectErr: true,
},
{
desc: "identical override",
chart: chartFixture,
data: originalValuesFixture,
},
{
desc: "valid override",
chart: chartFixture,
ok: true,
data: []byte("override: test"),
},
{
desc: "empty override",
chart: chartFixture,
ok: true,
data: []byte(""),
},
{
desc: "invalid",
chart: chartFixture,
data: []byte("!fail:"),
expectErr: true,
},
} }
if err != nil { for _, tt := range testCases {
t.Errorf("OverwriteChartDefaultValues() error = %v", err) t.Run(tt.desc, func(t *testing.T) {
return fixture := tt.chart
ok, err := OverwriteChartDefaultValues(&fixture, tt.data)
if ok != tt.ok {
t.Fatalf("should return %v, returned %v", tt.ok, ok)
} }
if err != nil && !tt.expectErr {
t.Fatalf("returned unexpected error: %v", err)
}
if err == nil && tt.expectErr {
t.Fatal("expected error")
}
for _, f := range fixture.Raw { for _, f := range fixture.Raw {
if f.Name == chartutil.ValuesfileName && !reflect.DeepEqual(f.Data, originalValuesFixture) { if f.Name == chartutil.ValuesfileName && reflect.DeepEqual(f.Data, originalValuesFixture) && tt.ok {
t.Error("OverwriteChartDefaultValues() should not override values.yaml in Raw field") t.Error("should override values.yaml in Raw field")
return
} }
} }
for _, f := range fixture.Files { for _, f := range fixture.Files {
if f.Name == chartutil.ValuesfileName && !reflect.DeepEqual(f.Data, originalValuesFixture) { if f.Name == chartutil.ValuesfileName && reflect.DeepEqual(f.Data, originalValuesFixture) && tt.ok {
t.Error("OverwriteChartDefaultValues() should not override values.yaml in Files field") t.Error("should override values.yaml in Files field")
return
} }
} }
}) })
} }
t.Run("values-error.yaml", func(t *testing.T) {
fixture := chartFixture
ok, err := OverwriteChartDefaultValues(&fixture, "values-error.yaml")
if ok {
t.Error("OverwriteChartDefaultValues() should return false")
}
if err == nil {
t.Error("OverwriteChartDefaultValues() expects an error")
return
} else if !strings.Contains(err.Error(), "failed to locate override values file") {
t.Error("OverwriteChartDefaultValues() returned invalid error")
return
}
})
t.Run("values-override.yaml", func(t *testing.T) {
fixture := chartFixture
ok, err := OverwriteChartDefaultValues(&fixture, "values-override.yaml")
if err != nil {
t.Errorf("OverwriteChartDefaultValues() error = %v", err)
return
}
if !ok {
t.Error("OverwriteChartDefaultValues() should return true")
return
}
for _, f := range fixture.Raw {
if f.Name == chartutil.ValuesfileName && string(f.Data) != string(overrideValuesFixture) {
t.Error("OverwriteChartDefaultValues() should override values.yaml in Raw field")
return
}
}
for _, f := range fixture.Files {
if f.Name == chartutil.ValuesfileName && string(f.Data) != string(overrideValuesFixture) {
t.Error("OverwriteChartDefaultValues() should override values.yaml in Files field")
return
}
}
// Context: the impossible chart, no values.yaml file defined!
fixture.Raw = fixture.Raw[1:]
fixture.Files = fixture.Files[1:]
ok, err = OverwriteChartDefaultValues(&fixture, "values-override.yaml")
if ok {
t.Error("OverwriteChartDefaultValues() should return false")
return
}
if err == nil {
t.Error("OverwriteChartDefaultValues() expects an error")
return
} else if !strings.Contains(err.Error(), "failed to locate values file") {
t.Error("OverwriteChartDefaultValues() returned invalid error")
return
}
})
t.Run("values-invalid.yaml", func(t *testing.T) {
fixture := chartFixture
fixture.Raw[0].Data = fixture.Raw[1].Data
fixture.Files[0].Data = fixture.Files[1].Data
ok, err := OverwriteChartDefaultValues(&fixture, "values-invalid.yaml")
if ok {
t.Error("OverwriteChartDefaultValues() should return false")
return
}
if err == nil {
t.Error("OverwriteChartDefaultValues() expects an error")
return
} else if !strings.Contains(err.Error(), "failed to parse override values file") {
t.Error("OverwriteChartDefaultValues() returned invalid error")
return
}
})
} }