feat(state): add support for setString in ReleaseSpec and HelmState (#1821)
* feat(state): add support for setString in ReleaseSpec and HelmState Signed-off-by: yxxhero <aiopsclub@163.com> * docs: add setString section to index.md for helm configuration Signed-off-by: yxxhero <aiopsclub@163.com> * tests: fix more tests Signed-off-by: yxxhero <aiopsclub@163.com> --------- Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
parent
b1f827394c
commit
bd12fa1cc3
|
|
@ -282,6 +282,16 @@ releases:
|
|||
domain: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com
|
||||
scheme: {{ env "SCHEME" | default "https" }}
|
||||
# Use `values` whenever possible!
|
||||
# `setString` translates to helm's `--set-string key=val`
|
||||
setString:
|
||||
# set a single array value in an array, translates to --set-string bar[0]={1,2}
|
||||
- name: bar[0]
|
||||
values:
|
||||
- 1
|
||||
- 2
|
||||
# set a templated value
|
||||
- name: namespace
|
||||
value: {{ .Namespace }}
|
||||
# `set` translates to helm's `--set key=val`, that is known to suffer from type issues like https://github.com/roboll/helmfile/issues/608
|
||||
set:
|
||||
# single value loaded from a local file, translates to --set-file foo.config=path/to/file
|
||||
|
|
|
|||
|
|
@ -4006,6 +4006,73 @@ myrelease4 testNamespace true true id:myrelease1 mychart1
|
|||
|
||||
assert.Equal(t, expected, out)
|
||||
}
|
||||
func testSetStringValuesTemplate(t *testing.T, goccyGoYaml bool) {
|
||||
t.Helper()
|
||||
|
||||
v := runtime.GoccyGoYaml
|
||||
runtime.GoccyGoYaml = goccyGoYaml
|
||||
t.Cleanup(func() {
|
||||
runtime.GoccyGoYaml = v
|
||||
})
|
||||
|
||||
files := map[string]string{
|
||||
"/path/to/helmfile.yaml": `
|
||||
releases:
|
||||
- name: zipkin
|
||||
chart: stable/zipkin
|
||||
values:
|
||||
- val2: "val2"
|
||||
valuesTemplate:
|
||||
- val1: '{{"{{ .Release.Name }}"}}'
|
||||
setString:
|
||||
- name: "name"
|
||||
value: "val"
|
||||
`,
|
||||
}
|
||||
expectedValues := []any{
|
||||
map[string]any{"val1": "zipkin"},
|
||||
map[string]any{"val2": "val2"}}
|
||||
expectedSetValues := []state.SetValue{
|
||||
{Name: "name", Value: "val"}}
|
||||
|
||||
app := appWithFs(&App{
|
||||
OverrideHelmBinary: DefaultHelmBinary,
|
||||
OverrideKubeContext: "default",
|
||||
Logger: newAppTestLogger(),
|
||||
Env: "default",
|
||||
FileOrDir: "helmfile.yaml",
|
||||
}, files)
|
||||
|
||||
expectNoCallsToHelm(app)
|
||||
|
||||
var specs []state.ReleaseSpec
|
||||
collectReleases := func(run *Run) (bool, []error) {
|
||||
specs = append(specs, run.state.Releases...)
|
||||
return false, nil
|
||||
}
|
||||
|
||||
err := app.ForEachState(
|
||||
collectReleases,
|
||||
false,
|
||||
SetFilter(true),
|
||||
)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
|
||||
if len(specs) != 1 {
|
||||
t.Fatalf("expected 1 release; got %d releases", len(specs))
|
||||
}
|
||||
actualValues := specs[0].Values
|
||||
actualSetStringValues := specs[0].SetStringValues
|
||||
|
||||
if !reflect.DeepEqual(expectedValues, actualValues) {
|
||||
t.Errorf("expected values: %v; got values: %v", expectedValues, actualValues)
|
||||
}
|
||||
if !reflect.DeepEqual(expectedSetValues, actualSetStringValues) {
|
||||
t.Errorf("expected set-string: %v; got set: %v", expectedValues, actualValues)
|
||||
}
|
||||
}
|
||||
|
||||
func testSetValuesTemplate(t *testing.T, goccyGoYaml bool) {
|
||||
t.Helper()
|
||||
|
|
@ -4089,6 +4156,16 @@ func TestSetValuesTemplate(t *testing.T) {
|
|||
})
|
||||
}
|
||||
|
||||
func TestSetStringValuesTemplate(t *testing.T) {
|
||||
t.Run("with goccy/go-yaml", func(t *testing.T) {
|
||||
testSetStringValuesTemplate(t, true)
|
||||
})
|
||||
|
||||
t.Run("with gopkg.in/yaml.v2", func(t *testing.T) {
|
||||
testSetStringValuesTemplate(t, false)
|
||||
})
|
||||
}
|
||||
|
||||
func location() string {
|
||||
_, fn, line, _ := goruntime.Caller(1)
|
||||
return fmt.Sprintf("%s:%d", filepath.Base(fn), line)
|
||||
|
|
|
|||
|
|
@ -315,6 +315,7 @@ type ReleaseSpec struct {
|
|||
Values []any `yaml:"values,omitempty"`
|
||||
Secrets []any `yaml:"secrets,omitempty"`
|
||||
SetValues []SetValue `yaml:"set,omitempty"`
|
||||
SetStringValues []SetValue `yaml:"setString,omitempty"`
|
||||
duration time.Duration
|
||||
|
||||
ValuesTemplate []any `yaml:"valuesTemplate,omitempty"`
|
||||
|
|
@ -3341,6 +3342,15 @@ func (st *HelmState) namespaceAndValuesFlags(helm helmexec.Interface, release *R
|
|||
flags = append(flags, setFlags...)
|
||||
}
|
||||
|
||||
if len(release.SetStringValues) > 0 {
|
||||
setStringFlags, err := st.setStringFlags(release.SetStringValues)
|
||||
if err != nil {
|
||||
return nil, files, fmt.Errorf("Failed to render set string value entry in %s for release %s: %v", st.FilePath, release.Name, err)
|
||||
}
|
||||
|
||||
flags = append(flags, setStringFlags...)
|
||||
}
|
||||
|
||||
/***********
|
||||
* START 'env' section for backwards compatibility
|
||||
***********/
|
||||
|
|
@ -3400,6 +3410,34 @@ func (st *HelmState) setFlags(setValues []SetValue) ([]string, error) {
|
|||
return flags, nil
|
||||
}
|
||||
|
||||
// setStringFlags is to generate the set-string flags for helm
|
||||
func (st *HelmState) setStringFlags(setValues []SetValue) ([]string, error) {
|
||||
var flags []string
|
||||
|
||||
for _, set := range setValues {
|
||||
if set.Value != "" {
|
||||
renderedValue, err := renderValsSecrets(st.valsRuntime, set.Value)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
flags = append(flags, "--set-string", fmt.Sprintf("%s=%s", escape(set.Name), escape(renderedValue[0])))
|
||||
} else if len(set.Values) > 0 {
|
||||
renderedValues, err := renderValsSecrets(st.valsRuntime, set.Values...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
items := make([]string, len(renderedValues))
|
||||
for i, raw := range renderedValues {
|
||||
items[i] = escape(raw)
|
||||
}
|
||||
v := strings.Join(items, ",")
|
||||
flags = append(flags, "--set-string", fmt.Sprintf("%s={%s}", escape(set.Name), v))
|
||||
}
|
||||
}
|
||||
|
||||
return flags, nil
|
||||
}
|
||||
|
||||
// renderValsSecrets helper function which renders 'ref+.*' secrets
|
||||
func renderValsSecrets(e vals.Evaluator, input ...string) ([]string, error) {
|
||||
output := make([]string, len(input))
|
||||
|
|
|
|||
|
|
@ -199,6 +199,8 @@ func (st *HelmState) releaseWithInheritedTemplate(r *ReleaseSpec, inheritancePat
|
|||
src.SetValuesTemplate = nil
|
||||
case "set":
|
||||
src.SetValues = nil
|
||||
case "setString":
|
||||
src.SetStringValues = nil
|
||||
case "secrets":
|
||||
src.Secrets = nil
|
||||
default:
|
||||
|
|
|
|||
|
|
@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) {
|
|||
run(testcase{
|
||||
subject: "baseline",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
want: "foo-values-5db58595d7",
|
||||
want: "foo-values-5b58697694",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different bytes content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: []byte(`{"k":"v"}`),
|
||||
want: "foo-values-78d88d86dd",
|
||||
want: "foo-values-58bff47d77",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different map content",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"},
|
||||
data: map[string]any{"k": "v"},
|
||||
want: "foo-values-f9c8967cd",
|
||||
want: "foo-values-5fb8948f75",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different chart",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"},
|
||||
want: "foo-values-cdfb97444",
|
||||
want: "foo-values-784b76684f",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "different name",
|
||||
release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"},
|
||||
want: "bar-values-749bc4c6d4",
|
||||
want: "bar-values-f48df5f49",
|
||||
})
|
||||
|
||||
run(testcase{
|
||||
subject: "specific ns",
|
||||
release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"},
|
||||
want: "myns-foo-values-7b74fbd6d6",
|
||||
want: "myns-foo-values-6b68696b8c",
|
||||
})
|
||||
|
||||
for id, n := range ids {
|
||||
|
|
|
|||
Loading…
Reference in New Issue