Compare commits

...

2 Commits

Author SHA1 Message Date
Vince-Chenal c40f03449e
Merge c617d4f5d6 into 55adae872e 2025-10-24 09:39:42 -04:00
Vincent Chenal c617d4f5d6 feat: add environments values inheritance
Signed-off-by: Vincent Chenal <vincent.chenal@protonmail.com>
2025-10-20 16:44:44 +02:00
9 changed files with 53 additions and 12 deletions

View File

@ -55,7 +55,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e
storage := state.NewStorage(opts.CalleePath, ld.logger, ld.fs) storage := state.NewStorage(opts.CalleePath, ld.logger, ld.fs)
envld := state.NewEnvironmentValuesLoader(storage, ld.fs, ld.logger, ld.remote) envld := state.NewEnvironmentValuesLoader(storage, ld.fs, ld.logger, ld.remote)
handler := state.MissingFileHandlerError handler := state.MissingFileHandlerError
vals, err := envld.LoadEnvironmentValues(&handler, args, environment.New(ld.env), ld.env) vals, err := envld.LoadEnvironmentValues(&handler, args, environment.New(ld.env), ld.env, nil)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -452,7 +452,7 @@ func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []any
valuesEntries := append([]any{}, entries...) valuesEntries := append([]any{}, entries...)
ld := NewEnvironmentValuesLoader(st.storage(), st.fs, st.logger, remote) ld := NewEnvironmentValuesLoader(st.storage(), st.fs, st.logger, remote)
var err error var err error
envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv, envName) envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv, envName, st.Features)
if err != nil { if err != nil {
return nil, err return nil, err
} }

View File

