Merge badd5b8e00 into ee4be4e342
This commit is contained in:
commit
bbe81400db
|
|
@ -7,12 +7,14 @@ import (
|
|||
"testing"
|
||||
|
||||
"dario.cat/mergo"
|
||||
"github.com/helmfile/vals"
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
"go.uber.org/zap"
|
||||
"go.uber.org/zap/zaptest"
|
||||
|
||||
"github.com/helmfile/helmfile/pkg/environment"
|
||||
"github.com/helmfile/helmfile/pkg/envvar"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/remote"
|
||||
"github.com/helmfile/helmfile/pkg/testhelper"
|
||||
|
|
@ -932,3 +934,450 @@ releases:
|
|||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergedReleaseTemplateData_IncludesReleaseValues(t *testing.T) {
|
||||
logger := zaptest.NewLogger(t).Sugar()
|
||||
testValsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
require.NoError(t, err)
|
||||
|
||||
yamlFile := "/example/path/to/helmfile.yaml"
|
||||
yamlContent := []byte(`environments:
|
||||
default:
|
||||
values:
|
||||
- env.yaml
|
||||
|
||||
releases:
|
||||
- name: myrelease
|
||||
chart: mychart
|
||||
values:
|
||||
- values.yaml
|
||||
`)
|
||||
|
||||
envYamlFile := "/example/path/to/env.yaml"
|
||||
envYamlContent := []byte(`envKey: envValue`)
|
||||
|
||||
valuesFile := "/example/path/to/values.yaml"
|
||||
valuesContent := []byte(`ingress:
|
||||
enabled: true
|
||||
host: example.com`)
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{
|
||||
envYamlFile: string(envYamlContent),
|
||||
valuesFile: string(valuesContent),
|
||||
})
|
||||
testFs.Cwd = "/example/path/to"
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
||||
env := environment.Environment{
|
||||
Name: "default",
|
||||
}
|
||||
state, err := NewCreator(logger, testFs.ToFileSystem(), testValsRuntime, nil, "", "", r, false, "").
|
||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "default", true, true, true, &env, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
release := state.Releases[0]
|
||||
|
||||
templateData, err := state.mergedReleaseTemplateData(&release)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ingress, ok := templateData.Values["ingress"]
|
||||
if !ok {
|
||||
t.Fatalf("expected .Values to contain 'ingress' key from release values")
|
||||
}
|
||||
ingressMap, ok := ingress.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected ingress to be a map, got %T", ingress)
|
||||
}
|
||||
if ingressMap["enabled"] != true {
|
||||
t.Errorf("expected ingress.enabled to be true, got %v", ingressMap["enabled"])
|
||||
}
|
||||
if ingressMap["host"] != "example.com" {
|
||||
t.Errorf("expected ingress.host to be 'example.com', got %v", ingressMap["host"])
|
||||
}
|
||||
|
||||
if templateData.Values["envKey"] != "envValue" {
|
||||
t.Errorf("expected envKey to be 'envValue', got %v", templateData.Values["envKey"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergedReleaseTemplateData_ReleaseValuesOverrideEnvValues(t *testing.T) {
|
||||
logger := zaptest.NewLogger(t).Sugar()
|
||||
testValsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
require.NoError(t, err)
|
||||
|
||||
yamlFile := "/example/path/to/helmfile.yaml"
|
||||
yamlContent := []byte(`environments:
|
||||
default:
|
||||
values:
|
||||
- env.yaml
|
||||
|
||||
releases:
|
||||
- name: myrelease
|
||||
chart: mychart
|
||||
values:
|
||||
- values.yaml
|
||||
`)
|
||||
|
||||
envYamlFile := "/example/path/to/env.yaml"
|
||||
envYamlContent := []byte(`replicaCount: 1`)
|
||||
|
||||
valuesFile := "/example/path/to/values.yaml"
|
||||
valuesContent := []byte(`replicaCount: 3`)
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{
|
||||
envYamlFile: string(envYamlContent),
|
||||
valuesFile: string(valuesContent),
|
||||
})
|
||||
testFs.Cwd = "/example/path/to"
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
||||
env := environment.Environment{
|
||||
Name: "default",
|
||||
}
|
||||
state, err := NewCreator(logger, testFs.ToFileSystem(), testValsRuntime, nil, "", "", r, false, "").
|
||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "default", true, true, true, &env, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
release := state.Releases[0]
|
||||
|
||||
templateData, err := state.mergedReleaseTemplateData(&release)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if templateData.Values["replicaCount"] != 3 {
|
||||
t.Errorf("expected replicaCount to be 3 (release value overriding env), got %v", templateData.Values["replicaCount"])
|
||||
}
|
||||
}
|
||||
|
||||
func TestMergedReleaseTemplateData_InlineValues(t *testing.T) {
|
||||
logger := zaptest.NewLogger(t).Sugar()
|
||||
testValsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
require.NoError(t, err)
|
||||
|
||||
yamlFile := "/example/path/to/helmfile.yaml"
|
||||
yamlContent := []byte(`releases:
|
||||
- name: myrelease
|
||||
chart: mychart
|
||||
values:
|
||||
- ingress:
|
||||
enabled: true
|
||||
host: example.com
|
||||
`)
|
||||
|
||||
testFs := testhelper.NewTestFs(map[string]string{})
|
||||
testFs.Cwd = "/example/path/to"
|
||||
|
||||
r := remote.NewRemote(logger, testFs.Cwd, testFs.ToFileSystem())
|
||||
state, err := NewCreator(logger, testFs.ToFileSystem(), testValsRuntime, nil, "", "", r, false, "").
|
||||
ParseAndLoad(yamlContent, filepath.Dir(yamlFile), yamlFile, "default", true, true, true, nil, nil)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
release := state.Releases[0]
|
||||
|
||||
templateData, err := state.mergedReleaseTemplateData(&release)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
ingress, ok := templateData.Values["ingress"]
|
||||
if !ok {
|
||||
t.Fatalf("expected .Values to contain 'ingress' key from inline release values")
|
||||
}
|
||||
ingressMap, ok := ingress.(map[string]any)
|
||||
if !ok {
|
||||
t.Fatalf("expected ingress to be a map, got %T", ingress)
|
||||
}
|
||||
if ingressMap["enabled"] != true {
|
||||
t.Errorf("expected ingress.enabled to be true, got %v", ingressMap["enabled"])
|
||||
}
|
||||
}
|
||||
|
||||
func newTestHelmStateWithFiles(t *testing.T, files map[string]string) *HelmState {
|
||||
t.Helper()
|
||||
const basePath = "/project"
|
||||
logger := zaptest.NewLogger(t).Sugar()
|
||||
valsRuntime, err := vals.New(vals.Options{CacheSize: 32})
|
||||
require.NoError(t, err)
|
||||
|
||||
testFs := testhelper.NewTestFs(files)
|
||||
testFs.Cwd = basePath
|
||||
|
||||
return &HelmState{
|
||||
logger: logger,
|
||||
fs: testFs.ToFileSystem(),
|
||||
valsRuntime: valsRuntime,
|
||||
basePath: basePath,
|
||||
FilePath: basePath + "/helmfile.yaml",
|
||||
RenderedValues: map[string]any{},
|
||||
}
|
||||
}
|
||||
|
||||
func TestResolveReleaseValues_Empty(t *testing.T) {
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{})
|
||||
|
||||
release := &ReleaseSpec{Name: "myrelease", Chart: "mychart"}
|
||||
result, err := st.resolveReleaseValues(release)
|
||||
require.NoError(t, err)
|
||||
assert.Empty(t, result)
|
||||
}
|
||||
|
||||
func TestResolveReleaseValues_FromFile(t *testing.T) {
|
||||
valuesFile := "/project/values.yaml"
|
||||
valuesContent := `replicaCount: 2
|
||||
image:
|
||||
repository: nginx
|
||||
tag: "1.21"
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
valuesFile: valuesContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{
|
||||
Name: "myrelease",
|
||||
Chart: "mychart",
|
||||
Values: []any{
|
||||
"values.yaml",
|
||||
},
|
||||
}
|
||||
result, err := st.resolveReleaseValues(release)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 2, result["replicaCount"])
|
||||
imageMap, ok := result["image"].(map[string]any)
|
||||
require.True(t, ok, "expected image to be a map")
|
||||
assert.Equal(t, "nginx", imageMap["repository"])
|
||||
}
|
||||
|
||||
func TestResolveReleaseValues_InlineMap(t *testing.T) {
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{})
|
||||
|
||||
release := &ReleaseSpec{
|
||||
Name: "myrelease",
|
||||
Chart: "mychart",
|
||||
Values: []any{
|
||||
map[string]any{
|
||||
"replicaCount": 5,
|
||||
"service": map[string]any{
|
||||
"type": "ClusterIP",
|
||||
"port": 80,
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := st.resolveReleaseValues(release)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 5, result["replicaCount"])
|
||||
serviceMap, ok := result["service"].(map[string]any)
|
||||
require.True(t, ok, "expected service to be a map")
|
||||
assert.Equal(t, "ClusterIP", serviceMap["type"])
|
||||
}
|
||||
|
||||
func TestResolveReleaseValues_MultipleSourcesMerged(t *testing.T) {
|
||||
baseValuesFile := "/project/base.yaml"
|
||||
baseValuesContent := `replicaCount: 1
|
||||
service:
|
||||
type: ClusterIP
|
||||
`
|
||||
overrideValuesFile := "/project/override.yaml"
|
||||
overrideValuesContent := `replicaCount: 3
|
||||
ingress:
|
||||
enabled: true
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
baseValuesFile: baseValuesContent,
|
||||
overrideValuesFile: overrideValuesContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{
|
||||
Name: "myrelease",
|
||||
Chart: "mychart",
|
||||
Values: []any{
|
||||
"base.yaml",
|
||||
"override.yaml",
|
||||
},
|
||||
}
|
||||
result, err := st.resolveReleaseValues(release)
|
||||
require.NoError(t, err)
|
||||
// override.yaml value wins
|
||||
assert.Equal(t, 3, result["replicaCount"])
|
||||
// from base.yaml
|
||||
serviceMap, ok := result["service"].(map[string]any)
|
||||
require.True(t, ok, "expected service to be a map")
|
||||
assert.Equal(t, "ClusterIP", serviceMap["type"])
|
||||
// from override.yaml
|
||||
ingressMap, ok := result["ingress"].(map[string]any)
|
||||
require.True(t, ok, "expected ingress to be a map")
|
||||
assert.Equal(t, true, ingressMap["enabled"])
|
||||
}
|
||||
|
||||
func TestResolveReleaseValues_FileAndInlineMerged(t *testing.T) {
|
||||
valuesFile := "/project/values.yaml"
|
||||
valuesContent := `replicaCount: 1
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
valuesFile: valuesContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{
|
||||
Name: "myrelease",
|
||||
Chart: "mychart",
|
||||
Values: []any{
|
||||
"values.yaml",
|
||||
map[string]any{
|
||||
"extraEnv": "production",
|
||||
},
|
||||
},
|
||||
}
|
||||
result, err := st.resolveReleaseValues(release)
|
||||
require.NoError(t, err)
|
||||
assert.Equal(t, 1, result["replicaCount"])
|
||||
assert.Equal(t, "production", result["extraEnv"])
|
||||
}
|
||||
|
||||
func TestRenderValuesFileToBytesWithData_PlainYAML(t *testing.T) {
|
||||
valuesFile := "/project/values.yaml"
|
||||
valuesContent := `replicaCount: 2
|
||||
image:
|
||||
repository: nginx
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
valuesFile: valuesContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{Name: "myrelease", Chart: "mychart"}
|
||||
tmplData := st.createReleaseTemplateData(release, map[string]any{})
|
||||
|
||||
result, err := st.renderValuesFileToBytesWithData(valuesFile, tmplData)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(result), "replicaCount: 2")
|
||||
assert.Contains(t, string(result), "repository: nginx")
|
||||
}
|
||||
|
||||
func TestRenderValuesFileToBytesWithData_WithValuesTemplate(t *testing.T) {
|
||||
valuesFile := "/project/values.yaml.gotmpl"
|
||||
valuesContent := `replicaCount: {{ .Values.replicaCount }}
|
||||
enabled: {{ .Values.ingress.enabled }}
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
valuesFile: valuesContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{Name: "myrelease", Chart: "mychart"}
|
||||
tmplData := st.createReleaseTemplateData(release, map[string]any{
|
||||
"replicaCount": 3,
|
||||
"ingress": map[string]any{
|
||||
"enabled": true,
|
||||
},
|
||||
})
|
||||
|
||||
result, err := st.renderValuesFileToBytesWithData(valuesFile, tmplData)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(result), "replicaCount: 3")
|
||||
assert.Contains(t, string(result), "enabled: true")
|
||||
}
|
||||
|
||||
func TestRenderValuesFileToBytesWithData_WithReleaseTemplate(t *testing.T) {
|
||||
valuesFile := "/project/values.yaml.gotmpl"
|
||||
valuesContent := `releaseName: {{ .Release.Name }}
|
||||
releaseNamespace: {{ .Release.Namespace }}
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
valuesFile: valuesContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{Name: "myapp", Chart: "mychart", Namespace: "production"}
|
||||
tmplData := st.createReleaseTemplateData(release, map[string]any{})
|
||||
|
||||
result, err := st.renderValuesFileToBytesWithData(valuesFile, tmplData)
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(result), "releaseName: myapp")
|
||||
assert.Contains(t, string(result), "releaseNamespace: production")
|
||||
}
|
||||
|
||||
func TestGenerateTemporaryReleaseValuesFilesWithData_StringPath(t *testing.T) {
|
||||
t.Setenv(envvar.TempDir, t.TempDir())
|
||||
|
||||
patchFile := "/project/patch.yaml.gotmpl"
|
||||
patchContent := `enabled: {{ .Values.ingress.enabled }}
|
||||
host: {{ .Values.ingress.host }}
|
||||
`
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{
|
||||
patchFile: patchContent,
|
||||
})
|
||||
|
||||
release := &ReleaseSpec{Name: "myrelease", Chart: "mychart"}
|
||||
tmplData := st.createReleaseTemplateData(release, map[string]any{
|
||||
"ingress": map[string]any{
|
||||
"enabled": true,
|
||||
"host": "example.com",
|
||||
},
|
||||
})
|
||||
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFilesWithData(
|
||||
release,
|
||||
[]any{"patch.yaml.gotmpl"},
|
||||
func() (releaseTemplateData, error) { return tmplData, nil },
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, generatedFiles, 1)
|
||||
|
||||
// The temp files are created on the real OS filesystem via os.Create, so we read them with os.ReadFile
|
||||
content, err := filesystem.DefaultFileSystem().ReadFile(generatedFiles[0])
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(content), "enabled: true")
|
||||
assert.Contains(t, string(content), "host: example.com")
|
||||
}
|
||||
|
||||
func TestGenerateTemporaryReleaseValuesFilesWithData_InlineMap(t *testing.T) {
|
||||
t.Setenv(envvar.TempDir, t.TempDir())
|
||||
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{})
|
||||
|
||||
release := &ReleaseSpec{Name: "myrelease", Chart: "mychart"}
|
||||
tmplData := st.createReleaseTemplateData(release, map[string]any{})
|
||||
|
||||
inlineValues := map[string]any{
|
||||
"replicaCount": 5,
|
||||
"service": map[string]any{
|
||||
"type": "NodePort",
|
||||
},
|
||||
}
|
||||
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFilesWithData(
|
||||
release,
|
||||
[]any{inlineValues},
|
||||
func() (releaseTemplateData, error) { return tmplData, nil },
|
||||
)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, generatedFiles, 1)
|
||||
|
||||
// The temp files are created on the real OS filesystem via os.Create, so we read them with os.ReadFile
|
||||
content, err := filesystem.DefaultFileSystem().ReadFile(generatedFiles[0])
|
||||
require.NoError(t, err)
|
||||
assert.Contains(t, string(content), "replicaCount: 5")
|
||||
assert.Contains(t, string(content), "NodePort")
|
||||
}
|
||||
|
||||
func TestGenerateTemporaryReleaseValuesFilesWithData_UnknownTypeError(t *testing.T) {
|
||||
st := newTestHelmStateWithFiles(t, map[string]string{})
|
||||
|
||||
release := &ReleaseSpec{Name: "myrelease", Chart: "mychart"}
|
||||
tmplData := st.createReleaseTemplateData(release, map[string]any{})
|
||||
|
||||
// Passing an unsupported type (int) should return an error
|
||||
_, err := st.generateTemporaryReleaseValuesFilesWithData(
|
||||
release,
|
||||
[]any{42},
|
||||
func() (releaseTemplateData, error) { return tmplData, nil },
|
||||
)
|
||||
require.Error(t, err)
|
||||
assert.Contains(t, err.Error(), "unexpected type of value")
|
||||
}
|
||||
|
|
|
|||
|
|
@ -404,9 +404,28 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
shouldRun = true
|
||||
}
|
||||
|
||||
// patchTemplateData is computed lazily on first use: only when an actual string patch/transformer
|
||||
// file path is rendered. Inline-map entries don't require template data at all, so we avoid
|
||||
// unnecessary I/O for releases whose patches are all inline maps or that have no string entries.
|
||||
var (
|
||||
cachedPatchTemplateData releaseTemplateData
|
||||
cachedPatchTemplateDataErr error
|
||||
cachedPatchTemplateDataSet bool
|
||||
)
|
||||
getPatchTemplateData := func() (releaseTemplateData, error) {
|
||||
if !cachedPatchTemplateDataSet {
|
||||
cachedPatchTemplateDataSet = true
|
||||
cachedPatchTemplateData, cachedPatchTemplateDataErr = st.mergedReleaseTemplateData(release)
|
||||
if cachedPatchTemplateDataErr != nil {
|
||||
cachedPatchTemplateDataErr = fmt.Errorf("failed to compute merged release values for patch rendering: %v", cachedPatchTemplateDataErr)
|
||||
}
|
||||
}
|
||||
return cachedPatchTemplateData, cachedPatchTemplateDataErr
|
||||
}
|
||||
|
||||
jsonPatches := release.JSONPatches
|
||||
if len(jsonPatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, jsonPatches)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFilesWithData(release, jsonPatches, getPatchTemplateData)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
|
@ -420,7 +439,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
strategicMergePatches := release.StrategicMergePatches
|
||||
if len(strategicMergePatches) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, strategicMergePatches)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFilesWithData(release, strategicMergePatches, getPatchTemplateData)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
|
@ -434,7 +453,7 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp
|
|||
|
||||
transformers := release.Transformers
|
||||
if len(transformers) > 0 {
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, transformers)
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFilesWithData(release, transformers, getPatchTemplateData)
|
||||
if err != nil {
|
||||
return nil, clean, err
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,6 +36,7 @@ import (
|
|||
"github.com/helmfile/helmfile/pkg/event"
|
||||
"github.com/helmfile/helmfile/pkg/filesystem"
|
||||
"github.com/helmfile/helmfile/pkg/helmexec"
|
||||
"github.com/helmfile/helmfile/pkg/maputil"
|
||||
"github.com/helmfile/helmfile/pkg/remote"
|
||||
"github.com/helmfile/helmfile/pkg/tmpl"
|
||||
"github.com/helmfile/helmfile/pkg/yaml"
|
||||
|
|
@ -3916,15 +3917,93 @@ func (st *HelmState) newReleaseTemplateData(release *ReleaseSpec) releaseTemplat
|
|||
return templateData
|
||||
}
|
||||
|
||||
func (st *HelmState) newReleaseTemplateFuncMap(dir string) template.FuncMap {
|
||||
r := tmpl.NewFileRenderer(st.fs, dir, nil)
|
||||
|
||||
return r.Context.CreateFuncMap()
|
||||
func (st *HelmState) mergedReleaseTemplateData(release *ReleaseSpec) (releaseTemplateData, error) {
|
||||
releaseValues, err := st.resolveReleaseValues(release)
|
||||
if err != nil {
|
||||
return releaseTemplateData{}, err
|
||||
}
|
||||
mergedVals := maputil.MergeMaps(st.Values(), releaseValues)
|
||||
return st.createReleaseTemplateData(release, mergedVals), nil
|
||||
}
|
||||
|
||||
func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) {
|
||||
templateData := st.newReleaseTemplateData(release)
|
||||
// prepareReleaseValuesEntries normalizes release.Values path entries (applying ValuesPathPrefix)
|
||||
// and evaluates any vals ref+ secrets, returning the fully-rendered values slice ready for processing.
|
||||
func (st *HelmState) prepareReleaseValuesEntries(release *ReleaseSpec) ([]any, error) {
|
||||
values := []any{}
|
||||
for _, v := range release.Values {
|
||||
switch typedValue := v.(type) {
|
||||
case string:
|
||||
path := st.storage().normalizePath(release.ValuesPathPrefix + typedValue)
|
||||
values = append(values, path)
|
||||
default:
|
||||
values = append(values, typedValue)
|
||||
}
|
||||
}
|
||||
|
||||
valuesMapSecretsRendered, err := st.valsRuntime.Eval(map[string]any{"values": values})
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valuesSecretsRendered, ok := valuesMapSecretsRendered["values"].([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"])
|
||||
}
|
||||
|
||||
return valuesSecretsRendered, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) resolveReleaseValues(release *ReleaseSpec) (map[string]any, error) {
|
||||
merged := map[string]any{}
|
||||
|
||||
valuesSecretsRendered, err := st.prepareReleaseValuesEntries(release)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, v := range valuesSecretsRendered {
|
||||
switch typedValue := v.(type) {
|
||||
case string:
|
||||
paths, skip, err := st.storage().resolveFile(st.getReleaseMissingFileHandler(release), "values", typedValue, st.getReleaseMissingFileHandlerConfig(release).resolveFileOptions()...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if skip {
|
||||
continue
|
||||
}
|
||||
if len(paths) > 1 {
|
||||
return nil, fmt.Errorf("glob patterns in release values are not supported for template data resolution: value=%q, resolvedPaths=%v", typedValue, paths)
|
||||
}
|
||||
path := paths[0]
|
||||
|
||||
yamlBytes, err := st.RenderReleaseValuesFileToBytes(release, path)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("failed to render values file \"%s\": %v", typedValue, err)
|
||||
}
|
||||
|
||||
var vals map[string]any
|
||||
if err := yaml.Unmarshal(yamlBytes, &vals); err != nil {
|
||||
return nil, fmt.Errorf("failed to parse values file \"%s\": %v", typedValue, err)
|
||||
}
|
||||
|
||||
merged = maputil.MergeMaps(merged, vals)
|
||||
case map[any]any:
|
||||
strMap, err := maputil.CastKeysToStrings(typedValue)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
merged = maputil.MergeMaps(merged, strMap)
|
||||
case map[string]any:
|
||||
merged = maputil.MergeMaps(merged, typedValue)
|
||||
default:
|
||||
return nil, fmt.Errorf("unexpected type of value in release values: value=%v, type=%T", typedValue, typedValue)
|
||||
}
|
||||
}
|
||||
|
||||
return merged, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) renderValuesFileToBytesWithData(path string, templateData releaseTemplateData) ([]byte, error) {
|
||||
r := tmpl.NewFileRenderer(st.fs, filepath.Dir(path), templateData)
|
||||
rawBytes, err := r.RenderToBytes(path)
|
||||
if err != nil {
|
||||
|
|
@ -3955,6 +4034,27 @@ func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path s
|
|||
return rawBytes, nil
|
||||
}
|
||||
|
||||
func (st *HelmState) generateTemporaryReleaseValuesFilesWithData(release *ReleaseSpec, values []any, getTemplateData func() (releaseTemplateData, error)) ([]string, error) {
|
||||
return st.generateTemporaryReleaseValuesFilesCore(release, values, func(path string) ([]byte, error) {
|
||||
templateData, err := getTemplateData()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return st.renderValuesFileToBytesWithData(path, templateData)
|
||||
})
|
||||
}
|
||||
|
||||
func (st *HelmState) newReleaseTemplateFuncMap(dir string) template.FuncMap {
|
||||
r := tmpl.NewFileRenderer(st.fs, dir, nil)
|
||||
|
||||
return r.Context.CreateFuncMap()
|
||||
}
|
||||
|
||||
func (st *HelmState) RenderReleaseValuesFileToBytes(release *ReleaseSpec, path string) ([]byte, error) {
|
||||
templateData := st.newReleaseTemplateData(release)
|
||||
return st.renderValuesFileToBytesWithData(path, templateData)
|
||||
}
|
||||
|
||||
func (st *HelmState) storage() *Storage {
|
||||
return &Storage{
|
||||
FilePath: st.FilePath,
|
||||
|
|
@ -4080,6 +4180,14 @@ func (st *HelmState) getMissingFileHandler() *string {
|
|||
}
|
||||
|
||||
func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, values []any) ([]string, error) {
|
||||
return st.generateTemporaryReleaseValuesFilesCore(release, values, func(path string) ([]byte, error) {
|
||||
return st.RenderReleaseValuesFileToBytes(release, path)
|
||||
})
|
||||
}
|
||||
|
||||
// generateTemporaryReleaseValuesFilesCore is the shared implementation for generating temporary values files.
|
||||
// renderStringValue is called for each string value entry after the file path has been resolved.
|
||||
func (st *HelmState) generateTemporaryReleaseValuesFilesCore(release *ReleaseSpec, values []any, renderStringValue func(path string) ([]byte, error)) ([]string, error) {
|
||||
generatedFiles := []string{}
|
||||
|
||||
for _, value := range values {
|
||||
|
|
@ -4098,45 +4206,86 @@ func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, v
|
|||
}
|
||||
path := paths[0]
|
||||
|
||||
yamlBytes, err := st.RenderReleaseValuesFileToBytes(release, path)
|
||||
yamlBytes, err := renderStringValue(path)
|
||||
if err != nil {
|
||||
return generatedFiles, fmt.Errorf("failed to render values files \"%s\": %v", typedValue, err)
|
||||
}
|
||||
|
||||
valfile, err := createTempValuesFile(release, yamlBytes)
|
||||
if err := func() error {
|
||||
valfile, err := createTempValuesFile(release, yamlBytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = valfile.Close()
|
||||
}()
|
||||
|
||||
if _, err := valfile.Write(yamlBytes); err != nil {
|
||||
return fmt.Errorf("failed to write %s: %v", valfile.Name(), err)
|
||||
}
|
||||
|
||||
st.logger.Debugf("Successfully generated the value file from %s to %s", path, valfile.Name())
|
||||
|
||||
generatedFiles = append(generatedFiles, valfile.Name())
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return generatedFiles, err
|
||||
}
|
||||
case map[any]any:
|
||||
strMap, err := maputil.CastKeysToStrings(typedValue)
|
||||
if err != nil {
|
||||
return generatedFiles, err
|
||||
}
|
||||
defer func() {
|
||||
_ = valfile.Close()
|
||||
}()
|
||||
if err := func() error {
|
||||
valfile, err := createTempValuesFile(release, strMap)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = valfile.Close()
|
||||
}()
|
||||
|
||||
if _, err := valfile.Write(yamlBytes); err != nil {
|
||||
return generatedFiles, fmt.Errorf("failed to write %s: %v", valfile.Name(), err)
|
||||
}
|
||||
encoder := yaml.NewEncoder(valfile)
|
||||
defer func() {
|
||||
_ = encoder.Close()
|
||||
}()
|
||||
|
||||
st.logger.Debugf("Successfully generated the value file at %s. produced:\n%s", path, string(yamlBytes))
|
||||
if err := encoder.Encode(strMap); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generatedFiles = append(generatedFiles, valfile.Name())
|
||||
case map[any]any, map[string]any:
|
||||
valfile, err := createTempValuesFile(release, typedValue)
|
||||
if err != nil {
|
||||
generatedFiles = append(generatedFiles, valfile.Name())
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return generatedFiles, err
|
||||
}
|
||||
defer func() {
|
||||
_ = valfile.Close()
|
||||
}()
|
||||
case map[string]any:
|
||||
if err := func() error {
|
||||
valfile, err := createTempValuesFile(release, typedValue)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
defer func() {
|
||||
_ = valfile.Close()
|
||||
}()
|
||||
|
||||
encoder := yaml.NewEncoder(valfile)
|
||||
defer func() {
|
||||
_ = encoder.Close()
|
||||
}()
|
||||
encoder := yaml.NewEncoder(valfile)
|
||||
defer func() {
|
||||
_ = encoder.Close()
|
||||
}()
|
||||
|
||||
if err := encoder.Encode(typedValue); err != nil {
|
||||
if err := encoder.Encode(typedValue); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
generatedFiles = append(generatedFiles, valfile.Name())
|
||||
|
||||
return nil
|
||||
}(); err != nil {
|
||||
return generatedFiles, err
|
||||
}
|
||||
|
||||
generatedFiles = append(generatedFiles, valfile.Name())
|
||||
default:
|
||||
return generatedFiles, fmt.Errorf("unexpected type of value: value=%v, type=%T", typedValue, typedValue)
|
||||
}
|
||||
|
|
@ -4145,27 +4294,11 @@ func (st *HelmState) generateTemporaryReleaseValuesFiles(release *ReleaseSpec, v
|
|||
}
|
||||
|
||||
func (st *HelmState) generateVanillaValuesFiles(release *ReleaseSpec) ([]string, error) {
|
||||
values := []any{}
|
||||
for _, v := range release.Values {
|
||||
switch typedValue := v.(type) {
|
||||
case string:
|
||||
path := st.storage().normalizePath(release.ValuesPathPrefix + typedValue)
|
||||
values = append(values, path)
|
||||
default:
|
||||
values = append(values, v)
|
||||
}
|
||||
}
|
||||
|
||||
valuesMapSecretsRendered, err := st.valsRuntime.Eval(map[string]any{"values": values})
|
||||
valuesSecretsRendered, err := st.prepareReleaseValuesEntries(release)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
valuesSecretsRendered, ok := valuesMapSecretsRendered["values"].([]any)
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("Failed to render values in %s for release %s: type %T isn't supported", st.FilePath, release.Name, valuesMapSecretsRendered["values"])
|
||||
}
|
||||
|
||||
generatedFiles, err := st.generateTemporaryReleaseValuesFiles(release, valuesSecretsRendered)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
|
|
|||
Loading…
Reference in New Issue