@ -3,6 +3,7 @@ package state
import ( import (
"fmt" "fmt"
"path/filepath" "path/filepath"
"slices"
"strings" "strings"
"dario.cat/mergo" "dario.cat/mergo"
@ -36,13 +37,15 @@ func NewEnvironmentValuesLoader(storage *Storage, fs *filesystem.FileSystem, log
} }
} }
func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *string, valuesEntries []any, ctxEnv *environment.Environment, envName string) (map[string]any, error) { func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *string, valuesEntries []any, ctxEnv *environment.Environment, envName string, features []string) (map[string]any, error) {
var ( var (
result = map[string]any{} result = map[string]any{}
hclLoader = hcllang.NewHCLLoader(ld.fs, ld.logger) hclLoader = hcllang.NewHCLLoader(ld.fs, ld.logger)
err error err error
) )
layeredValues := slices.Contains(features, FeatureLayeredEnvironmentValues)
for _, entry := range valuesEntries { for _, entry := range valuesEntries {
maps := []any{} maps := []any{}
@ -65,7 +68,14 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str
if strings.HasSuffix(f, ".hcl") { if strings.HasSuffix(f, ".hcl") {
hclLoader.AddFile(f) hclLoader.AddFile(f)
} else { } else {
tmplData := NewEnvironmentTemplateData(env, "", env.Values) values := env.Values
if layeredValues {
values, err = mapMerge(values, []any{result})
if err != nil {
return nil, err
}
}
tmplData := NewEnvironmentTemplateData(env, "", values)
r := tmpl.NewFileRenderer(ld.fs, filepath.Dir(f), tmplData) r := tmpl.NewFileRenderer(ld.fs, filepath.Dir(f), tmplData)
bytes, err := r.RenderToBytes(f) bytes, err := r.RenderToBytes(f)
if err != nil { if err != nil {

View File

@ -29,7 +29,7 @@ func newLoader() *EnvironmentValuesLoader {
func TestEnvValsLoad_SingleValuesFile(t *testing.T) { func TestEnvValsLoad_SingleValuesFile(t *testing.T) {
l := newLoader() l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.5.yaml"}, nil, "") actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.5.yaml"}, nil, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -87,7 +87,7 @@ func TestEnvValsLoad_EnvironmentNameFile(t *testing.T) {
for _, tt := range tests { for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) { t.Run(tt.name, func(t *testing.T) {
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.6.yaml.gotmpl"}, tt.env, tt.envName) actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.6.yaml.gotmpl"}, tt.env, tt.envName, nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -103,7 +103,7 @@ func TestEnvValsLoad_EnvironmentNameFile(t *testing.T) {
func TestEnvValsLoad_SingleValuesFileRemote(t *testing.T) { func TestEnvValsLoad_SingleValuesFileRemote(t *testing.T) {
l := newLoader() l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"git::https://github.com/helm/helm.git@cmd/helm/testdata/output/values.yaml?ref=v3.8.1"}, nil, "") actual, err := l.LoadEnvironmentValues(nil, []any{"git::https://github.com/helm/helm.git@cmd/helm/testdata/output/values.yaml?ref=v3.8.1"}, nil, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -121,7 +121,7 @@ func TestEnvValsLoad_SingleValuesFileRemote(t *testing.T) {
func TestEnvValsLoad_OverwriteNilValue_Issue1150(t *testing.T) { func TestEnvValsLoad_OverwriteNilValue_Issue1150(t *testing.T) {
l := newLoader() l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.1.yaml", "testdata/values.2.yaml"}, nil, "") actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.1.yaml", "testdata/values.2.yaml"}, nil, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -143,7 +143,7 @@ func TestEnvValsLoad_OverwriteNilValue_Issue1150(t *testing.T) {
func TestEnvValsLoad_OverwriteWithNilValue_Issue1154(t *testing.T) { func TestEnvValsLoad_OverwriteWithNilValue_Issue1154(t *testing.T) {
l := newLoader() l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.3.yaml", "testdata/values.4.yaml"}, nil, "") actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.3.yaml", "testdata/values.4.yaml"}, nil, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -166,7 +166,7 @@ func TestEnvValsLoad_OverwriteWithNilValue_Issue1154(t *testing.T) {
func TestEnvValsLoad_OverwriteEmptyValue_Issue1168(t *testing.T) { func TestEnvValsLoad_OverwriteEmptyValue_Issue1168(t *testing.T) {
l := newLoader() l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/issues/1168/addons.yaml", "testdata/issues/1168/addons2.yaml"}, nil, "") actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/issues/1168/addons.yaml", "testdata/issues/1168/addons2.yaml"}, nil, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -191,7 +191,7 @@ func TestEnvValsLoad_OverwriteEmptyValue_Issue1168(t *testing.T) {
func TestEnvValsLoad_MultiHCL(t *testing.T) { func TestEnvValsLoad_MultiHCL(t *testing.T) {
l := newLoader() l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.7.hcl", "testdata/values.8.hcl"}, nil, "") actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.7.hcl", "testdata/values.8.hcl"}, nil, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -234,7 +234,7 @@ func TestEnvValsLoad_EnvironmentValues(t *testing.T) {
env := environment.New("test") env := environment.New("test")
env.Values["foo"] = "bar" env.Values["foo"] = "bar"
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.9.yaml.gotmpl"}, env, "") actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.9.yaml.gotmpl"}, env, "", nil)
if err != nil { if err != nil {
t.Fatal(err) t.Fatal(err)
} }
@ -247,3 +247,22 @@ func TestEnvValsLoad_EnvironmentValues(t *testing.T) {
t.Error(diff) t.Error(diff)
} }
} }
func TestEnvValsLoad_LayeredValues(t *testing.T) {
l := newLoader()
actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/layered.1.yaml", "testdata/layered.2.yaml.gotmpl", "testdata/layered.3.yaml.gotmpl"}, nil, "", []string{FeatureLayeredEnvironmentValues})
if err != nil {
t.Fatal(err)
}
expected := map[string]any{
"somevalue": string("foo"),
"someothervalue": string("new foo"),
"greeting": string("hello new foo"),
}
if diff := cmp.Diff(expected, actual); diff != "" {
t.Error(diff)
}
}

6
pkg/state/features.go Normal file
View File

@ -0,0 +1,6 @@
package state
const (
// FeatureLayeredEnvironmentValues is the feature flag for layered environment values
FeatureLayeredEnvironmentValues = "layeredEnvironmentValues"
)

View File

@ -89,6 +89,8 @@ type ReleaseSetSpec struct {
MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"` MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"`
LockFile string `yaml:"lockFilePath,omitempty"` LockFile string `yaml:"lockFilePath,omitempty"`
Features []string `yaml:"features,omitempty"`
} }
type MissingFileHandlerConfig struct { type MissingFileHandlerConfig struct {

2
pkg/state/testdata/layered.1.yaml vendored Normal file
View File

@ -0,0 +1,2 @@
somevalue: foo
greeting: hello

View File

@ -0,0 +1 @@
someothervalue: "new {{ .Values.somevalue }}"

View File

@ -0,0 +1 @@
greeting: "{{ .Values.greeting }} {{ .Values.someothervalue }}"