From 9fa0529304e8e0838bf63412f2c1ab4ebdc91b6a Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Tue, 28 Apr 2026 09:01:48 +0800 Subject: [PATCH 01/28] fix: apply post-renderer to output-dir-template output (#2531) * fix: apply post-renderer to output-dir-template output When --output-dir and --post-renderer are both passed to helm template, Helm writes pre-post-renderer content to files and sends post-renderer output to stdout. This workaround strips --output-dir from helm flags, captures the post-renderer-processed stdout, and writes it to the output directory. Fixes #2515 Signed-off-by: yxxhero * test: add integration test for issue-2515 (post-renderer with output-dir-template) Verifies that --post-renderer output is written to files when --output-dir-template is set, instead of pre-renderer content. Signed-off-by: yxxhero * fix: address review comments - correct HasPrefix args, fix output dir structure, fix test mock init Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/33d92423-fc47-4080-8307-5af9b16dd9c6 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: wrap file operation errors with context in post-renderer workaround Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/33d92423-fc47-4080-8307-5af9b16dd9c6 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: correct chart path and use absolute case dir path in integration test Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/43b7a794-1e7b-4577-8829-deb544a1a105 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: restrict --output-dir + --post-renderer workaround to Helm 3 only Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/229b14e2-b1ad-4f19-bd00-b8f7821383cd Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: clean up stale templates dir on re-runs in Helm 3 post-renderer workaround Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/f6c66284-8eca-4db3-8711-c9b6d3a9c179 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: detect --post-renderer= form and use targeted file cleanup Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/8c9e4af4-84ae-4cbd-bc0a-8fcd9adddaed Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * feat: add Helm 4 post-renderer plugin and enable Helm 4 issue-2515 integration test Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/3da2949c-a9d6-4e16-9b4a-a7e241080089 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: search recursively for YAML files in Helm 4 output-dir integration test Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/c5d33143-f611-40db-b73a-e5189d944ffd Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: limit find depth and truncate log in Helm 4 integration test fallback message Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/c5d33143-f611-40db-b73a-e5189d944ffd Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Signed-off-by: yxxhero Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- pkg/helmexec/exec.go | 68 ++++++++++++++++-- pkg/helmexec/exec_test.go | 72 +++++++++++++++++-- test/integration/run.sh | 1 + test/integration/test-cases/issue-2515.sh | 62 ++++++++++++++++ .../test-cases/issue-2515/input/filter.bash | 2 + .../input/helm-plugin-filter/filter.sh | 6 ++ .../input/helm-plugin-filter/plugin.yaml | 8 +++ .../test-cases/issue-2515/input/helmfile.yaml | 13 ++++ 8 files changed, 224 insertions(+), 8 deletions(-) create mode 100644 test/integration/test-cases/issue-2515.sh create mode 100755 test/integration/test-cases/issue-2515/input/filter.bash create mode 100755 test/integration/test-cases/issue-2515/input/helm-plugin-filter/filter.sh create mode 100644 test/integration/test-cases/issue-2515/input/helm-plugin-filter/plugin.yaml create mode 100644 test/integration/test-cases/issue-2515/input/helmfile.yaml diff --git a/pkg/helmexec/exec.go b/pkg/helmexec/exec.go index cdfa90a8..a2153576 100644 --- a/pkg/helmexec/exec.go +++ b/pkg/helmexec/exec.go @@ -646,17 +646,77 @@ func (helm *execer) TemplateRelease(name string, chart string, flags ...string) helm.logger.Infof("Templating release=%v, chart=%v", name, redactedURL(chart)) args := []string{"template", name, chart} - out, err := helm.exec(append(args, flags...), map[string]string{}, nil) - var outputToFile bool + var hasPostRenderer bool for _, f := range flags { - if strings.HasPrefix("--output-dir", f) { + if f == "--output-dir" || strings.HasPrefix(f, "--output-dir=") { outputToFile = true - break + } + if f == "--post-renderer" || strings.HasPrefix(f, "--post-renderer=") { + hasPostRenderer = true } } + if outputToFile && hasPostRenderer && helm.IsHelm3() { + // Helm 3 does not apply --post-renderer to files written by --output-dir. + // It writes pre-post-renderer content to files and sends post-renderer output to stdout. + // Helm 4 handles this correctly, so the workaround is only needed for Helm 3. + // Workaround: run without --output-dir, capture stdout (with post-renderer applied), + // and write the output to the output directory ourselves. + var outputDir string + filteredFlags := make([]string, 0, len(flags)) + for i := 0; i < len(flags); i++ { + if flags[i] == "--output-dir" && i+1 < len(flags) { + outputDir = flags[i+1] + i++ + continue + } + if strings.HasPrefix(flags[i], "--output-dir=") { + outputDir = strings.TrimPrefix(flags[i], "--output-dir=") + continue + } + filteredFlags = append(filteredFlags, flags[i]) + } + + if outputDir == "" { + return fmt.Errorf("output dir not found for template command") + } + + out, err := helm.exec(append(args, filteredFlags...), map[string]string{}, nil) + if err != nil { + return err + } + + templatesDir := filepath.Join(outputDir, "templates") + legacyOutputPath := filepath.Join(outputDir, name+".yaml") + outputPath := filepath.Join(templatesDir, name+".yaml") + + if removeErr := os.Remove(legacyOutputPath); removeErr != nil && !os.IsNotExist(removeErr) { + return fmt.Errorf("failed to remove legacy output file %s: %w", legacyOutputPath, removeErr) + } + + // Remove only the specific file written by the previous run to avoid clobbering + // unrelated files in a shared output directory. + if removeErr := os.Remove(outputPath); removeErr != nil && !os.IsNotExist(removeErr) { + return fmt.Errorf("failed to remove stale output file %s: %w", outputPath, removeErr) + } + + if len(out) > 0 { + if mkdirErr := os.MkdirAll(templatesDir, 0755); mkdirErr != nil { + return fmt.Errorf("failed to create templates directory %s: %w", templatesDir, mkdirErr) + } + + if writeErr := os.WriteFile(outputPath, append(out, '\n'), 0644); writeErr != nil { + return fmt.Errorf("failed to write output file %s: %w", outputPath, writeErr) + } + helm.logger.Debugf("Wrote post-renderer output to %s", outputPath) + } + return nil + } + + out, err := helm.exec(append(args, flags...), map[string]string{}, nil) + if outputToFile { // With --output-dir is passed to helm-template, // we can safely direct all the logs from it to our logger. diff --git a/pkg/helmexec/exec_test.go b/pkg/helmexec/exec_test.go index 9644c21a..2d4dc577 100644 --- a/pkg/helmexec/exec_test.go +++ b/pkg/helmexec/exec_test.go @@ -21,8 +21,9 @@ import ( // Mocking the command-line runner type mockRunner struct { - output []byte - err error + output []byte + versionOutput []byte // if set, returned for "helm version --short" probe; overrides default Helm 4 fallback + err error } func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]string, stdin io.Reader) ([]byte, error) { @@ -30,8 +31,13 @@ func (mock *mockRunner) ExecuteStdIn(cmd string, args []string, env map[string]s } func (mock *mockRunner) Execute(cmd string, args []string, env map[string]string, enableLiveOutput bool) ([]byte, error) { - if len(mock.output) == 0 && strings.Join(args, " ") == "version --short" { - return []byte("v4.0.1+g12500dd"), nil + if strings.Join(args, " ") == "version --short" { + if mock.versionOutput != nil { + return mock.versionOutput, nil + } + if len(mock.output) == 0 { + return []byte("v4.0.1+g12500dd"), nil + } } return mock.output, mock.err } @@ -1255,6 +1261,64 @@ exec: helm --kubeconfig config --kube-context dev template release https://examp } } +func Test_Template_PostRendererWithOutputDir(t *testing.T) { + tests := []struct { + name string + postRendererFlag string + }{ + {"separate flags", "--post-renderer"}, + {"combined flag", "--post-renderer=/bin/echo"}, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + tmpDir := t.TempDir() + var buffer bytes.Buffer + logger := NewLogger(&buffer, "debug") + + // Use Helm 3 version for the version probe so the Helm 3 workaround is applied. + // The workaround is not needed for Helm 4, which natively applies --post-renderer to --output-dir output. + runner := &mockRunner{versionOutput: []byte("v3.20.0")} + helm, err := New("helm", HelmExecOptions{}, logger, "config", "dev", runner) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + runner.output = []byte("apiVersion: v1\nkind: Namespace\n") + + var flags []string + if tt.postRendererFlag == "--post-renderer" { + flags = []string{"--post-renderer", "/bin/echo", "--output-dir", tmpDir, "--values", "file.yml"} + } else { + flags = []string{tt.postRendererFlag, "--output-dir", tmpDir, "--values", "file.yml"} + } + + err = helm.TemplateRelease("myrelease", "path/to/chart", flags...) + if err != nil { + t.Fatalf("unexpected error: %v", err) + } + + outputPath := filepath.Join(tmpDir, "templates", "myrelease.yaml") + data, err := os.ReadFile(outputPath) + if err != nil { + t.Fatalf("expected output file %s to exist: %v", outputPath, err) + } + + expected := "apiVersion: v1\nkind: Namespace\n\n" + if string(data) != expected { + t.Errorf("output file content:\nactual=%q\nexpect=%q", string(data), expected) + } + + outputLog := buffer.String() + if strings.Contains(outputLog, "--output-dir") { + t.Errorf("helm should NOT have been called with --output-dir, got: %s", outputLog) + } + if !strings.Contains(outputLog, "--post-renderer") { + t.Errorf("helm should have been called with --post-renderer, got: %s", outputLog) + } + }) + } +} + func Test_IsHelm3(t *testing.T) { helm3Runner := mockRunner{output: []byte("v3.0.0+ge29ce2a\n")} helm, err := New("helm", HelmExecOptions{}, NewLogger(os.Stdout, "info"), "", "dev", &helm3Runner) diff --git a/test/integration/run.sh b/test/integration/run.sh index 9fb6ecac..14d4b435 100755 --- a/test/integration/run.sh +++ b/test/integration/run.sh @@ -115,6 +115,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes . ${dir}/test-cases/yaml-overwrite.sh . ${dir}/test-cases/chart-needs.sh . ${dir}/test-cases/postrender.sh +. ${dir}/test-cases/issue-2515.sh . ${dir}/test-cases/chartify.sh . ${dir}/test-cases/deps-mr-1011.sh . ${dir}/test-cases/deps-kustomization-i-1402.sh diff --git a/test/integration/test-cases/issue-2515.sh b/test/integration/test-cases/issue-2515.sh new file mode 100644 index 00000000..c1919c37 --- /dev/null +++ b/test/integration/test-cases/issue-2515.sh @@ -0,0 +1,62 @@ +issue_2515_case_dir="$(cd "${cases_dir}/issue-2515" && pwd)" +issue_2515_tmp=$(mktemp -d) + +# Determine the post-renderer argument. +# Helm 3 accepts an executable script; Helm 4 requires a plugin name. +if [ "${HELMFILE_HELM4}" = "1" ]; then + test_start "issue-2515 post-renderer with output-dir-template (Helm 4)" + info "Installing filter post-renderer plugin for Helm 4" + ${helm} plugin uninstall filter &>/dev/null || true + ${helm} plugin install ${issue_2515_case_dir}/input/helm-plugin-filter ${PLUGIN_INSTALL_FLAGS} || fail "Failed to install filter plugin" + issue_2515_postrenderer_arg="filter" +else + test_start "issue-2515 post-renderer with output-dir-template" + issue_2515_postrenderer_arg="${issue_2515_case_dir}/input/filter.bash" +fi + +info "Testing that --post-renderer output is written to files when --output-dir-template is set" + +issue_2515_output_dir="${issue_2515_tmp}/output" + +${helmfile} -f ${issue_2515_case_dir}/input/helmfile.yaml \ + template \ + --post-renderer ${issue_2515_postrenderer_arg} \ + --output-dir-template "${issue_2515_output_dir}/{{.Release.Name}}" \ + &> ${issue_2515_tmp}/log || fail "helmfile template should not fail" + +if [ "${HELMFILE_HELM4}" = "1" ]; then + # Helm 4 natively applies --post-renderer to --output-dir output. + # The directory structure may differ from Helm 3 (no guaranteed templates/ subdir), + # so search recursively for any YAML file. Fall back to stdout (log) if no files written. + issue_2515_output_file=$(find "${issue_2515_output_dir}" -maxdepth 5 -type f \( -name '*.yaml' -o -name '*.yml' \) 2>/dev/null | head -n 1) + if [ -z "${issue_2515_output_file}" ]; then + # Helm 4 may write post-rendered output to stdout rather than files + issue_2515_output_file="${issue_2515_tmp}/log" + if ! grep -q "postrendered" "${issue_2515_output_file}"; then + fail "Expected post-rendered YAML (namespace postrendered) in output files under ${issue_2515_output_dir} or stdout. Dir: $(find ${issue_2515_output_dir} 2>/dev/null || echo 'not found'). Log (last 50 lines): $(tail -50 ${issue_2515_output_file})" + fi + fi +else + issue_2515_templates_dir="${issue_2515_output_dir}/issue-2515/templates" + if [ ! -d "${issue_2515_templates_dir}" ]; then + fail "Expected templates directory ${issue_2515_templates_dir} to exist" + fi + issue_2515_output_file=$(find "${issue_2515_templates_dir}" -type f \( -name '*.yaml' -o -name '*.yml' \) | head -n 1) + if [ -z "${issue_2515_output_file}" ]; then + fail "Expected rendered YAML file under ${issue_2515_templates_dir}" + fi +fi + +if grep -q "original-cm" "${issue_2515_output_file}"; then + fail "Output should contain post-renderer output (Namespace), not original templates (original-cm). File contents: $(cat ${issue_2515_output_file})" +fi + +if ! grep -q "postrendered" "${issue_2515_output_file}"; then + fail "Output should contain post-renderer content (namespace postrendered). File contents: $(cat ${issue_2515_output_file})" +fi + +if [ "${HELMFILE_HELM4}" = "1" ]; then + test_pass "issue-2515 post-renderer with output-dir-template (Helm 4)" +else + test_pass "issue-2515 post-renderer with output-dir-template" +fi diff --git a/test/integration/test-cases/issue-2515/input/filter.bash b/test/integration/test-cases/issue-2515/input/filter.bash new file mode 100755 index 00000000..b069f429 --- /dev/null +++ b/test/integration/test-cases/issue-2515/input/filter.bash @@ -0,0 +1,2 @@ +#!/usr/bin/env bash +printf -- "---\napiVersion: v1\nkind: Namespace\nmetadata:\n name: postrendered\n" diff --git a/test/integration/test-cases/issue-2515/input/helm-plugin-filter/filter.sh b/test/integration/test-cases/issue-2515/input/helm-plugin-filter/filter.sh new file mode 100755 index 00000000..172b0694 --- /dev/null +++ b/test/integration/test-cases/issue-2515/input/helm-plugin-filter/filter.sh @@ -0,0 +1,6 @@ +#!/usr/bin/env bash +# Discard stdin (pre-rendered content) and output a fixed Namespace resource. +# This verifies that the post-renderer output — not the original templates — is +# written to the output directory. +cat > /dev/null +printf -- "---\napiVersion: v1\nkind: Namespace\nmetadata:\n name: postrendered\n" diff --git a/test/integration/test-cases/issue-2515/input/helm-plugin-filter/plugin.yaml b/test/integration/test-cases/issue-2515/input/helm-plugin-filter/plugin.yaml new file mode 100644 index 00000000..59517564 --- /dev/null +++ b/test/integration/test-cases/issue-2515/input/helm-plugin-filter/plugin.yaml @@ -0,0 +1,8 @@ +apiVersion: v1 +type: postrenderer/v1 +name: filter +version: 0.1.0 +runtime: subprocess +runtimeConfig: + platformCommand: + - command: ${HELM_PLUGIN_DIR}/filter.sh diff --git a/test/integration/test-cases/issue-2515/input/helmfile.yaml b/test/integration/test-cases/issue-2515/input/helmfile.yaml new file mode 100644 index 00000000..1940ca05 --- /dev/null +++ b/test/integration/test-cases/issue-2515/input/helmfile.yaml @@ -0,0 +1,13 @@ +releases: +- name: issue-2515 + chart: ../../../charts/raw + values: + - templates: + - | + apiVersion: v1 + kind: ConfigMap + metadata: + name: original-cm + namespace: {{ .Release.Namespace }} + data: + key: value From ee4be4e34259d79d8bbb69b7a792a3446bba86ea Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Wed, 29 Apr 2026 06:24:20 +0800 Subject: [PATCH 02/28] build(deps): bump go.uber.org/zap from 1.27.1 to 1.28.0 (#2557) Bumps [go.uber.org/zap](https://github.com/uber-go/zap) from 1.27.1 to 1.28.0. - [Release notes](https://github.com/uber-go/zap/releases) - [Changelog](https://github.com/uber-go/zap/blob/master/CHANGELOG.md) - [Commits](https://github.com/uber-go/zap/compare/v1.27.1...v1.28.0) --- updated-dependencies: - dependency-name: go.uber.org/zap dependency-version: 1.28.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 59bfb35a..12ffe9ab 100644 --- a/go.mod +++ b/go.mod @@ -29,7 +29,7 @@ require ( github.com/zclconf/go-cty v1.18.1 github.com/zclconf/go-cty-yaml v1.2.0 go.szostok.io/version v1.2.0 - go.uber.org/zap v1.27.1 + go.uber.org/zap v1.28.0 go.yaml.in/yaml/v2 v2.4.4 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/sync v0.20.0 diff --git a/go.sum b/go.sum index b9bb0889..13e88f39 100644 --- a/go.sum +++ b/go.sum @@ -878,8 +878,8 @@ go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y= -go.uber.org/zap v1.27.1 h1:08RqriUEv8+ArZRYSTXy1LeBScaMpVSTBhCeaZYfMYc= -go.uber.org/zap v1.27.1/go.mod h1:GB2qFLM7cTU87MWRP2mPIjqfIDnGu+VIO4V/SdhGo2E= +go.uber.org/zap v1.28.0 h1:IZzaP1Fv73/T/pBMLk4VutPl36uNC+OSUh3JLG3FIjo= +go.uber.org/zap v1.28.0/go.mod h1:rDLpOi171uODNm/mxFcuYWxDsqWSAVkFdX4XojSKg/Q= go.yaml.in/yaml/v2 v2.4.4 h1:tuyd0P+2Ont/d6e2rl3be67goVK4R6deVxCUX5vyPaQ= go.yaml.in/yaml/v2 v2.4.4/go.mod h1:gMZqIpDtDqOfM0uNfy0SkpRhvUryYH0Z6wdMYcacYXQ= go.yaml.in/yaml/v3 v3.0.4 h1:tfq32ie2Jv2UxXFdLJdh3jXuOzWiL1fo0bu/FbuKpbc= From eb00d0c61b60e5a22eebe6e22a4b7777381544e5 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 11:24:50 +0800 Subject: [PATCH 03/28] build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.100.0 to 1.100.1 (#2558) build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.100.0 to 1.100.1. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.100.0...service/s3/v1.100.1) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/s3 dependency-version: 1.100.1 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 22 +++++++++++----------- go.sum | 44 ++++++++++++++++++++++---------------------- 2 files changed, 33 insertions(+), 33 deletions(-) diff --git a/go.mod b/go.mod index 12ffe9ab..e385e615 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/aws/aws-sdk-go-v2/config v1.32.16 - github.com/aws/aws-sdk-go-v2/service/s3 v1.100.0 + github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-test/deep v1.1.1 github.com/gofrs/flock v0.13.0 @@ -155,18 +155,18 @@ require ( github.com/asaskevich/govalidator v0.0.0-20230301143203-a9d515a09cc2 // indirect github.com/atotto/clipboard v0.1.4 // indirect github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect - github.com/aws/aws-sdk-go-v2 v1.41.6 // indirect - github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 // indirect + github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect + github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect github.com/aws/aws-sdk-go-v2/credentials v1.19.15 // indirect github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.2 // indirect - github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 // indirect - github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 // indirect - github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22 // indirect + github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect + github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect + github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect @@ -174,7 +174,7 @@ require ( github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect - github.com/aws/smithy-go v1.25.0 // indirect + github.com/aws/smithy-go v1.25.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect github.com/cenkalti/backoff/v4 v4.3.0 // indirect diff --git a/go.sum b/go.sum index 13e88f39..77516904 100644 --- a/go.sum +++ b/go.sum @@ -152,10 +152,10 @@ github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 h1:HrMVYtly2IVqg9E github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774/go.mod h1:5wi5YYOpfuAKwL5XLFYopbgIl/v7NZxaJpa/4X6yFKE= github.com/aws/aws-sdk-go v1.55.8 h1:JRmEUbU52aJQZ2AjX4q4Wu7t4uZjOu71uyNmaWlUkJQ= github.com/aws/aws-sdk-go v1.55.8/go.mod h1:ZkViS9AqA6otK+JBBNH2++sx1sgxrPKcSzPPvQkUtXk= -github.com/aws/aws-sdk-go-v2 v1.41.6 h1:1AX0AthnBQzMx1vbmir3Y4WsnJgiydmnJjiLu+LvXOg= -github.com/aws/aws-sdk-go-v2 v1.41.6/go.mod h1:dy0UzBIfwSeot4grGvY1AqFWN5zgziMmWGzysDnHFcQ= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9 h1:adBsCIIpLbLmYnkQU+nAChU5yhVTvu5PerROm+/Kq2A= -github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.9/go.mod h1:uOYhgfgThm/ZyAuJGNQ5YgNyOlYfqnGpTHXvk3cpykg= +github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6tLY8= +github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= +github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= github.com/aws/aws-sdk-go-v2/config v1.32.16 h1:Q0iQ7quUgJP0F/SCRTieScnaMdXr9h/2+wze1u3cNeM= github.com/aws/aws-sdk-go-v2/config v1.32.16/go.mod h1:duCCnJEFqpt2RC6no1iK6q+8HpwOAkiUua0pY507dQc= github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4= @@ -164,24 +164,24 @@ github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 h1:IOGsJ1xVWhsi+ZO7/NW8Ou github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22/go.mod h1:b+hYdbU+jGKfXE8kKM6g1+h+L/Go3vMvzlxBsiuGsxg= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.2 h1:1i1SUOTLk0TbMh7+eJYxgv1r1f47BfR69LL6yaELoI0= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.2/go.mod h1:bo7DhmS/OyVeAJTC768nEk92YKWskqJ4gn0gB5e59qQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22 h1:GmLa5Kw1ESqtFpXsx5MmC84QWa/ZrLZvlJGa2y+4kcQ= -github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.22/go.mod h1:6sW9iWm9DK9YRpRGga/qzrzNLgKpT2cIxb7Vo2eNOp0= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22 h1:dY4kWZiSaXIzxnKlj17nHnBcXXBfac6UlsAx2qL6XrU= -github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.22/go.mod h1:KIpEUx0JuRZLO7U6cbV204cWAEco2iC3l061IxlwLtI= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23 h1:FPXsW9+gMuIeKmz7j6ENWcWtBGTe1kH8r9thNt5Uxx4= -github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.23/go.mod h1:7J8iGMdRKk6lw2C+cMIphgAnT8uTwBwNOsGkyOCm80U= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8 h1:HtOTYcbVcGABLOVuPYaIihj6IlkqubBwFj10K5fxRek= -github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.8/go.mod h1:VsK9abqQeGlzPgUr+isNWzPlK2vKe9INMLWnY65f5Xs= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14 h1:xnvDEnw+pnj5mctWiYuFbigrEzSm35x7k4KS/ZkCANg= -github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.14/go.mod h1:yS5rNogD8e0Wu9+l3MUwr6eENBzEeGejvINpN5PAYfY= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22 h1:PUmZeJU6Y1Lbvt9WFuJ0ugUK2xn6hIWUBBbKuOWF30s= -github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.22/go.mod h1:nO6egFBoAaoXze24a2C0NjQCvdpk8OueRoYimvEB9jo= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22 h1:SE+aQ4DEqG53RRCAIHlCf//B2ycxGH7jFkpnAh/kKPM= -github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.22/go.mod h1:ES3ynECd7fYeJIL6+oax+uIEljmfps0S70BaQzbMd/o= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= +github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23/go.mod h1:xYWD6BS9ywC5bS3sz9Xh04whO/hzK2plt2Zkyrp4JuA= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 h1:bpd8vxhlQi2r1hiueOw02f/duEPTMK59Q4QMAoTTtTo= +github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23/go.mod h1:15DfR2nw+CRHIk0tqNyifu3G1YdAOy68RftkhMDDwYk= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24 h1:OQqn11BtaYv1WLUowvcA30MpzIu8Ti4pcLPIIyoKZrA= +github.com/aws/aws-sdk-go-v2/internal/v4a v1.4.24/go.mod h1:X5ZJyfwVrWA96GzPmUCWFQaEARPR7gCrpq2E92PJwAE= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9 h1:FLudkZLt5ci0ozzgkVo8BJGwvqNaZbTWb3UcucAateA= +github.com/aws/aws-sdk-go-v2/service/internal/accept-encoding v1.13.9/go.mod h1:w7wZ/s9qK7c8g4al+UyoF1Sp/Z45UwMGcqIzLWVQHWk= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 h1:ieLCO1JxUWuxTZ1cRd0GAaeX7O6cIxnwk7tc1LsQhC4= +github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15/go.mod h1:e3IzZvQ3kAWNykvE0Tr0RDZCMFInMvhku3qNpcIQXhM= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/kuIwgEsOPLkaHu0O+m4fNgLU8B3vxQ+72jTPw= +github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= +github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 h1:PgD1y0ZagPokGIZPmejCBUySBzOFDN+leZxCOfb1OEQ= github.com/aws/aws-sdk-go-v2/service/kms v1.50.4/go.mod h1:FfXDb5nXrsoGgxsBFxwxr3vdHXheC2tV+6lmuLghhjQ= -github.com/aws/aws-sdk-go-v2/service/s3 v1.100.0 h1:7G26Sae6PMKn4kMcU5JzNfrm1YrKwyOhowXPYR2WiWY= -github.com/aws/aws-sdk-go-v2/service/s3 v1.100.0/go.mod h1:Fw9aqhJicIVee1VytBBjH+l+5ov6/PhbtIK/u3rt/ls= +github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 h1:mxuT1xE+dI54NW3RkNjP8DUT5HXqbkiAFvfdyDFwE5c= +github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5 h1:z2ayoK3pOvf8ODj/vPR0FgAS5ONruBq0F94SRoW/BIU= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5/go.mod h1:mpZB5HAl4ZIISod9qCi12xZ170TbHX9CCJV5y7nb7QU= github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA= @@ -194,8 +194,8 @@ github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 h1:oK/njaL8GtyEihkWMD4k3Vg github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20/go.mod h1:JHs8/y1f3zY7U5WcuzoJ/yAYGYtNIVPKLIbp61euvmg= github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcuxk8l+PG4+A0exds= github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo= -github.com/aws/smithy-go v1.25.0 h1:Sz/XJ64rwuiKtB6j98nDIPyYrV1nVNJ4YU74gttcl5U= -github.com/aws/smithy-go v1.25.0/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= +github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= +github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8= github.com/beorn7/perks v1.0.1 h1:VlbKKnNfV8bJzeqoa4cOKqO6bYr3WgKZxO8Z16+hsOM= From 27f40b64b89581617368e8fa0ea6cf3cb5bffdf9 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 30 Apr 2026 15:34:53 +0800 Subject: [PATCH 04/28] build(deps): bump github.com/aws/aws-sdk-go-v2/config from 1.32.16 to 1.32.17 (#2559) build(deps): bump github.com/aws/aws-sdk-go-v2/config Bumps [github.com/aws/aws-sdk-go-v2/config](https://github.com/aws/aws-sdk-go-v2) from 1.32.16 to 1.32.17. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/config/v1.32.16...config/v1.32.17) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/config dependency-version: 1.32.17 dependency-type: direct:production update-type: version-update:semver-patch ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 14 +++++++------- go.sum | 28 ++++++++++++++-------------- 2 files changed, 21 insertions(+), 21 deletions(-) diff --git a/go.mod b/go.mod index e385e615..c5caf972 100644 --- a/go.mod +++ b/go.mod @@ -6,7 +6,7 @@ require ( dario.cat/mergo v1.0.2 github.com/Masterminds/semver/v3 v3.4.0 github.com/Masterminds/sprig/v3 v3.3.0 - github.com/aws/aws-sdk-go-v2/config v1.32.16 + github.com/aws/aws-sdk-go-v2/config v1.32.17 github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-test/deep v1.1.1 @@ -157,8 +157,8 @@ require ( github.com/avelino/slugify v0.0.0-20180501145920-855f152bd774 // indirect github.com/aws/aws-sdk-go-v2 v1.41.7 // indirect github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 // indirect - github.com/aws/aws-sdk-go-v2/credentials v1.19.15 // indirect - github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 // indirect + github.com/aws/aws-sdk-go-v2/credentials v1.19.16 // indirect + github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 // indirect github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.2 // indirect github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 // indirect github.com/aws/aws-sdk-go-v2/internal/endpoints/v2 v2.7.23 // indirect @@ -169,11 +169,11 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 // indirect github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5 // indirect - github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 // indirect + github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect github.com/aws/aws-sdk-go-v2/service/ssm v1.68.4 // indirect - github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 // indirect - github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 // indirect - github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 // indirect + github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect + github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect + github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect github.com/aws/smithy-go v1.25.1 // indirect github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect github.com/blang/semver/v4 v4.0.0 // indirect diff --git a/go.sum b/go.sum index 77516904..eec8bec4 100644 --- a/go.sum +++ b/go.sum @@ -156,12 +156,12 @@ github.com/aws/aws-sdk-go-v2 v1.41.7 h1:DWpAJt66FmnnaRIOT/8ASTucrvuDPZASqhhLey6t github.com/aws/aws-sdk-go-v2 v1.41.7/go.mod h1:4LAfZOPHNVNQEckOACQx60Y8pSRjIkNZQz1w92xpMJc= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10 h1:gx1AwW1Iyk9Z9dD9F4akX5gnN3QZwUB20GGKH/I+Rho= github.com/aws/aws-sdk-go-v2/aws/protocol/eventstream v1.7.10/go.mod h1:qqY157uZoqm5OXq/amuaBJyC9hgBCBQnsaWnPe905GY= -github.com/aws/aws-sdk-go-v2/config v1.32.16 h1:Q0iQ7quUgJP0F/SCRTieScnaMdXr9h/2+wze1u3cNeM= -github.com/aws/aws-sdk-go-v2/config v1.32.16/go.mod h1:duCCnJEFqpt2RC6no1iK6q+8HpwOAkiUua0pY507dQc= -github.com/aws/aws-sdk-go-v2/credentials v1.19.15 h1:fyvgWTszojq8hEnMi8PPBTvZdTtEVmAVyo+NFLHBhH4= -github.com/aws/aws-sdk-go-v2/credentials v1.19.15/go.mod h1:gJiYyMOjNg8OEdRWOf3CrFQxM2a98qmrtjx1zuiQfB8= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22 h1:IOGsJ1xVWhsi+ZO7/NW8OuZZBtMJLZbk4P5HDjJO0jQ= -github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.22/go.mod h1:b+hYdbU+jGKfXE8kKM6g1+h+L/Go3vMvzlxBsiuGsxg= +github.com/aws/aws-sdk-go-v2/config v1.32.17 h1:FpL4/758/diKwqbytU0prpuiu60fgXKUWCpDJtApclU= +github.com/aws/aws-sdk-go-v2/config v1.32.17/go.mod h1:OXqUMzgXytfoF9JaKkhrOYsyh72t9G+MJH8mMRaexOE= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16 h1:r3RJBuU7X9ibt8RHbMjWE6y60QbKBiII6wSrXnapxSU= +github.com/aws/aws-sdk-go-v2/credentials v1.19.16/go.mod h1:6cx7zqDENJDbBIIWX6P8s0h6hqHC8Avbjh9Dseo27ug= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23 h1:UuSfcORqNSz/ey3VPRS8TcVH2Ikf0/sC+Hdj400QI6U= +github.com/aws/aws-sdk-go-v2/feature/ec2/imds v1.18.23/go.mod h1:+G/OSGiOFnSOkYloKj/9M35s74LgVAdJBSD5lsFfqKg= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.2 h1:1i1SUOTLk0TbMh7+eJYxgv1r1f47BfR69LL6yaELoI0= github.com/aws/aws-sdk-go-v2/feature/s3/manager v1.22.2/go.mod h1:bo7DhmS/OyVeAJTC768nEk92YKWskqJ4gn0gB5e59qQ= github.com/aws/aws-sdk-go-v2/internal/configsources v1.4.23 h1:GpT/TrnBYuE5gan2cZbTtvP+JlHsutdmlV2YfEyNde0= @@ -184,16 +184,16 @@ github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 h1:mxuT1xE+dI54NW3RkNjP8DUT5HXq github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5 h1:z2ayoK3pOvf8ODj/vPR0FgAS5ONruBq0F94SRoW/BIU= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5/go.mod h1:mpZB5HAl4ZIISod9qCi12xZ170TbHX9CCJV5y7nb7QU= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.10 h1:a1Fq/KXn75wSzoJaPQTgZO0wHGqE9mjFnylnqEPTchA= -github.com/aws/aws-sdk-go-v2/service/signin v1.0.10/go.mod h1:p6+MXNxW7IA6dMgHfTAzljuwSKD0NCm/4lbS4t6+7vI= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= +github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= github.com/aws/aws-sdk-go-v2/service/ssm v1.68.4 h1:5Wg8AAAnIWM2LE/0KFGqllZff96bm4dBs+uerYFfReE= github.com/aws/aws-sdk-go-v2/service/ssm v1.68.4/go.mod h1:nph0ypDLWm9D9iA9zOX39W/N+A4GqwzlxA13jzXVD4k= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.16 h1:x6bKbmDhsgSZwv6q19wY/u3rLk/3FGjJWyqKcIRufpE= -github.com/aws/aws-sdk-go-v2/service/sso v1.30.16/go.mod h1:CudnEVKRtLn0+3uMV0yEXZ+YZOKnAtUJ5DmDhilVnIw= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20 h1:oK/njaL8GtyEihkWMD4k3VgHCT64RQKkZwh0DG5j8ak= -github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.20/go.mod h1:JHs8/y1f3zY7U5WcuzoJ/yAYGYtNIVPKLIbp61euvmg= -github.com/aws/aws-sdk-go-v2/service/sts v1.42.0 h1:ks8KBcZPh3PYISr5dAiXCM5/Thcuxk8l+PG4+A0exds= -github.com/aws/aws-sdk-go-v2/service/sts v1.42.0/go.mod h1:pFw33T0WLvXU3rw1WBkpMlkgIn54eCB5FYLhjDc9Foo= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= +github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= +github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21/go.mod h1:4vIRDq+CJB2xFAXZ+YgGUTiEft7oAQlhIs71xcSeuVg= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 h1:F/M5Y9I3nwr2IEpshZgh1GeHpOItExNM9L1euNuh/fk= +github.com/aws/aws-sdk-go-v2/service/sts v1.42.1/go.mod h1:mTNxImtovCOEEuD65mKW7DCsL+2gjEH+RPEAexAzAio= github.com/aws/smithy-go v1.25.1 h1:J8ERsGSU7d+aCmdQur5Txg6bVoYelvQJgtZehD12GkI= github.com/aws/smithy-go v1.25.1/go.mod h1:YE2RhdIuDbA5E5bTdciG9KrW3+TiEONeUWCqxX9i1Fc= github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k= From 88455b1ce894cdad7f69081fc7c6f411069f9f4a Mon Sep 17 00:00:00 2001 From: Sianao Date: Thu, 30 Apr 2026 20:08:34 +0800 Subject: [PATCH 05/28] update readme add install from source (#2561) * update readme install method Signed-off-by: Sianao * update chinese install desc Signed-off-by: Sianao --------- Signed-off-by: Sianao --- README-zh_CN.md | 7 +++++++ README.md | 7 +++++++ 2 files changed, 14 insertions(+) diff --git a/README-zh_CN.md b/README-zh_CN.md index e97cb391..33cf1b80 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -61,6 +61,13 @@ Helmfile 是一个声明式Helm Chart管理工具 > 安装后请运行一次 `helmfile init`。 检查[helm-diff](https://github.com/databus23/helm-diff) 等插件安装正确。 +**方式4: 源码安装** + +依赖: [Go](https://golang.org/dl/) + + + +` go install github.com/helmfile/helmfile@latest ` ## 使用 让我们从最简单的 helmfile 开始,逐渐改进它以适应您的用例! diff --git a/README.md b/README.md index 7a655893..9f6b2d0e 100644 --- a/README.md +++ b/README.md @@ -75,6 +75,13 @@ For more details, see [run as a container](https://helmfile.readthedocs.io/en/la > Make sure to run `helmfile init` once after installation. Helmfile uses the [helm-diff](https://github.com/databus23/helm-diff) plugin. +**4: Build from source** + +requirements: [Go](https://golang.org/dl/) + + + +` go install github.com/helmfile/helmfile@latest ` ## Getting Started Let's start with a simple `helmfile` and gradually improve it to fit your use-case! From 10deabb1423d878fa446f523787dc5aeb8ebb04b Mon Sep 17 00:00:00 2001 From: Copilot <198982749+Copilot@users.noreply.github.com> Date: Thu, 30 Apr 2026 12:30:41 -0400 Subject: [PATCH 06/28] Honor `skipSchemaValidation` during chartification when `forceNamespace` is set (#2550) * fix(state): honor skipSchemaValidation in chartify template args Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/a695cbff-c37a-403a-9658-09f4fdaa65d0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test(state): harden chartify skip-schema flag detection Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/a695cbff-c37a-403a-9658-09f4fdaa65d0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix(state): propagate cli skip-schema-validation to chartify Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/70ebf027-0ab5-4bdb-a4b4-5a77c822ee95 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --- pkg/app/app.go | 4 ++ pkg/state/helmx.go | 26 +++++++---- pkg/state/issue_2549_test.go | 86 ++++++++++++++++++++++++++++++++++++ pkg/state/state.go | 32 ++++++++++++++ 4 files changed, 139 insertions(+), 9 deletions(-) create mode 100644 pkg/state/issue_2549_test.go diff --git a/pkg/app/app.go b/pkg/app/app.go index 1ee1cb48..29a1ce40 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -166,6 +166,7 @@ func (a *App) Diff(c DiffConfigProvider) error { SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), + SkipSchemaValidation: c.SkipSchemaValidation(), IncludeCRDs: &includeCRDs, Validate: c.Validate(), Concurrency: c.Concurrency(), @@ -237,6 +238,7 @@ func (a *App) Template(c TemplateConfigProvider) error { SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), + SkipSchemaValidation: c.SkipSchemaValidation(), IncludeCRDs: &includeCRDs, SkipCleanup: c.SkipCleanup(), Validate: c.Validate(), @@ -420,6 +422,7 @@ func (a *App) Sync(c SyncConfigProvider) error { SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), + SkipSchemaValidation: c.SkipSchemaValidation(), Wait: c.Wait(), WaitRetries: c.WaitRetries(), WaitForJobs: c.WaitForJobs(), @@ -456,6 +459,7 @@ func (a *App) Apply(c ApplyConfigProvider) error { SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), + SkipSchemaValidation: c.SkipSchemaValidation(), Wait: c.Wait(), WaitRetries: c.WaitRetries(), WaitForJobs: c.WaitForJobs(), diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index b337cd30..f1a2daeb 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -135,20 +135,28 @@ func (st *HelmState) appendPostRenderArgsFlags(flags []string, release *ReleaseS // append skip-schema-validation flags to helm flags func (st *HelmState) appendSkipSchemaValidationFlags(flags []string, release *ReleaseSpec, skipSchemaValidation bool) []string { - switch { - // Check if SkipSchemaValidation is true in the release spec. - case release.SkipSchemaValidation != nil && *release.SkipSchemaValidation: - flags = append(flags, "--skip-schema-validation") - // Check if skipSchemaValidation argument is true. - case skipSchemaValidation: - flags = append(flags, "--skip-schema-validation") - // Check if SkipSchemaValidation is true in HelmDefaults. - case st.HelmDefaults.SkipSchemaValidation != nil && *st.HelmDefaults.SkipSchemaValidation: + if st.shouldSkipSchemaValidation(release, skipSchemaValidation) { flags = append(flags, "--skip-schema-validation") } return flags } +func (st *HelmState) shouldSkipSchemaValidation(release *ReleaseSpec, skipSchemaValidation bool) bool { + switch { + // Check if SkipSchemaValidation is true in the release spec. + case release.SkipSchemaValidation != nil && *release.SkipSchemaValidation: + return true + // Check if skipSchemaValidation argument is true. + case skipSchemaValidation: + return true + // Check if SkipSchemaValidation is true in HelmDefaults. + case st.HelmDefaults.SkipSchemaValidation != nil && *st.HelmDefaults.SkipSchemaValidation: + return true + default: + return false + } +} + // append suppress-output-line-regex flags to helm diff flags func (st *HelmState) appendSuppressOutputLineRegexFlags(flags []string, release *ReleaseSpec, suppressOutputLineRegex []string) []string { suppressOutputLineRegexFlags := []string{} diff --git a/pkg/state/issue_2549_test.go b/pkg/state/issue_2549_test.go new file mode 100644 index 00000000..3dd6e343 --- /dev/null +++ b/pkg/state/issue_2549_test.go @@ -0,0 +1,86 @@ +package state + +import ( + "testing" + + "github.com/stretchr/testify/require" +) + +func TestAppendSkipSchemaValidationFlagToChartifyTemplateArgs(t *testing.T) { + enable := true + + tests := []struct { + name string + defaults HelmSpec + release *ReleaseSpec + fromCLI bool + templateArgs string + want string + }{ + { + name: "adds flag from release setting", + release: &ReleaseSpec{ + SkipSchemaValidation: &enable, + }, + want: "--skip-schema-validation", + }, + { + name: "adds flag from helm defaults", + defaults: HelmSpec{ + SkipSchemaValidation: &enable, + }, + release: &ReleaseSpec{}, + want: "--skip-schema-validation", + }, + { + name: "appends flag to existing args", + release: &ReleaseSpec{ + SkipSchemaValidation: &enable, + }, + templateArgs: "--kube-context default", + want: "--kube-context default --skip-schema-validation", + }, + { + name: "does not duplicate existing flag", + release: &ReleaseSpec{ + SkipSchemaValidation: &enable, + }, + templateArgs: "--skip-schema-validation --kube-context default", + want: "--skip-schema-validation --kube-context default", + }, + { + name: "does not treat similar flag values as existing flag", + release: &ReleaseSpec{ + SkipSchemaValidation: &enable, + }, + templateArgs: "--set name=foo--skip-schema-validation", + want: "--set name=foo--skip-schema-validation --skip-schema-validation", + }, + { + name: "adds flag from cli setting", + release: &ReleaseSpec{}, + fromCLI: true, + templateArgs: "--kube-context default", + want: "--kube-context default --skip-schema-validation", + }, + { + name: "does not add flag when disabled", + release: &ReleaseSpec{}, + templateArgs: "--kube-context default", + want: "--kube-context default", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + st := &HelmState{ + ReleaseSetSpec: ReleaseSetSpec{ + HelmDefaults: tt.defaults, + }, + } + + got := st.appendSkipSchemaValidationFlagToChartifyTemplateArgs(tt.templateArgs, tt.release, tt.fromCLI) + require.Equal(t, tt.want, got) + }) + } +} diff --git a/pkg/state/state.go b/pkg/state/state.go index f51a0dd1..594680e5 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -1341,6 +1341,8 @@ type ChartPrepareOptions struct { SkipRefresh bool SkipResolve bool SkipCleanup bool + // SkipSchemaValidation configures chartify to pass --skip-schema-validation to helm-template run by it. + SkipSchemaValidation bool // Validate configures chartify to pass --validate to helm-template run by it. // It's required when one of your chart relies on Capabilities.APIVersions in a template Validate bool @@ -1622,6 +1624,12 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re } } + chartifyOpts.TemplateArgs = st.appendSkipSchemaValidationFlagToChartifyTemplateArgs( + chartifyOpts.TemplateArgs, + release, + opts.SkipSchemaValidation, + ) + out, err := c.Chartify(release.Name, chartPath, chartify.WithChartifyOpts(chartifyOpts)) if err != nil { return "", false, err @@ -1634,6 +1642,30 @@ func (st *HelmState) processChartification(chartification *Chartify, release *Re return chartPath, buildDeps, nil } +func (st *HelmState) appendSkipSchemaValidationFlagToChartifyTemplateArgs(templateArgs string, release *ReleaseSpec, skipSchemaValidation bool) string { + if !st.shouldSkipSchemaValidation(release, skipSchemaValidation) || hasTemplateArg(templateArgs, "--skip-schema-validation") { + return templateArgs + } + + return appendTemplateArg(templateArgs, "--skip-schema-validation") +} + +func hasTemplateArg(templateArgs, arg string) bool { + for _, token := range strings.Fields(templateArgs) { + if token == arg || strings.HasPrefix(token, arg+"=") { + return true + } + } + return false +} + +func appendTemplateArg(templateArgs, arg string) string { + if templateArgs == "" { + return arg + } + return templateArgs + " " + arg +} + // processLocalChart handles local chart processing func (st *HelmState) processLocalChart(normalizedChart, dir string, release *ReleaseSpec, helmfileCommand string, opts ChartPrepareOptions, isLocal bool) (string, error) { chartPath := normalizedChart From d3c68299dac38bce7425c652821f104c39814a46 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 1 May 2026 18:57:16 +0800 Subject: [PATCH 07/28] build(deps): bump github.com/Masterminds/semver/v3 from 3.4.0 to 3.5.0 (#2565) Bumps [github.com/Masterminds/semver/v3](https://github.com/Masterminds/semver) from 3.4.0 to 3.5.0. - [Release notes](https://github.com/Masterminds/semver/releases) - [Changelog](https://github.com/Masterminds/semver/blob/master/CHANGELOG.md) - [Commits](https://github.com/Masterminds/semver/compare/v3.4.0...v3.5.0) --- updated-dependencies: - dependency-name: github.com/Masterminds/semver/v3 dependency-version: 3.5.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index c5caf972..73601744 100644 --- a/go.mod +++ b/go.mod @@ -4,7 +4,7 @@ go 1.26.2 require ( dario.cat/mergo v1.0.2 - github.com/Masterminds/semver/v3 v3.4.0 + github.com/Masterminds/semver/v3 v3.5.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/aws/aws-sdk-go-v2/config v1.32.17 github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 diff --git a/go.sum b/go.sum index eec8bec4..1e25c93b 100644 --- a/go.sum +++ b/go.sum @@ -114,8 +114,8 @@ github.com/MakeNowJust/heredoc v1.0.0 h1:cXCdzVdstXyiTqTvfqk9SDHpKNjxuom+DOlyEeQ github.com/MakeNowJust/heredoc v1.0.0/go.mod h1:mG5amYoWBHf8vpLOuehzbGGw0EHxpZZ6lCpQ4fNJ8LE= github.com/Masterminds/goutils v1.1.1 h1:5nUrii3FMTL5diU80unEVvNevw1nH4+ZV4DSLVJLSYI= github.com/Masterminds/goutils v1.1.1/go.mod h1:8cTjp+g8YejhMuvIA5y2vz3BpJxksy863GQaJW2MFNU= -github.com/Masterminds/semver/v3 v3.4.0 h1:Zog+i5UMtVoCU8oKka5P7i9q9HgrJeGzI9SA1Xbatp0= -github.com/Masterminds/semver/v3 v3.4.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= +github.com/Masterminds/semver/v3 v3.5.0 h1:kQceYJfbupGfZOKZQg0kou0DgAKhzDg2NZPAwZ/2OOE= +github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= github.com/Masterminds/sprig/v3 v3.3.0 h1:mQh0Yrg1XPo6vjYXgtf5OtijNAKJRNcTdOOGZe3tPhs= github.com/Masterminds/sprig/v3 v3.3.0/go.mod h1:Zy1iXRYNqNLUolqCpL4uhk6SHUMAOSCzdgBfDb35Lz0= github.com/Masterminds/squirrel v1.5.4 h1:uUcX/aBc8O7Fg9kaISIUsHXdKuqehiXAMQTYX8afzqM= From dac42105dd2dd230f45d16a05de042c0921761f3 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Fri, 1 May 2026 20:29:24 +0800 Subject: [PATCH 08/28] fix: deduplicate chart dependencies in helmfile.lock (#2567) When multiple releases reference the same chart with the same name, repository, and version, helmfile deps would write duplicate entries to helmfile.lock. This adds deduplication of resolved dependencies after sorting and before writing the lock file. Fixes #2562 Signed-off-by: yxxhero --- pkg/state/chart_dependency.go | 16 ++++++++ pkg/state/chart_dependency_test.go | 63 ++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+) diff --git a/pkg/state/chart_dependency.go b/pkg/state/chart_dependency.go index 60101c8a..7f88081c 100644 --- a/pkg/state/chart_dependency.go +++ b/pkg/state/chart_dependency.go @@ -129,6 +129,20 @@ func (d *ResolvedDependencies) Get(chart, versionConstraint string) (string, err return "", fmt.Errorf("no resolved dependency found for \"%s\", running \"helmfile deps\" may resolve the issue", chart) } +func dedupResolvedDependencies(deps []ResolvedChartDependency) []ResolvedChartDependency { + seen := map[string]bool{} + result := make([]ResolvedChartDependency, 0, len(deps)) + for _, dep := range deps { + key := dep.ChartName + "|" + dep.Repository + "|" + dep.Version + if seen[key] { + continue + } + seen[key] = true + result = append(result, dep) + } + return result +} + func (st *HelmState) mergeLockedDependencies() (*HelmState, error) { filename, unresolved := getUnresolvedDependenciess(st) @@ -369,6 +383,8 @@ func (m *chartDependencyManager) doUpdate(chartLockFile string, unresolved *Unre return lockedReqs.ResolvedDependencies[i].ChartName < lockedReqs.ResolvedDependencies[j].ChartName }) + lockedReqs.ResolvedDependencies = dedupResolvedDependencies(lockedReqs.ResolvedDependencies) + lockedReqs.Version = version.Version() updatedLockFileContent, err = yaml.Marshal(lockedReqs) diff --git a/pkg/state/chart_dependency_test.go b/pkg/state/chart_dependency_test.go index 23c97c5c..70585b1c 100644 --- a/pkg/state/chart_dependency_test.go +++ b/pkg/state/chart_dependency_test.go @@ -6,6 +6,69 @@ import ( "github.com/stretchr/testify/require" ) +func TestDedupResolvedDependencies(t *testing.T) { + tests := []struct { + name string + input []ResolvedChartDependency + expected []ResolvedChartDependency + }{ + { + name: "no duplicates", + input: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "redis", Repository: "https://charts.bitnami.com/bitnami", Version: "17.0.7"}, + }, + expected: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "redis", Repository: "https://charts.bitnami.com/bitnami", Version: "17.0.7"}, + }, + }, + { + name: "duplicates removed", + input: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + }, + expected: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + }, + }, + { + name: "same chart different versions kept", + input: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.1"}, + }, + expected: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.1"}, + }, + }, + { + name: "same chart different repos kept", + input: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "app-template", Repository: "https://other.com", Version: "4.6.2"}, + }, + expected: []ResolvedChartDependency{ + {ChartName: "app-template", Repository: "https://example.com", Version: "4.6.2"}, + {ChartName: "app-template", Repository: "https://other.com", Version: "4.6.2"}, + }, + }, + { + name: "empty input", + input: []ResolvedChartDependency{}, + expected: []ResolvedChartDependency{}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result := dedupResolvedDependencies(tt.input) + require.Equal(t, tt.expected, result) + }) + } +} + func TestGetUnresolvedDependenciess(t *testing.T) { tests := []struct { name string From bda57b741fb97e2cae4e84cc9ca2141dff235d87 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sat, 2 May 2026 08:17:48 +0800 Subject: [PATCH 09/28] build(deps): replace werf/kubedog-for-werf-helm with werf/kubedog (#2568) * build(deps): replace werf/kubedog-for-werf-helm with werf/kubedog Replace the fork github.com/werf/kubedog-for-werf-helm with the upstream github.com/werf/kubedog. The fork was a temporary compatibility shim; the upstream repository now includes the necessary k8s API fixes. Signed-off-by: yxxhero * fix(kubedog): initialize InformerFactory to prevent nil pointer panic The upstream werf/kubedog now requires an InformerFactory for its resource trackers (deployment, statefulset, daemonset, job, canary), but the multitrack layer still passes nil. Bypass the broken multitrack feed layer by creating resource trackers directly with a properly initialized InformerFactory. Signed-off-by: yxxhero * fix(lint): correct misspelling of canceled Signed-off-by: yxxhero --------- Signed-off-by: yxxhero --- go.mod | 5 +- go.sum | 7 +- pkg/kubedog/tracker.go | 317 +++++++++++++++++++++++++++++++++-------- 3 files changed, 262 insertions(+), 67 deletions(-) diff --git a/go.mod b/go.mod index 73601744..5caf1f49 100644 --- a/go.mod +++ b/go.mod @@ -25,7 +25,7 @@ require ( github.com/tatsushid/go-prettytable v0.0.0-20141013043238-ed2d14c29939 github.com/tj/assert v0.0.3 github.com/variantdev/dag v1.1.0 - github.com/werf/kubedog-for-werf-helm v0.0.0-20241217155728-9d45c48b82b6 + github.com/werf/kubedog v0.13.1-0.20260217150136-ed58edf34eac github.com/zclconf/go-cty v1.18.1 github.com/zclconf/go-cty-yaml v1.2.0 go.szostok.io/version v1.2.0 @@ -180,7 +180,6 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect - github.com/chanced/caps v1.0.2 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/containerd/containerd v1.7.30 // indirect @@ -309,8 +308,6 @@ require ( github.com/werf/logboek v0.6.1 // indirect github.com/x448/float16 v0.8.4 // indirect github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect - github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 // indirect - github.com/xeipuuv/gojsonschema v1.2.0 // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect github.com/yandex-cloud/go-genproto v0.69.0 // indirect diff --git a/go.sum b/go.sum index 1e25c93b..10477c13 100644 --- a/go.sum +++ b/go.sum @@ -217,8 +217,6 @@ github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UF github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= -github.com/chanced/caps v1.0.2 h1:RELvNN4lZajqSXJGzPaU7z8B4LK2+o2Oc/upeWdgMOA= -github.com/chanced/caps v1.0.2/go.mod h1:SJhRzeYLKJ3OmzyQXhdZ7Etj7lqqWoPtQ1zcSJRtQjs= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= @@ -779,8 +777,8 @@ github.com/urfave/cli v1.22.17 h1:SYzXoiPfQjHBbkYxbew5prZHS1TOLT3ierW8SYLqtVQ= github.com/urfave/cli v1.22.17/go.mod h1:b0ht0aqgH/6pBYzzxURyrM4xXNgsoT/n2ZzwQiEhNVo= github.com/variantdev/dag v1.1.0 h1:xodYlSng33KWGvIGMpKUyLcIZRXKiNUx612mZJqYrDg= github.com/variantdev/dag v1.1.0/go.mod h1:pH1TQsNSLj2uxMo9NNl9zdGy01Wtn+/2MT96BrKmVyE= -github.com/werf/kubedog-for-werf-helm v0.0.0-20241217155728-9d45c48b82b6 h1:lpgQPTCp+wNJfTqJWtR6A5gRA4e4m/eRJFV7V18XCoA= -github.com/werf/kubedog-for-werf-helm v0.0.0-20241217155728-9d45c48b82b6/go.mod h1:PA9xGVKX9Il6sCgvPrcB3/FahRme3bXRz4BuylvAssc= +github.com/werf/kubedog v0.13.1-0.20260217150136-ed58edf34eac h1:kGp4G79ZiV61SxyLeh6kErzv+u5YBaAxp23oL6T/IJo= +github.com/werf/kubedog v0.13.1-0.20260217150136-ed58edf34eac/go.mod h1:gu4EY4hxtiYVDy5o6WE2lRZS0YWqrOV0HS//GTYyrUE= github.com/werf/logboek v0.6.1 h1:oEe6FkmlKg0z0n80oZjLplj6sXcBeLleCkjfOOZEL2g= github.com/werf/logboek v0.6.1/go.mod h1:Gez5J4bxekyr6MxTmIJyId1F61rpO+0/V4vjCIEIZmk= github.com/x448/float16 v0.8.4 h1:qLwI1I70+NjRFUR3zs1JPUCgaCXSh3SW62uAKT1mSBM= @@ -788,7 +786,6 @@ github.com/x448/float16 v0.8.4/go.mod h1:14CWIYCyZA/cWjXOioeEpHeN/83MdbZDRQHoFcY github.com/xdg-go/pbkdf2 v1.0.0/go.mod h1:jrpuAogTd400dnrH08LKmI/xc1MbPOebTwRqcT5RDeI= github.com/xdg-go/scram v1.1.2/go.mod h1:RT/sEzTbU5y00aCK8UOx6R7YryM0iF1N2MOmC3kKLN4= github.com/xdg-go/stringprep v1.0.4/go.mod h1:mPGuuIYwz7CmR2bT9j4GbQqutWS1zV24gijq1dTyGkM= -github.com/xeipuuv/gojsonpointer v0.0.0-20180127040702-4e3ac2762d5f/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb h1:zGWFAtiMcyryUHoUjUJX0/lt1H2+i2Ka2n+D3DImSNo= github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb/go.mod h1:N2zxlSyiKSe5eX1tZViRH5QA0qijqEDrYZiPEAiq3wU= github.com/xeipuuv/gojsonreference v0.0.0-20180127040603-bd5ef7bd5415 h1:EzJWgHovont7NscjpAxXsDA8S8BMYve8Y5+7cuRE7R0= diff --git a/pkg/kubedog/tracker.go b/pkg/kubedog/tracker.go index 6dcb2bc4..867116f8 100644 --- a/pkg/kubedog/tracker.go +++ b/pkg/kubedog/tracker.go @@ -9,8 +9,14 @@ import ( "sync" "time" - "github.com/werf/kubedog-for-werf-helm/pkg/tracker" - "github.com/werf/kubedog-for-werf-helm/pkg/trackers/rollout/multitrack" + "github.com/werf/kubedog/pkg/informer" + "github.com/werf/kubedog/pkg/tracker" + "github.com/werf/kubedog/pkg/tracker/canary" + "github.com/werf/kubedog/pkg/tracker/daemonset" + "github.com/werf/kubedog/pkg/tracker/deployment" + "github.com/werf/kubedog/pkg/tracker/job" + "github.com/werf/kubedog/pkg/tracker/statefulset" + "github.com/werf/kubedog/pkg/trackers/dyntracker/util" "go.uber.org/zap" "k8s.io/apimachinery/pkg/api/meta" "k8s.io/client-go/discovery" @@ -20,6 +26,7 @@ import ( "k8s.io/client-go/rest" "k8s.io/client-go/restmapper" "k8s.io/client-go/tools/clientcmd" + watchtools "k8s.io/client-go/tools/watch" "github.com/helmfile/helmfile/pkg/resource" ) @@ -187,6 +194,12 @@ func getOrCreateClients(kubeContext, kubeconfig string, qps float32, burst int) return cache, nil } +type trackTarget struct { + kind string + name string + namespace string +} + func (t *Tracker) TrackResources(ctx context.Context, resources []*resource.Resource) error { if len(resources) == 0 { t.logger.Info("No resources to track") @@ -201,81 +214,269 @@ func (t *Tracker) TrackResources(ctx context.Context, resources []*resource.Reso t.logger.Infof("Tracking %d resources with kubedog (filtered from %d total)", len(filtered), len(resources)) - specs := multitrack.MultitrackSpecs{} + targets := t.buildTargets(filtered) + if len(targets) == 0 { + t.logger.Info("No trackable resources found (only Deployment, StatefulSet, DaemonSet, Job, and Canary are supported)") + return nil + } - for _, res := range filtered { + t.logger.Infof("Tracking breakdown: %s", t.targetSummary(targets)) + + watchErrCh := make(chan error, len(targets)) + informerFactory := informer.NewConcurrentInformerFactory( + ctx.Done(), + watchErrCh, + t.dynamicClient, + informer.ConcurrentInformerFactoryOptions{}, + ) + + opts := tracker.Options{ + ParentContext: ctx, + Timeout: t.trackOptions.Timeout, + LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince), + } + + var wg sync.WaitGroup + errCh := make(chan error, len(targets)) + + for _, target := range targets { + wg.Add(1) + go func(tgt trackTarget) { + defer wg.Done() + if err := t.trackSingleResource(tgt, informerFactory, opts); err != nil { + errCh <- fmt.Errorf("%s/%s tracking failed: %w", tgt.kind, tgt.name, err) + } + }(target) + } + + done := make(chan struct{}) + go func() { + wg.Wait() + close(done) + }() + + select { + case err := <-errCh: + return fmt.Errorf("tracking failed: %w", err) + case <-done: + t.logger.Info("All resources tracked successfully") + return nil + case <-ctx.Done(): + return fmt.Errorf("tracking canceled: %w", ctx.Err()) + } +} + +func (t *Tracker) trackSingleResource(target trackTarget, informerFactory *util.Concurrent[*informer.InformerFactory], opts tracker.Options) error { + parentContext := opts.ParentContext + if parentContext == nil { + parentContext = context.Background() + } + ctx, cancel := watchtools.ContextWithOptionalTimeout(parentContext, opts.Timeout) + defer cancel() + + trackErrCh := make(chan error, 1) + doneCh := make(chan struct{}) + + switch target.kind { + case "deploy": + tr := deployment.NewTracker(target.name, target.namespace, t.clientSet, informerFactory, opts) + go t.runDeploymentTracker(ctx, tr, trackErrCh, doneCh) + return t.waitDeploymentTracker(ctx, tr, trackErrCh, doneCh) + case "sts": + tr := statefulset.NewTracker(target.name, target.namespace, t.clientSet, informerFactory, opts) + go t.runStatefulSetTracker(ctx, tr, trackErrCh, doneCh) + return t.waitStatefulSetTracker(ctx, tr, trackErrCh, doneCh) + case "ds": + tr := daemonset.NewTracker(target.name, target.namespace, t.clientSet, informerFactory, opts) + go t.runDaemonSetTracker(ctx, tr, trackErrCh, doneCh) + return t.waitDaemonSetTracker(ctx, tr, trackErrCh, doneCh) + case "job": + tr := job.NewTracker(target.name, target.namespace, t.clientSet, informerFactory, opts) + go t.runJobTracker(ctx, tr, trackErrCh, doneCh) + return t.waitJobTracker(ctx, tr, trackErrCh, doneCh) + case "canary": + tr := canary.NewTracker(target.name, target.namespace, t.clientSet, t.dynamicClient, informerFactory, opts) + go t.runCanaryTracker(ctx, tr, trackErrCh, doneCh) + return t.waitCanaryTracker(ctx, tr, trackErrCh, doneCh) + default: + return fmt.Errorf("unsupported resource kind: %s", target.kind) + } +} + +func (t *Tracker) runDeploymentTracker(ctx context.Context, tr *deployment.Tracker, errCh chan<- error, doneCh chan<- struct{}) { + if err := tr.Track(ctx); err != nil { + errCh <- err + } else { + close(doneCh) + } +} + +func (t *Tracker) waitDeploymentTracker(ctx context.Context, tr *deployment.Tracker, trackErrCh <-chan error, doneCh <-chan struct{}) error { + for { + select { + case <-tr.Ready: + t.logger.Debugf("Deployment %s/%s is ready", tr.Namespace, tr.ResourceName) + return nil + case status := <-tr.Failed: + return fmt.Errorf("deployment %s/%s failed: %s", tr.Namespace, tr.ResourceName, status.FailedReason) + case err := <-trackErrCh: + return err + case <-doneCh: + return nil + case <-ctx.Done(): + return fmt.Errorf("tracking canceled for deployment %s/%s: %w", tr.Namespace, tr.ResourceName, ctx.Err()) + } + } +} + +func (t *Tracker) runStatefulSetTracker(ctx context.Context, tr *statefulset.Tracker, errCh chan<- error, doneCh chan<- struct{}) { + if err := tr.Track(ctx); err != nil { + errCh <- err + } else { + close(doneCh) + } +} + +func (t *Tracker) waitStatefulSetTracker(ctx context.Context, tr *statefulset.Tracker, trackErrCh <-chan error, doneCh <-chan struct{}) error { + for { + select { + case <-tr.Ready: + t.logger.Debugf("StatefulSet %s/%s is ready", tr.Namespace, tr.ResourceName) + return nil + case status := <-tr.Failed: + return fmt.Errorf("statefulset %s/%s failed: %s", tr.Namespace, tr.ResourceName, status.FailedReason) + case err := <-trackErrCh: + return err + case <-doneCh: + return nil + case <-ctx.Done(): + return fmt.Errorf("tracking canceled for statefulset %s/%s: %w", tr.Namespace, tr.ResourceName, ctx.Err()) + } + } +} + +func (t *Tracker) runDaemonSetTracker(ctx context.Context, tr *daemonset.Tracker, errCh chan<- error, doneCh chan<- struct{}) { + if err := tr.Track(ctx); err != nil { + errCh <- err + } else { + close(doneCh) + } +} + +func (t *Tracker) waitDaemonSetTracker(ctx context.Context, tr *daemonset.Tracker, trackErrCh <-chan error, doneCh <-chan struct{}) error { + for { + select { + case <-tr.Ready: + t.logger.Debugf("DaemonSet %s/%s is ready", tr.Namespace, tr.ResourceName) + return nil + case status := <-tr.Failed: + return fmt.Errorf("daemonset %s/%s failed: %s", tr.Namespace, tr.ResourceName, status.FailedReason) + case err := <-trackErrCh: + return err + case <-doneCh: + return nil + case <-ctx.Done(): + return fmt.Errorf("tracking canceled for daemonset %s/%s: %w", tr.Namespace, tr.ResourceName, ctx.Err()) + } + } +} + +func (t *Tracker) runJobTracker(ctx context.Context, tr *job.Tracker, errCh chan<- error, doneCh chan<- struct{}) { + if err := tr.Track(ctx); err != nil { + errCh <- err + } else { + close(doneCh) + } +} + +func (t *Tracker) waitJobTracker(ctx context.Context, tr *job.Tracker, trackErrCh <-chan error, doneCh <-chan struct{}) error { + for { + select { + case <-tr.Succeeded: + t.logger.Debugf("Job %s/%s succeeded", tr.Namespace, tr.ResourceName) + return nil + case status := <-tr.Failed: + return fmt.Errorf("job %s/%s failed: %s", tr.Namespace, tr.ResourceName, status.FailedReason) + case err := <-trackErrCh: + return err + case <-doneCh: + return nil + case <-ctx.Done(): + return fmt.Errorf("tracking canceled for job %s/%s: %w", tr.Namespace, tr.ResourceName, ctx.Err()) + } + } +} + +func (t *Tracker) runCanaryTracker(ctx context.Context, tr *canary.Tracker, errCh chan<- error, doneCh chan<- struct{}) { + if err := tr.Track(ctx); err != nil { + errCh <- err + } else { + close(doneCh) + } +} + +func (t *Tracker) waitCanaryTracker(ctx context.Context, tr *canary.Tracker, trackErrCh <-chan error, doneCh <-chan struct{}) error { + for { + select { + case <-tr.Succeeded: + t.logger.Debugf("Canary %s/%s succeeded", tr.Namespace, tr.ResourceName) + return nil + case status := <-tr.Failed: + return fmt.Errorf("canary %s/%s failed: %s", tr.Namespace, tr.ResourceName, status.FailedReason) + case err := <-trackErrCh: + return err + case <-doneCh: + return nil + case <-ctx.Done(): + return fmt.Errorf("tracking canceled for canary %s/%s: %w", tr.Namespace, tr.ResourceName, ctx.Err()) + } + } +} + +func (t *Tracker) buildTargets(resources []*resource.Resource) []trackTarget { + var targets []trackTarget + for _, res := range resources { namespace := res.Namespace if namespace == "" { namespace = t.namespace } + kind := "" switch strings.ToLower(res.Kind) { case "deployment", "deploy": - specs.Deployments = append(specs.Deployments, multitrack.MultitrackSpec{ - ResourceName: res.Name, - Namespace: namespace, - SkipLogs: !t.trackOptions.Logs, - }) + kind = "deploy" case "statefulset", "sts": - specs.StatefulSets = append(specs.StatefulSets, multitrack.MultitrackSpec{ - ResourceName: res.Name, - Namespace: namespace, - SkipLogs: !t.trackOptions.Logs, - }) + kind = "sts" case "daemonset", "ds": - specs.DaemonSets = append(specs.DaemonSets, multitrack.MultitrackSpec{ - ResourceName: res.Name, - Namespace: namespace, - SkipLogs: !t.trackOptions.Logs, - }) + kind = "ds" case "job": - specs.Jobs = append(specs.Jobs, multitrack.MultitrackSpec{ - ResourceName: res.Name, - Namespace: namespace, - SkipLogs: !t.trackOptions.Logs, - }) + kind = "job" case "canary": - specs.Canaries = append(specs.Canaries, multitrack.MultitrackSpec{ - ResourceName: res.Name, - Namespace: namespace, - SkipLogs: !t.trackOptions.Logs, - }) + kind = "canary" default: t.logger.Debugf("Skipping unsupported kind %s for resource %s/%s", res.Kind, namespace, res.Name) + continue } + + targets = append(targets, trackTarget{ + kind: kind, + name: res.Name, + namespace: namespace, + }) } + return targets +} - totalResources := len(specs.Deployments) + len(specs.StatefulSets) + - len(specs.DaemonSets) + len(specs.Jobs) + len(specs.Canaries) - - if totalResources == 0 { - t.logger.Info("No trackable resources found (only Deployment, StatefulSet, DaemonSet, Job, and Canary are supported)") - return nil +func (t *Tracker) targetSummary(targets []trackTarget) string { + counts := make(map[string]int) + for _, tgt := range targets { + counts[tgt.kind]++ } - - t.logger.Infof("Tracking breakdown: Deployments=%d, StatefulSets=%d, DaemonSets=%d, Jobs=%d, Canaries=%d", - len(specs.Deployments), len(specs.StatefulSets), len(specs.DaemonSets), - len(specs.Jobs), len(specs.Canaries)) - - opts := multitrack.MultitrackOptions{ - Options: tracker.Options{ - ParentContext: ctx, - Timeout: t.trackOptions.Timeout, - LogsFromTime: time.Now().Add(-t.trackOptions.LogsSince), - }, - StatusProgressPeriod: 5 * time.Second, - DynamicClient: t.dynamicClient, - DiscoveryClient: t.discovery, - Mapper: t.mapper, + parts := make([]string, 0, len(counts)) + for kind, count := range counts { + parts = append(parts, fmt.Sprintf("%ss=%d", kind, count)) } - - err := multitrack.Multitrack(t.clientSet, specs, opts) - if err != nil { - return fmt.Errorf("tracking failed: %w", err) - } - - t.logger.Info("All resources tracked successfully") - return nil + return strings.Join(parts, ", ") } func (t *Tracker) filterResources(resources []*resource.Resource) []*resource.Resource { From acecac18342ba54ef05c14757b8734c3c5807f99 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sat, 2 May 2026 09:02:29 +0800 Subject: [PATCH 10/28] build(deps): bump helmfile/vals from v0.43.9 to v0.44.0 (#2569) Update vals to v0.44.0 to fix YC_TOKEN json-string handling for Yandex Lockbox secret decryption. Also bumps transitive dependencies including yandex-cloud/go-genproto (0.69.0 -> 0.75.0) which contains the actual fix. Add replace directives to pin k8s.io/{api,apimachinery,client-go} to v0.35.4 for compatibility with helm and kubedog dependencies that still require removed k8s.io/api packages (scheduling/v1alpha1, autoscaling/v2beta2). Closes #2564 Signed-off-by: yxxhero --- go.mod | 91 ++++++++++++++------------- go.sum | 190 +++++++++++++++++++++++++++++---------------------------- 2 files changed, 146 insertions(+), 135 deletions(-) diff --git a/go.mod b/go.mod index 5caf1f49..2e740f82 100644 --- a/go.mod +++ b/go.mod @@ -18,7 +18,7 @@ require ( github.com/hashicorp/go-getter/v2 v2.2.3 github.com/hashicorp/hcl/v2 v2.24.0 github.com/helmfile/chartify v0.26.3 - github.com/helmfile/vals v0.43.9 + github.com/helmfile/vals v0.44.0 github.com/spf13/cobra v1.10.2 github.com/spf13/pflag v1.0.10 github.com/stretchr/testify v1.11.1 @@ -38,17 +38,23 @@ require ( helm.sh/helm/v3 v3.20.2 helm.sh/helm/v4 v4.1.4 k8s.io/apimachinery v0.36.0 - k8s.io/client-go v0.35.4 + k8s.io/client-go v0.36.0 +) + +replace ( + k8s.io/api => k8s.io/api v0.35.4 + k8s.io/apimachinery => k8s.io/apimachinery v0.35.4 + k8s.io/client-go => k8s.io/client-go v0.35.4 ) require ( cloud.google.com/go v0.123.0 // indirect cloud.google.com/go/iam v1.7.0 // indirect - cloud.google.com/go/storage v1.62.0 // indirect + cloud.google.com/go/storage v1.62.1 // indirect filippo.io/age v1.3.1 // indirect github.com/Azure/go-autorest v14.2.0+incompatible // indirect github.com/Azure/go-autorest/autorest/adal v0.9.23 // indirect - github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 // indirect + github.com/Azure/go-autorest/autorest/azure/cli v0.4.7 // indirect github.com/Azure/go-autorest/autorest/date v0.3.0 // indirect github.com/Azure/go-autorest/logger v0.2.1 // indirect github.com/Azure/go-autorest/tracing v0.6.0 // indirect @@ -58,10 +64,10 @@ require ( github.com/blang/semver v3.5.1+incompatible // indirect github.com/dimchansky/utfbom v1.1.1 // indirect github.com/fatih/color v1.19.0 - github.com/fujiwara/tfstate-lookup v1.10.0 // indirect + github.com/fujiwara/tfstate-lookup v1.11.0 // indirect github.com/golang-jwt/jwt/v4 v4.5.2 // indirect github.com/golang/groupcache v0.0.0-20210331224755-41bb18bfe9da // indirect - github.com/google/go-querystring v1.1.0 // indirect + github.com/google/go-querystring v1.2.0 // indirect github.com/google/uuid v1.6.0 // indirect github.com/googleapis/gax-go/v2 v2.21.0 // indirect github.com/goware/prefixer v0.0.0-20160118172347-395022866408 // indirect @@ -69,20 +75,20 @@ require ( github.com/hashicorp/go-cleanhttp v0.5.2 // indirect github.com/hashicorp/go-multierror v1.1.1 // indirect github.com/hashicorp/go-rootcerts v1.0.2 // indirect - github.com/hashicorp/go-slug v0.16.4 // indirect + github.com/hashicorp/go-slug v0.16.8 // indirect github.com/hashicorp/go-sockaddr v1.0.7 // indirect - github.com/hashicorp/go-tfe v1.84.0 // indirect + github.com/hashicorp/go-tfe v1.99.0 // indirect github.com/hashicorp/golang-lru v1.0.2 // indirect github.com/hashicorp/hcl v1.0.1-vault-7 // indirect github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e // indirect github.com/hashicorp/vault/api v1.23.0 // indirect github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect - github.com/itchyny/gojq v0.12.16 // indirect + github.com/itchyny/gojq v0.12.19 // indirect github.com/klauspost/compress v1.18.0 // indirect github.com/lib/pq v1.11.2 // indirect github.com/mattn/go-colorable v0.1.14 // indirect - github.com/mattn/go-runewidth v0.0.15 // indirect + github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/go-homedir v1.1.0 // indirect github.com/mitchellh/go-wordwrap v1.0.1 // indirect @@ -97,12 +103,12 @@ require ( github.com/spf13/cast v1.7.0 // indirect github.com/ulikunitz/xz v0.5.15 // indirect go.uber.org/atomic v1.9.0 // indirect - golang.org/x/net v0.52.0 // indirect + golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect golang.org/x/sys v0.43.0 // indirect - golang.org/x/text v0.35.0 // indirect + golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect - google.golang.org/api v0.275.0 // indirect + google.golang.org/api v0.276.0 // indirect google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 // indirect google.golang.org/grpc v1.80.0 // indirect google.golang.org/protobuf v1.36.12-0.20260120151049-f2248ac996af // indirect @@ -117,18 +123,18 @@ require ( cloud.google.com/go/auth v0.20.0 // indirect cloud.google.com/go/auth/oauth2adapt v0.2.8 // indirect cloud.google.com/go/compute/metadata v0.9.0 // indirect - cloud.google.com/go/kms v1.27.0 // indirect - cloud.google.com/go/longrunning v0.8.0 // indirect + cloud.google.com/go/kms v1.29.0 // indirect + cloud.google.com/go/longrunning v0.9.0 // indirect cloud.google.com/go/monitoring v1.24.3 // indirect - cloud.google.com/go/secretmanager v1.18.0 // indirect + cloud.google.com/go/secretmanager v1.19.0 // indirect filippo.io/edwards25519 v1.1.1 // indirect filippo.io/hpke v0.4.0 // indirect github.com/1Password/connect-sdk-go v1.5.3 // indirect github.com/1password/onepassword-sdk-go v0.3.1 // indirect github.com/AlecAivazis/survey/v2 v2.3.6 // indirect - github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 // indirect + github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 // indirect - github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 // indirect + github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/storage/armstorage v1.8.1 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azkeys v1.4.0 // indirect github.com/Azure/azure-sdk-for-go/sdk/security/keyvault/azsecrets v1.4.0 // indirect @@ -137,7 +143,7 @@ require ( github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c // indirect github.com/AzureAD/microsoft-authentication-library-for-go v1.6.0 // indirect github.com/BurntSushi/toml v1.6.0 // indirect - github.com/DelineaXPM/tss-sdk-go/v3 v3.0.1 // indirect + github.com/DelineaXPM/tss-sdk-go/v3 v3.0.2 // indirect github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 // indirect github.com/GoogleCloudPlatform/opentelemetry-operations-go/exporter/metric v0.55.0 // indirect @@ -167,10 +173,10 @@ require ( github.com/aws/aws-sdk-go-v2/service/internal/checksum v1.9.15 // indirect github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 // indirect github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 // indirect - github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 // indirect - github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5 // indirect + github.com/aws/aws-sdk-go-v2/service/kms v1.51.0 // indirect + github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.6 // indirect github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 // indirect - github.com/aws/aws-sdk-go-v2/service/ssm v1.68.4 // indirect + github.com/aws/aws-sdk-go-v2/service/ssm v1.68.5 // indirect github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 // indirect github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 // indirect github.com/aws/aws-sdk-go-v2/service/sts v1.42.1 // indirect @@ -180,6 +186,8 @@ require ( github.com/cenkalti/backoff/v4 v4.3.0 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/chai2010/gettext-go v1.0.2 // indirect + github.com/clipperhouse/stringish v0.1.1 // indirect + github.com/clipperhouse/uax29/v2 v2.3.0 // indirect github.com/cloudflare/circl v1.6.3 // indirect github.com/cncf/xds/go v0.0.0-20251210132809-ee656c7534f5 // indirect github.com/containerd/containerd v1.7.30 // indirect @@ -192,7 +200,7 @@ require ( github.com/danieljoos/wincred v1.2.2 // indirect github.com/dustin/go-humanize v1.0.1 // indirect github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a // indirect - github.com/emicklei/go-restful/v3 v3.12.2 // indirect + github.com/emicklei/go-restful/v3 v3.13.0 // indirect github.com/envoyproxy/go-control-plane/envoy v1.36.0 // indirect github.com/envoyproxy/protoc-gen-validate v1.3.0 // indirect github.com/evanphx/json-patch v5.9.11+incompatible // indirect @@ -211,25 +219,25 @@ require ( github.com/go-jose/go-jose/v4 v4.1.4 // indirect github.com/go-logr/logr v1.4.3 // indirect github.com/go-logr/stdr v1.2.2 // indirect - github.com/go-openapi/analysis v0.24.3 // indirect + github.com/go-openapi/analysis v0.25.0 // indirect github.com/go-openapi/errors v0.22.7 // indirect github.com/go-openapi/jsonpointer v0.22.5 // indirect github.com/go-openapi/jsonreference v0.21.5 // indirect github.com/go-openapi/loads v0.23.3 // indirect - github.com/go-openapi/runtime v0.29.3 // indirect + github.com/go-openapi/runtime v0.29.4 // indirect github.com/go-openapi/spec v0.22.4 // indirect - github.com/go-openapi/strfmt v0.26.0 // indirect + github.com/go-openapi/strfmt v0.26.1 // indirect github.com/go-openapi/swag v0.24.1 // indirect github.com/go-openapi/swag/cmdutils v0.24.0 // indirect - github.com/go-openapi/swag/conv v0.25.5 // indirect - github.com/go-openapi/swag/fileutils v0.25.5 // indirect + github.com/go-openapi/swag/conv v0.26.0 // indirect + github.com/go-openapi/swag/fileutils v0.26.0 // indirect github.com/go-openapi/swag/jsonname v0.25.5 // indirect - github.com/go-openapi/swag/jsonutils v0.25.5 // indirect + github.com/go-openapi/swag/jsonutils v0.26.0 // indirect github.com/go-openapi/swag/loading v0.25.5 // indirect github.com/go-openapi/swag/mangling v0.25.5 // indirect github.com/go-openapi/swag/netutils v0.24.0 // indirect - github.com/go-openapi/swag/stringutils v0.25.5 // indirect - github.com/go-openapi/swag/typeutils v0.25.5 // indirect + github.com/go-openapi/swag/stringutils v0.26.0 // indirect + github.com/go-openapi/swag/typeutils v0.26.0 // indirect github.com/go-openapi/swag/yamlutils v0.25.5 // indirect github.com/go-openapi/validate v0.25.2 // indirect github.com/go-resty/resty/v2 v2.13.1 // indirect @@ -240,7 +248,7 @@ require ( github.com/golang-jwt/jwt/v5 v5.3.0 // indirect github.com/google/btree v1.1.3 // indirect github.com/google/gnostic-models v0.7.0 // indirect - github.com/google/go-jsonnet v0.21.0 // indirect + github.com/google/go-jsonnet v0.22.0 // indirect github.com/google/s2a-go v0.1.9 // indirect github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510 // indirect github.com/googleapis/enterprise-certificate-proxy v0.3.14 // indirect @@ -250,14 +258,14 @@ require ( github.com/hashicorp/go-safetemp v1.0.0 // indirect github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 // indirect github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 // indirect - github.com/hashicorp/go-version v1.7.0 // indirect + github.com/hashicorp/go-version v1.8.0 // indirect github.com/hashicorp/golang-lru/v2 v2.0.7 // indirect - github.com/hashicorp/hcp-sdk-go v0.171.0 // indirect + github.com/hashicorp/hcp-sdk-go v0.172.0 // indirect github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f // indirect github.com/huaweicloud/huaweicloud-sdk-go-v3 v0.1.187 // indirect github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca // indirect - github.com/infisical/go-sdk v0.7.0 // indirect - github.com/itchyny/timefmt-go v0.1.6 // indirect + github.com/infisical/go-sdk v0.7.1 // indirect + github.com/itchyny/timefmt-go v0.1.8 // indirect github.com/jmoiron/sqlx v1.4.0 // indirect github.com/json-iterator/go v1.1.13-0.20220915233716-71ac16282d12 // indirect github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51 // indirect @@ -286,7 +294,6 @@ require ( github.com/peterbourgon/diskv v2.0.1+incompatible // indirect github.com/pkg/browser v0.0.0-20240102092130-5ac0b6a4141c // indirect github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10 // indirect - github.com/rivo/uniseg v0.4.7 // indirect github.com/rs/zerolog v1.26.1 // indirect github.com/rubenv/sql-migrate v1.8.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect @@ -310,7 +317,7 @@ require ( github.com/xeipuuv/gojsonpointer v0.0.0-20190905194746-02993c407bfb // indirect github.com/xlab/treeprint v1.2.0 // indirect github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e // indirect - github.com/yandex-cloud/go-genproto v0.69.0 // indirect + github.com/yandex-cloud/go-genproto v0.75.0 // indirect github.com/yandex-cloud/go-sdk v0.31.0 // indirect github.com/youmark/pkcs8 v0.0.0-20240726163527-a2c0da244d78 // indirect github.com/zalando/go-keyring v0.2.6 // indirect @@ -326,17 +333,17 @@ require ( go.opentelemetry.io/otel/trace v1.43.0 // indirect go.opentelemetry.io/proto/otlp v1.9.0 // indirect go.uber.org/multierr v1.11.0 // indirect - golang.org/x/crypto v0.49.0 // indirect + golang.org/x/crypto v0.50.0 // indirect golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect - golang.org/x/mod v0.33.0 // indirect - golang.org/x/tools v0.42.0 // indirect + golang.org/x/mod v0.34.0 // indirect + golang.org/x/tools v0.43.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/gookit/color.v1 v1.1.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect - k8s.io/api v0.35.4 // indirect + k8s.io/api v0.36.0 // indirect k8s.io/apiextensions-apiserver v0.35.1 // indirect k8s.io/apiserver v0.35.1 // indirect k8s.io/cli-runtime v0.35.1 // indirect diff --git a/go.sum b/go.sum index 10477c13..699e938c 100644 --- a/go.sum +++ b/go.sum @@ -15,18 +15,18 @@ cloud.google.com/go/compute/metadata v0.9.0 h1:pDUj4QMoPejqq20dK0Pg2N4yG9zIkYGdB cloud.google.com/go/compute/metadata v0.9.0/go.mod h1:E0bWwX5wTnLPedCKqk3pJmVgCBSM6qQI1yTBdEb3C10= cloud.google.com/go/iam v1.7.0 h1:JD3zh0C6LHl16aCn5Akff0+GELdp1+4hmh6ndoFLl8U= cloud.google.com/go/iam v1.7.0/go.mod h1:tetWZW1PD/m6vcuY2Zj/aU0eCHNPuxedbnbRTyKXvdY= -cloud.google.com/go/kms v1.27.0 h1:iYYgoD0HJIqz35A+He1G0dS5qTQzQsDXFsyXwzkUCXM= -cloud.google.com/go/kms v1.27.0/go.mod h1:KPxrdf61iYEOZ86uPwR86muBpSik2y4Ion6e83fVl1Q= +cloud.google.com/go/kms v1.29.0 h1:bAW1C5FQf+6GhPkywQzPlsULALCG7c16qpXLFGV9ivY= +cloud.google.com/go/kms v1.29.0/go.mod h1:YIyXZym11R5uovJJt4oN5eUL3oPmirF3yKeIh6QAf4U= cloud.google.com/go/logging v1.13.2 h1:qqlHCBvieJT9Cdq4QqYx1KPadCQ2noD4FK02eNqHAjA= cloud.google.com/go/logging v1.13.2/go.mod h1:zaybliM3yun1J8mU2dVQ1/qDzjbOqEijZCn6hSBtKak= -cloud.google.com/go/longrunning v0.8.0 h1:LiKK77J3bx5gDLi4SMViHixjD2ohlkwBi+mKA7EhfW8= -cloud.google.com/go/longrunning v0.8.0/go.mod h1:UmErU2Onzi+fKDg2gR7dusz11Pe26aknR4kHmJJqIfk= +cloud.google.com/go/longrunning v0.9.0 h1:0EzbDEGsAvOZNbqXopgniY0w0a1phvu5IdUFq8grmqY= +cloud.google.com/go/longrunning v0.9.0/go.mod h1:pkTz846W7bF4o2SzdWJ40Hu0Re+UoNT6Q5t+igIcb8E= cloud.google.com/go/monitoring v1.24.3 h1:dde+gMNc0UhPZD1Azu6at2e79bfdztVDS5lvhOdsgaE= cloud.google.com/go/monitoring v1.24.3/go.mod h1:nYP6W0tm3N9H/bOw8am7t62YTzZY+zUeQ+Bi6+2eonI= -cloud.google.com/go/secretmanager v1.18.0 h1:VA/ynUUapUF3+xrm0R1dMx8i21p2jfRAWFpokYPncKU= -cloud.google.com/go/secretmanager v1.18.0/go.mod h1:9OmSuOeiiUicANglrbdKWSnT3gYkRcXuUQDk7dDW0zU= -cloud.google.com/go/storage v1.62.0 h1:w2pQJhpUqVerMON45vatE2FpCYsNTf7OHjkn6ux5mMU= -cloud.google.com/go/storage v1.62.0/go.mod h1:T5hz3qzcpnxZ5LdKc7y8Tw7lh4v9zeeVyrD/cLJAzZU= +cloud.google.com/go/secretmanager v1.19.0 h1:dm9BK06xl+hrxp2unT2psjZeypPj5c6uPiABb6fmicE= +cloud.google.com/go/secretmanager v1.19.0/go.mod h1:9OmSuOeiiUicANglrbdKWSnT3gYkRcXuUQDk7dDW0zU= +cloud.google.com/go/storage v1.62.1 h1:Os0G3XbUbjZumkpDUf2Y0rLoXJTCF1kU2kWUujKYXD8= +cloud.google.com/go/storage v1.62.1/go.mod h1:cpYz/kRVZ+UQAF1uHeea10/9ewcRbxGoGNKsS9daSXA= cloud.google.com/go/trace v1.11.7 h1:kDNDX8JkaAG3R2nq1lIdkb7FCSi1rCmsEtKVsty7p+U= cloud.google.com/go/trace v1.11.7/go.mod h1:TNn9d5V3fQVf6s4SCveVMIBS2LJUqo73GACmq/Tky0s= dario.cat/mergo v1.0.2 h1:85+piFYR1tMbRrLcDwR18y4UKJ3aH1Tbzi24VRW1TK8= @@ -46,14 +46,14 @@ github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24 h1:bvDV9 github.com/AdaLogics/go-fuzz-headers v0.0.0-20230811130428-ced1acdcaa24/go.mod h1:8o94RPi1/7XTJvwPpRSzSUedZrtlirdB3r9Z20bi2f8= github.com/AlecAivazis/survey/v2 v2.3.6 h1:NvTuVHISgTHEHeBFqt6BHOe4Ny/NwGZr7w+F8S9ziyw= github.com/AlecAivazis/survey/v2 v2.3.6/go.mod h1:4AuI9b7RjAR+G7v9+C4YSlX/YL3K3cWNXgWXOhllqvI= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0 h1:fou+2+WFTib47nS+nz/ozhEBnvU96bKHy6LjRsY4E28= -github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.0/go.mod h1:t76Ruy8AHvUAC8GfMWJMa0ElSbuIcO03NLpynfbgsPA= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1 h1:jHb/wfvRikGdxMXYV3QG/SzUOPYN9KEUUuC0Yd0/vC0= +github.com/Azure/azure-sdk-for-go/sdk/azcore v1.21.1/go.mod h1:pzBXCYn05zvYIrwLgtK8Ap8QcjRg+0i76tMQdWN6wOk= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1 h1:Hk5QBxZQC1jb2Fwj6mpzme37xbCDdNTxU7O9eb5+LB4= github.com/Azure/azure-sdk-for-go/sdk/azidentity v1.13.1/go.mod h1:IYus9qsFobWIc2YVwe/WPjcnyCkPKtnHAqUYeebc8z0= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2 h1:yz1bePFlP5Vws5+8ez6T3HWXPmwOK7Yvq8QxDBD3SKY= github.com/Azure/azure-sdk-for-go/sdk/azidentity/cache v0.3.2/go.mod h1:Pa9ZNPuoNu/GztvBSKk9J1cDJW6vk/n0zLtV4mgd8N8= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2 h1:9iefClla7iYpfYWdzPCRDozdmndjTm8DXdpCzPajMgA= -github.com/Azure/azure-sdk-for-go/sdk/internal v1.11.2/go.mod h1:XtLgD3ZD34DAaVIIAyG3objl5DynM3CQ/vMcbBNJZGI= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0 h1:fhqpLE3UEXi9lPaBRpQ6XuRW0nU7hgg4zlmZZa+a9q4= +github.com/Azure/azure-sdk-for-go/sdk/internal v1.12.0/go.mod h1:7dCRMLwisfRH3dBupKeNCioWYUZ4SS09Z14H+7i8ZoY= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0 h1:2qsIIvxVT+uE6yrNldntJKlLRgxGbZ85kgtz5SNBhMw= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/internal/v3 v3.1.0/go.mod h1:AW8VEadnhw9xox+VaVd9sP7NjzOAnaZBLRH6Tq3cJ38= github.com/Azure/azure-sdk-for-go/sdk/resourcemanager/resources/armresources v1.2.0 h1:Dd+RhdJn0OTtVGaeDLZpcumkIVCtA/3/Fo42+eoYvVM= @@ -72,11 +72,11 @@ github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c h1:udKWzYgxTojEK github.com/Azure/go-ansiterm v0.0.0-20250102033503-faa5f7b0171c/go.mod h1:xomTg63KZ2rFqZQzSB4Vz2SUXa1BpHTVz9L5PTmPC4E= github.com/Azure/go-autorest v14.2.0+incompatible h1:V5VMDjClD3GiElqLWO7mz2MxNAK/vTfRHdAubSIPRgs= github.com/Azure/go-autorest v14.2.0+incompatible/go.mod h1:r+4oMnoxhatjLLJ6zxSWATqVooLgysK6ZNox3g/xq24= -github.com/Azure/go-autorest/autorest/adal v0.9.18/go.mod h1:XVVeme+LZwABT8K5Lc3hA4nAe8LDBVle26gTrguhhPQ= +github.com/Azure/go-autorest/autorest/adal v0.9.22/go.mod h1:XuAbAEUv2Tta//+voMI038TrJBqjKam0me7qR+L8Cmk= github.com/Azure/go-autorest/autorest/adal v0.9.23 h1:Yepx8CvFxwNKpH6ja7RZ+sKX+DWYNldbLiALMC3BTz8= github.com/Azure/go-autorest/autorest/adal v0.9.23/go.mod h1:5pcMqFkdPhviJdlEy3kC/v1ZLnQl0MH6XA5YCcMhy4c= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.6 h1:w77/uPk80ZET2F+AfQExZyEWtn+0Rk/uw17m9fv5Ajc= -github.com/Azure/go-autorest/autorest/azure/cli v0.4.6/go.mod h1:piCfgPho7BiIDdEQ1+g4VmKyD5y+p/XtSNqE6Hc4QD0= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.7 h1:Q9R3utmFg9K1B4OYtAZ7ZUUvIUdzQt7G2MN5Hi/d670= +github.com/Azure/go-autorest/autorest/azure/cli v0.4.7/go.mod h1:bVrAueELJ0CKLBpUHDIvD516TwmHmzqwCpvONWRsw3s= github.com/Azure/go-autorest/autorest/date v0.3.0 h1:7gUk1U5M/CQbp9WoqinNzJar+8KY+LPI6wiWrP/myHw= github.com/Azure/go-autorest/autorest/date v0.3.0/go.mod h1:BI0uouVdmngYNUzGWeSYnokU+TrmwEsOqdt8Y6sso74= github.com/Azure/go-autorest/autorest/mocks v0.4.1/go.mod h1:LTp+uSrOhSkaKrUy935gNZuuIPPVsHlr9DSOxSayd+k= @@ -96,8 +96,8 @@ github.com/BurntSushi/toml v1.6.0 h1:dRaEfpa2VI55EwlIW72hMRHdWouJeRF7TPYhI+AUQjk github.com/BurntSushi/toml v1.6.0/go.mod h1:ukJfTF/6rtPPRCnwkur4qwRxa8vTRFBF0uk2lLoLwho= github.com/DATA-DOG/go-sqlmock v1.5.2 h1:OcvFkGmslmlZibjAjaHm3L//6LiuBgolP7OputlJIzU= github.com/DATA-DOG/go-sqlmock v1.5.2/go.mod h1:88MAG/4G7SMwSE3CeA0ZKzrT5CiOU3OJ+JlNzwDqpNU= -github.com/DelineaXPM/tss-sdk-go/v3 v3.0.1 h1:4JBJukbaTjv2gJogF3MxZkrt7i+ayRhM//FgdJTKJ3Q= -github.com/DelineaXPM/tss-sdk-go/v3 v3.0.1/go.mod h1:VmyoHQ25FhSVHTI3/ptQNOviNEMfCy2ALAf/3E4Eqxg= +github.com/DelineaXPM/tss-sdk-go/v3 v3.0.2 h1:8wRzxlo6fujNoDbnp6PnawY3moxqQelxpJGzTHG7Qoo= +github.com/DelineaXPM/tss-sdk-go/v3 v3.0.2/go.mod h1:VmyoHQ25FhSVHTI3/ptQNOviNEMfCy2ALAf/3E4Eqxg= github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4 h1:s7/zwMi5w+KnlumDVbX1+P6mNAk5o7Wvx0VmvrQ7Bm0= github.com/DopplerHQ/cli v0.5.11-0.20230908185655-7aef4713e1a4/go.mod h1:ipnA9Lpn5YM+FDSQZ7VWNjcuVurchInoGKm+v7O0sGs= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0 h1:DHa2U07rk8syqvCge0QIGMCE1WxGj9njT44GH7zNJLQ= @@ -178,16 +178,16 @@ github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23 h1:pbrxO/ku github.com/aws/aws-sdk-go-v2/service/internal/presigned-url v1.13.23/go.mod h1:/CMNUqoj46HpS3MNRDEDIwcgEnrtZlKRaHNaHxIFpNA= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1lTAbnRg5OK528EUg744nW7F73U8DKw= github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= -github.com/aws/aws-sdk-go-v2/service/kms v1.50.4 h1:PgD1y0ZagPokGIZPmejCBUySBzOFDN+leZxCOfb1OEQ= -github.com/aws/aws-sdk-go-v2/service/kms v1.50.4/go.mod h1:FfXDb5nXrsoGgxsBFxwxr3vdHXheC2tV+6lmuLghhjQ= +github.com/aws/aws-sdk-go-v2/service/kms v1.51.0 h1:696UM+NwOrETBCLQJyCAGtVmmZmziBT59yMwgg6Fvrw= +github.com/aws/aws-sdk-go-v2/service/kms v1.51.0/go.mod h1:GBO/aaEi47QldDVoqw2CsM2UZQDoqDiFIMJD/ztHPs0= github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 h1:mxuT1xE+dI54NW3RkNjP8DUT5HXqbkiAFvfdyDFwE5c= github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5 h1:z2ayoK3pOvf8ODj/vPR0FgAS5ONruBq0F94SRoW/BIU= -github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.5/go.mod h1:mpZB5HAl4ZIISod9qCi12xZ170TbHX9CCJV5y7nb7QU= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.6 h1:XR42AXidhYs4HwH0I+yElLXVt7zb2hAyNHQJe6Blv7w= +github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.6/go.mod h1:nOTsSVQlAsgwVRdtZYtECSnsInF8IUhrpnclCPat7Fs= github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= github.com/aws/aws-sdk-go-v2/service/signin v1.0.11/go.mod h1:R82ZRExE/nheo0N+T8zHPcLRTcH8MGsnR3BiVGX0TwI= -github.com/aws/aws-sdk-go-v2/service/ssm v1.68.4 h1:5Wg8AAAnIWM2LE/0KFGqllZff96bm4dBs+uerYFfReE= -github.com/aws/aws-sdk-go-v2/service/ssm v1.68.4/go.mod h1:nph0ypDLWm9D9iA9zOX39W/N+A4GqwzlxA13jzXVD4k= +github.com/aws/aws-sdk-go-v2/service/ssm v1.68.5 h1:TY5Vh7uXQgJVuc6ahI6toLcRajG1aYSDCP3a0xsPvmo= +github.com/aws/aws-sdk-go-v2/service/ssm v1.68.5/go.mod h1:UkzShnbxHRIIL2cHi/7fBGLUAZIVTEADQjaA53bWWCE= github.com/aws/aws-sdk-go-v2/service/sso v1.30.17 h1:7byT8HUWrgoRp6sXjxtZwgOKfhss5fW6SkLBtqzgRoE= github.com/aws/aws-sdk-go-v2/service/sso v1.30.17/go.mod h1:xNWknVi4Ezm1vg1QsB/5EWpAJURq22uqd38U8qKvOJc= github.com/aws/aws-sdk-go-v2/service/ssooidc v1.35.21 h1:+1Kl1zx6bWi4X7cKi3VYh29h8BvsCoHQEQ6ST9X8w7w= @@ -218,6 +218,10 @@ github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XL github.com/chai2010/gettext-go v1.0.2 h1:1Lwwip6Q2QGsAdl/ZKPCwTe9fe0CjlUbqj5bFNSjIRk= github.com/chai2010/gettext-go v1.0.2/go.mod h1:y+wnP2cHYaVj19NZhYKAwEMH2CI1gNHeQQ+5AjwawxA= github.com/client9/misspell v0.3.4/go.mod h1:qj6jICC3Q7zFZvVWo7KLAzC3yx5G7kyvSDkc90ppPyw= +github.com/clipperhouse/stringish v0.1.1 h1:+NSqMOr3GR6k1FdRhhnXrLfztGzuG+VuFDfatpWHKCs= +github.com/clipperhouse/stringish v0.1.1/go.mod h1:v/WhFtE1q0ovMta2+m+UbpZ+2/HEXNWYXQgCt4hdOzA= +github.com/clipperhouse/uax29/v2 v2.3.0 h1:SNdx9DVUqMoBuBoW3iLOj4FQv3dN5mDtuqwuhIGpJy4= +github.com/clipperhouse/uax29/v2 v2.3.0/go.mod h1:Wn1g7MK6OoeDT0vL+Q0SQLDz/KpfsVRgg6W7ihQeh4g= github.com/cloudflare/circl v1.6.3 h1:9GPOhQGF9MCYUeXyMYlqTR6a5gTrgR/fBLXvUgtVcg8= github.com/cloudflare/circl v1.6.3/go.mod h1:2eXP6Qfat4O/Yhh8BznvKnJ+uzEoTQ6jVKJRn81BiS4= github.com/cncf/udpa/go v0.0.0-20191209042840-269d4d468f6f/go.mod h1:M8M6+tZqaGXZJjfX53e64911xZQV5JYwmTeXPW+k8Sc= @@ -280,8 +284,8 @@ github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkp github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a h1:UwSIFv5g5lIvbGgtf3tVwC7Ky9rmMFBp0RMs+6f6YqE= github.com/dylibso/observe-sdk/go v0.0.0-20240819160327-2d926c5d788a/go.mod h1:C8DzXehI4zAbrdlbtOByKX6pfivJTBiV9Jjqv56Yd9Q= -github.com/emicklei/go-restful/v3 v3.12.2 h1:DhwDP0vY3k8ZzE0RunuJy8GhNpPL6zqLkDf9B/a0/xU= -github.com/emicklei/go-restful/v3 v3.12.2/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= +github.com/emicklei/go-restful/v3 v3.13.0 h1:C4Bl2xDndpU6nJ4bc1jXd+uTmYPVUwkD6bFY/oTyCes= +github.com/emicklei/go-restful/v3 v3.13.0/go.mod h1:6n3XBCmQQb25CM2LCACGz8ukIrRry+4bhvbpWn3mrbc= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/go-control-plane v0.14.0 h1:hbG2kr4RuFj222B6+7T83thSPqLjwBIfQawTkC++2HA= @@ -314,8 +318,8 @@ github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZ github.com/foxcpp/go-mockdns v1.2.0/go.mod h1:IhLeSFGed3mJIAXPH2aiRQB+kqz7oqu8ld2qVbOu7Wk= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/fujiwara/tfstate-lookup v1.10.0 h1:RtVV1PUO+C7hqmRKhecS/ed6XOZ4KDiJmbHPFvX7ZJE= -github.com/fujiwara/tfstate-lookup v1.10.0/go.mod h1:hBwXB7lKy4kPEM+szko7rzAq04TGpBzzNY8Wm8hG6UE= +github.com/fujiwara/tfstate-lookup v1.11.0 h1:Td8nSGyicw90vHhQhMEDs/ouDYPswQyS2sHgxgH44Tc= +github.com/fujiwara/tfstate-lookup v1.11.0/go.mod h1:USksi9piDklLjGmdisD2g/hScJLUHfJMfLygyQk5FnA= github.com/fxamacker/cbor/v2 v2.9.0 h1:NpKPmjDBgUfBms6tr6JZkTHtfFGcMKsw3eGcmD/sapM= github.com/fxamacker/cbor/v2 v2.9.0/go.mod h1:vM4b+DJCtHn+zz7h3FFp/hDAI9WNWCsZj23V5ytsSxQ= github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e h1:y/1nzrdF+RPds4lfoEpNhjfmzlgZtPqyO3jMzrqDQws= @@ -337,8 +341,8 @@ github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-logr/zapr v1.3.0 h1:XGdV8XW8zdwFiwOA2Dryh1gj2KRQyOOoNmBy4EplIcQ= github.com/go-logr/zapr v1.3.0/go.mod h1:YKepepNBd1u/oyhd/yQmtjVXmm9uML4IXUgMOwR8/Gg= -github.com/go-openapi/analysis v0.24.3 h1:a1hrvMr8X0Xt69KP5uVTu5jH62DscmDifrLzNglAayk= -github.com/go-openapi/analysis v0.24.3/go.mod h1:Nc+dWJ/FxZbhSow5Yh3ozg5CLJioB+XXT6MdLvJUsUw= +github.com/go-openapi/analysis v0.25.0 h1:EnjAq1yO8wEO9HbPmY8vLPEIkdZuuFhCAKBPvCB7bCs= +github.com/go-openapi/analysis v0.25.0/go.mod h1:5WFTRE43WLkPG9r9OtlMfqkkvUTYLVVCIxLlEpyF8kE= github.com/go-openapi/errors v0.22.7 h1:JLFBGC0Apwdzw3484MmBqspjPbwa2SHvpDm0u5aGhUA= github.com/go-openapi/errors v0.22.7/go.mod h1://QW6SD9OsWtH6gHllUCddOXDL0tk0ZGNYHwsw4sW3w= github.com/go-openapi/jsonpointer v0.22.5 h1:8on/0Yp4uTb9f4XvTrM2+1CPrV05QPZXu+rvu2o9jcA= @@ -347,42 +351,42 @@ github.com/go-openapi/jsonreference v0.21.5 h1:6uCGVXU/aNF13AQNggxfysJ+5ZcU4nEAe github.com/go-openapi/jsonreference v0.21.5/go.mod h1:u25Bw85sX4E2jzFodh1FOKMTZLcfifd1Q+iKKOUxExw= github.com/go-openapi/loads v0.23.3 h1:g5Xap1JfwKkUnZdn+S0L3SzBDpcTIYzZ5Qaag0YDkKQ= github.com/go-openapi/loads v0.23.3/go.mod h1:NOH07zLajXo8y55hom0omlHWDVVvCwBM/S+csCK8LqA= -github.com/go-openapi/runtime v0.29.3 h1:h5twGaEqxtQg40ePiYm9vFFH1q06Czd7Ot6ufdK0w/Y= -github.com/go-openapi/runtime v0.29.3/go.mod h1:8A1W0/L5eyNJvKciqZtvIVQvYO66NlB7INMSZ9bw/oI= +github.com/go-openapi/runtime v0.29.4 h1:k2lDxrGoSAJRdhFG2tONKMpkizY/4X1cciSdtzk4Jjo= +github.com/go-openapi/runtime v0.29.4/go.mod h1:K0k/2raY6oqXJnZAgWJB2i/12QKrhUKpZcH4PfV9P18= github.com/go-openapi/spec v0.22.4 h1:4pxGjipMKu0FzFiu/DPwN3CTBRlVM2yLf/YTWorYfDQ= github.com/go-openapi/spec v0.22.4/go.mod h1:WQ6Ai0VPWMZgMT4XySjlRIE6GP1bGQOtEThn3gcWLtQ= -github.com/go-openapi/strfmt v0.26.0 h1:SDdQLyOEqu8W96rO1FRG1fuCtVyzmukky0zcD6gMGLU= -github.com/go-openapi/strfmt v0.26.0/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y= +github.com/go-openapi/strfmt v0.26.1 h1:7zGCHji7zSYDC2tCXIusoxYQz/48jAf2q+sF6wXTG+c= +github.com/go-openapi/strfmt v0.26.1/go.mod h1:Zslk5VZPOISLwmWTMBIS7oiVFem1o1EI6zULY8Uer7Y= github.com/go-openapi/swag v0.24.1 h1:DPdYTZKo6AQCRqzwr/kGkxJzHhpKxZ9i/oX0zag+MF8= github.com/go-openapi/swag v0.24.1/go.mod h1:sm8I3lCPlspsBBwUm1t5oZeWZS0s7m/A+Psg0ooRU0A= github.com/go-openapi/swag/cmdutils v0.24.0 h1:KlRCffHwXFI6E5MV9n8o8zBRElpY4uK4yWyAMWETo9I= github.com/go-openapi/swag/cmdutils v0.24.0/go.mod h1:uxib2FAeQMByyHomTlsP8h1TtPd54Msu2ZDU/H5Vuf8= -github.com/go-openapi/swag/conv v0.25.5 h1:wAXBYEXJjoKwE5+vc9YHhpQOFj2JYBMF2DUi+tGu97g= -github.com/go-openapi/swag/conv v0.25.5/go.mod h1:CuJ1eWvh1c4ORKx7unQnFGyvBbNlRKbnRyAvDvzWA4k= -github.com/go-openapi/swag/fileutils v0.25.5 h1:B6JTdOcs2c0dBIs9HnkyTW+5gC+8NIhVBUwERkFhMWk= -github.com/go-openapi/swag/fileutils v0.25.5/go.mod h1:V3cT9UdMQIaH4WiTrUc9EPtVA4txS0TOmRURmhGF4kc= +github.com/go-openapi/swag/conv v0.26.0 h1:5yGGsPYI1ZCva93U0AoKi/iZrNhaJEjr324YVsiD89I= +github.com/go-openapi/swag/conv v0.26.0/go.mod h1:tpAmIL7X58VPnHHiSO4uE3jBeRamGsFsfdDeDtb5ECE= +github.com/go-openapi/swag/fileutils v0.26.0 h1:WJoPRvsA7QRiiWluowkLJa9jaYR7FCuxmDvnCgaRRxU= +github.com/go-openapi/swag/fileutils v0.26.0/go.mod h1:0WDJ7lp67eNjPMO50wAWYlKvhOb6CQ37rzR7wrgI8Tc= github.com/go-openapi/swag/jsonname v0.25.5 h1:8p150i44rv/Drip4vWI3kGi9+4W9TdI3US3uUYSFhSo= github.com/go-openapi/swag/jsonname v0.25.5/go.mod h1:jNqqikyiAK56uS7n8sLkdaNY/uq6+D2m2LANat09pKU= -github.com/go-openapi/swag/jsonutils v0.25.5 h1:XUZF8awQr75MXeC+/iaw5usY/iM7nXPDwdG3Jbl9vYo= -github.com/go-openapi/swag/jsonutils v0.25.5/go.mod h1:48FXUaz8YsDAA9s5AnaUvAmry1UcLcNVWUjY42XkrN4= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5 h1:SX6sE4FrGb4sEnnxbFL/25yZBb5Hcg1inLeErd86Y1U= -github.com/go-openapi/swag/jsonutils/fixtures_test v0.25.5/go.mod h1:/2KvOTrKWjVA5Xli3DZWdMCZDzz3uV/T7bXwrKWPquo= +github.com/go-openapi/swag/jsonutils v0.26.0 h1:FawFML2iAXsPqmERscuMPIHmFsoP1tOqWkxBaKNMsnA= +github.com/go-openapi/swag/jsonutils v0.26.0/go.mod h1:2VmA0CJlyFqgawOaPI9psnjFDqzyivIqLYN34t9p91E= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0 h1:apqeINu/ICHouqiRZbyFvuDge5jCmmLTqGQ9V95EaOM= +github.com/go-openapi/swag/jsonutils/fixtures_test v0.26.0/go.mod h1:AyM6QT8uz5IdKxk5akv0y6u4QvcL9GWERt0Jx/F/R8Y= github.com/go-openapi/swag/loading v0.25.5 h1:odQ/umlIZ1ZVRteI6ckSrvP6e2w9UTF5qgNdemJHjuU= github.com/go-openapi/swag/loading v0.25.5/go.mod h1:I8A8RaaQ4DApxhPSWLNYWh9NvmX2YKMoB9nwvv6oW6g= github.com/go-openapi/swag/mangling v0.25.5 h1:hyrnvbQRS7vKePQPHHDso+k6CGn5ZBs5232UqWZmJZw= github.com/go-openapi/swag/mangling v0.25.5/go.mod h1:6hadXM/o312N/h98RwByLg088U61TPGiltQn71Iw0NY= github.com/go-openapi/swag/netutils v0.24.0 h1:Bz02HRjYv8046Ycg/w80q3g9QCWeIqTvlyOjQPDjD8w= github.com/go-openapi/swag/netutils v0.24.0/go.mod h1:WRgiHcYTnx+IqfMCtu0hy9oOaPR0HnPbmArSRN1SkZM= -github.com/go-openapi/swag/stringutils v0.25.5 h1:NVkoDOA8YBgtAR/zvCx5rhJKtZF3IzXcDdwOsYzrB6M= -github.com/go-openapi/swag/stringutils v0.25.5/go.mod h1:PKK8EZdu4QJq8iezt17HM8RXnLAzY7gW0O1KKarrZII= -github.com/go-openapi/swag/typeutils v0.25.5 h1:EFJ+PCga2HfHGdo8s8VJXEVbeXRCYwzzr9u4rJk7L7E= -github.com/go-openapi/swag/typeutils v0.25.5/go.mod h1:itmFmScAYE1bSD8C4rS0W+0InZUBrB2xSPbWt6DLGuc= +github.com/go-openapi/swag/stringutils v0.26.0 h1:qZQngLxs5s7SLijc3N2ZO+fUq2o8LjuWAASSrJuh+xg= +github.com/go-openapi/swag/stringutils v0.26.0/go.mod h1:sWn5uY+QIIspwPhvgnqJsH8xqFT2ZbYcvbcFanRyhFE= +github.com/go-openapi/swag/typeutils v0.26.0 h1:2kdEwdiNWy+JJdOvu5MA2IIg2SylWAFuuyQIKYybfq4= +github.com/go-openapi/swag/typeutils v0.26.0/go.mod h1:oovDuIUvTrEHVMqWilQzKzV4YlSKgyZmFh7AlfABNVE= github.com/go-openapi/swag/yamlutils v0.25.5 h1:kASCIS+oIeoc55j28T4o8KwlV2S4ZLPT6G0iq2SSbVQ= github.com/go-openapi/swag/yamlutils v0.25.5/go.mod h1:Gek1/SjjfbYvM+Iq4QGwa/2lEXde9n2j4a3wI3pNuOQ= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.1 h1:NZOrZmIb6PTv5LTFxr5/mKV/FjbUzGE7E6gLz7vFoOQ= -github.com/go-openapi/testify/enable/yaml/v2 v2.4.1/go.mod h1:r7dwsujEHawapMsxA69i+XMGZrQ5tRauhLAjV/sxg3Q= -github.com/go-openapi/testify/v2 v2.4.1 h1:zB34HDKj4tHwyUQHrUkpV0Q0iXQ6dUCOQtIqn8hE6Iw= -github.com/go-openapi/testify/v2 v2.4.1/go.mod h1:HCPmvFFnheKK2BuwSA0TbbdxJ3I16pjwMkYkP4Ywn54= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2 h1:5zRca5jw7lzVREKCZVNBpysDNBjj74rBh0N2BGQbSR0= +github.com/go-openapi/testify/enable/yaml/v2 v2.4.2/go.mod h1:XVevPw5hUXuV+5AkI1u1PeAm27EQVrhXTTCPAF85LmE= +github.com/go-openapi/testify/v2 v2.4.2 h1:tiByHpvE9uHrrKjOszax7ZvKB7QOgizBWGBLuq0ePx4= +github.com/go-openapi/testify/v2 v2.4.2/go.mod h1:SgsVHtfooshd0tublTtJ50FPKhujf47YRqauXXOUxfw= github.com/go-openapi/validate v0.25.2 h1:12NsfLAwGegqbGWr2CnvT65X/Q2USJipmJ9b7xDJZz0= github.com/go-openapi/validate v0.25.2/go.mod h1:Pgl1LpPPGFnZ+ys4/hTlDiRYQdI1ocKypgE+8Q8BLfY= github.com/go-playground/assert/v2 v2.0.1/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= @@ -411,7 +415,6 @@ github.com/gofrs/flock v0.8.1/go.mod h1:F1TvTiK9OcQqauNUHlbJvyl9Qa1QvF/gOUDKA14j github.com/gofrs/flock v0.13.0 h1:95JolYOvGMqeH31+FC7D2+uULf6mG61mEZ/A8dRYMzw= github.com/gofrs/flock v0.13.0/go.mod h1:jxeyy9R1auM5S6JYDBhDt+E2TCo7DkratH4Pgi8P+Z0= github.com/golang-jwt/jwt/v4 v4.0.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= -github.com/golang-jwt/jwt/v4 v4.2.0/go.mod h1:/xlHOz8bRuivTWchD4jCa+NbatV+wEUSzwAxVc6locg= github.com/golang-jwt/jwt/v4 v4.5.0/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= github.com/golang-jwt/jwt/v4 v4.5.2 h1:YtQM7lnr8iZ+j5q71MGKkNw9Mn7AjHM68uc9g5fXeUI= github.com/golang-jwt/jwt/v4 v4.5.2/go.mod h1:m21LjoU+eqJr34lmDMbreY2eSTRJ1cv77w39/MY0Ch0= @@ -444,12 +447,13 @@ github.com/google/go-cmp v0.3.0/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMyw github.com/google/go-cmp v0.3.1/go.mod h1:8QqcDgzrUqlUb/G2PQTWiueGozuR1884gddMywk6iLU= github.com/google/go-cmp v0.4.0/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= github.com/google/go-cmp v0.5.2/go.mod h1:v8dTdLbMG2kIc/vJvl+f65V22dbkXbowE6jgT/gNBxE= +github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= -github.com/google/go-jsonnet v0.21.0 h1:43Bk3K4zMRP/aAZm9Po2uSEjY6ALCkYUVIcz9HLGMvA= -github.com/google/go-jsonnet v0.21.0/go.mod h1:tCGAu8cpUpEZcdGMmdOu37nh8bGgqubhI5v2iSk3KJQ= -github.com/google/go-querystring v1.1.0 h1:AnCroh3fv4ZBgVIf1Iwtovgjaw/GiKJo8M8yD/fhyJ8= -github.com/google/go-querystring v1.1.0/go.mod h1:Kcdr2DB4koayq7X8pmAG4sNG59So17icRSOU623lUBU= +github.com/google/go-jsonnet v0.22.0 h1:o0bOAIE+9SIfRZ7FXQPuta0mHLLE0AwbY/L5GTH5CH8= +github.com/google/go-jsonnet v0.22.0/go.mod h1:pLhKpu0/ODjL2Zev4y+CmCoHKAgONT1gSLQyriuYh9w= +github.com/google/go-querystring v1.2.0 h1:yhqkPbu2/OH+V9BfpCVPZkNmUXhb2gBxJArfhIxNtP0= +github.com/google/go-querystring v1.2.0/go.mod h1:8IFJqpSRITyJ8QhQ13bmbeMBDfmeEJZD5A0egEOmkqU= github.com/google/gofuzz v1.0.0/go.mod h1:dBl0BpW6vV/+mYPU4Po3pmUjxk6FQPldtuIdl/M65Eg= github.com/google/martian/v3 v3.3.3 h1:DIhPTQrbPkgs2yJYdXU/eNACCG5DVQjySNRNlflZ9Fc= github.com/google/martian/v3 v3.3.3/go.mod h1:iEPrYcgCF7jA9OtScMFQyAlZZ4YXTKEtJ1E6RWzmBA0= @@ -502,16 +506,16 @@ github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0 h1:U+kC2dOhMFQctRfhK0gRct github.com/hashicorp/go-secure-stdlib/parseutil v0.2.0/go.mod h1:Ll013mhdmsVDuoIXVfBtvgGJsXDYkTw1kooNcoCXuE0= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2 h1:kes8mmyCpxJsI7FTwtzRqEy9CdjCtrXrXGuOpxEA7Ts= github.com/hashicorp/go-secure-stdlib/strutil v0.1.2/go.mod h1:Gou2R9+il93BqX25LAKCLuM+y9U2T4hlwvT1yprcna4= -github.com/hashicorp/go-slug v0.16.4 h1:kI0mOUVjbBsyocwO29pZIQzzkBnfQNdU4eqlUpNdNVA= -github.com/hashicorp/go-slug v0.16.4/go.mod h1:THWVTAXwJEinbsp4/bBRcmbaO5EYNLTqxbG4tZ3gCYQ= +github.com/hashicorp/go-slug v0.16.8 h1:f4/sDZqRsxx006HrE6e9BE5xO9lWXydKhVoH6Kb0v1M= +github.com/hashicorp/go-slug v0.16.8/go.mod h1:hB4mUcVHl4RPu0205s0fwmB9i31MxQgeafGkko3FD+Y= github.com/hashicorp/go-sockaddr v1.0.7 h1:G+pTkSO01HpR5qCxg7lxfsFEZaG+C0VssTy/9dbT+Fw= github.com/hashicorp/go-sockaddr v1.0.7/go.mod h1:FZQbEYa1pxkQ7WLpyXJ6cbjpT8q0YgQaK/JakXqGyWw= -github.com/hashicorp/go-tfe v1.84.0 h1:aq4zLtr0beMjoe1bjMPkv9tW4wWTix37SBX8ct8vJWw= -github.com/hashicorp/go-tfe v1.84.0/go.mod h1:6dUFMBKh0jkxlRsrw7bYD2mby0efdwE4dtlAuTogIzA= +github.com/hashicorp/go-tfe v1.99.0 h1:JJToLgw5swACi3Z6Hap3zFS8UfA40R/PdUVQU3xh7Hk= +github.com/hashicorp/go-tfe v1.99.0/go.mod h1:JIqznMwZd8flUhPif5d2sprKcFkD4sWJSIQ6E8iAuIA= github.com/hashicorp/go-uuid v1.0.3 h1:2gKiV6YVmrJ1i2CKKa9obLvRieoRGviZFL26PcT/Co8= github.com/hashicorp/go-uuid v1.0.3/go.mod h1:6SBZvOh/SIDV7/2o3Jml5SYk/TvGqwFJ/bN7x4byOro= -github.com/hashicorp/go-version v1.7.0 h1:5tqGy27NaOTB8yJKUZELlFAS/LTKJkrmONwQKeRZfjY= -github.com/hashicorp/go-version v1.7.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= +github.com/hashicorp/go-version v1.8.0 h1:KAkNb1HAiZd1ukkxDFGmokVZe1Xy9HG6NUp+bPle2i4= +github.com/hashicorp/go-version v1.8.0/go.mod h1:fltr4n8CU8Ke44wwGCBoEymUuxUHl09ZGVZPK5anwXA= github.com/hashicorp/golang-lru v1.0.2 h1:dV3g9Z/unq5DpblPpw+Oqcv4dU/1omnb4Ok8iPY6p1c= github.com/hashicorp/golang-lru v1.0.2/go.mod h1:iADmTwqILo4mZ8BN3D2Q6+9jd8WM5uGBxy+E8yxSoD4= github.com/hashicorp/golang-lru/arc/v2 v2.0.5 h1:l2zaLDubNhW4XO3LnliVj0GXO3+/CGNJAg1dcN2Fpfw= @@ -522,16 +526,16 @@ github.com/hashicorp/hcl v1.0.1-vault-7 h1:ag5OxFVy3QYTFTJODRzTKVZ6xvdfLLCA1cy/Y github.com/hashicorp/hcl v1.0.1-vault-7/go.mod h1:XYhtn6ijBSAj6n4YqAaf7RBPS4I06AItNorpy+MoQNM= github.com/hashicorp/hcl/v2 v2.24.0 h1:2QJdZ454DSsYGoaE6QheQZjtKZSUs9Nh2izTWiwQxvE= github.com/hashicorp/hcl/v2 v2.24.0/go.mod h1:oGoO1FIQYfn/AgyOhlg9qLC6/nOJPX3qGbkZpYAcqfM= -github.com/hashicorp/hcp-sdk-go v0.171.0 h1:4jD9R6shs3vFh/FfJPeX1qev20YUMcpiWN4joiSBdAk= -github.com/hashicorp/hcp-sdk-go v0.171.0/go.mod h1:v2vbpNIrmgUTelW4Z+ur+aQuSPxeaVK3xytFdpEXvSg= +github.com/hashicorp/hcp-sdk-go v0.172.0 h1:j4VrSN2yd8prFb8Y0gQWQbTpsV5uVPgYEUozOGfPOOc= +github.com/hashicorp/hcp-sdk-go v0.172.0/go.mod h1:v2vbpNIrmgUTelW4Z+ur+aQuSPxeaVK3xytFdpEXvSg= github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e h1:xwy/1T0cxHWaLx2MM0g4BlaQc1BXn/9835mPrBqwSPU= github.com/hashicorp/jsonapi v1.4.3-0.20250220162346-81a76b606f3e/go.mod h1:kWfdn49yCjQvbpnvY1dxxAuAFzISwrrMDQOcu6NsFoM= github.com/hashicorp/vault/api v1.23.0 h1:gXgluBsSECfRWTSW9niY2jwg2e9mMJc4WoHNv4g3h6A= github.com/hashicorp/vault/api v1.23.0/go.mod h1:zransKiB9ftp+kgY8ydjnvCU7Wk8i9L0DYWpXeMj9ko= github.com/helmfile/chartify v0.26.3 h1:2wR0yfqtP/yG9y6uqM6nSKZ7W0E+nhhGGRsl14TOVVs= github.com/helmfile/chartify v0.26.3/go.mod h1:/ReUGTnbNHIV5tKAGXODkRtS7HwnUiJi2EXbJ34RzgY= -github.com/helmfile/vals v0.43.9 h1:S1hjtq6OtF5tusREQH7KlImr1W11xmFV3q1+HhWLBnY= -github.com/helmfile/vals v0.43.9/go.mod h1:9QB7tH1yN8HcIPYo5kxzh6vP0VI4rTmiQqIP6NVudMs= +github.com/helmfile/vals v0.44.0 h1:9Yf5JDIl3JUHE1XWR9GopurvAbuXowCSsgUShB4aWcI= +github.com/helmfile/vals v0.44.0/go.mod h1:siAvy7f4VPPCrgLGzDOW21ZbvR6Tbf9g7oGRme9fMH4= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec h1:qv2VnGeEQHchGaZ/u7lxST/RaJw+cv273q79D81Xbog= github.com/hinshun/vt10x v0.0.0-20220119200601-820417d04eec/go.mod h1:Q48J4R4DvxnHolD5P8pOtXigYlRuPLGl6moFx3ulM68= github.com/hokaccha/go-prettyjson v0.0.0-20211117102719-0474bc63780f h1:7LYC+Yfkj3CTRcShK0KOL/w6iTiKyqqBA9a41Wnggw8= @@ -544,12 +548,12 @@ github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca h1:T54Ema1 github.com/ianlancetaylor/demangle v0.0.0-20240805132620-81f5be970eca/go.mod h1:gx7rwoVhcfuVKG5uya9Hs3Sxj7EIvldVofAWIUtGouw= github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8= github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw= -github.com/infisical/go-sdk v0.7.0 h1:x9/1PczL+ioVD1jCp4LHQzPpBawatbmkjAC0S1OAtUA= -github.com/infisical/go-sdk v0.7.0/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= -github.com/itchyny/gojq v0.12.16 h1:yLfgLxhIr/6sJNVmYfQjTIv0jGctu6/DgDoivmxTr7g= -github.com/itchyny/gojq v0.12.16/go.mod h1:6abHbdC2uB9ogMS38XsErnfqJ94UlngIJGlRAIj4jTM= -github.com/itchyny/timefmt-go v0.1.6 h1:ia3s54iciXDdzWzwaVKXZPbiXzxxnv1SPGFfM/myJ5Q= -github.com/itchyny/timefmt-go v0.1.6/go.mod h1:RRDZYC5s9ErkjQvTvvU7keJjxUYzIISJGxm9/mAERQg= +github.com/infisical/go-sdk v0.7.1 h1:26upmNiIuXJgZEQdH8ThLZ18EIGdg9ifMm+fBGXSmP0= +github.com/infisical/go-sdk v0.7.1/go.mod h1:yEfXF+3YDDXiJ9zzJUSzW6me6XXPPEDK52fSU6JfpCA= +github.com/itchyny/gojq v0.12.19 h1:ttXA0XCLEMoaLOz5lSeFOZ6u6Q3QxmG46vfgI4O0DEs= +github.com/itchyny/gojq v0.12.19/go.mod h1:5galtVPDywX8SPSOrqjGxkBeDhSxEW1gSxoy7tn1iZY= +github.com/itchyny/timefmt-go v0.1.8 h1:1YEo1JvfXeAHKdjelbYr/uCuhkybaHCeTkH8Bo791OI= +github.com/itchyny/timefmt-go v0.1.8/go.mod h1:5E46Q+zj7vbTgWY8o5YkMeYb4I6GeWLFnetPy5oBrAI= github.com/jmespath/go-jmespath v0.4.0 h1:BEgLn5cpjn8UN1mAw4NjwDrS35OdebyEtFe+9YPoQUg= github.com/jmespath/go-jmespath v0.4.0/go.mod h1:T8mJZnbsbmF+m6zOOFylbeCJqk5+pHWvzYPziyZiYoo= github.com/jmoiron/sqlx v1.4.0 h1:1PLqN7S1UYp5t4SrVVnt4nUVNemrDAtxlulVe+Qgm3o= @@ -590,8 +594,8 @@ github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Ky github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY= github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y= github.com/mattn/go-runewidth v0.0.10/go.mod h1:RAqKPSqVFrSLVXbA8x7dzmKdmGzieGRCM46jaSJTDAk= -github.com/mattn/go-runewidth v0.0.15 h1:UNAjwbU9l54TA3KzvqLGxwWjHmMgBUVhBiTjelZgg3U= -github.com/mattn/go-runewidth v0.0.15/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w= +github.com/mattn/go-runewidth v0.0.19 h1:v++JhqYnZuu5jSKrk9RbgF5v4CGUjqRfBm05byFGLdw= +github.com/mattn/go-runewidth v0.0.19/go.mod h1:XBkDxAl56ILZc9knddidhrOlY5R/pDhgLpndooCuJAs= github.com/mattn/go-sqlite3 v1.14.22 h1:2gZY6PC6kBnID23Tichd1K+Z0oS6nE/XwU+Vz/5o4kU= github.com/mattn/go-sqlite3 v1.14.22/go.mod h1:Uh1q+B4BYcTPb+yiD3kU8Ct7aC0hY9fxUwlHK0RXw+Y= github.com/mgutz/ansi v0.0.0-20170206155736-9520e82c474b/go.mod h1:01TrycV0kFyexm33Z7vhZRXopbI8J3TDReVlkTgMUxE= @@ -694,9 +698,6 @@ github.com/redis/go-redis/extra/redisotel/v9 v9.0.5/go.mod h1:WZjPDy7VNzn77AAfnA github.com/redis/go-redis/v9 v9.7.3 h1:YpPyAayJV+XErNsatSElgRZZVCwXX9QzkKYNvO7x0wM= github.com/redis/go-redis/v9 v9.7.3/go.mod h1:bGUrSggJ9X9GUmZpZNEOQKaANxSGgOEBRltRTZHSvrA= github.com/rivo/uniseg v0.1.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc= -github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ= -github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= github.com/rogpeppe/go-internal v1.14.1/go.mod h1:MaRKkUm5W0goXpeCfT7UZI6fk/L7L7so1lCWt35ZSgc= github.com/rs/xid v1.3.0/go.mod h1:trrq9SKmegXys3aeAKXMUTdJsYXVwGY3RLcfgqegfbg= @@ -796,8 +797,8 @@ github.com/xlab/treeprint v1.2.0 h1:HzHnuAF1plUN2zGlAFHbSQP2qJ0ZAD3XF5XD7OesXRQ= github.com/xlab/treeprint v1.2.0/go.mod h1:gj5Gd3gPdKtR1ikdDK6fnFLdmIS0X30kTTuNd/WEJu0= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e h1:JVG44RsyaB9T2KIHavMF/ppJZNG9ZpyihvCd0w101no= github.com/xo/terminfo v0.0.0-20220910002029-abceb7e1c41e/go.mod h1:RbqR21r5mrJuqunuUZ/Dhy/avygyECGrLceyNeo4LiM= -github.com/yandex-cloud/go-genproto v0.69.0 h1:gfl5l9KqntQ0oShbDHCD60HB76Dm3IXETUro1mg0Rjs= -github.com/yandex-cloud/go-genproto v0.69.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= +github.com/yandex-cloud/go-genproto v0.75.0 h1:5XhK9CZtYqY6i3u62LETNgF1FwXN/D3EoxivJNbxyw4= +github.com/yandex-cloud/go-genproto v0.75.0/go.mod h1:0LDD/IZLIUIV4iPH+YcF+jysO3jkSvADFGm4dCAuwQo= github.com/yandex-cloud/go-sdk v0.31.0 h1:iPixKMu7t64xziWRIEW3pKkq3kGuvgNmiwH/Vl1FcqY= github.com/yandex-cloud/go-sdk v0.31.0/go.mod h1:C27Pqw9umTq3vi3ZM8tfmc5Rb0rt6Fxnl7nimQT1aM0= github.com/youmark/pkcs8 v0.0.0-20181117223130-1be2e3e5546d/go.mod h1:rHwXgn7JulP+udvsHwJoVG1YGAP6VLg4y9I5dyZdqmA= @@ -849,8 +850,8 @@ go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjC go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0 h1:lSZHgNHfbmQTPfuTmWVkEu8J8qXaQwuV30pjCcAUvP8= -go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.42.0/go.mod h1:so9ounLcuoRDu033MW/E0AD4hhUjVqswrMF5FoZlBcw= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= +go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= @@ -890,11 +891,12 @@ golang.org/x/crypto v0.0.0-20211215165025-cf75a172585e/go.mod h1:P+XmwS30IXTQdn5 golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.0.0-20220722155217-630584e8d5aa/go.mod h1:IxCIyHEi3zRg3s0A5j5BB6A9Jmi73HwBIUl50j+osU4= golang.org/x/crypto v0.6.0/go.mod h1:OFC/31mSvZgRz0V1QTNCzfAI1aIRzbiufJtkMIlEp58= +golang.org/x/crypto v0.17.0/go.mod h1:gCAAfMLgwOJRpTjQ2zCCt2OcSfYMTeZVSRtQlPC7Nq4= golang.org/x/crypto v0.19.0/go.mod h1:Iy9bg/ha4yyC70EfRS8jz+B6ybOBKMaSxLj6P6oBDfU= golang.org/x/crypto v0.22.0/go.mod h1:vr6Su+7cTlO45qkww3VDJlzDn0ctJvRgYbC2NvXHt+M= golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v8= -golang.org/x/crypto v0.49.0 h1:+Ng2ULVvLHnJ/ZFEq4KdcDd/cfjrrjjNSXNzxg0Y4U4= -golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= +golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= +golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= @@ -904,8 +906,8 @@ golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHl golang.org/x/mod v0.4.2/go.mod h1:s0Qsj1ACt9ePp/hMypM3fl4fZqREWJwdYDEqhRiZZUA= golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4/go.mod h1:jJ57K6gSWd91VN4djpZkiMVwK6gcyfeH4XE8wZrZaV4= golang.org/x/mod v0.8.0/go.mod h1:iBbtSCu2XBx23ZKBPSOrRkjjQPZFPuis4dIYUhu/chs= -golang.org/x/mod v0.33.0 h1:tHFzIWbBifEmbwtGz65eaWyGiGZatSrT9prnU8DbVL8= -golang.org/x/mod v0.33.0/go.mod h1:swjeQEj+6r7fODbD2cqrnje9PnziFuw4bmLbBZFrQ5w= +golang.org/x/mod v0.34.0 h1:xIHgNUUnW6sYkcM5Jleh05DvLOtwc6RitGHbDk4akRI= +golang.org/x/mod v0.34.0/go.mod h1:ykgH52iCZe79kzLLMhyCUzhMci+nQj+0XkbXpNYtVjY= golang.org/x/net v0.0.0-20180724234803-3673e40ba225/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20180826012351-8a410e7b638d/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190213061140-3a22650c66bd/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -922,8 +924,8 @@ golang.org/x/net v0.6.0/go.mod h1:2Tu9+aMcznHK/AK1HMvgo6xiTLG5rD5rZLDS+rp2Bjs= golang.org/x/net v0.10.0/go.mod h1:0qNGK6F8kojg2nk9dLZ2mShWaEBan6FAoqfSigmmuDg= golang.org/x/net v0.21.0/go.mod h1:bIjVDfnllIU7BJ2DNgfnXvpSvtn8VRwhlsaeUTyUS44= golang.org/x/net v0.25.0/go.mod h1:JkAGAh7GEvH74S6FOH42FLoXpXbE/aqXSrIQjXgsiwM= -golang.org/x/net v0.52.0 h1:He/TN1l0e4mmR3QqHMT2Xab3Aj3L9qjbhRm78/6jrW0= -golang.org/x/net v0.52.0/go.mod h1:R1MAz7uMZxVMualyPXb+VaqGSa3LIaUqk0eEt3w36Sw= +golang.org/x/net v0.53.0 h1:d+qAbo5L0orcWAr0a9JweQpjXF19LMXJE8Ey7hwOdUA= +golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/oauth2 v0.0.0-20180821212333-d2e6202438be/go.mod h1:N/0e6XlmueqKjAGxoOufVs8QHGRruUQn6yWY3a++T0U= golang.org/x/oauth2 v0.36.0 h1:peZ/1z27fi9hUOFCAZaHyrpWG5lwe0RJEEEeH0ThlIs= golang.org/x/oauth2 v0.36.0/go.mod h1:YDBUJMTkDnJS+A4BP4eZBjCqtokkg1hODuPjwiGPO7Q= @@ -957,6 +959,7 @@ golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.5.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.8.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= +golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= @@ -967,6 +970,7 @@ golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4 golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= golang.org/x/term v0.5.0/go.mod h1:jMB1sMXY+tzblOD4FWmEbocvup2/aLOaQEp7JmGp78k= golang.org/x/term v0.8.0/go.mod h1:xPskH00ivmX89bAKVGSKKtLOWNx2+17Eiy94tnKShWo= +golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= @@ -982,8 +986,8 @@ golang.org/x/text v0.7.0/go.mod h1:mrYo+phRRbMaCq/xk9113O4dZlRixOauAjOtrjsXDZ8= golang.org/x/text v0.9.0/go.mod h1:e1OnstbJyHTd6l/uOt8jFFHp6TRDWZR/bV3emEE/zU8= golang.org/x/text v0.14.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= golang.org/x/text v0.15.0/go.mod h1:18ZOQIKpY8NJVqYksKHtTdi31H5itFRjB5/qKTNYzSU= -golang.org/x/text v0.35.0 h1:JOVx6vVDFokkpaq1AEptVzLTpDe9KGpj5tR4/X+ybL8= -golang.org/x/text v0.35.0/go.mod h1:khi/HExzZJ2pGnjenulevKNX1W67CUy0AsXcNubPGCA= +golang.org/x/text v0.36.0 h1:JfKh3XmcRPqZPKevfXVpI1wXPTqbkE5f7JA92a55Yxg= +golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/time v0.5.0/go.mod h1:3BpzKBy/shNhVucY/MWOyx10tF3SFh9QdLuxbVysPQM= golang.org/x/time v0.15.0 h1:bbrp8t3bGUeFOx08pvsMYRTCVSMk89u4tKbNOZbp88U= golang.org/x/time v0.15.0/go.mod h1:Y4YMaQmXwGQZoFaVFk4YpCt4FLQMYKZe9oeV/f4MSno= @@ -997,16 +1001,16 @@ golang.org/x/tools v0.1.1/go.mod h1:o0xws9oXOQQZyjljx8fwUC0k7L1pTE6eaCbjGeHmOkk= golang.org/x/tools v0.1.7/go.mod h1:LGqMHiF4EqQNHR1JncWGqT5BVaXmza+X+BDGol+dOxo= golang.org/x/tools v0.1.12/go.mod h1:hNGJHUnrk76NpqgfD5Aqm5Crs+Hm0VOH/i9J2+nxYbc= golang.org/x/tools v0.6.0/go.mod h1:Xwgl3UAJ/d3gWutnCtw505GrjyAbvKui8lOU390QaIU= -golang.org/x/tools v0.42.0 h1:uNgphsn75Tdz5Ji2q36v/nsFSfR/9BRFvqhGBaJGd5k= -golang.org/x/tools v0.42.0/go.mod h1:Ma6lCIwGZvHK6XtgbswSoWroEkhugApmsXyrUmBhfr0= +golang.org/x/tools v0.43.0 h1:12BdW9CeB3Z+J/I/wj34VMl8X+fEXBxVR90JeMX5E7s= +golang.org/x/tools v0.43.0/go.mod h1:uHkMso649BX2cZK6+RpuIPXS3ho2hZo4FVwfoy1vIk0= golang.org/x/xerrors v0.0.0-20190717185122-a985d3407aa7/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/api v0.275.0 h1:vfY5d9vFVJeWEZT65QDd9hbndr7FyZ2+6mIzGAh71NI= -google.golang.org/api v0.275.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= +google.golang.org/api v0.276.0 h1:nVArUtfLEihtW+b0DdcqRGK1xoEm2+ltAihyztq7MKY= +google.golang.org/api v0.276.0/go.mod h1:Fnag/EWUPIcJXuIkP1pjoTgS5vdxlk3eeemL7Do6bvw= google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9YwlJXL52JkM= google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= @@ -1060,8 +1064,8 @@ k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= -k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= -k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= +k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= +k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE= From a8e8b670866d5676f89238d147bbc5d36de5e496 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sat, 2 May 2026 10:04:28 +0800 Subject: [PATCH 11/28] fix: use --post-renderer-args=VALUE format to prevent Helm flag parsing failure (#2570) * fix: use --post-renderer-args=VALUE format to prevent Helm flag parsing failure When postRendererArgs contains values like short flags (e.g. -v), passing --post-renderer-args and the value as separate arguments causes Helm to interpret the value as its own flag. Using the --post-renderer-args=VALUE format unambiguously binds the value to the flag. Fixes #2563 Signed-off-by: yxxhero * fix: update hasFlagWithValue doc/errors and add -v short-flag test cases - Update hasFlagWithValue doc comment to describe both '--flag value' and '--flag=value' forms - Update t.Errorf messages in app_test.go to reflect both accepted formats - Add 'post-renderer-args-short-flag-value' test case (-v) to both TestHelmState_flagsForUpgrade and TestHelmState_flagsForTemplate to verify --post-renderer-args=-v emission (core regression from #2563) Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/dd95f046-358b-4867-9069-9432c1b5318e Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Signed-off-by: yxxhero Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- pkg/app/app_test.go | 14 ++++++--- pkg/state/helmx.go | 2 +- pkg/state/state_test.go | 68 +++++++++++++++++++++++++++++++++-------- 3 files changed, 66 insertions(+), 18 deletions(-) diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index aa792b33..f979544c 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -3100,12 +3100,16 @@ func newPostRendererTestApp(t *testing.T, files map[string]string) (*App, *mockH return app, helm } -// hasFlagWithValue reports whether flags contains "--flagName value" as adjacent entries. +// hasFlagWithValue reports whether flags contains either "--flagName value" as adjacent entries +// or "--flagName=value" as a single entry. func hasFlagWithValue(flags []string, flagName, value string) bool { for i, f := range flags { if f == flagName && i+1 < len(flags) && flags[i+1] == value { return true } + if f == flagName+"="+value { + return true + } } return false } @@ -3162,10 +3166,10 @@ releases: t.Errorf("expected --post-renderer foo in flags, got %v", flags) } if !hasFlagWithValue(flags, "--post-renderer-args", "--arg1") { - t.Errorf("expected --post-renderer-args --arg1 in flags, got %v", flags) + t.Errorf("expected --post-renderer-args=--arg1 or --post-renderer-args --arg1 in flags, got %v", flags) } if !hasFlagWithValue(flags, "--post-renderer-args", "--arg2") { - t.Errorf("expected --post-renderer-args --arg2 in flags, got %v", flags) + t.Errorf("expected --post-renderer-args=--arg2 or --post-renderer-args --arg2 in flags, got %v", flags) } }) } @@ -3219,7 +3223,7 @@ releases: flags := helm.templated[0].flags if !hasFlagWithValue(flags, "--post-renderer-args", "--cli-arg") { - t.Errorf("expected --post-renderer-args --cli-arg in flags (CLI should override helmDefaults), got %v", flags) + t.Errorf("expected --post-renderer-args=--cli-arg or --post-renderer-args --cli-arg in flags (CLI should override helmDefaults), got %v", flags) } if hasFlagWithValue(flags, "--post-renderer-args", "--default-arg") { t.Errorf("unexpected --post-renderer-args --default-arg in flags (CLI should override helmDefaults), got %v", flags) @@ -3276,7 +3280,7 @@ releases: flags := helm.templated[0].flags if !hasFlagWithValue(flags, "--post-renderer-args", "--release-arg") { - t.Errorf("expected --post-renderer-args --release-arg in flags (release should override CLI), got %v", flags) + t.Errorf("expected --post-renderer-args=--release-arg or --post-renderer-args --release-arg in flags (release should override CLI), got %v", flags) } if hasFlagWithValue(flags, "--post-renderer-args", "--cli-arg") { t.Errorf("unexpected --post-renderer-args --cli-arg in flags (release should override CLI), got %v", flags) diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index f1a2daeb..ada74c88 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -127,7 +127,7 @@ func (st *HelmState) appendPostRenderArgsFlags(flags []string, release *ReleaseS } for _, arg := range postRendererArgsFlags { if arg != "" { - flags = append(flags, "--post-renderer-args", arg) + flags = append(flags, "--post-renderer-args="+arg) } } return flags diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 0d10624a..8e6f2ba9 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -926,8 +926,8 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--arg1", - "--post-renderer-args", "--arg2", + "--post-renderer-args=--arg1", + "--post-renderer-args=--arg2", "--namespace", "test-namespace", }, }, @@ -949,7 +949,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--release-arg", + "--post-renderer-args=--release-arg", "--namespace", "test-namespace", }, }, @@ -972,7 +972,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--release-arg", + "--post-renderer-args=--release-arg", "--namespace", "test-namespace", }, }, @@ -997,7 +997,7 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--cli-arg", + "--post-renderer-args=--cli-arg", "--namespace", "test-namespace", }, }, @@ -1022,7 +1022,29 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--release-arg", + "--post-renderer-args=--release-arg", + "--namespace", "test-namespace", + }, + }, + { + name: "post-renderer-args-short-flag-value", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &enable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &disable, + Name: "test-charts", + Namespace: "test-namespace", + CreateNamespace: &disable, + PostRendererArgs: []string{"-v"}, + }, + want: []string{ + "--version", "0.1", + "--post-renderer-args=-v", "--namespace", "test-namespace", }, }, @@ -1340,8 +1362,8 @@ func TestHelmState_flagsForTemplate(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--arg1", - "--post-renderer-args", "--arg2", + "--post-renderer-args=--arg1", + "--post-renderer-args=--arg2", "--namespace", "test-namespace", }, }, @@ -1363,7 +1385,7 @@ func TestHelmState_flagsForTemplate(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--release-arg", + "--post-renderer-args=--release-arg", "--namespace", "test-namespace", }, }, @@ -1386,7 +1408,7 @@ func TestHelmState_flagsForTemplate(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--release-arg", + "--post-renderer-args=--release-arg", "--namespace", "test-namespace", }, }, @@ -1411,7 +1433,7 @@ func TestHelmState_flagsForTemplate(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--cli-arg", + "--post-renderer-args=--cli-arg", "--namespace", "test-namespace", }, }, @@ -1436,7 +1458,29 @@ func TestHelmState_flagsForTemplate(t *testing.T) { }, want: []string{ "--version", "0.1", - "--post-renderer-args", "--release-arg", + "--post-renderer-args=--release-arg", + "--namespace", "test-namespace", + }, + }, + { + name: "post-renderer-args-short-flag-value", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &enable, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &disable, + Name: "test-charts", + Namespace: "test-namespace", + CreateNamespace: &disable, + PostRendererArgs: []string{"-v"}, + }, + want: []string{ + "--version", "0.1", + "--post-renderer-args=-v", "--namespace", "test-namespace", }, }, From 08a22772f701474c21a39b8a7c0c0dea70990567 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sun, 3 May 2026 18:32:30 +0800 Subject: [PATCH 12/28] feat: add --write-output flag to helmfile fetch for air-gapped environments (#2572) * feat: add --write-output flag to helmfile fetch for air-gapped environments Add --write-output flag to helmfile fetch that outputs a modified helmfile.yaml with chart references updated to point to downloaded local chart paths. Combined with --output-dir, this enables preparing all charts for deployment in air-gapped environments. Usage: helmfile fetch --output-dir ./charts --write-output > helmfile-airgapped.yaml Fixes #2571 Signed-off-by: yxxhero Signed-off-by: yxxhero * fix: update fetch-write-output integration test grep to match YAML list item chart field Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d00f71ab-d40d-4220-9b11-97674597685f Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: send status messages to stderr and enforce sequential processing in --write-output mode Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d338e24c-4f6f-4a59-a319-4b975e0efdcb Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: restore SequentialHelmfiles after Fetch and use %s for YAML string formatting Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/cfa9f3f4-c72f-4760-9c51-88bc6f30add2 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: add test for SequentialHelmfiles restore after Fetch with --write-output Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/cfa9f3f4-c72f-4760-9c51-88bc6f30add2 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: disable live output on --write-output and fix shell quoting/portability in integration test Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b0eb0d3d-493b-4d77-b8eb-2a5c0ce70d86 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: use unquoted ${helmfile} variable to allow word splitting for EXTRA_HELMFILE_FLAGS Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d025a111-f7d0-439e-bf14-5508c40d0b51 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: restore helm.EnableLiveOutput after Fetch --write-output via defer Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/ddb8c5fc-ebd1-4f09-9474-5da58938a219 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: strengthen enableLiveOutput restore assertion with non-trivial initial value Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d1d0ba9e-5c97-48e1-b761-8bdee391efb2 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * feat: restrict --write-output to a single helmfile state file with clear error Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/f608a0d0-7f52-4e3f-9fac-ab966bd01efb Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * refactor: apply code review suggestions for variable and test naming Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/f608a0d0-7f52-4e3f-9fac-ab966bd01efb Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: replace naked return with explicit return ok, errs to fix nakedret lint error Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/4b060131-a977-44b0-98f7-42bc108ae8e8 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: buffer YAML output and update --write-output flag description Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/50c6ad2e-125c-43c1-b9c3-37fe1686a8eb Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: shorten --write-output flag description, move detail to Long help Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/50c6ad2e-125c-43c1-b9c3-37fe1686a8eb Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Signed-off-by: yxxhero Signed-off-by: yxxhero Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- cmd/fetch.go | 9 + pkg/app/app.go | 73 +++++++- pkg/app/app_test.go | 172 +++++++++++++++++- pkg/app/config.go | 1 + pkg/app/run.go | 2 +- pkg/config/fetch.go | 13 +- test/integration/run.sh | 1 + .../test-cases/fetch-write-output.sh | 39 ++++ .../input/helmfile.yaml.gotmpl | 4 + 9 files changed, 306 insertions(+), 8 deletions(-) create mode 100644 test/integration/test-cases/fetch-write-output.sh create mode 100644 test/integration/test-cases/fetch-write-output/input/helmfile.yaml.gotmpl diff --git a/cmd/fetch.go b/cmd/fetch.go index 3ff693e5..fb4c6adf 100644 --- a/cmd/fetch.go +++ b/cmd/fetch.go @@ -15,6 +15,14 @@ func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command { cmd := &cobra.Command{ Use: "fetch", Short: "Fetch charts from state file", + Long: `Fetch downloads all charts referenced in the Helmfile state. + +Useful for air-gapped environments: download charts with --output-dir and --write-output, +then transfer the output directory and the generated helmfile.yaml to the air-gapped environment. + +The --write-output flag requires a single helmfile state file specified with -f. +It fails if the input resolves to multiple state files (e.g. a directory or a helmfile +with nested helmfiles: entries).`, RunE: func(cmd *cobra.Command, args []string) error { fetchImpl := config.NewFetchImpl(globalCfg, fetchOptions) err := config.NewCLIConfigImpl(fetchImpl.GlobalImpl) @@ -35,6 +43,7 @@ func NewFetchCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.IntVar(&fetchOptions.Concurrency, "concurrency", 0, "maximum number of concurrent helm processes to run, 0 is unlimited") f.StringVar(&fetchOptions.OutputDir, "output-dir", "", "directory to store charts (default: temporary directory which is deleted when the command terminates)") f.StringVar(&fetchOptions.OutputDirTemplate, "output-dir-template", state.DefaultFetchOutputDirTemplate, "go text template for generating the output directory. Available fields: {{ .OutputDir }}, {{ .ChartName }}, {{ .Release.* }}, {{ .Environment.Name }}, {{ .Environment.KubeContext }}, {{ .Environment.Values.* }}") + f.BoolVar(&fetchOptions.WriteOutput, "write-output", false, "write a helmfile.yaml to stdout with chart references updated to local chart paths; requires --output-dir and a single helmfile (use -f)") return cmd } diff --git a/pkg/app/app.go b/pkg/app/app.go index 29a1ce40..6b9738c6 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -393,7 +393,49 @@ func (a *App) Unittest(c UnittestConfigProvider) error { } func (a *App) Fetch(c FetchConfigProvider) error { - return a.ForEachState(func(run *Run) (ok bool, errs []error) { + if c.WriteOutput() && c.OutputDir() == "" { + return fmt.Errorf("--output-dir is required when --write-output is set") + } + + if c.WriteOutput() { + // Force sequential processing to ensure YAML documents are emitted in order + // without interleaving when multiple helmfile state files are processed. + // Restore the original value when Fetch returns so the App instance is not + // permanently mutated (important for tests and library usage). + prev := a.SequentialHelmfiles + a.SequentialHelmfiles = true + defer func() { a.SequentialHelmfiles = prev }() + } + + // processedStateFileCount tracks how many state files have been processed when + // --write-output is set; used to detect multi-file inputs early and return + // a clear error instead of silently producing semantically incorrect YAML. + var processedStateFileCount int + + // yamlOutput buffers the generated YAML document so that nothing is written to + // stdout until ForEachState completes successfully. This prevents partial/corrupted + // output reaching stdout when a later state file (or chart download error) causes + // the operation to fail. + var yamlOutput strings.Builder + + err := a.ForEachState(func(run *Run) (ok bool, errs []error) { + if c.WriteOutput() { + processedStateFileCount++ + if processedStateFileCount > 1 { + return false, []error{fmt.Errorf( + "--write-output requires a single helmfile state file, but multiple were found; " + + "use -f to specify a single helmfile instead of a directory or a helmfile with nested helmfiles: entries", + )} + } + + // Disable live output to avoid Helm progress/status lines being streamed + // to stdout and corrupting the YAML document emitted by --write-output. + // Restore the original value when this callback returns so the cached helm + // exec instance is not permanently mutated (important for tests and library usage). + run.helm.SetEnableLiveOutput(false) + defer run.helm.SetEnableLiveOutput(a.EnableLiveOutput) + } + prepErr := run.withPreparedCharts("pull", state.ChartPrepareOptions{ ForceDownload: true, SkipRefresh: c.SkipRefresh(), @@ -403,6 +445,27 @@ func (a *App) Fetch(c FetchConfigProvider) error { OutputDirTemplate: c.OutputDirTemplate(), Concurrency: c.Concurrency(), }, func() []error { + if c.WriteOutput() { + for i := range run.state.Releases { + rel := &run.state.Releases[i] + if rel.ChartPath != "" { + rel.Chart = rel.ChartPath + rel.ChartPath = "" + } + } + + stateYaml, yamlErr := run.state.ToYaml() + if yamlErr != nil { + return []error{yamlErr} + } + + sourceFile, pathErr := run.state.FullFilePath() + if pathErr != nil { + return []error{pathErr} + } + fmt.Fprintf(&yamlOutput, "---\n# Source: %s\n\n%s", sourceFile, stateYaml) + } + return nil }) @@ -410,8 +473,14 @@ func (a *App) Fetch(c FetchConfigProvider) error { errs = append(errs, prepErr) } - return + return ok, errs }, false, SetFilter(true)) + + if err == nil && c.WriteOutput() { + fmt.Print(yamlOutput.String()) + } + + return err } func (a *App) Sync(c SyncConfigProvider) error { diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index f979544c..bcc996f3 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2478,6 +2478,10 @@ func (c configImpl) EnforceNeedsAreInstalled() bool { return c.enforceNeedsAreInstalled } +func (c configImpl) WriteOutput() bool { + return false +} + type applyConfig struct { args string cascade string @@ -2805,8 +2809,9 @@ func MockExecer(logger *zap.SugaredLogger, kubeContext string) (helmexec.Interfa // mocking helmexec.Interface type mockHelmExec struct { - templated []mockTemplates - repos []mockRepo + templated []mockTemplates + repos []mockRepo + enableLiveOutput bool } type mockTemplates struct { @@ -2846,6 +2851,7 @@ func (helm *mockHelmExec) SetHelmBinary(bin string) { } func (helm *mockHelmExec) SetEnableLiveOutput(enableLiveOutput bool) { + helm.enableLiveOutput = enableLiveOutput } func (helm *mockHelmExec) SetDisableForceUpdate(forceUpdate bool) { @@ -4587,6 +4593,168 @@ releases: "state should contain source helmfile name:\n%s\n", out) } +type fetchConfigImpl struct { + configImpl + outputDir string + outputDirTemplate string + writeOutput bool +} + +func (f fetchConfigImpl) OutputDir() string { + return f.outputDir +} + +func (f fetchConfigImpl) OutputDirTemplate() string { + return f.outputDirTemplate +} + +func (f fetchConfigImpl) WriteOutput() bool { + return f.writeOutput +} + +func TestFetch_WriteOutputRequiresOutputDir(t *testing.T) { + files := map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: myrelease1 + chart: mychart1 +`, + } + + var buffer bytes.Buffer + syncWriter := testhelper.NewSyncWriter(&buffer) + logger := helmexec.NewLogger(syncWriter, "debug") + + app := appWithFs(&App{ + OverrideHelmBinary: DefaultHelmBinary, + fs: ffs.DefaultFileSystem(), + OverrideKubeContext: "default", + DisableKubeVersionAutoDetection: true, + Env: "default", + Logger: logger, + Namespace: "testNamespace", + }, files) + + expectNoCallsToHelm(app) + + err := app.Fetch(fetchConfigImpl{ + writeOutput: true, + outputDir: "", + }) + assert.Error(t, err) + assert.Contains(t, err.Error(), "--output-dir is required") +} + +func TestFetch_WriteOutput_ErrorsOnMultipleStateFiles(t *testing.T) { + // Two separate helmfile state files in a helmfile.d directory simulate the + // multi-file scenario that --write-output cannot safely handle: the resulting + // multi-document YAML stream would be merged by Helmfile in a way that can + // alter semantics (helmDefaults override, broken relative paths, etc.). + files := map[string]string{ + "/path/to/helmfile.d/first.yaml": ` +releases: +- name: release1 + chart: chart1 +`, + "/path/to/helmfile.d/second.yaml": ` +releases: +- name: release2 + chart: chart2 +`, + } + + var buffer bytes.Buffer + syncWriter := testhelper.NewSyncWriter(&buffer) + logger := helmexec.NewLogger(syncWriter, "debug") + + valsRuntime, err := vals.New(vals.Options{CacheSize: 32}) + if err != nil { + t.Fatalf("unexpected error creating vals runtime: %v", err) + } + + helm := &mockHelmExec{} + + app := appWithFs(&App{ + OverrideHelmBinary: DefaultHelmBinary, + fs: ffs.DefaultFileSystem(), + OverrideKubeContext: "default", + DisableKubeVersionAutoDetection: true, + Env: "default", + Logger: logger, + helms: map[helmKey]helmexec.Interface{ + createHelmKey(DefaultHelmBinary, "default"): helm, + }, + Namespace: "testNamespace", + valsRuntime: valsRuntime, + }, files) + + outputDir := t.TempDir() + + fetchErr := app.Fetch(fetchConfigImpl{ + writeOutput: true, + outputDir: outputDir, + }) + assert.Error(t, fetchErr, "expected error when --write-output is used with multiple state files") + assert.Contains(t, fetchErr.Error(), "--write-output requires a single helmfile state file") +} + +func TestFetch_WriteOutputRestoresSequentialHelmfiles(t *testing.T) { + files := map[string]string{ + "/path/to/helmfile.yaml": ` +releases: +- name: myrelease1 + chart: mychart1 +`, + } + + var buffer bytes.Buffer + syncWriter := testhelper.NewSyncWriter(&buffer) + logger := helmexec.NewLogger(syncWriter, "debug") + + valsRuntime, err := vals.New(vals.Options{CacheSize: 32}) + if err != nil { + t.Fatalf("unexpected error creating vals runtime: %v", err) + } + + // Use a real mock helm exec (not noCallHelmExec) so that Fetch can proceed + // past the validation check and enter the SequentialHelmfiles mutation block. + // Start with enableLiveOutput = true so the restore path is actually exercised: + // Fetch will call SetEnableLiveOutput(false), then the deferred restore call + // SetEnableLiveOutput(true) (a.EnableLiveOutput). If the defer were missing, + // helm.enableLiveOutput would remain false and the assertion below would fail. + helm := &mockHelmExec{enableLiveOutput: true} + + app := appWithFs(&App{ + OverrideHelmBinary: DefaultHelmBinary, + fs: ffs.DefaultFileSystem(), + OverrideKubeContext: "default", + DisableKubeVersionAutoDetection: true, + Env: "default", + Logger: logger, + helms: map[helmKey]helmexec.Interface{ + createHelmKey(DefaultHelmBinary, "default"): helm, + }, + Namespace: "testNamespace", + valsRuntime: valsRuntime, + // Start with SequentialHelmfiles = false; it must be restored after Fetch. + SequentialHelmfiles: false, + // Start with EnableLiveOutput = true; the deferred restore must bring it back. + EnableLiveOutput: true, + }, files) + + outputDir := t.TempDir() + + // Fetch with --write-output + --output-dir enters the mutation block, + // temporarily sets SequentialHelmfiles = true and helm.EnableLiveOutput = false, + // then restores both when it returns. + _ = app.Fetch(fetchConfigImpl{ + writeOutput: true, + outputDir: outputDir, + }) + assert.False(t, app.SequentialHelmfiles, "SequentialHelmfiles should be restored to false after Fetch returns") + assert.True(t, helm.enableLiveOutput, "helm.enableLiveOutput should be restored to true (a.EnableLiveOutput) after Fetch returns") +} + func TestList(t *testing.T) { files := map[string]string{ "/path/to/helmfile.d/first.yaml": ` diff --git a/pkg/app/config.go b/pkg/app/config.go index 9b92e498..8ff2a5cb 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -242,6 +242,7 @@ type FetchConfigProvider interface { SkipRefresh() bool OutputDir() string OutputDirTemplate() string + WriteOutput() bool concurrencyConfig } diff --git a/pkg/app/run.go b/pkg/app/run.go index 91e25bb5..4c124df2 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -88,7 +88,7 @@ func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepare dir = tempDir } else { dir = opts.OutputDir - fmt.Printf("Charts will be downloaded to: %s\n", dir) + fmt.Fprintf(os.Stderr, "Charts will be downloaded to: %s\n", dir) } if _, err := r.state.TriggerGlobalPrepareEvent(helmfileCommand); err != nil { diff --git a/pkg/config/fetch.go b/pkg/config/fetch.go index 462ecdf4..64a53eb4 100644 --- a/pkg/config/fetch.go +++ b/pkg/config/fetch.go @@ -1,6 +1,6 @@ package config -// FetchOptions is the options for the build command +// FetchOptions is the options for the fetch command type FetchOptions struct { // Concurrency is the maximum number of concurrent helm processes to run, 0 is unlimited Concurrency int @@ -8,14 +8,16 @@ type FetchOptions struct { OutputDir string // OutputDirTemplate is the go template to generate the path of output directory OutputDirTemplate string + // WriteOutput writes a helmfile.yaml with chart references updated to point to downloaded local chart paths + WriteOutput bool } -// NewFetchOptions creates a new Apply +// NewFetchOptions creates a new FetchOptions func NewFetchOptions() *FetchOptions { return &FetchOptions{} } -// FetchImpl is impl for applyOptions +// FetchImpl is impl for fetchOptions type FetchImpl struct { *GlobalImpl *FetchOptions @@ -43,3 +45,8 @@ func (c *FetchImpl) OutputDir() string { func (c *FetchImpl) OutputDirTemplate() string { return c.FetchOptions.OutputDirTemplate } + +// WriteOutput returns whether to write a modified helmfile.yaml with local chart paths +func (c *FetchImpl) WriteOutput() bool { + return c.FetchOptions.WriteOutput +} diff --git a/test/integration/run.sh b/test/integration/run.sh index 14d4b435..e6b491ec 100755 --- a/test/integration/run.sh +++ b/test/integration/run.sh @@ -99,6 +99,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes . ${dir}/test-cases/issue-2502-race-condition-local-chart.sh . ${dir}/test-cases/chart-deps-condition.sh . ${dir}/test-cases/fetch-forl-local-chart.sh +. ${dir}/test-cases/fetch-write-output.sh . ${dir}/test-cases/suppress-output-line-regex.sh . ${dir}/test-cases/chartify-jsonPatches-and-strategicMergePatches.sh . ${dir}/test-cases/include-template-func.sh diff --git a/test/integration/test-cases/fetch-write-output.sh b/test/integration/test-cases/fetch-write-output.sh new file mode 100644 index 00000000..53e39321 --- /dev/null +++ b/test/integration/test-cases/fetch-write-output.sh @@ -0,0 +1,39 @@ +fetch_write_output_input_dir="${cases_dir}/fetch-write-output/input" + +fetch_write_output_tmp=$(mktemp -d) + +case_title="fetch with --write-output for air-gapped environments" + +test_start "$case_title" + +info "Testing helmfile fetch --write-output with local chart" +output=$(${helmfile} -f "${fetch_write_output_input_dir}/helmfile.yaml.gotmpl" fetch --output-dir "${fetch_write_output_tmp}" --write-output 2>/dev/null) \ + || fail "\"helmfile fetch --write-output\" shouldn't fail" + +info "Verifying stdout does not contain non-YAML status messages" +echo "${output}" | grep -q "^Charts will be downloaded to:" && fail "stdout should not contain 'Charts will be downloaded to:' (should be on stderr)" || true + +info "Verifying output contains YAML document separator" +echo "${output}" | grep -q "^---" || fail "output should contain YAML document separator" + +info "Verifying output contains source helmfile reference" +echo "${output}" | grep -q "# Source:" || fail "output should contain source helmfile reference" + +info "Verifying output contains release name" +echo "${output}" | grep -q "name: local-chart" || fail "output should contain release name" + +info "Verifying output contains updated chart path pointing to output dir" +echo "${output}" | grep -q "chart:" || fail "output should contain chart field" + +info "Verifying chart files exist in output directory" +cat "${fetch_write_output_tmp}/helmfile-tests/local-chart/raw/latest/Chart.yaml" || fail "Chart.yaml should exist in fetched output directory" + +info "Verifying the chart path in output matches the actual downloaded location" +chart_path=$(echo "${output}" | grep -E "^[[:space:]]+(-[[:space:]]+)?chart:" | head -1 | sed 's/.*chart: *//' | tr -d '"') +if [ ! -f "${chart_path}/Chart.yaml" ]; then + fail "chart path '${chart_path}' from output should point to a directory containing Chart.yaml" +fi + +rm -rf "${fetch_write_output_tmp}" + +test_pass "$case_title" diff --git a/test/integration/test-cases/fetch-write-output/input/helmfile.yaml.gotmpl b/test/integration/test-cases/fetch-write-output/input/helmfile.yaml.gotmpl new file mode 100644 index 00000000..f4561b44 --- /dev/null +++ b/test/integration/test-cases/fetch-write-output/input/helmfile.yaml.gotmpl @@ -0,0 +1,4 @@ +releases: +- name: local-chart + chart: ../../../charts/raw + namespace: local-chart From 902c5ced17e670cd02fb9b16324007e029fec697 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sun, 3 May 2026 19:03:11 +0800 Subject: [PATCH 13/28] feat: add 'create' subcommand to scaffold helmfile deployment projects (#2574) * feat: add 'create' subcommand to scaffold helmfile deployment projects Add 'helmfile create [NAME]' command that generates a best-practice helmfile project structure with: - helmfile.yaml with commented examples (helmDefaults, repositories, environments, releases) - environments/default.yaml for environment-specific values - values/.gitkeep placeholder for release values Supports --output-dir/-o for custom output path and --force to overwrite existing files. Validates project name to prevent path traversal. Signed-off-by: yxxhero * fix: add overwrite protection for all scaffold files and unit tests for create command - pkg/app/create.go: extract writeFileIfNotExists helper that respects the --force flag; all three scaffold files (helmfile.yaml, environments/default.yaml, values/.gitkeep) now refuse to overwrite without --force - pkg/config/create.go: ValidateConfig now checks all three scaffold paths and reports every already-existing file in a single error before proceeding, instead of only checking helmfile.yaml - pkg/app/create_test.go: add unit tests covering new directory, current directory, per-file overwrite rejection without --force, and full overwrite with --force Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/eb6d9e4b-0f72-4e26-b841-e1e39a2b2e83 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: remove redundant absDir from ValidateConfig error message Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/eb6d9e4b-0f72-4e26-b841-e1e39a2b2e83 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: address create command review feedback - cmd/create.go: add config.NewCLIConfigImpl() call for consistency with other subcommands; update --force flag help text to list all overwritten files - pkg/config/create.go: delegate to c.GlobalImpl.ValidateConfig() at end of ValidateConfig() for global option validation (--color/--no-color) - pkg/config/create_test.go: add unit tests for CreateImpl.ValidateConfig() covering path separator rejection, '..' rejection, existing-file detection per-file and with --force, and global color conflict delegation Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/6327d657-5888-4b94-85fb-def80c0a193f Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: clarify test helper name and comment in create_test.go Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/6327d657-5888-4b94-85fb-def80c0a193f Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: atomic preflight check in App.Create before any writes Refactor Create to collect all conflicting scaffold paths up front before writing anything. When --force is not set and any scaffold file already exists, the command returns a single error listing all conflicts without touching the filesystem. Also removes the now-unnecessary writeFileIfNotExists helper and adds a test (TestCreate_PreflightAtomicOnLaterConflict) verifying that a conflict on a later file (e.g. environments/default.yaml) prevents even the first file (helmfile.yaml) from being created. Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/aae6f2e6-7f9e-42b8-afa3-78edd3215127 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: handle non-IsNotExist Stat errors in preflight check; add whitespace name test; fix gci formatting - pkg/app/create.go: treat os.Stat errors that are NOT os.IsNotExist as hard errors in the preflight scan, surfacing permission/IO issues before any writes happen; remove trailing blank line that caused gci failure - pkg/config/create.go: same non-IsNotExist error handling in ValidateConfig - pkg/config/create_test.go: add TestCreateImpl_ValidateConfig_WhitespaceOnlyName covering the " " (whitespace-only) name rejection branch Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/d6574f56-f46d-46f7-99d9-e0b0b897b3b5 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * refactor: eliminate duplicated scaffold existence check; use O_EXCL for TOCTOU protection - pkg/config/create.go: remove file-existence check from ValidateConfig (duplicate of App.Create's preflight); ValidateConfig now only validates the project name and delegates to GlobalImpl.ValidateConfig. Remove unused os/path/filepath imports. - pkg/app/create.go: add writeScaffoldFile helper that uses O_CREATE|O_EXCL when force=false, so a file appearing between the preflight check and the actual write is caught rather than silently overwritten (TOCTOU protection). - pkg/config/create_test.go: remove four file-existence tests that tested the now-deleted ValidateConfig logic; file-conflict coverage remains in pkg/app/create_test.go. Simplify ValidName and GlobalColorConflict tests. Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/82f82e72-934f-416c-8662-5060e92284fa Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: wrap O_EXCL error with --force hint; add writeScaffoldFile unit tests - pkg/app/create.go: wrap os.IsExist error from writeScaffoldFile with a message that names the conflicting file and suggests --force, so the user gets actionable output even in the TOCTOU case - pkg/app/create_test.go: add TestWriteScaffoldFile_CreatesNewFile, TestWriteScaffoldFile_ExistingFileNoForce, and TestWriteScaffoldFile_ExistingFileWithForce to cover the helper directly Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/82f82e72-934f-416c-8662-5060e92284fa Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: wrap App.Create errors in *app.Error; reject '.' as project name; add '.' name test - pkg/app/create.go: wrap all App.Create fmt.Errorf returns with appError("", ...) so toCLIError produces a clean user-friendly message instead of "unexpected error: *fmt.wrapError: ..." - pkg/config/create.go: reject "." as a NAME alongside ".." to prevent accidentally scaffolding into the current directory via a named argument - pkg/config/create_test.go: add TestCreateImpl_ValidateConfig_NameDot Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/6d64508e-2d66-47e9-a02a-7669a2f481b7 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: drop unused outputDir param from test helper to fix unparam lint error Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/11cd65e9-c5ef-4195-9375-bc929169616b Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: drop unused force param from test helper to fix unparam lint error Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/0e1bdac5-708f-4615-ae6d-e22fc1e921f2 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Signed-off-by: yxxhero Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- cmd/create.go | 45 +++++++++ cmd/root.go | 1 + pkg/app/config.go | 8 ++ pkg/app/create.go | 145 ++++++++++++++++++++++++++ pkg/app/create_test.go | 208 ++++++++++++++++++++++++++++++++++++++ pkg/config/create.go | 62 ++++++++++++ pkg/config/create_test.go | 66 ++++++++++++ 7 files changed, 535 insertions(+) create mode 100644 cmd/create.go create mode 100644 pkg/app/create.go create mode 100644 pkg/app/create_test.go create mode 100644 pkg/config/create.go create mode 100644 pkg/config/create_test.go diff --git a/cmd/create.go b/cmd/create.go new file mode 100644 index 00000000..3fc6b0ab --- /dev/null +++ b/cmd/create.go @@ -0,0 +1,45 @@ +package cmd + +import ( + "github.com/spf13/cobra" + + "github.com/helmfile/helmfile/pkg/app" + "github.com/helmfile/helmfile/pkg/config" +) + +func NewCreateCmd(globalCfg *config.GlobalImpl) *cobra.Command { + options := config.NewCreateOptions() + cmd := &cobra.Command{ + Use: "create [NAME]", + Short: "Create a helmfile deployment project scaffold", + Long: `Create a helmfile deployment project with best-practice directory structure. + +Generates: + - helmfile.yaml Main configuration with commented examples + - environments/ Environment-specific value files + - values/ Release-specific value files + +If NAME is provided, creates the project in a new directory named NAME. +Otherwise, creates the project in the current directory.`, + Args: cobra.MaximumNArgs(1), + RunE: func(cmd *cobra.Command, args []string) error { + if len(args) > 0 { + options.Name = args[0] + } + createImpl := config.NewCreateImpl(globalCfg, options) + if err := config.NewCLIConfigImpl(createImpl.GlobalImpl); err != nil { + return err + } + if err := createImpl.ValidateConfig(); err != nil { + return err + } + a := app.New(createImpl) + return toCLIError(createImpl.GlobalImpl, a.Create(createImpl)) + }, + } + f := cmd.Flags() + f.StringVarP(&options.OutputDir, "output-dir", "o", "", "Output directory (default: NAME or current directory)") + f.BoolVar(&options.Force, "force", false, "Overwrite existing scaffold files (helmfile.yaml, environments/default.yaml, values/.gitkeep)") + + return cmd +} diff --git a/cmd/root.go b/cmd/root.go index cd333dd4..40876dbc 100644 --- a/cmd/root.go +++ b/cmd/root.go @@ -92,6 +92,7 @@ func NewRootCmd(globalConfig *config.GlobalOptions) (*cobra.Command, error) { } cmd.AddCommand( + NewCreateCmd(globalImpl), NewInitCmd(globalImpl), NewApplyCmd(globalImpl), NewBuildCmd(globalImpl), diff --git a/pkg/app/config.go b/pkg/app/config.go index 8ff2a5cb..aa7cfa5b 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -326,6 +326,14 @@ type InitConfigProvider interface { Force() bool } +type CreateConfigProvider interface { + Name() string + OutputDir() string + Force() bool + + loggingConfig +} + type PrintEnvConfigProvider interface { Output() string } diff --git a/pkg/app/create.go b/pkg/app/create.go new file mode 100644 index 00000000..91e02f53 --- /dev/null +++ b/pkg/app/create.go @@ -0,0 +1,145 @@ +package app + +import ( + "fmt" + "os" + "path/filepath" + "strings" +) + +const ( + helmfileYAMLTemplate = `# Helmfile configuration +# Documentation: https://helmfile.readthedocs.io/ + +# Common Helm defaults applied to all releases +helmDefaults: + createNamespace: true + wait: true + timeout: 300 + +# # Helm chart repositories +# repositories: +# - name: bitnami +# url: https://charts.bitnami.com/bitnami +# - name: ingress-nginx +# url: https://kubernetes.github.io/ingress-nginx +# - name: prometheus-community +# url: https://prometheus-community.github.io/helm-charts + +# # Environment-specific values +# # Usage: helmfile -e apply +# environments: +# default: +# values: +# - environments/default.yaml +# staging: +# values: +# - environments/staging.yaml +# production: +# values: +# - environments/production.yaml + +# # Helm releases +# releases: +# - name: my-app +# namespace: my-app +# chart: bitnami/nginx +# version: ~18.0.0 +# values: +# - values/my-app.yaml +# # secrets: +# # - secrets/my-app.yaml +# # hooks: +# # - events: ["presync"] +# # command: kubectl +# # args: ["apply", "-f", "manifests/"] +` + + envDefaultYAMLTemplate = `# Default environment values +# These values are available in helmfile.yaml as {{ .Values }} +# Example: +# replicaCount: 1 +# image: +# repository: nginx +# tag: latest +` +) + +func (a *App) Create(c CreateConfigProvider) error { + outputDir := c.OutputDir() + absDir, err := filepath.Abs(outputDir) + if err != nil { + return appError("", fmt.Errorf("failed to resolve output directory: %w", err)) + } + + // Scaffold file paths (intermediate directories may not exist yet). + helmfilePath := filepath.Join(absDir, "helmfile.yaml") + envFilePath := filepath.Join(absDir, "environments", "default.yaml") + gitkeepPath := filepath.Join(absDir, "values", ".gitkeep") + + // Preflight: when --force is not set, check all scaffold paths before + // writing anything so the command fails atomically rather than leaving a + // partially-written project directory. + if !c.Force() { + var existing []string + for _, p := range []string{helmfilePath, envFilePath, gitkeepPath} { + _, statErr := os.Stat(p) + if statErr == nil { + existing = append(existing, p) + } else if !os.IsNotExist(statErr) { + return appError("", fmt.Errorf("failed to check %s: %w", p, statErr)) + } + } + if len(existing) > 0 { + return appError("", fmt.Errorf("the following files already exist, use --force to overwrite: %s", strings.Join(existing, ", "))) + } + } + + // Create directories. + for _, dir := range []string{absDir, filepath.Join(absDir, "environments"), filepath.Join(absDir, "values")} { + if err := os.MkdirAll(dir, 0o755); err != nil { + return appError("", fmt.Errorf("failed to create directory %s: %w", dir, err)) + } + } + + // Write scaffold files. + files := []struct { + path string + content []byte + }{ + {helmfilePath, []byte(helmfileYAMLTemplate)}, + {envFilePath, []byte(envDefaultYAMLTemplate)}, + {gitkeepPath, []byte("")}, + } + for _, f := range files { + if err := writeScaffoldFile(f.path, f.content, c.Force()); err != nil { + return appError("", fmt.Errorf("failed to write %s: %w", f.path, err)) + } + c.Logger().Infof("created %s", f.path) + } + + c.Logger().Infof("\nhelmfile project created in %s\n\nNext steps:\n cd %s\n # Edit helmfile.yaml to add your releases\n helmfile apply", absDir, absDir) + return nil +} + +// writeScaffoldFile writes content to path. When force is false it uses +// O_EXCL so that a file appearing between the preflight check and the write +// is caught rather than silently overwritten (TOCTOU protection). +func writeScaffoldFile(path string, content []byte, force bool) error { + if force { + return os.WriteFile(path, content, 0o644) + } + f, err := os.OpenFile(path, os.O_CREATE|os.O_EXCL|os.O_WRONLY, 0o644) + if err != nil { + if os.IsExist(err) { + return fmt.Errorf("file %s already exists, use --force to overwrite: %w", path, err) + } + return err + } + _, werr := f.Write(content) + cerr := f.Close() + if werr != nil { + return werr + } + return cerr +} diff --git a/pkg/app/create_test.go b/pkg/app/create_test.go new file mode 100644 index 00000000..10b18297 --- /dev/null +++ b/pkg/app/create_test.go @@ -0,0 +1,208 @@ +package app + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "go.uber.org/zap" +) + +// mockCreateConfigProvider is a test double for CreateConfigProvider. +type mockCreateConfigProvider struct { + name string + outputDir string + force bool + logger *zap.SugaredLogger +} + +func (m *mockCreateConfigProvider) Name() string { return m.name } +func (m *mockCreateConfigProvider) OutputDir() string { return m.outputDir } +func (m *mockCreateConfigProvider) Force() bool { return m.force } +func (m *mockCreateConfigProvider) Logger() *zap.SugaredLogger { + if m.logger != nil { + return m.logger + } + return newTestLogger() +} + +func newMockCreateConfig(outputDir string, force bool) *mockCreateConfigProvider { + return &mockCreateConfigProvider{outputDir: outputDir, force: force} +} + +func TestCreate_NewDirectory(t *testing.T) { + dir := t.TempDir() + outDir := filepath.Join(dir, "myproject") + + a := &App{} + cfg := newMockCreateConfig(outDir, false) + + require.NoError(t, a.Create(cfg)) + + // Verify all scaffold files were created. + assertFileContent(t, filepath.Join(outDir, "helmfile.yaml"), helmfileYAMLTemplate) + assertFileContent(t, filepath.Join(outDir, "environments", "default.yaml"), envDefaultYAMLTemplate) + assertFileExists(t, filepath.Join(outDir, "values", ".gitkeep")) +} + +func TestCreate_CurrentDirectory(t *testing.T) { + dir := t.TempDir() + + a := &App{} + cfg := newMockCreateConfig(dir, false) + + require.NoError(t, a.Create(cfg)) + + assertFileContent(t, filepath.Join(dir, "helmfile.yaml"), helmfileYAMLTemplate) + assertFileContent(t, filepath.Join(dir, "environments", "default.yaml"), envDefaultYAMLTemplate) + assertFileExists(t, filepath.Join(dir, "values", ".gitkeep")) +} + +func TestCreate_ExistingHelmfileYAMLNoForce(t *testing.T) { + dir := t.TempDir() + // Pre-create helmfile.yaml + require.NoError(t, os.WriteFile(filepath.Join(dir, "helmfile.yaml"), []byte("existing"), 0o644)) + + a := &App{} + cfg := newMockCreateConfig(dir, false) + + err := a.Create(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "already exist") + assert.Contains(t, err.Error(), "--force") + + // Verify the existing file was not overwritten. + content, readErr := os.ReadFile(filepath.Join(dir, "helmfile.yaml")) + require.NoError(t, readErr) + assert.Equal(t, "existing", string(content)) +} + +func TestCreate_ExistingEnvDefaultYAMLNoForce(t *testing.T) { + dir := t.TempDir() + envDir := filepath.Join(dir, "environments") + require.NoError(t, os.MkdirAll(envDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(envDir, "default.yaml"), []byte("existing"), 0o644)) + + a := &App{} + cfg := newMockCreateConfig(dir, false) + + err := a.Create(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "already exist") + assert.Contains(t, err.Error(), "--force") + + // Verify the existing file was not overwritten. + content, readErr := os.ReadFile(filepath.Join(envDir, "default.yaml")) + require.NoError(t, readErr) + assert.Equal(t, "existing", string(content)) +} + +func TestCreate_ExistingGitkeepNoForce(t *testing.T) { + dir := t.TempDir() + valuesDir := filepath.Join(dir, "values") + require.NoError(t, os.MkdirAll(valuesDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(valuesDir, ".gitkeep"), []byte("existing"), 0o644)) + + a := &App{} + cfg := newMockCreateConfig(dir, false) + + err := a.Create(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "already exist") + assert.Contains(t, err.Error(), "--force") + + // Verify the existing file was not overwritten. + content, readErr := os.ReadFile(filepath.Join(valuesDir, ".gitkeep")) + require.NoError(t, readErr) + assert.Equal(t, "existing", string(content)) +} + +// TestCreate_PreflightAtomicOnLaterConflict verifies that when only a later +// scaffold file exists (e.g. environments/default.yaml but not helmfile.yaml), +// the preflight check catches it and no files are written at all. +func TestCreate_PreflightAtomicOnLaterConflict(t *testing.T) { + dir := t.TempDir() + envDir := filepath.Join(dir, "environments") + require.NoError(t, os.MkdirAll(envDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(envDir, "default.yaml"), []byte("existing"), 0o644)) + + a := &App{} + cfg := newMockCreateConfig(dir, false) + + err := a.Create(cfg) + require.Error(t, err) + assert.Contains(t, err.Error(), "already exist") + + // helmfile.yaml must NOT have been created (preflight aborted before any write). + _, statErr := os.Stat(filepath.Join(dir, "helmfile.yaml")) + assert.True(t, os.IsNotExist(statErr), "helmfile.yaml should not have been created") +} + +func TestCreate_ExistingFilesWithForce(t *testing.T) { + dir := t.TempDir() + + // Pre-create all scaffold files with different content. + require.NoError(t, os.WriteFile(filepath.Join(dir, "helmfile.yaml"), []byte("old"), 0o644)) + envDir := filepath.Join(dir, "environments") + require.NoError(t, os.MkdirAll(envDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(envDir, "default.yaml"), []byte("old"), 0o644)) + valuesDir := filepath.Join(dir, "values") + require.NoError(t, os.MkdirAll(valuesDir, 0o755)) + require.NoError(t, os.WriteFile(filepath.Join(valuesDir, ".gitkeep"), []byte("old"), 0o644)) + + a := &App{} + cfg := newMockCreateConfig(dir, true) + + require.NoError(t, a.Create(cfg)) + + // Verify scaffold files were overwritten with the template content. + assertFileContent(t, filepath.Join(dir, "helmfile.yaml"), helmfileYAMLTemplate) + assertFileContent(t, filepath.Join(dir, "environments", "default.yaml"), envDefaultYAMLTemplate) + assertFileExists(t, filepath.Join(dir, "values", ".gitkeep")) +} + +// assertFileContent asserts that the file at path exists and contains wantContent. +func assertFileContent(t *testing.T, path, wantContent string) { + t.Helper() + content, err := os.ReadFile(path) + require.NoError(t, err, "file %s should exist", path) + assert.Equal(t, wantContent, string(content)) +} + +// assertFileExists asserts that the file at path exists. +func assertFileExists(t *testing.T, path string) { + t.Helper() + _, err := os.Stat(path) + assert.NoError(t, err, "file %s should exist", path) +} + +func TestWriteScaffoldFile_CreatesNewFile(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "new.yaml") + require.NoError(t, writeScaffoldFile(path, []byte("hello"), false)) + assertFileContent(t, path, "hello") +} + +func TestWriteScaffoldFile_ExistingFileNoForce(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "existing.yaml") + require.NoError(t, os.WriteFile(path, []byte("original"), 0o644)) + + err := writeScaffoldFile(path, []byte("new"), false) + require.Error(t, err) + assert.Contains(t, err.Error(), "--force") + + // Original content must be unchanged. + assertFileContent(t, path, "original") +} + +func TestWriteScaffoldFile_ExistingFileWithForce(t *testing.T) { + dir := t.TempDir() + path := filepath.Join(dir, "existing.yaml") + require.NoError(t, os.WriteFile(path, []byte("original"), 0o644)) + + require.NoError(t, writeScaffoldFile(path, []byte("new"), true)) + assertFileContent(t, path, "new") +} diff --git a/pkg/config/create.go b/pkg/config/create.go new file mode 100644 index 00000000..0be1ef07 --- /dev/null +++ b/pkg/config/create.go @@ -0,0 +1,62 @@ +package config + +import ( + "fmt" + "strings" +) + +type CreateOptions struct { + Name string + OutputDir string + Force bool +} + +func NewCreateOptions() *CreateOptions { + return &CreateOptions{} +} + +type CreateImpl struct { + *GlobalImpl + *CreateOptions +} + +func NewCreateImpl(g *GlobalImpl, o *CreateOptions) *CreateImpl { + return &CreateImpl{ + GlobalImpl: g, + CreateOptions: o, + } +} + +func (c *CreateImpl) Name() string { + return c.CreateOptions.Name +} + +func (c *CreateImpl) OutputDir() string { + if c.CreateOptions.OutputDir != "" { + return c.CreateOptions.OutputDir + } + if c.CreateOptions.Name != "" { + return c.CreateOptions.Name + } + return "." +} + +func (c *CreateImpl) Force() bool { + return c.CreateOptions.Force +} + +func (c *CreateImpl) ValidateConfig() error { + name := c.CreateOptions.Name + if name != "" { + if strings.ContainsAny(name, "/\\") { + return fmt.Errorf("invalid project name %q: must not contain path separators", name) + } + if name == ".." || name == "." { + return fmt.Errorf("invalid project name %q", name) + } + if strings.TrimSpace(name) == "" { + return fmt.Errorf("project name must not be empty or whitespace only") + } + } + return c.GlobalImpl.ValidateConfig() +} diff --git a/pkg/config/create_test.go b/pkg/config/create_test.go new file mode 100644 index 00000000..a0ccfac8 --- /dev/null +++ b/pkg/config/create_test.go @@ -0,0 +1,66 @@ +package config + +import ( + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func newTestCreateImplWithDefaults(name string) *CreateImpl { + return NewCreateImpl(NewGlobalImpl(&GlobalOptions{}), &CreateOptions{ + Name: name, + }) +} + +func TestCreateImpl_ValidateConfig_NameWithForwardSlash(t *testing.T) { + c := newTestCreateImplWithDefaults("foo/bar") + err := c.ValidateConfig() + require.Error(t, err) + assert.Contains(t, err.Error(), "must not contain path separators") +} + +func TestCreateImpl_ValidateConfig_NameWithBackslash(t *testing.T) { + c := newTestCreateImplWithDefaults(`foo\bar`) + err := c.ValidateConfig() + require.Error(t, err) + assert.Contains(t, err.Error(), "must not contain path separators") +} + +func TestCreateImpl_ValidateConfig_NameDotDot(t *testing.T) { + c := newTestCreateImplWithDefaults("..") + err := c.ValidateConfig() + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid project name") +} + +func TestCreateImpl_ValidateConfig_NameDot(t *testing.T) { + c := newTestCreateImplWithDefaults(".") + err := c.ValidateConfig() + require.Error(t, err) + assert.Contains(t, err.Error(), "invalid project name") +} + +func TestCreateImpl_ValidateConfig_WhitespaceOnlyName(t *testing.T) { + c := newTestCreateImplWithDefaults(" ") + err := c.ValidateConfig() + require.Error(t, err) + assert.Contains(t, err.Error(), "must not be empty or whitespace only") +} + +func TestCreateImpl_ValidateConfig_ValidName(t *testing.T) { + c := newTestCreateImplWithDefaults("myproject") + require.NoError(t, c.ValidateConfig()) +} + +func TestCreateImpl_ValidateConfig_GlobalColorConflict(t *testing.T) { + // Delegates to GlobalImpl.ValidateConfig which rejects --color + --no-color. + c := NewCreateImpl( + NewGlobalImpl(&GlobalOptions{Color: true, NoColor: true}), + &CreateOptions{}, + ) + err := c.ValidateConfig() + require.Error(t, err) + assert.Contains(t, err.Error(), "--color") + assert.Contains(t, err.Error(), "--no-color") +} From e703b15075bae4ff944c53c0ffe95dbfeb09202e Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sun, 3 May 2026 19:33:33 +0800 Subject: [PATCH 14/28] docs: restructure documentation and improve newcomer experience (#2573) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: add --write-output flag to helmfile fetch for air-gapped environments Add --write-output flag to helmfile fetch that outputs a modified helmfile.yaml with chart references updated to point to downloaded local chart paths. Combined with --output-dir, this enables preparing all charts for deployment in air-gapped environments. Usage: helmfile fetch --output-dir ./charts --write-output > helmfile-airgapped.yaml Fixes #2571 Signed-off-by: yxxhero Signed-off-by: yxxhero * docs: restructure documentation and improve newcomer experience Split the monolithic index.md (1990 lines) into focused topic pages, update mkdocs.yml navigation, and add missing documentation for undocumented code features. Structure changes: - Extract configuration.md (helmfile.yaml reference) - Extract cli.md (CLI commands and flags) - Extract templating.md (template syntax and env vars) - Extract environments.md (environment configuration) - Extract releases.md (DAG, needs, selectors) - Extract hooks.md (lifecycle hooks) - Extract integrations.md (ArgoCD, Azure ACR, OCI) - Slim index.md to ~270 line landing page with step-by-step tutorial Newcomer improvements: - Add 5-step Getting Started tutorial with explanations - Reorganize nav: Getting Started now shows core learning path (Writing Helmfile → Values → Environments → Releases) - Add Quick Reference table to configuration.md - Simplify writing-helmfile.md title Code-vs-docs gap fixes: - Document 23 undocumented release fields (valuesTemplate, setTemplate, forceNamespace, adopt, trackMode, etc.) - Document 6 undocumented helmDefaults fields (enableDNS, forceConflicts, skipRefresh, takeOwnership, etc.) - Document print-env command and missing CLI flags - Document kubectlApply hook field - Document environment defaults field and merge order - Document kubedogQPS/kubedogBurst advanced settings - Document template partials (_*.tpl) auto-loading Cleanup: - Fix Docker image version from v0.156.0 to v1.1.0 - Fix heading nesting in advanced-features.md - Update experimental-features.md with current features - Fix broken cross-references and anchor links Signed-off-by: yxxhero * Revert changes to pkg/app from docs/restructure-and-improve branch Signed-off-by: yxxhero * docs: add create subcommand to README and CLI reference Signed-off-by: yxxhero --------- Signed-off-by: yxxhero Signed-off-by: yxxhero --- README-zh_CN.md | 8 +- README.md | 8 +- docs/advanced-features.md | 56 +- docs/builtin-objects.md | 4 +- docs/cli.md | 380 +++++++ docs/configuration.md | 528 +++++++++ docs/environments.md | 332 ++++++ docs/experimental-features.md | 27 +- docs/hooks.md | 294 +++++ docs/index.md | 1914 ++------------------------------- docs/integrations.md | 136 +++ docs/releases.md | 188 ++++ docs/templating.md | 236 ++++ docs/writing-helmfile.md | 4 +- mkdocs.yml | 23 +- 15 files changed, 2288 insertions(+), 1850 deletions(-) create mode 100644 docs/cli.md create mode 100644 docs/configuration.md create mode 100644 docs/environments.md create mode 100644 docs/hooks.md create mode 100644 docs/integrations.md create mode 100644 docs/releases.md create mode 100644 docs/templating.md diff --git a/README-zh_CN.md b/README-zh_CN.md index 33cf1b80..dafa3ab9 100644 --- a/README-zh_CN.md +++ b/README-zh_CN.md @@ -72,7 +72,13 @@ Helmfile 是一个声明式Helm Chart管理工具 让我们从最简单的 helmfile 开始,逐渐改进它以适应您的用例! -假设表示您 helm releases 的期望状态的 helmfile.yaml 看起来像这样: +使用脚手架命令生成具有最佳实践目录结构的项目: + +```console +helmfile create my-project && cd my-project +``` + +或者手动创建 `helmfile.yaml`。假设表示您 helm releases 的期望状态的 helmfile.yaml 看起来像这样: ```yaml repositories: diff --git a/README.md b/README.md index 9f6b2d0e..e170cd0f 100644 --- a/README.md +++ b/README.md @@ -86,7 +86,13 @@ requirements: [Go](https://golang.org/dl/) Let's start with a simple `helmfile` and gradually improve it to fit your use-case! -Suppose the `helmfile.yaml` representing the desired state of your helm releases looks like: +Generate a project scaffold with best-practice directory structure: + +```console +helmfile create my-project && cd my-project +``` + +Or create a `helmfile.yaml` manually. Suppose the `helmfile.yaml` representing the desired state of your helm releases looks like: ```yaml repositories: diff --git a/docs/advanced-features.md b/docs/advanced-features.md index 555c6bfc..63238b51 100644 --- a/docs/advanced-features.md +++ b/docs/advanced-features.md @@ -1,4 +1,4 @@ -## Advanced Features +# Advanced Features - [Resource Tracking with Kubedog](#resource-tracking-with-kubedog) - [Import Configuration Parameters into Helmfile](#import-configuration-parameters-into-helmfile) @@ -6,11 +6,11 @@ - [Adhoc Kustomization of Helm Charts](#adhoc-kustomization-of-helm-charts) - [Adding dependencies without forking the chart](#adding-dependencies-without-forking-the-chart) -### Resource Tracking with Kubedog +## Resource Tracking with Kubedog Helmfile can use [kubedog](https://github.com/werf/kubedog) for advanced resource tracking instead of Helm's built-in `--wait` flag. This provides more detailed feedback and control over deployment progress. -#### Basic Usage +### Basic Usage Enable kubedog tracking in your `helmfile.yaml`: @@ -29,13 +29,13 @@ Or use command-line flags: helmfile apply --track-mode kubedog --track-timeout 300 --track-logs ``` -#### Configuration Options +### Configuration Options - **`trackMode`**: Set to `kubedog` to enable kubedog tracking, or `helm-legacy` to use Helm v4's legacy wait mode (default: `helm`) - **`trackTimeout`**: Timeout in seconds for tracking resources (default: 300) - **`trackLogs`**: Enable real-time log streaming from tracked resources -#### Track Modes +### Track Modes Helmfile supports three track modes: @@ -43,7 +43,7 @@ Helmfile supports three track modes: - **`helm-legacy`**: Uses Helm v4's `--wait=legacy` flag. This is useful when migrating from Helm v3 to Helm v4 and you have charts that may have compatibility issues with the new watcher-based wait mechanism (e.g., charts with `livenessProbe` but no `startupProbe`). Note: This mode only works with Helm v4; with Helm v3 it falls back to regular `--wait`. - **`kubedog`**: Uses kubedog for advanced resource tracking with detailed feedback -#### Resource Filtering +### Resource Filtering Control which resources to track using whitelist/blacklist: @@ -62,7 +62,7 @@ releases: - Secret ``` -#### Specific Resource Tracking +### Specific Resource Tracking Track only specific resources by name and namespace: @@ -79,7 +79,7 @@ releases: name: myapp-job ``` -#### Priority Rules +### Priority Rules Resource filtering follows this priority (highest to lowest): @@ -87,14 +87,14 @@ Resource filtering follows this priority (highest to lowest): 2. **`skipKinds`**: Blacklist resource kinds 3. **`trackKinds`**: Whitelist resource kinds -#### Benefits +### Benefits - **Real-time feedback**: See deployment progress with detailed status updates - **Log streaming**: View container logs during deployment - **Fine-grained control**: Track only the resources you care about - **Better debugging**: Immediate visibility into deployment issues -#### Helm v4 Legacy Wait Mode +### Helm v4 Legacy Wait Mode When using Helm v4 with charts that have broken `livenessProbe` configurations without `startupProbe`, the default `--wait=watcher` mode may fail. Helm v4 introduces `--wait=legacy` which uses the simpler polling mechanism compatible with Helm v3's behavior. @@ -113,16 +113,32 @@ Or via command-line: helmfile apply --track-mode helm-legacy ``` -#### Compatibility +### Compatibility - **`helm`**: Default mode, uses Helm's built-in `--wait` flag - **`helm-legacy`**: Uses Helm v4's `--wait=legacy` flag (only available in Helm v4) - **`kubedog`**: Uses kubedog library for advanced resource tracking - Kubedog tracking is compatible with Helm 3.x and 4.x - Kubedog is a compiled dependency and is only used when `trackMode: kubedog` is set -- Works with charts that deploy supported workload kinds (currently `Deployment`, `StatefulSet`, `DaemonSet`, and `Job`); other resource kinds are created by Helm/Helmfile as usual but are ignored by the kubedog tracker +- Works with charts that deploy supported workload kinds (currently `Deployment`, `StatefulSet`, `DaemonSet`, `Job`, and `Canary`); other resource kinds are created by Helm/Helmfile as usual but are ignored by the kubedog tracker -### Import Configuration Parameters into Helmfile +### Advanced Kubedog Settings + +```yaml +releases: + - name: myapp + chart: ./charts/myapp + trackMode: kubedog + trackTimeout: 600 + trackLogs: true + kubedogQPS: 5.0 + kubedogBurst: 10 +``` + +- **`kubedogQPS`**: QPS (queries per second) for the kubedog kubernetes client (default: uses cluster defaults) +- **`kubedogBurst`**: Burst for the kubedog kubernetes client (default: uses cluster defaults) + +## Import Configuration Parameters into Helmfile Helmfile integrates [vals]() to import configuration parameters from following backends: @@ -136,7 +152,7 @@ See [Vals "Supported Backends"](https://github.com/helmfile/vals#supported-backe This feature was implemented in https://github.com/roboll/helmfile/pull/906. If you're curious about how it's designed and how it works, please review the pull request. -### Deploy Kustomizations with Helmfile +## Deploy Kustomizations with Helmfile You can deploy [kustomize](https://github.com/kubernetes-sigs/kustomize) "kustomization"s with Helmfile. @@ -212,7 +228,7 @@ After all, Helmfile just installs the temporary chart like standard charts, whic Please also see [test/advanced/helmfile.yaml](https://github.com/helmfile/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of kustomization support and more. -### Adhoc Kustomization of Helm charts +## Adhoc Kustomization of Helm charts With Helmfile's integration with Kustomize, not only deploying Kustomization as a Helm chart, you can kustomize charts before installation. @@ -225,7 +241,7 @@ Currently, Helmfile allows you to set the following fields for kustomizing the c - `releases[].jsonPatches` - [`releases[].transformers`](#transformers) -#### `strategicMergePatches` +### `strategicMergePatches` You can add/update any Kubernetes resource field rendered from a Helm chart by specifying `releases[].strategicMergePatches`: @@ -269,7 +285,7 @@ There's also `releases[].jsonPatches` that works similarly to `strategicMergePat Please also see [test/advanced/helmfile.yaml](https://github.com/helmfile/helmfile/tree/master/test/advanced/helmfile.yaml) for an example of patching support and more. -#### `transformers` +### `transformers` You can set `transformers` to apply [Kustomize's transformers](https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead). @@ -331,7 +347,7 @@ transformers: Please see https://github.com/kubernetes-sigs/kustomize/blob/master/examples/configureBuiltinPlugin.md#configuring-the-builtin-plugins-instead for more information on how to declare transformers. -### Adding dependencies without forking the chart +## Adding dependencies without forking the chart With Helmfile, you can add chart dependencies to a Helm chart without forking it. @@ -418,7 +434,7 @@ dependencies: Please read https://github.com/roboll/helmfile/issues/1762#issuecomment-816341251 for more details. -#### OCI chart dependencies +### OCI chart dependencies With Helmfile version v0.146.0 or later, you can add OCI chart to chart dependencies. @@ -433,7 +449,7 @@ releases: version: 1.5 ``` -### Lockfile per environment +## Lockfile per environment In some cases it can be handy for CI/CD pipelines to be able to roll out updates gradually for environments, such as staging and production while using the same set of charts. This can be achieved by using `lockFilePath` in combination with environments, such as: diff --git a/docs/builtin-objects.md b/docs/builtin-objects.md index a3039854..2daca963 100644 --- a/docs/builtin-objects.md +++ b/docs/builtin-objects.md @@ -1,4 +1,6 @@ -# helmfile template built-in objects +# Built-in Objects + +## helmfile template built-in objects - `Environment`: The information about the environment. This is set by the `--environment` flag. It has several objects inside of it: diff --git a/docs/cli.md b/docs/cli.md new file mode 100644 index 00000000..cc264720 --- /dev/null +++ b/docs/cli.md @@ -0,0 +1,380 @@ +# CLI Reference + +## CLI Reference + +``` +Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot +V1 mode = false +YAML library = go.yaml.in/yaml/v3 + +Usage: + helmfile [command] + +Available Commands: + apply Apply all resources from state file only when there are changes + build Build all resources from state file + create Create a helmfile deployment project scaffold + cache Cache management + charts DEPRECATED: sync releases from state file (helm upgrade --install) + completion Generate the autocompletion script for the specified shell + delete DEPRECATED: delete releases from state file (helm delete) + deps Update charts based on their requirements + destroy Destroys and then purges releases + diff Diff releases defined in state file + fetch Fetch charts from state file + help Help about any command + init Initialize the helmfile, includes version checking and installation of helm and plug-ins + lint Lint charts from state file (helm lint) + list List releases defined in state file + repos Add chart repositories defined in state file + show-dag It prints a table with 3 columns, GROUP, RELEASE, and DEPENDENCIES. GROUP is the unsigned, monotonically increasing integer starting from 1. All the releases with the same GROUP are deployed concurrently. Everything in GROUP 2 starts being deployed only after everything in GROUP 1 got successfully deployed. RELEASE is the release that belongs to the GROUP. DEPENDENCIES is the list of releases that the RELEASE depends on. It should always be empty for releases in GROUP 1. DEPENDENCIES for a release in GROUP 2 should have some or all dependencies appeared in GROUP 1. It can be "some" because Helmfile simplifies the DAGs of releases into a DAG of groups, so that Helmfile always produce a single DAG for everything written in helmfile.yaml, even when there are technically two or more independent DAGs of releases in it. + status Retrieve status of releases in state file + sync Sync releases defined in state file + template Template releases defined in state file + test Test charts from state file (helm test) + unittest Unit test charts from state file using helm-unittest plugin + version Print the CLI version + write-values Write values files for releases. Similar to `helmfile template`, write values files instead of manifests. + +Flags: + --allow-no-matching-release Do not exit with an error code if the provided selector has no matching releases. + -c, --chart string Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }} + --color Output with color + --debug Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect + --disable-force-update do not force helm repos to update when executing "helm repo add" + --enable-live-output Show live output from the Helm binary Stdout/Stderr into Helmfile own Stdout/Stderr. + It only applies for the Helm CLI commands, Stdout/Stderr for Hooks are still displayed only when it's execution finishes. + -e, --environment string specify the environment name. Overrides "HELMFILE_ENVIRONMENT" OS environment variable when specified. defaults to "default" + -f, --file helmfile.yaml load config from file or directory. defaults to "helmfile.yaml" or "helmfile.yaml.gotmpl" or "helmfile.d" (means "helmfile.d/*.yaml" or "helmfile.d/*.yaml.gotmpl") in this preference. Specify - to load the config from the standard input. + -b, --helm-binary string Path to the helm binary (default "helm") + -h, --help help for helmfile + -i, --interactive Request confirmation before attempting to modify clusters + --kube-context string Set kubectl context. Uses current context by default + -k, --kustomize-binary string Path to the kustomize binary (default "kustomize") + --log-level string Set log level, default info (default "info") + -n, --namespace string Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }} + --no-color Output without color + -q, --quiet Silence output. Equivalent to log-level warn + -l, --selector stringArray Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. + A release must match all labels in a group in order to be used. Multiple groups can be specified at once. + "--selector tier=frontend,tier!=proxy --selector tier=backend" will match all frontend, non-proxy releases AND all backend releases. + The name of a release can be used as a label: "--selector name=myrelease" + --skip-deps skip running "helm repo update" and "helm dependency build" + --state-values-file stringArray specify state values in a YAML file. Used to override .Values within the helmfile template (not values template). + --state-values-set stringArray set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template). + --state-values-set-string stringArray set state STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template). + --sequential-helmfiles Process helmfile.d files sequentially in alphabetical order instead of in parallel + --strip-args-values-on-exit-error Strip the potential secret values of the helm command args contained in a helmfile error message (default true) + -v, --version version for helmfile + +Use "helmfile [command] --help" for more information about a command. +``` + +**Note:** Each command has its own specific flags. Use `helmfile [command] --help` to see command-specific options. For example, `helmfile sync --help` shows operational flags like `--timeout`, `--wait`, and `--wait-for-jobs`. + +### init + +The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically. + +### cache + +The `helmfile cache` sub-command is designed for cache management. Go-getter-backed remote file system are cached by `helmfile`. There is no TTL implemented, if you need to update the cached files or directories, you need to clean individually or run a full cleanup with `helmfile cache cleanup` + +#### OCI Chart Cache + +OCI charts are cached in the shared cache directory (`~/.cache/helmfile` by default, or `$HELMFILE_CACHE_HOME`). This cache is shared across all helmfile processes. + +**Cache Behavior:** + +- When a chart exists in the shared cache and is valid, it is reused without re-downloading +- The `--skip-refresh` flag can be used to skip checking for updates to cached charts stored in process-specific temporary directories (it does not affect charts already present in the shared cache) +- When running multiple helmfile processes in parallel (e.g., as an ArgoCD plugin), charts in the shared cache are not refreshed/deleted to prevent race conditions + +**Forcing a Cache Refresh:** + +To force a refresh of cached OCI charts, run: +```bash +helmfile cache cleanup +``` + +This will clear the shared cache, allowing the next helmfile command to re-download charts. + +#### cache info + +Display information about the cache directory. + +#### cache cleanup + +Remove all cached files from the cache directory. + +### sync + +The `helmfile sync` sub-command sync your cluster state as described in your `helmfile`. The default helmfile is `helmfile.yaml`, but any YAML file can be passed by specifying a `--file path/to/your/yaml/file` flag. + +Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the +dependencies of any referenced local charts. + +#### Common sync flags + +* `--timeout SECONDS` - Override the default timeout for all releases in this sync operation. This takes precedence over `helmDefaults.timeout` and per-release `timeout` settings. +* `--wait` - Override the default wait behavior for all releases +* `--wait-for-jobs` - Override the default wait-for-jobs behavior for all releases + +Examples: + +```bash +# Override timeout for all releases to 10 minutes +helmfile sync --timeout 600 + +# Combine timeout with wait flags +helmfile sync --timeout 900 --wait --wait-for-jobs + +# Target specific releases with custom timeout +helmfile sync --selector tier=backend --timeout 1200 +``` + +For Helm 2.9+ you can use a username and password to authenticate to a remote repository. + +### deps + +The `helmfile deps` sub-command locks your helmfile state and local charts dependencies. + +It basically runs `helm dependency update` on your helmfile state file and all the referenced local charts, so that you get a "lock" file per each helmfile state or local chart. + +All the other `helmfile` sub-commands like `sync` use chart versions recorded in the lock files, so that e.g. untested chart versions won't suddenly get deployed to the production environment. + +For example, the lock file for a helmfile state file named `helmfile.1.yaml` will be `helmfile.1.lock`. The lock file for a local chart would be `requirements.lock`, which is the same as `helm`. + +The lock file can be changed using `lockFilePath` in helm state, which makes it possible to for example have a different lock file per environment via templating. + +It is recommended to version-control all the lock files, so that they can be used in the production deployment pipeline for extra reproducibility. + +To bring in chart updates systematically, it would also be a good idea to run `helmfile deps` regularly, test it, and then update the lock files in the version-control system. + +### diff + +The `helmfile diff` sub-command executes the [helm-diff](https://github.com/databus23/helm-diff) plugin across all of +the charts/releases defined in the manifest. + +To supply the diff functionality Helmfile needs the [helm-diff](https://github.com/databus23/helm-diff) plugin v2.9.0+1 or greater installed. For Helm 2.3+ +you should be able to simply execute `helm plugin install https://github.com/databus23/helm-diff`. For more details +please look at their [documentation](https://github.com/databus23/helm-diff#helm-diff-plugin). + +### apply + +The `helmfile apply` sub-command begins by executing `diff`. If `diff` finds that there is any changes, `sync` is executed. Adding `--interactive` instructs Helmfile to request your confirmation before `sync`. + +An expected use-case of `apply` is to schedule it to run periodically, so that you can auto-fix skews between the desired and the current state of your apps running on Kubernetes clusters. + +### destroy + +The `helmfile destroy` sub-command uninstalls and purges all the releases defined in the manifests. + +`helmfile --interactive destroy` instructs Helmfile to request your confirmation before actually deleting releases. + +`destroy` basically runs `helm uninstall --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead. +If `--skip-charts` flag is not set, destroy would prepare all releases, by fetching charts and templating them. + +### delete (DEPRECATED) + +The `helmfile delete` sub-command deletes all the releases defined in the manifests. + +`helmfile --interactive delete` instructs Helmfile to request your confirmation before actually deleting releases. + +Note that `delete` doesn't purge releases. So `helmfile delete && helmfile sync` results in sync failed due to that releases names are not deleted but preserved for future references. If you really want to remove releases for reuse, add `--purge` flag to run it like `helmfile delete --purge`. +If `--skip-charts` flag is not set, destroy would prepare all releases, by fetching charts and templating them. + +### secrets + +The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin to be executed to decrypt the file. + +To supply the secret functionality Helmfile needs the `helm secrets` plugin installed. For Helm 2.3+ +you should be able to simply execute `helm plugin install https://github.com/jkroepke/helm-secrets +`. + +### test + +The `helmfile test` sub-command runs a `helm test` against specified releases in the manifest, default to all + +Use `--cleanup` to delete pods upon completion. + +### lint + +The `helmfile lint` sub-command runs a `helm lint` across all of the charts/releases defined in the manifest. Non local charts will be fetched into a temporary folder which will be deleted once the task is completed. + +### unittest + +The `helmfile unittest` sub-command runs `helm unittest` (from the [helm-unittest plugin](https://github.com/helm-unittest/helm-unittest)) on releases that have `unitTests` defined. It automatically generates the final merged values files for each release and passes them to `helm unittest`. + +This requires the `helm-unittest` plugin to be installed. You can install it with: + +```bash +helm plugin install https://github.com/helm-unittest/helm-unittest +``` + +Releases without `unitTests` defined are skipped. Non-local charts will be fetched into a temporary folder which will be deleted once the task is completed. + +Example helmfile configuration: + +```yaml +releases: + - name: my-app + chart: ./charts/my-app + values: + - values.yaml + unitTests: + - tests +``` + +The `unitTests` paths are relative to the chart directory and follow helm-unittest conventions. +If a path does not contain glob characters, it is treated as a directory and `/*_test.yaml` is appended automatically. +You can also specify explicit glob patterns (e.g., `tests/**/*_test.yaml`). + +Running `helmfile unittest` will: + +1. Merge all values files defined for the release +2. Run `helm unittest ./charts/my-app --values --file tests/*_test.yaml` + +You can pass additional flags: + +```bash +# Run with additional values +helmfile unittest --values extra-values.yaml + +# Run with --set overrides +helmfile unittest --set key=value + +# Target specific releases +helmfile unittest --selector name=my-app + +# Fail fast on first test failure +helmfile unittest --fail-fast + +# Enable colored output (Helm 3 only; ignored on Helm 4 due to flag parsing issues) +helmfile unittest --color + +# Enable verbose plugin output +helmfile unittest --debug-plugin + +# Pass extra arguments to helm unittest +helmfile unittest --args "--strict" +``` + +### create + +The `helmfile create` sub-command generates a helmfile deployment project scaffold with best-practice directory structure. + +```bash +# Create a project in a new directory +helmfile create my-project + +# Create a project in the current directory +helmfile create + +# Specify a custom output directory +helmfile create my-project --output-dir /path/to/project + +# Overwrite existing scaffold files +helmfile create my-project --force +``` + +This generates: + +* `helmfile.yaml` — Main configuration with commented examples for repositories, environments, and releases +* `environments/default.yaml` — Default environment values file +* `values/.gitkeep` — Placeholder for release-specific value files + +**Flags:** + +| Flag | Default | Description | +|------|---------|-------------| +| `-o`, `--output-dir` | `""` | Output directory (defaults to NAME or current directory) | +| `--force` | false | Overwrite existing scaffold files | + +The command validates the project name (no path separators, `.`, `..`, or whitespace-only names). Without `--force`, it atomically checks all target paths before writing to avoid partial scaffolds. + +### fetch + +The `helmfile fetch` sub-command downloads or copies local charts to a local directory for debug purpose. The local directory +must be specified with `--output-dir`. + +### list + +The `helmfile list` sub-command lists releases defined in the manifest. Optional `--output` flag accepts `json` to output releases in JSON format. + +If `--skip-charts` flag is not set, list would prepare all releases, by fetching charts and templating them. + +### version + +The `helmfile version` sub-command prints the version of Helmfile.Optional `-o` flag accepts `json` `yaml` `short` to output version in JSON, YAML or short format. + +default it will check for the latest version of Helmfile and print a tip if the current version is not the latest. To disable this behavior, set environment variable `HELMFILE_UPGRADE_NOTICE_DISABLED` to any non-empty value. + +### show-dag + +It prints a table with 3 columns, GROUP, RELEASE, and DEPENDENCIES. + +GROUP is the unsigned, monotonically increasing integer starting from 1. All the releases with the same GROUP are deployed concurrently. Everything in GROUP 2 starts being deployed only after everything in GROUP 1 got successfully deployed. + +RELEASE is the release that belongs to the GROUP. + +DEPENDENCIES is the list of releases that the RELEASE depends on. It should always be empty for releases in GROUP 1. DEPENDENCIES for a release in GROUP 2 should have some or all dependencies appeared in GROUP 1. It can be "some" because Helmfile simplifies the DAGs of releases into a DAG of groups, so that Helmfile always produce a single DAG for everything written in helmfile.yaml, even when there are technically two or more independent DAGs of releases in it. + +### print-env + +The `helmfile print-env` sub-command prints the parsed environment configuration including merged values (with decrypted secrets). This is useful for debugging environment configuration. + +```bash +# Print environment in YAML format (default) +helmfile print-env + +# Print environment in JSON format +helmfile print-env --output json + +# Print a specific environment +helmfile print-env -e production +``` + +### status + +The `helmfile status` sub-command retrieves the status of releases in the state file by running `helm status` for each release. + +### Additional CLI Flags + +The following global flags are also available but not shown in the main help output: + +| Flag | Default | Description | +|------|---------|-------------| +| `--kubeconfig` | `""` | Use a particular kubeconfig file | +| `--skip-refresh` | false | Skip running `helm repo update` (lighter than `--skip-deps` which also skips dependency build) | +| `--enforce-plugin-verification` | false | Fail plugin installation if verification is not supported | +| `--oci-plain-http` | false | Use plain HTTP for OCI registries (required for local/insecure registries in Helm 4) | + +#### fetch flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--output-dir` | temp dir | Directory to store charts. If not set, a temporary directory is used and deleted when the command terminates | +| `--output-dir-template` | (default template) | Go text template for generating the output directory. Available fields: `{{ .OutputDir }}`, `{{ .ChartName }}`, `{{ .Release.* }}`, `{{ .Environment.Name }}`, `{{ .Environment.KubeContext }}`, `{{ .Environment.Values.* }}` | +| `--write-output` | false | Write a helmfile.yaml to stdout with chart references updated to point to the downloaded local chart paths. Requires `--output-dir` | +| `--concurrency` | 0 | Maximum number of concurrent helm processes to run, 0 is unlimited | + +This is useful for air-gapped environments: download charts with `--output-dir` and `--write-output`, then transfer the output directory and the generated helmfile.yaml to the air-gapped environment. + +#### destroy flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--skip-charts` | false | Don't prepare charts when destroying releases | +| `--deleteWait` | false | Override helmDefaults.wait, sets `helm uninstall --wait` | +| `--deleteTimeout` | 300 | Time in seconds to wait for helm uninstall | +| `--cascade` | background | Pass cascade to helm exec | +| `--concurrency` | 0 | Maximum number of concurrent helm processes to run, 0 is unlimited | + +#### list flags + +| Flag | Default | Description | +|------|---------|-------------| +| `--skip-charts` | false | Don't prepare charts when listing releases | +| `--keep-temp-dir` | false | Keep temporary directory after listing | +| `--output` | `""` | Output format: `json` for JSON output | diff --git a/docs/configuration.md b/docs/configuration.md new file mode 100644 index 00000000..7fd94b0a --- /dev/null +++ b/docs/configuration.md @@ -0,0 +1,528 @@ +# Configuration Reference + +This page is a comprehensive reference for all options available in `helmfile.yaml`. + +**If you're new to Helmfile**, start with the [Getting Started](index.md#getting-started) tutorial on the home page, then read [Writing Helmfile](writing-helmfile.md) for patterns. Come back here when you need to look up a specific field. + +**CAUTION**: This documentation is for the development version of Helmfile. If you are looking for the documentation for any of releases, please switch to the corresponding release tag like [v0.143.4](https://github.com/helmfile/helmfile/tree/v0.143.4). + +## Quick Reference + +A `helmfile.yaml` has these top-level sections: + +| Section | Purpose | +|---------|---------| +| `repositories` | Helm chart repositories to use | +| `releases` | The Helm releases to deploy (the core of helmfile) | +| `helmDefaults` | Default Helm options for all releases | +| `environments` | Environment-specific values (dev, staging, prod) | +| `helmfiles` | Include other helmfile.yaml files (nesting) | +| `bases` | Shared base files merged before this helmfile | +| `values` | Default values available in templates | +| `commonLabels` | Labels applied to all releases | +| `templates` | Reusable release templates | +| `hooks` | Global lifecycle hooks | +| `apiVersions` / `kubeVersion` | Kubernetes version capabilities | + +## Full Reference + +The default name for a helmfile is `helmfile.yaml`: + +```yaml +# Chart repositories used from within this state file +# +# Use `helm-s3` and `helm-git` and whatever Helm Downloader plugins +# to use repositories other than the official repository or one backend by chartmuseum. +repositories: +# To use official "stable" charts a.k.a https://github.com/helm/charts/tree/master/stable +- name: stable + url: https://charts.helm.sh/stable +# To use official "incubator" charts a.k.a https://github.com/helm/charts/tree/master/incubator +- name: incubator + url: https://charts.helm.sh/incubator +# helm-git powered repository: You can treat any Git repository as a charts repository +- name: polaris + url: git+https://github.com/reactiveops/polaris@deploy/helm?ref=master +# Advanced configuration: You can setup basic or tls auth and optionally enable helm OCI integration +- name: roboll + url: roboll.io/charts + certFile: optional_client_cert + keyFile: optional_client_key + # username is retrieved from the environment with the format _USERNAME for CI usage, here ROBOLL_USERNAME + username: optional_username + # password is retrieved from the environment with the format _PASSWORD for CI usage, here ROBOLL_PASSWORD + password: optional_password + oci: true + passCredentials: true + verify: true + keyring: path/to/keyring.gpg +# Advanced configuration: You can use a ca bundle to use an https repo +# with a self-signed certificate +- name: insecure + url: https://charts.my-insecure-domain.com + caFile: optional_ca_crt +# Advanced configuration: You can skip the verification of TLS for an https repo +- name: skipTLS + url: https://ss.my-insecure-domain.com + skipTLSVerify: true +# Advanced configuration: Connect to a repo served over plain http +- name: plainHTTP + url: http://just.http.domain.com + plainHttp: true + +# context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext + +# Path to alternative helm binary (--helm-binary) +# Supports both Helm 3.x and Helm 4.x +helmBinary: path/to/helm + +# Path to alternative kustomize binary (--kustomize-binary) +kustomizeBinary: path/to/kustomize + +# Path to alternative lock file. The default is .lock, i.e for helmfile.yaml it's helmfile.lock. +lockFilePath: path/to/lock.file + +# Default values to set for args along with dedicated keys that can be set by contributors, cli args take precedence over these. +# In other words, unset values results in no flags passed to helm. +# See the helm usage (helm SUBCOMMAND -h) for more info on default values when those flags aren't provided. +helmDefaults: + kubeContext: kube-context #dedicated default key for kube-context (--kube-context) + cleanupOnFail: false #dedicated default key for helm flag --cleanup-on-fail + # additional and global args passed to helm (default "") + args: + - "--set k=v" + diffArgs: + - "--suppress-secrets" + syncArgs: + - "--labels=app.kubernetes.io/managed-by=helmfile" + # verify the chart before upgrading (only works with packaged charts not directories) (default false) + verify: true + keyring: path/to/keyring.gpg + # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint' (default false) + skipSchemaValidation: false + # wait for k8s resources via --wait. (default false) + wait: true + # DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm. + # This configuration is ignored and preserved only for backward compatibility. + # waitRetries: 3 + # if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false) + waitForJobs: true + # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) + timeout: 600 + # performs pods restart for the resource if applicable (default false) + recreatePods: true + # forces resource update through delete/recreate if needed (default false) + force: false + # limit the maximum number of revisions saved per release. Use 0 for no limit. (default 10) + historyMax: 10 + # automatically create release namespaces if they do not exist (default true) + createNamespace: true + # if used with charts museum allows to pull unstable charts for deployment, for example: if 1.2.3 and 1.2.4-dev versions exist and set to true, 1.2.4-dev will be pulled (default false) + devel: true + # When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart. + # Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547 + skipDeps: false + # If set to true, reuses the last release's values and merges them with ones provided in helmfile. + # This attribute, can be overriden in CLI with --reset/reuse-values flag of apply/sync/diff subcommands + reuseValues: false + # propagate `--post-renderer` to helmv3 template and helm install + postRenderer: "path/to/postRenderer" + # propagate `--post-renderer-args` to helmv3 template and helm install. This allows using Powershell + # scripts on Windows as a post renderer + postRendererArgs: + - PowerShell + - "-Command" + - "theScript.ps1" + # cascade `--cascade` to helmv3 delete, available values: background, foreground, or orphan, default: background + cascade: "background" + # insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart + insecureSkipTLSVerify: false + # plainHttp is true if fetching the remote chart should be done using HTTP + plainHttp: false + # --wait flag for destroy/delete, if set to true, will wait until all resources are deleted before mark delete command as successful + deleteWait: false + # Timeout is the time in seconds to wait for helmfile destroy/delete (default 300) + deleteTimeout: 300 + # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 + suppressOutputLineRegex: + - "version" + # syncReleaseLabels is a list of labels to be added to the release when syncing. + syncReleaseLabels: false + + +# these labels will be applied to all releases in a Helmfile. Useful in templating if you have a helmfile per environment or customer and don't want to copy the same label to each release +commonLabels: + hello: world + +# The desired states of Helm releases. +# +# Helmfile runs various helm commands to converge the current state in the live cluster to the desired state defined here. +releases: + # Published chart example + - name: vault # name of this release + namespace: vault # target namespace + createNamespace: true # automatically create release namespace (default true) + labels: # Arbitrary key value pairs for filtering releases + foo: bar + chart: roboll/vault-secret-manager # the chart being installed to create this release, referenced by `repository/chart` syntax + version: ~1.24.1 # the semver of the chart. range constraint is supported + condition: vault.enabled # The values lookup key for filtering releases. Corresponds to the boolean value of `vault.enabled`, where `vault` is an arbitrary value + missingFileHandler: Warn # set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues. + missingFileHandlerConfig: + # Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error. + # See https://github.com/helmfile/helmfile/issues/392 + ignoreMissingGitBranch: true + # Values files used for rendering the chart + values: + # Value files passed via --values + - vault.yaml + # Inline values, passed via a temporary values file and --values, so that it doesn't suffer from type issues like --set + - address: https://vault.example.com + # Go template available in inline values and values files. + - image: + # The end result is more or less YAML. So do `quote` to prevent number-like strings from accidentally parsed into numbers! + # See https://github.com/roboll/helmfile/issues/608 + tag: {{ requiredEnv "IMAGE_TAG" | quote }} + # Otherwise: + # tag: "{{ requiredEnv "IMAGE_TAG" }}" + # tag: !!string {{ requiredEnv "IMAGE_TAG" }} + db: + username: {{ requiredEnv "DB_USERNAME" }} + # value taken from environment variable. Quotes are necessary. Will throw an error if the environment variable is not set. $DB_PASSWORD needs to be set in the calling environment ex: export DB_PASSWORD='password1' + password: {{ requiredEnv "DB_PASSWORD" }} + proxy: + # Interpolate environment variable with a fixed string + 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 + - name: foo.config + file: path/to/file + # set a single array value in an array, translates to --set bar[0]={1,2} + - name: bar[0] + values: + - 1 + - 2 + # set a templated value + - name: namespace + value: {{ .Namespace }} + # will attempt to decrypt it using helm-secrets plugin + secrets: + - vault_secret.yaml + # Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods, force and reuseValues. + verify: true + keyring: path/to/keyring.gpg + # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint' (default false) + skipSchemaValidation: false + wait: true + # DEPRECATED: waitRetries is no longer supported - see documentation above + # waitRetries: 3 + waitForJobs: true + timeout: 60 + recreatePods: true + force: false + reuseValues: false + # set `false` to uninstall this release on sync. (default true) + installed: true + # Defines the strategy to use when updating. Possible value is: + # - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts). + updateStrategy: "" + # restores previous state in case of failed release (default false) + atomic: true + # when true, cleans up any new resources created during a failed release (default false) + cleanupOnFail: false + # --kube-context to be passed to helm commands + # See https://github.com/roboll/helmfile/issues/642 + # (default "", which means the standard kubeconfig, either ~/kubeconfig or the file pointed by $KUBECONFIG environment variable) + kubeContext: kube-context + # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 + # It may be helpful to deploy charts with helm api v1 CRDS + # https://github.com/roboll/helmfile/pull/1373 + disableValidation: false + # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 + # It is useful when any release contains custom resources for CRDs that is not yet installed onto the cluster. + # https://github.com/roboll/helmfile/pull/1618 + disableValidationOnInstall: false + # passes --disable-openapi-validation to helm diff plugin, this requires diff plugin >= 3.1.2 + # It may be helpful to deploy charts with helm api v1 CRDS + # https://github.com/roboll/helmfile/pull/1373 + disableOpenAPIValidation: false + # limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) + historyMax: 10 + # When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart. + # Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547 + skipDeps: false + # propagate `--post-renderer` to helmv3 template and helm install + postRenderer: "path/to/postRenderer" + # propagate `--post-renderer-args` to helmv3 template and helm install. This allows using Powershell + # scripts on Windows as a post renderer + postRendererArgs: + - PowerShell + - "-Command" + - "theScript.ps1" + # cascade `--cascade` to helmv3 delete, available values: background, foreground, or orphan, default: background + cascade: "background" + # insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart + insecureSkipTLSVerify: false + # plainHttp is true if fetching the remote chart should be done using HTTP + plainHttp: false + # suppressDiff skip the helm diff output. Useful for charts which produces large not helpful diff, default: false + suppressDiff: false + # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 + suppressOutputLineRegex: + - "version" + # syncReleaseLabels is a list of labels to be added to the release when syncing. + syncReleaseLabels: false + # unitTests is a list of test file or directory paths for helm-unittest integration. + # When specified, `helmfile unittest` will run `helm unittest` with the merged values and these test paths. + # Requires the helm-unittest plugin: https://github.com/helm-unittest/helm-unittest + unitTests: + - tests/vault + + + # Local chart example + - name: grafana # name of this release + namespace: another # target namespace + chart: ../my-charts/grafana # the chart being installed to create this release, referenced by relative path to local helmfile + values: + - "../../my-values/grafana/values.yaml" # Values file (relative path to manifest) + - ./values/{{ requiredEnv "PLATFORM_ENV" }}/config.yaml # Values file taken from path with environment variable. $PLATFORM_ENV must be set in the calling environment. + wait: true + +# +# Advanced Configuration: Nested States +# +helmfiles: +- # Path to the helmfile state file being processed BEFORE releases in this state file + path: path/to/subhelmfile.yaml + # Label selector used for filtering releases in the nested state. + # For example, `name=prometheus` in this context is equivalent to processing the nested state like + # helmfile -f path/to/subhelmfile.yaml -l name=prometheus sync + selectors: + - name=prometheus + # Override state values + values: + # Values files merged into the nested state's values + - additional.values.yaml + # One important aspect of using values here is that they first need to be defined in the values section + # of the origin helmfile, so in this example key1 needs to be in the values or environments.NAME.values of path/to/subhelmfile.yaml + # Inline state values merged into the nested state's values + - key1: val1 +- # All the nested state files under `helmfiles:` is processed in the order of definition. + # So it can be used for preparation for your main `releases`. An example would be creating CRDs required by `releases` in the parent state file. + path: path/to/mycrd.helmfile.yaml +- # Terraform-module-like URL for importing a remote directory and use a file in it as a nested-state file + # The nested-state file is locally checked-out along with the remote directory containing it. + # Therefore all the local paths in the file are resolved relative to the file + path: git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0 +- # By default git repositories aren't updated unless the ref is updated. + # Alternatively, refer to a named ref and disable the caching. + path: git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=main&cache=false +# If set to "Error", return an error when a subhelmfile points to a +# non-existent path. The default behavior is to print a warning and continue. +missingFileHandler: Error +missingFileHandlerConfig: + # Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error. + # See https://github.com/helmfile/helmfile/issues/392 + ignoreMissingGitBranch: true + +# +# Advanced Configuration: Environments +# + +# The list of environments managed by helmfile. +# +# The default is `environments: {"default": {}}` which implies: +# +# - `{{ .Environment.Name }}` evaluates to "default" +# - `{{ .Values }}` being empty +environments: + # The "default" environment is available and used when `helmfile` is run without `--environment NAME`. + default: + # Everything from the values.yaml is available via `{{ .Values.KEY }}`. + # Suppose `{"foo": {"bar": 1}}` contained in the values.yaml below, + # `{{ .Values.foo.bar }}` is evaluated to `1`. + values: + - environments/default/values.yaml + # Everything from the values.hcl in the `values` block is available via `{{ .Values.KEY }}`. + # More details in its dedicated section + - environments/default/values.hcl + # Each entry in values can be either a file path or inline values. + # The below is an example of inline values, which is merged to the `.Values` + - myChartVer: 1.0.0-dev + # Any environment other than `default` is used only when `helmfile` is run with `--environment NAME`. + # That is, the "production" env below is used when and only when it is run like `helmfile --environment production sync`. + production: + values: + - environments/production/values.yaml + - myChartVer: 1.0.0 + # disable vault release processing + - vault: + enabled: false + ## `secrets.yaml` is decrypted by `helm-secrets` and available via `{{ .Environment.Values.KEY }}` + secrets: + - environments/production/secrets.yaml + # Instructs helmfile to fail when unable to find a environment values file listed under `environments.NAME.values`. + # + # Possible values are "Error", "Warn", "Info", "Debug". The default is "Error". + # + # Use "Warn", "Info", or "Debug" if you want helmfile to not fail when a values file is missing, while just leaving + # a message about the missing file at the log-level. + missingFileHandler: Error + missingFileHandlerConfig: + # Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error. + # See https://github.com/helmfile/helmfile/issues/392 + ignoreMissingGitBranch: true + # kubeContext to use for this environment + kubeContext: kube-context + +# +# Advanced Configuration: Layering +# +# Helmfile merges all the "base" state files and this state file before processing. +# +# Assuming this state file is named `helmfile.yaml`, all the files are merged in the order of: +# environments.yaml <- defaults.yaml <- templates.yaml <- helmfile.yaml +bases: +- environments.yaml +- defaults.yaml +- templates.yaml + +# +# Advanced Configuration: API Capabilities +# +# 'helmfile template' renders releases locally without querying an actual cluster, +# and in this case `.Capabilities.APIVersions` cannot be populated. +# When a chart queries for a specific CRD or the Kubernetes version, this can lead to unexpected results. +# +# Note that `Capabilities.KubeVersion` is deprecated in Helm 3 and `helm template` won't populate it. +# All you can do is fix your chart to respect `.Capabilities.APIVersions` instead, rather than trying to figure out +# how to set `Capabilities.KubeVersion` in Helmfile. +# +# Configure a fixed list of API versions to pass to 'helm template' via the --api-versions flag with the below: +apiVersions: +- example/v1 + +# Set the kubeVersion to render the chart with your desired Kubernetes version. +# The flag --kube-version was deprecated in helm v3 but it was added again. +# For further information https://github.com/helm/helm/issues/7326 +kubeVersion: v1.21 +``` + +### Additional helmDefaults fields + +The following `helmDefaults` fields are also available but not shown in the example above: + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `enableDNS` | bool | false | Enable DNS lookups when rendering templates | +| `skipCRDs` | bool | false | Skip CRDs during installation | +| `skipRefresh` | bool | false | Skip running `helm dependency up` | +| `forceConflicts` | bool | false | Force server-side apply changes against conflicts (Helm 4 only) | +| `takeOwnership` | bool | false | Take ownership of existing resources | +| `trackMode` | string | `""` | Default tracking mode for resources. See [Advanced Features](advanced-features.md#resource-tracking-with-kubedog) | +| `disableAutoDetectedKubeVersionForDiff` | bool | false | Disable auto-detected kubeVersion being passed to helm diff | + +### Additional release fields + +The following per-release fields are also available: + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `valuesTemplate` | list | | Like `values` but template expressions are rendered before being passed to Helm | +| `setTemplate` | list | | Like `set` but template expressions are rendered before being passed to Helm | +| `apiVersions` | list | | Per-release API versions (overrides top-level `apiVersions`) | +| `kubeVersion` | string | | Per-release kube version (overrides top-level `kubeVersion`) | +| `valuesPathPrefix` | string | | Prefix for values file paths | +| `verifyTemplate` | string | | Templated verify flag (e.g., `{{ .Values.verify \| default "false" }}`) | +| `waitTemplate` | string | | Templated wait flag | +| `installedTemplate` | string | | Templated installed flag | +| `adopt` | list | | List of resources to adopt (passes `--adopt` to Helm) | +| `forceGoGetter` | bool | false | Force go-getter URL parsing for the chart field. Useful when go-getter URL parsing fails unexpectedly | +| `forceNamespace` | string | | Force namespace on all K8s resources rendered by the chart, even when the template doesn't use `{{ .Namespace }}`. Use with caution | +| `skipRefresh` | bool | false | Per-release skip for `helm dependency up` | +| `disableAutoDetectedKubeVersionForDiff` | bool | false | Disable auto-detected kubeVersion for helm diff on this release | +| `takeOwnership` | bool | false | Take ownership of existing resources for this release | +| `forceConflicts` | bool | false | Force server-side apply against conflicts (Helm 4 only) | +| `description` | string | | Description of the release | +| `enableDNS` | bool | false | Enable DNS lookups when rendering templates | + +### Release tracking fields (kubedog) + +See [Advanced Features](advanced-features.md#resource-tracking-with-kubedog) for more details: + +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `trackMode` | string | `""` | Track mode: `helm`, `helm-legacy`, or `kubedog` | +| `trackTimeout` | int | 300 | Tracking timeout in seconds | +| `trackLogs` | bool | false | Enable real-time log streaming | +| `trackKinds` | list | | Whitelist of resource kinds to track | +| `skipKinds` | list | | Blacklist of resource kinds to skip | +| `trackResources` | list | | Specific resources to track (objects with `kind`, `name`, `namespace`) | +| `kubedogQPS` | float | | QPS for kubedog kubernetes client | +| `kubedogBurst` | int | | Burst for kubedog kubernetes client | + +### Hook kubectlApply + +Hooks also support a `kubectlApply` field for running `kubectl apply` directly: + +```yaml +releases: +- name: myapp + chart: mychart + hooks: + - events: ["presync"] + showlogs: true + kubectlApply: + filename: manifests/my-resource.yaml +``` + +Or with kustomize: + +```yaml + hooks: + - events: ["presync"] + showlogs: true + kubectlApply: + kustomize: overlays/default/ +``` + +### Repository additional fields + +| Field | Type | Description | +|-------|------|-------------| +| `registryConfig` | string | Path to registry configuration file | +| `managed` | string | Managed repository mode | + +### Template Partials + +Files matching `_*.tpl` in the same directory as the helmfile are automatically loaded as helper templates. For example, a file named `_helpers.tpl` can define named templates that are reusable across your helmfile: + +`_helpers.tpl`: +``` +{{- define "myapp.labels" -}} +app: myapp +env: {{ .Environment.Name }} +{{- end -}} +``` + +`helmfile.yaml`: +```yaml +releases: +- name: myapp + chart: mychart + values: + - labels: {{ include "myapp.labels" . | toYaml | nindent 4 }} +``` diff --git a/docs/environments.md b/docs/environments.md new file mode 100644 index 00000000..41eb7476 --- /dev/null +++ b/docs/environments.md @@ -0,0 +1,332 @@ +# Environments + +## Environment + +When you want to customize the contents of `helmfile.yaml` or `values.yaml` files per environment, use this feature. + +You can define as many environments as you want under `environments` in `helmfile.yaml`. +`environments` section should be separated from `releases` with `---`. + +The environment name defaults to `default`, that is, `helmfile sync` implies the `default` environment. +The selected environment name can be referenced from `helmfile.yaml` and `values.yaml.gotmpl` by `{{ .Environment.Name }}`. + +If you want to specify a non-default environment, provide a `--environment NAME` flag to `helmfile` like `helmfile --environment production sync`. + +The below example shows how to define a production-only release: + +```yaml +environments: + default: + production: + +--- + +releases: +- name: newrelic-agent + installed: {{ eq .Environment.Name "production" | toYaml }} + # snip +- name: myapp + # snip +``` + +### Environment Values +Helmfile supports 3 values languages : +- Straight yaml +- Go templates to generate straight yaml +- HCL + +Environment Values allows you to inject a set of values specific to the selected environment, into `values.yaml` templates. +Use it to inject common values from the environment to multiple values files, to make your configuration DRY. + +Suppose you have three files `helmfile.yaml`, `production.yaml` and `values.yaml.gotmpl`: + +`helmfile.yaml` + +```yaml +environments: + production: + values: + - production.yaml + +--- + +releases: +- name: myapp + values: + - values.yaml.gotmpl +``` + +`production.yaml` + +```yaml +domain: prod.example.com +releaseName: prod +``` + +`values.yaml.gotmpl` + +```yaml +domain: {{ .Values | get "domain" "dev.example.com" }} +``` + +`helmfile sync` installs `myapp` with the value `domain=dev.example.com`, +whereas `helmfile --environment production sync` installs the app with the value `domain=prod.example.com`. + +For even more flexibility, you can now use values declared in the `environments:` section in other parts of your helmfiles: + +consider: +`default.yaml` + +```yaml +domain: dev.example.com +releaseName: dev +``` + +```yaml +environments: + default: + values: + - default.yaml + production: + values: + - production.yaml # bare .yaml file, content will be used verbatim + - other.yaml.gotmpl # template directives with potential side-effects like `exec` and `readFile` will be honoured + +--- + +releases: +- name: myapp-{{ .Values.releaseName }} # release name will be one of `dev` or `prod` depending on selected environment + values: + - values.yaml.gotmpl +- name: production-specific-release + # this release would be installed only if selected environment is `production` + installed: {{ eq .Values.releaseName "prod" | toYaml }} + ... +``` + +#### HCL specifications + +Since Helmfile v0.164.0, HCL language is supported for environment values only. +HCL values supports interpolations and sharing values across files + +* Only `.hcl` suffixed files will be interpreted as is +* Helmfile supports 2 different blocks: `values` and `locals` +* `values` block is a shared block where all values are accessible everywhere in all loaded files +* `locals` block can't reference external values apart from the ones in the block itself, and where its defined values are only accessible in its local file +* Only values in `values` blocks are made available to the final root `.Values` (e.g : ` values { myvar = "var" }` is accessed through `{{ .Values.myvar }}`) +* There can only be 1 `locals` block per file +* Helmfile hcl `values` are referenced using the `hv` accessor. +* Helmfile hcl `locals` are referenced using the `local` accessor. +* When the same key is defined multiple times across imported `.hcl` files in `values` blocks, values from later files override those from earlier files (last file loaded wins). Map values are merged per key, while list values are replaced as a whole (i.e. not deep-merged). Mixed-types overrides (e.g. bool -> string) are supported (latest value/type wins). +* All cty [standard library functions](`https://pkg.go.dev/github.com/zclconf/go-cty@v1.14.3/cty/function/stdlib`) are available and custom functions could be created in the future + +Consider the following example : + +```terraform +# values1.hcl +locals { + hostname = "host1" +} +values { + domain = "DEV.EXAMPLE.COM" + hostnameV1 = "${local.hostname}.${lower(hv.domain)}" # "host1.dev.example.com" +} +``` +```terraform +# values2.hcl +locals { + hostname = "host2" +} + +values { + hostnameV2 = "${local.hostname}.${hv.domain}" # "host2.DEV.EXAMPLE.COM" +} +``` +#### Note on Environment.Values vs Values + +The `{{ .Values.foo }}` syntax is the recommended way of using environment values. + +Prior to this [pull request](https://github.com/roboll/helmfile/pull/647), environment values were made available through the `{{ .Environment.Values.foo }}` syntax. +This is still working but is **deprecated** and the new `{{ .Values.foo }}` syntax should be used instead. + +You can read more infos about the feature proposal [here](https://github.com/roboll/helmfile/issues/640). + +### Environment Secrets + +Environment Secrets *(not to be confused with Kubernetes Secrets)* are encrypted versions of `Environment Values`. +You can list any number of `secrets.yaml` files created using `helm secrets` or `sops`, so that +Helmfile could automatically decrypt and merge the secrets into the environment values. + +First you must have the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin installed along with a +`.sops.yaml` file to configure the method of encryption (this can be in the same directory as your helmfile or +in the subdirectory containing your secrets files). + +Then suppose you have a secret `foo.bar` defined in `environments/production/secrets.yaml`: + +```yaml +foo.bar: "mysupersecretstring" +``` + +You can then encrypt it with `helm secrets enc environments/production/secrets.yaml` + +Then reference that encrypted file in `helmfile.yaml`: + +```yaml +environments: + production: + secrets: + - environments/production/secrets.yaml + +--- + +releases: +- name: myapp + chart: mychart + values: + - values.yaml.gotmpl +``` + +Then the environment secret `foo.bar` can be referenced by the below template expression in your `values.yaml.gotmpl`: + +```yaml +{{ .Values.foo.bar }} +``` + +#### Loading remote Environment secrets files + +Since Helmfile v0.149.0, you can use `go-getter`-style URLs to refer to remote secrets files, the same way as in values files: +```yaml +environments: + staging: + secrets: + - git::https://{{ env "GITHUB_PAT" }}@github.com/org/repo.git@/environments/staging.secret.yaml?ref=main + - http://$HOSTNAME/artifactory/example-repo-local/test.tgz@environments/staging.secret.yaml + production: + secrets: + - git::https://{{ env "GITHUB_PAT" }}@github.com/org/repo.git@/environments/production.secret.yaml?ref=main + - http://$HOSTNAME/artifactory/example-repo-local/test.tgz@environments/production.secret.yaml +``` + +### Loading remote Environment values files + +Since Helmfile v0.118.8, you can use `go-getter`-style URLs to refer to remote values files: + +```yaml +environments: + cluster-azure-us-west: + values: + - git::https://git.company.org/helmfiles/global/azure.yaml?ref=master + - git::https://git.company.org/helmfiles/global/us-west.yaml?ref=master + - git::https://gitlab.com/org/repository-name.git@/config/config.test.yaml?ref=main # Public Gilab Repo + cluster-gcp-europe-west: + values: + - git::https://git.company.org/helmfiles/global/gcp.yaml?ref=master + - git::https://git.company.org/helmfiles/global/europe-west.yaml?ref=master + - git::https://ci:{{ env "CI_JOB_TOKEN" }}@gitlab.com/org/repository-name.git@/config.dev.yaml?ref={{ env "APP_COMMIT_SHA" }} # Private Gitlab Repo + staging: + values: + - git::https://{{ env "GITHUB_PAT" }}@github.com/[$GITHUB_ORGorGITHUB_USER]/repository-name.git@/values.dev.yaml?ref=main #Github Private repo + - http://$HOSTNAME/artifactory/example-repo-local/test.tgz@values.yaml #Artifactory url +--- + +releases: + - ... +``` + +Since Helmfile v0.158.0, support more protocols, such as: s3, https, http +``` +values: + - s3::https://helm-s3-values-example.s3.us-east-2.amazonaws.com/values.yaml + - s3://helm-s3-values-example/subdir/values.yaml + - https://john:doe@helm-s3-values-example.s3.us-east-2.amazonaws.com/values.yaml + - http://helm-s3-values-example.s3.us-east-2.amazonaws.com/values.yaml +``` + +For more information about the supported protocols see: [go-getter Protocol-Specific Options](https://github.com/hashicorp/go-getter#protocol-specific-options-1). + +This is particularly useful when you co-locate helmfiles within your project repo but want to reuse the definitions in a global repo. + +### Environment values precedence +With the introduction of HCL, a new value precedence was introduced over environment values. +Here is the order of precedence from least to greatest (the last one overrides all others) +1. `yaml` / `yaml.gotmpl` +2. `hcl` +3. `yaml` secrets + +Example: + +--- + +```yaml +# values1.yaml +domain: "dev.example.com" +``` + +```terraform +# values2.hcl +values { + domain = "overdev.example.com" + env = "dev" + willBeOverridden = "override_me" +} +``` + +```terraform +# values3.hcl +values { + env = "local" +} +``` + +```yaml +# secrets.yml (assuming this one has been encrypted) +willBeOverridden: overridden +``` + +``` +# helmfile.yaml.gotmpl +environments: + default: + values: + - values1.yaml + - values2.hcl + - values3.hcl + secrets: + - secrets.yml +--- +releases: +- name: random-release + [...] + values: + domain: "{{ .Values.domain }}" # == "overdev.example.com" + env: "{{ .Values.env }}" # == "local" + willBeOverridden: "{{ .Values.willBeOverridden }}" # == "overridden" +``` + +### Environment defaults + +In addition to `values`, environments support a `defaults` block that provides a separate layer of default values. These are merged **before** `values`, giving `values` higher priority: + +```yaml +environments: + default: + defaults: + - cluster: dev + replicas: 1 + values: + - replicas: 3 +``` + +The merge order for environment values is: + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ 1. Environment defaults (merged first, lowest priority) │ +│ 2. Environment values (yaml/yaml.gotmpl) │ +│ 3. Environment values (HCL) │ +│ 4. Environment secrets (non-HCL, decrypted) │ +│ 5. CLI overrides (--state-values-set, --state-values-file) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +In the example above, `{{ .Values.replicas }}` would be `3` (values overrides defaults) and `{{ .Values.cluster }}` would be `dev` (only defined in defaults). diff --git a/docs/experimental-features.md b/docs/experimental-features.md index f081173b..6cbb3e1b 100644 --- a/docs/experimental-features.md +++ b/docs/experimental-features.md @@ -1,7 +1,30 @@ # Experimental Features -This document describes the experimental features that are available in Helmfile v1. +This document describes the experimental features that are available in Helmfile. Any experimental feature may be removed or changed in a future release without notice. -- HCL helmfile-values-file support (PR #1423) +Enable experimental features with the environment variable: + +```bash +# Enable all experimental features +export HELMFILE_EXPERIMENTAL=true + +# Enable a specific feature +export HELMFILE_EXPERIMENTAL=explicit-selector-inheritance +``` + +## explicit-selector-inheritance + +By default, CLI selectors (e.g., `helmfile -l name=myapp sync`) are inherited by sub-helmfiles. This experimental feature changes the behavior so that sub-helmfiles without explicit `selectors` do **not** inherit selectors from their parent or the CLI. + +When enabled: +* Sub-helmfiles without `selectors` do not inherit parent/CLI selectors +* Use `selectorsInherited: true` on a sub-helmfile to explicitly opt into inheriting selectors +* `selectors: []` selects all releases (same as current behavior) + +See [Selectors and needs](releases.md#selectors) for detailed examples. + +## HCL helmfile-values-file support + +HCL language is supported for environment values files (`.hcl` suffix). This was introduced as experimental in PR #1423 and is now a stable feature. See [Environments](environments.md#hcl-specifications) for details. diff --git a/docs/hooks.md b/docs/hooks.md new file mode 100644 index 00000000..9c538271 --- /dev/null +++ b/docs/hooks.md @@ -0,0 +1,294 @@ +# Hooks + +## Hooks + +A Helmfile hook is a per-release extension point that is composed of: + +* `events` +* `command` +* `args` +* `showlogs` +* `kubectlApply` (alternative to `command`/`args`) + +Helmfile triggers various `events` while it is running. +Once `events` are triggered, associated `hooks` are executed, by running the `command` with `args`. The standard output of the `command` will be displayed if `showlogs` is set and it's value is `true`. + +Hooks exec order follows the order of definition in the helmfile state. + +Currently supported `events` are: + +* `prepare` +* `preapply` +* `presync` +* `preuninstall` +* `postuninstall` +* `postsync` +* `cleanup` + +Hooks associated to `prepare` events are triggered after each release in your helmfile is loaded from YAML, before execution. +`prepare` hooks are triggered on the release as long as it is not excluded by the helmfile selector(e.g. `helmfile -l key=value`). + +Hooks associated to `presync` events are triggered before each release is synced (installed or upgraded) on the cluster. +This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`. + +`preapply` hooks are triggered before a release is uninstalled, installed, or upgraded as part of `helmfile apply`. +This is the ideal event to hook into when you are going to use `helmfile apply` for every kind of change. Note that preapply hooks will only run if at least one release has changes to apply. Be sure to make each `preapply` hook command idempotent. Otherwise, rerunning `helmfile apply` on a transient failure may end up either breaking your cluster, or the hook that runs for the second time will never succeed. + +`preuninstall` hooks are triggered immediately before a release is uninstalled as part of `helmfile apply`, `helmfile sync`, `helmfile delete`, and `helmfile destroy`. + +`postuninstall` hooks are triggered immediately after successful uninstall of a release while running `helmfile apply`, `helmfile sync`, `helmfile delete`, `helmfile destroy`. + +`postsync` hooks are triggered after each release is synced (installed or upgraded) on the cluster, regardless if the sync was successful or not. +This is the ideal place to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`. + +`cleanup` hooks are triggered after each release is processed. +This is the counterpart to `prepare`, as any release on which `prepare` has been triggered gets `cleanup` triggered as well. + +The following is an example hook that just prints the contextual information provided to hook: + +```yaml +releases: +- name: myapp + chart: mychart + # *snip* + hooks: + - events: ["prepare", "cleanup"] + showlogs: true + command: "echo" + args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\ +"] +``` + +Let's say you ran `helmfile --environment prod sync`, the above hook results in executing: + +``` +echo {{Environment.Name}} {{.Release.Name}} {{.HelmfileCommand}} +``` + +Whereas the template expressions are executed thus the command becomes: + +``` +echo prod myapp sync +``` + +Now, replace `echo` with any command you like, and rewrite `args` that actually conforms to the command, so that you can integrate any command that does: + +* templating +* linting +* testing + +Hooks expose additional template expressions: + +`.Event.Name` is the name of the hook event. + +`.Event.Error` is the error generated by a failed release, exposed for `postsync` hooks only when a release fails, otherwise its value is `nil`. + +You can use the hooks event expressions to send notifications to platforms such as `Slack`, `MS Teams`, etc. + +The following example passes arguments to a script which sends a notification: + +```yaml +releases: +- name: myapp + chart: mychart + # *snip* + hooks: + - events: + - presync + - postsync + showlogs: true + command: notify.sh + args: + - --event + - '{{`{{ .Event.Name }}`}}' + - --status + - '{{`{{ if .Event.Error }}failure{{ else }}success{{ end }}`}}' + - --environment + - '{{`{{ .Environment.Name }}`}}' + - --namespace + - '{{`{{ .Release.Namespace }}`}}' + - --release + - '{{`{{ .Release.Name }}`}}' +``` + +For templating, imagine that you created a hook that generates a helm chart on-the-fly by running an external tool like ksonnet, kustomize, or your own template engine. +It will allow you to write your helm releases with any language you like, while still leveraging goodies provided by helm. + +### Hooks, Kubectl and Environments + +Hooks can also be used in combination with small tasks using `kubectl` directly, +e.g.: in order to install a custom storage class. + +In the following example, a specific release depends on a custom storage class. +Further, all enviroments have a default kube context configured where releases are deployed into. +The `.Environment.KubeContext` is used in order to apply / remove the YAML to the correct context depending on the environment. + +`environments.yaml`: + +```yaml +environments: + dev: + values: + - ../values/default.yaml + - ../values/dev.yaml + kubeContext: dev-cluster + prod: + values: + - ../values/default.yaml + - ../values/prod.yaml + kubeContext: prod-cluster +``` + +`helmfile.yaml`: + +```yaml +bases: + - ./environments.yaml + +--- +releases: + - name: myService + namespace: my-ns + installed: true + chart: mychart + version: "1.2.3" + values: + - ../services/my-service/values.yaml.gotmpl + hooks: + - events: ["presync"] + showlogs: true + command: "kubectl" + args: + - "apply" + - "-f" + - "./custom-storage-class.yaml" + - "--context" + - "{{`{{.Environment.KubeContext}}`}}" + - events: ["postuninstall"] + showlogs: true + command: "kubectl" + args: + - "delete" + - "-f" + - "./custom-storage-class.yaml" + - "--context" + - "{{`{{.Environment.KubeContext}}`}}" +``` + +### Global Hooks + +In contrast to the per release hooks mentioned above these are run only once at the very beginning and end of the execution of a helmfile command and only the `prepare` and `cleanup` hooks are available respectively. + +They use the same syntax as per release hooks, but at the top level of your helmfile: + +```yaml +hooks: +- events: ["prepare", "cleanup"] + showlogs: true + command: "echo" + args: ["{{`{{.Environment.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\ +"] +``` + +### Helmfile + Kustomize + +Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features +like rollback, history, and so on? This section is for you! + +The combination of `hooks` and [helmify-kustomize](https://gist.github.com/mumoshu/f9d0bd98e0eb77f636f79fc2fb130690) +enables you to integrate [kustomize](https://github.com/kubernetes-sigs/kustomize) into Helmfile. + +That is, you can use `kustomize` to build a local helm chart from a kustomize overlay. + +Let's assume you have a kustomize project named `foo-kustomize` like this: + +``` +foo-kustomize/ +├── base +│   ├── configMap.yaml +│   ├── deployment.yaml +│   ├── kustomization.yaml +│   └── service.yaml +└── overlays + ├── default + │   ├── kustomization.yaml + │   └── map.yaml + ├── production + │   ├── deployment.yaml + │   └── kustomization.yaml + └── staging + ├── kustomization.yaml + └── map.yaml + +5 directories, 10 files +``` + +Write `helmfile.yaml`: + +```yaml +- name: kustomize + chart: ./foo + hooks: + - events: ["prepare", "cleanup"] + command: "../helmify" + args: ["{{`{{if eq .Event.Name \"prepare\"}}build{{else}}clean{{end}}`}}", "{{`{{.Release.Ch\ +art}}`}}", "{{`{{.Environment.Name}}`}}"] +``` + +Run `helmfile --environment staging sync` and see it results in helmfile running `kustomize build foo-kustomize/overlays/staging > foo/templates/all.yaml`. + +Voilà! You can mix helm releases that are backed by remote charts, local charts, and even kustomize overlays. + +### kubectlApply Hook + +Instead of specifying `command` and `args`, you can use the `kubectlApply` field to run `kubectl apply` directly: + +```yaml +releases: +- name: myapp + chart: mychart + hooks: + - events: ["presync"] + showlogs: true + kubectlApply: + filename: manifests/custom-resource.yaml +``` + +Or apply a kustomize overlay: + +```yaml + hooks: + - events: ["presync"] + showlogs: true + kubectlApply: + kustomize: overlays/default/ +``` + +The `kubectlApply` field accepts either: +* `filename:` - runs `kubectl apply -f ` +* `kustomize:` - runs `kubectl apply -k ` + +**Note:** `filename` and `kustomize` cannot be used together. When `kubectlApply` is set, the `command` field is ignored with a warning. + +### Hook Template Data + +Hooks have access to the following template data: + +Per-release hooks: +* `{{ .Environment.Name }}` - the environment name +* `{{ .Environment.KubeContext }}` - the environment kube context +* `{{ .Release.Name }}` - the release name +* `{{ .Release.Namespace }}` - the release namespace +* `{{ .Release.Labels }}` - the release labels +* `{{ .Release.Chart }}` - the release chart +* `{{ .Values }}` - state values +* `{{ .HelmfileCommand }}` - the helmfile command name (e.g., `sync`, `apply`) +* `{{ .Event.Name }}` - the hook event name +* `{{ .Event.Error }}` - the error (available in `postsync` hooks when a release fails) + +Global hooks: +* `{{ .Environment.Name }}` - the environment name +* `{{ .HelmfileCommand }}` - the helmfile command name +* `{{ .Event.Name }}` - the hook event name +* `{{ .Event.Error }}` - the error + diff --git a/docs/index.md b/docs/index.md index 3783a2bb..dc47d7d4 100644 --- a/docs/index.md +++ b/docs/index.md @@ -54,7 +54,7 @@ To avoid upgrades for each iteration of `helm`, the `helmfile` executable delega ## Installation * download one of [releases](https://github.com/helmfile/helmfile/releases) -* [run as a container](https://helmfile.readthedocs.io/en/latest/#running-as-a-container) +* [run as a container](#running-as-a-container) * Archlinux: install via `pacman -S helmfile` * openSUSE: install via `zypper in helmfile` assuming you are on Tumbleweed; if you are on Leap you must add the [kubic](https://download.opensuse.org/repositories/devel:/kubic/) repo for your distribution version once before that command, e.g. `zypper ar https://download.opensuse.org/repositories/devel:/kubic/openSUSE_Leap_\$releasever kubic` * Windows (using [scoop](https://scoop.sh/)): `scoop install helmfile` @@ -62,16 +62,16 @@ To avoid upgrades for each iteration of `helm`, the `helmfile` executable delega ### Running as a container -The [Helmfile Docker images are available in GHCR](https://github.com/helmfile/helmfile/pkgs/container/helmfile). There is no `latest` tag, since the `0.x` versions can contain breaking changes, so make sure you pick the right tag. Example using `helmfile 0.156.0`: +The [Helmfile Docker images are available in GHCR](https://github.com/helmfile/helmfile/pkgs/container/helmfile). Make sure you pick the right tag for your version. Example: ```sh-session -$ docker run --rm --net=host -v "${HOME}/.kube:/helm/.kube" -v "${HOME}/.config/helm:/helm/.config/helm" -v "${PWD}:/wd" --workdir /wd ghcr.io/helmfile/helmfile:v0.156.0 helmfile sync +$ docker run --rm --net=host -v "${HOME}/.kube:/helm/.kube" -v "${HOME}/.config/helm:/helm/.config/helm" -v "${PWD}:/wd" --workdir /wd ghcr.io/helmfile/helmfile:v1.1.0 helmfile sync ``` You can also use a shim to make calling the binary easier: ```sh-session -$ printf '%s\n' '#!/bin/sh' 'docker run --rm --net=host -v "${HOME}/.kube:/helm/.kube" -v "${HOME}/.config/helm:/helm/.config/helm" -v "${PWD}:/wd" --workdir /wd ghcr.io/helmfile/helmfile:v0.156.0 helmfile "$@"' | +$ printf '%s\n' '#!/bin/sh' 'docker run --rm --net=host -v "${HOME}/.kube:/helm/.kube" -v "${HOME}/.config/helm:/helm/.config/helm" -v "${PWD}:/wd" --workdir /wd ghcr.io/helmfile/helmfile:v1.1.0 helmfile "$@"' | tee helmfile $ chmod +x helmfile $ ./helmfile sync @@ -79,821 +79,125 @@ $ ./helmfile sync ## Getting Started -Let's start with a simple `helmfile` and gradually improve it to fit your use-case! +### Prerequisites -Suppose the `helmfile.yaml` representing the desired state of your helm releases looks like: +* A Kubernetes cluster (e.g., [minikube](https://minikube.sigs.k8s.io/), [kind](https://kind.sigs.k8s.io/), or a cloud provider) +* [Helm 3+](https://helm.sh/docs/intro/install/) installed (`helm version` to verify) + +### Step 1: Install Helmfile + +Choose one of the following: + +```bash +# macOS +brew install helmfile + +# Linux - download from GitHub releases +curl -L https://github.com/helmfile/helmfile/releases/latest/download/helmfile_$(uname -s)_$(uname -m) -o /usr/local/bin/helmfile && chmod +x /usr/local/bin/helmfile + +# Windows +scoop install helmfile +``` + +Verify: `helmfile version` + +### Step 2: Create your first helmfile.yaml + +Use `helmfile create` to generate a project scaffold with best-practice directory structure: + +```bash +# Scaffold a new project +helmfile create my-project +cd my-project +``` + +Or create a `helmfile.yaml` manually: ```yaml repositories: - - name: prometheus-community - url: https://prometheus-community.github.io/helm-charts + - name: prometheus-community + url: https://prometheus-community.github.io/helm-charts releases: -- name: prom-norbac-ubuntu - namespace: prometheus - chart: prometheus-community/prometheus - set: - - name: rbac.create - value: false + - name: my-prometheus + namespace: monitoring + createNamespace: true + chart: prometheus-community/prometheus + values: + - server: + persistentVolume: + enabled: false ``` -Install required dependencies using [init](https://helmfile.readthedocs.io/en/latest/#init): +**What does this do?** +* `repositories` — tells Helm where to find charts (like a package registry) +* `releases` — each entry is a Helm release to deploy + * `name` — a unique name for this deployment + * `namespace` — which Kubernetes namespace to deploy into + * `chart` — which Helm chart to use (`repository-name/chart-name`) + * `values` — customize the chart's default settings -```console +### Step 3: Initialize and deploy + +```bash +# Initialize - checks helm and installs required plugins helmfile init -``` -Sync your Kubernetes cluster state to the desired one by running: +# See what would be deployed (dry-run) +helmfile diff -```console +# Deploy to your cluster helmfile apply ``` -Congratulations! You now have your first Prometheus deployment running inside your cluster. +Congratulations! You now have Prometheus running in your cluster. -Iterate on the `helmfile.yaml` by referencing: +### Step 4: Make changes and re-apply -* [Configuration](#configuration) -* [CLI reference](#cli-reference). -* [Helmfile Best Practices Guide](writing-helmfile.md) -* [Values Merging and Data Flow](values-and-merging.md) - Understanding how Helmfile merges values - -## Configuration - -**CAUTION**: This documentation is for the development version of Helmfile. If you are looking for the documentation for any of releases, please switch to the corresponding release tag like [v0.143.4](https://github.com/helmfile/helmfile/tree/v0.143.4). - -The default name for a helmfile is `helmfile.yaml`: - -```yaml -# Chart repositories used from within this state file -# -# Use `helm-s3` and `helm-git` and whatever Helm Downloader plugins -# to use repositories other than the official repository or one backend by chartmuseum. -repositories: -# To use official "stable" charts a.k.a https://github.com/helm/charts/tree/master/stable -- name: stable - url: https://charts.helm.sh/stable -# To use official "incubator" charts a.k.a https://github.com/helm/charts/tree/master/incubator -- name: incubator - url: https://charts.helm.sh/incubator -# helm-git powered repository: You can treat any Git repository as a charts repository -- name: polaris - url: git+https://github.com/reactiveops/polaris@deploy/helm?ref=master -# Advanced configuration: You can setup basic or tls auth and optionally enable helm OCI integration -- name: roboll - url: roboll.io/charts - certFile: optional_client_cert - keyFile: optional_client_key - # username is retrieved from the environment with the format _USERNAME for CI usage, here ROBOLL_USERNAME - username: optional_username - # password is retrieved from the environment with the format _PASSWORD for CI usage, here ROBOLL_PASSWORD - password: optional_password - oci: true - passCredentials: true - verify: true - keyring: path/to/keyring.gpg -# Advanced configuration: You can use a ca bundle to use an https repo -# with a self-signed certificate -- name: insecure - url: https://charts.my-insecure-domain.com - caFile: optional_ca_crt -# Advanced configuration: You can skip the verification of TLS for an https repo -- name: skipTLS - url: https://ss.my-insecure-domain.com - skipTLSVerify: true -# Advanced configuration: Connect to a repo served over plain http -- name: plainHTTP - url: http://just.http.domain.com - plainHttp: true - -# context: kube-context # this directive is deprecated, please consider using helmDefaults.kubeContext - -# Path to alternative helm binary (--helm-binary) -# Supports both Helm 3.x and Helm 4.x -helmBinary: path/to/helm - -# Path to alternative kustomize binary (--kustomize-binary) -kustomizeBinary: path/to/kustomize - -# Path to alternative lock file. The default is .lock, i.e for helmfile.yaml it's helmfile.lock. -lockFilePath: path/to/lock.file - -# Default values to set for args along with dedicated keys that can be set by contributors, cli args take precedence over these. -# In other words, unset values results in no flags passed to helm. -# See the helm usage (helm SUBCOMMAND -h) for more info on default values when those flags aren't provided. -helmDefaults: - kubeContext: kube-context #dedicated default key for kube-context (--kube-context) - cleanupOnFail: false #dedicated default key for helm flag --cleanup-on-fail - # additional and global args passed to helm (default "") - args: - - "--set k=v" - diffArgs: - - "--suppress-secrets" - syncArgs: - - "--labels=app.kubernetes.io/managed-by=helmfile" - # verify the chart before upgrading (only works with packaged charts not directories) (default false) - verify: true - keyring: path/to/keyring.gpg - # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint' (default false) - skipSchemaValidation: false - # wait for k8s resources via --wait. (default false) - wait: true - # DEPRECATED: waitRetries is no longer supported as the --wait-retries flag was removed from Helm. - # This configuration is ignored and preserved only for backward compatibility. - # waitRetries: 3 - # if set and --wait enabled, will wait until all Jobs have been completed before marking the release as successful. It will wait for as long as --timeout (default false) - waitForJobs: true - # time in seconds to wait for any individual Kubernetes operation (like Jobs for hooks, and waits on pod/pvc/svc/deployment readiness) (default 300) - timeout: 600 - # performs pods restart for the resource if applicable (default false) - recreatePods: true - # forces resource update through delete/recreate if needed (default false) - force: false - # limit the maximum number of revisions saved per release. Use 0 for no limit. (default 10) - historyMax: 10 - # automatically create release namespaces if they do not exist (default true) - createNamespace: true - # if used with charts museum allows to pull unstable charts for deployment, for example: if 1.2.3 and 1.2.4-dev versions exist and set to true, 1.2.4-dev will be pulled (default false) - devel: true - # When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart. - # Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547 - skipDeps: false - # If set to true, reuses the last release's values and merges them with ones provided in helmfile. - # This attribute, can be overriden in CLI with --reset/reuse-values flag of apply/sync/diff subcommands - reuseValues: false - # propagate `--post-renderer` to helmv3 template and helm install - postRenderer: "path/to/postRenderer" - # propagate `--post-renderer-args` to helmv3 template and helm install. This allows using Powershell - # scripts on Windows as a post renderer - postRendererArgs: - - PowerShell - - "-Command" - - "theScript.ps1" - # cascade `--cascade` to helmv3 delete, available values: background, foreground, or orphan, default: background - cascade: "background" - # insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart - insecureSkipTLSVerify: false - # plainHttp is true if fetching the remote chart should be done using HTTP - plainHttp: false - # --wait flag for destroy/delete, if set to true, will wait until all resources are deleted before mark delete command as successful - deleteWait: false - # Timeout is the time in seconds to wait for helmfile destroy/delete (default 300) - deleteTimeout: 300 - # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 - suppressOutputLineRegex: - - "version" - # syncReleaseLabels is a list of labels to be added to the release when syncing. - syncReleaseLabels: false - - -# these labels will be applied to all releases in a Helmfile. Useful in templating if you have a helmfile per environment or customer and don't want to copy the same label to each release -commonLabels: - hello: world - -# The desired states of Helm releases. -# -# Helmfile runs various helm commands to converge the current state in the live cluster to the desired state defined here. -releases: - # Published chart example - - name: vault # name of this release - namespace: vault # target namespace - createNamespace: true # automatically create release namespace (default true) - labels: # Arbitrary key value pairs for filtering releases - foo: bar - chart: roboll/vault-secret-manager # the chart being installed to create this release, referenced by `repository/chart` syntax - version: ~1.24.1 # the semver of the chart. range constraint is supported - condition: vault.enabled # The values lookup key for filtering releases. Corresponds to the boolean value of `vault.enabled`, where `vault` is an arbitrary value - missingFileHandler: Warn # set to either "Error" or "Warn". "Error" instructs helmfile to fail when unable to find a values or secrets file. When "Warn", it prints the file and continues. - missingFileHandlerConfig: - # Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error. - # See https://github.com/helmfile/helmfile/issues/392 - ignoreMissingGitBranch: true - # Values files used for rendering the chart - values: - # Value files passed via --values - - vault.yaml - # Inline values, passed via a temporary values file and --values, so that it doesn't suffer from type issues like --set - - address: https://vault.example.com - # Go template available in inline values and values files. - - image: - # The end result is more or less YAML. So do `quote` to prevent number-like strings from accidentally parsed into numbers! - # See https://github.com/roboll/helmfile/issues/608 - tag: {{ requiredEnv "IMAGE_TAG" | quote }} - # Otherwise: - # tag: "{{ requiredEnv "IMAGE_TAG" }}" - # tag: !!string {{ requiredEnv "IMAGE_TAG" }} - db: - username: {{ requiredEnv "DB_USERNAME" }} - # value taken from environment variable. Quotes are necessary. Will throw an error if the environment variable is not set. $DB_PASSWORD needs to be set in the calling environment ex: export DB_PASSWORD='password1' - password: {{ requiredEnv "DB_PASSWORD" }} - proxy: - # Interpolate environment variable with a fixed string - 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 - - name: foo.config - file: path/to/file - # set a single array value in an array, translates to --set bar[0]={1,2} - - name: bar[0] - values: - - 1 - - 2 - # set a templated value - - name: namespace - value: {{ .Namespace }} - # will attempt to decrypt it using helm-secrets plugin - secrets: - - vault_secret.yaml - # Override helmDefaults options for verify, wait, waitForJobs, timeout, recreatePods, force and reuseValues. - verify: true - keyring: path/to/keyring.gpg - # --skip-schema-validation flag to helm 'install', 'upgrade' and 'lint' (default false) - skipSchemaValidation: false - wait: true - # DEPRECATED: waitRetries is no longer supported - see documentation above - # waitRetries: 3 - waitForJobs: true - timeout: 60 - recreatePods: true - force: false - reuseValues: false - # set `false` to uninstall this release on sync. (default true) - installed: true - # Defines the strategy to use when updating. Possible value is: - # - "reinstallIfForbidden": Performs an uninstall before the update only if the update is forbidden (e.g., due to permission issues or conflicts). - updateStrategy: "" - # restores previous state in case of failed release (default false) - atomic: true - # when true, cleans up any new resources created during a failed release (default false) - cleanupOnFail: false - # --kube-context to be passed to helm commands - # See https://github.com/roboll/helmfile/issues/642 - # (default "", which means the standard kubeconfig, either ~/kubeconfig or the file pointed by $KUBECONFIG environment variable) - kubeContext: kube-context - # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 - # It may be helpful to deploy charts with helm api v1 CRDS - # https://github.com/roboll/helmfile/pull/1373 - disableValidation: false - # passes --disable-validation to helm diff plugin, this requires diff plugin >= 3.1.2 - # It is useful when any release contains custom resources for CRDs that is not yet installed onto the cluster. - # https://github.com/roboll/helmfile/pull/1618 - disableValidationOnInstall: false - # passes --disable-openapi-validation to helm diff plugin, this requires diff plugin >= 3.1.2 - # It may be helpful to deploy charts with helm api v1 CRDS - # https://github.com/roboll/helmfile/pull/1373 - disableOpenAPIValidation: false - # limit the maximum number of revisions saved per release. Use 0 for no limit (default 10) - historyMax: 10 - # When set to `true`, skips running `helm dep up` and `helm dep build` on this release's chart. - # Useful when the chart is broken, like seen in https://github.com/roboll/helmfile/issues/1547 - skipDeps: false - # propagate `--post-renderer` to helmv3 template and helm install - postRenderer: "path/to/postRenderer" - # propagate `--post-renderer-args` to helmv3 template and helm install. This allows using Powershell - # scripts on Windows as a post renderer - postRendererArgs: - - PowerShell - - "-Command" - - "theScript.ps1" - # cascade `--cascade` to helmv3 delete, available values: background, foreground, or orphan, default: background - cascade: "background" - # insecureSkipTLSVerify is true if the TLS verification should be skipped when fetching remote chart - insecureSkipTLSVerify: false - # plainHttp is true if fetching the remote chart should be done using HTTP - plainHttp: false - # suppressDiff skip the helm diff output. Useful for charts which produces large not helpful diff, default: false - suppressDiff: false - # suppressOutputLineRegex is a list of regex patterns to suppress output lines from helm diff (default []), available in helmfile v0.162.0 - suppressOutputLineRegex: - - "version" - # syncReleaseLabels is a list of labels to be added to the release when syncing. - syncReleaseLabels: false - # unitTests is a list of test file or directory paths for helm-unittest integration. - # When specified, `helmfile unittest` will run `helm unittest` with the merged values and these test paths. - # Requires the helm-unittest plugin: https://github.com/helm-unittest/helm-unittest - unitTests: - - tests/vault - - - # Local chart example - - name: grafana # name of this release - namespace: another # target namespace - chart: ../my-charts/grafana # the chart being installed to create this release, referenced by relative path to local helmfile - values: - - "../../my-values/grafana/values.yaml" # Values file (relative path to manifest) - - ./values/{{ requiredEnv "PLATFORM_ENV" }}/config.yaml # Values file taken from path with environment variable. $PLATFORM_ENV must be set in the calling environment. - wait: true - -# -# Advanced Configuration: Nested States -# -helmfiles: -- # Path to the helmfile state file being processed BEFORE releases in this state file - path: path/to/subhelmfile.yaml - # Label selector used for filtering releases in the nested state. - # For example, `name=prometheus` in this context is equivalent to processing the nested state like - # helmfile -f path/to/subhelmfile.yaml -l name=prometheus sync - selectors: - - name=prometheus - # Override state values - values: - # Values files merged into the nested state's values - - additional.values.yaml - # One important aspect of using values here is that they first need to be defined in the values section - # of the origin helmfile, so in this example key1 needs to be in the values or environments.NAME.values of path/to/subhelmfile.yaml - # Inline state values merged into the nested state's values - - key1: val1 -- # All the nested state files under `helmfiles:` is processed in the order of definition. - # So it can be used for preparation for your main `releases`. An example would be creating CRDs required by `releases` in the parent state file. - path: path/to/mycrd.helmfile.yaml -- # Terraform-module-like URL for importing a remote directory and use a file in it as a nested-state file - # The nested-state file is locally checked-out along with the remote directory containing it. - # Therefore all the local paths in the file are resolved relative to the file - path: git::https://github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=0.40.0 -- # By default git repositories aren't updated unless the ref is updated. - # Alternatively, refer to a named ref and disable the caching. - path: git::ssh://git@github.com/cloudposse/helmfiles.git@releases/kiam.yaml?ref=main&cache=false -# If set to "Error", return an error when a subhelmfile points to a -# non-existent path. The default behavior is to print a warning and continue. -missingFileHandler: Error -missingFileHandlerConfig: - # Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error. - # See https://github.com/helmfile/helmfile/issues/392 - ignoreMissingGitBranch: true - -# -# Advanced Configuration: Environments -# - -# The list of environments managed by helmfile. -# -# The default is `environments: {"default": {}}` which implies: -# -# - `{{ .Environment.Name }}` evaluates to "default" -# - `{{ .Values }}` being empty -environments: - # The "default" environment is available and used when `helmfile` is run without `--environment NAME`. - default: - # Everything from the values.yaml is available via `{{ .Values.KEY }}`. - # Suppose `{"foo": {"bar": 1}}` contained in the values.yaml below, - # `{{ .Values.foo.bar }}` is evaluated to `1`. - values: - - environments/default/values.yaml - # Everything from the values.hcl in the `values` block is available via `{{ .Values.KEY }}`. - # More details in its dedicated section - - environments/default/values.hcl - # Each entry in values can be either a file path or inline values. - # The below is an example of inline values, which is merged to the `.Values` - - myChartVer: 1.0.0-dev - # Any environment other than `default` is used only when `helmfile` is run with `--environment NAME`. - # That is, the "production" env below is used when and only when it is run like `helmfile --environment production sync`. - production: - values: - - environments/production/values.yaml - - myChartVer: 1.0.0 - # disable vault release processing - - vault: - enabled: false - ## `secrets.yaml` is decrypted by `helm-secrets` and available via `{{ .Environment.Values.KEY }}` - secrets: - - environments/production/secrets.yaml - # Instructs helmfile to fail when unable to find a environment values file listed under `environments.NAME.values`. - # - # Possible values are "Error", "Warn", "Info", "Debug". The default is "Error". - # - # Use "Warn", "Info", or "Debug" if you want helmfile to not fail when a values file is missing, while just leaving - # a message about the missing file at the log-level. - missingFileHandler: Error - missingFileHandlerConfig: - # Ignores missing git branch error so that the Debug/Info/Warn handler can treat a missing branch as non-error. - # See https://github.com/helmfile/helmfile/issues/392 - ignoreMissingGitBranch: true - # kubeContext to use for this environment - kubeContext: kube-context - -# -# Advanced Configuration: Layering -# -# Helmfile merges all the "base" state files and this state file before processing. -# -# Assuming this state file is named `helmfile.yaml`, all the files are merged in the order of: -# environments.yaml <- defaults.yaml <- templates.yaml <- helmfile.yaml -bases: -- environments.yaml -- defaults.yaml -- templates.yaml - -# -# Advanced Configuration: API Capabilities -# -# 'helmfile template' renders releases locally without querying an actual cluster, -# and in this case `.Capabilities.APIVersions` cannot be populated. -# When a chart queries for a specific CRD or the Kubernetes version, this can lead to unexpected results. -# -# Note that `Capabilities.KubeVersion` is deprecated in Helm 3 and `helm template` won't populate it. -# All you can do is fix your chart to respect `.Capabilities.APIVersions` instead, rather than trying to figure out -# how to set `Capabilities.KubeVersion` in Helmfile. -# -# Configure a fixed list of API versions to pass to 'helm template' via the --api-versions flag with the below: -apiVersions: -- example/v1 - -# Set the kubeVersion to render the chart with your desired Kubernetes version. -# The flag --kube-version was deprecated in helm v3 but it was added again. -# For further information https://github.com/helm/helm/issues/7326 -kubeVersion: v1.21 -``` - -## Templating - -Helmfile uses [Go templates](https://godoc.org/text/template) for templating your helmfile.yaml. While go ships several built-in functions, we have added all of the functions in the [Sprig library](https://godoc.org/github.com/Masterminds/sprig). - -We also added the following functions: - -* [`env`](templating_funcs.md#env) -* [`requiredEnv`](templating_funcs.md#requiredenv) -* [`exec`](templating_funcs.md#exec) -* [`envExec`](templating_funcs.md#envexec) -* [`readFile`](templating_funcs.md#readfile) -* [`readDir`](templating_funcs.md#readdir) -* [`readDirEntries`](templating_funcs.md#readdirentries) -* [`toYaml`](templating_funcs.md#toyaml) -* [`fromYaml`](templating_funcs.md#fromyaml) -* [`setValueAtPath`](templating_funcs.md#setvalueatpath) -* [`get`](templating_funcs.md#get) (Sprig's original `get` is available as `sprigGet`) -* [`getOrNil`](templating_funcs.md#getornil) -* [`tpl`](templating_funcs.md#tpl) -* [`required`](templating_funcs.md#required) -* [`fetchSecretValue`](templating_funcs.md#fetchsecretvalue) -* [`expandSecretRefs`](templating_funcs.md#expandsecretrefs) -* [`include`](templating_funcs.md#include) - -More details on each function can be found at the ["Template Functions" page in our documentation](templating_funcs.md). - - -## Using environment variables - -Environment variables can be used in most places for templating the helmfile. Currently this is supported for `name`, `namespace`, `value` (in set), `values` and `url` (in repositories). - -Examples: +Edit `helmfile.yaml` to add another release: ```yaml repositories: -- name: your-private-git-repo-hosted-charts - url: https://{{ requiredEnv "GITHUB_TOKEN"}}@raw.githubusercontent.com/kmzfs/helm-repo-in-github/master/ -``` + - name: prometheus-community + url: https://prometheus-community.github.io/helm-charts -```yaml releases: - - name: {{ requiredEnv "NAME" }}-vault - namespace: {{ requiredEnv "NAME" }} - chart: roboll/vault-secret-manager + - name: my-prometheus + namespace: monitoring + createNamespace: true + chart: prometheus-community/prometheus values: - - db: - username: {{ requiredEnv "DB_USERNAME" }} - password: {{ requiredEnv "DB_PASSWORD" }} - set: - - name: proxy.domain - value: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com - - name: proxy.scheme - value: {{ env "SCHEME" | default "https" }} + - server: + persistentVolume: + enabled: false + + - name: my-grafana + namespace: monitoring + chart: grafana/grafana ``` -### Note +Run `helmfile apply` again — Helmfile will detect the new release and only deploy what changed. -If you wish to treat your enviroment variables as strings always, even if they are boolean or numeric values you can use `{{ env "ENV_NAME" | quote }}` or `"{{ env "ENV_NAME" }}"`. These approaches also work with `requiredEnv`. - -### Useful internal Helmfile environment variables - -Helmfile uses some OS environment variables to override default behaviour: - -* `HELMFILE_DISABLE_INSECURE_FEATURES` - disable insecure features, expecting `true` lower case -* `HELMFILE_DISABLE_RUNNER_UNIQUE_ID` - disable unique logging ID, expecting any non-empty value -* `HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS` - disable insecure template functions, expecting `true` lower case -* `HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE` - expecting non-empty value to use `helm status` to check release existence, instead of `helm list` which is the default behaviour -* `HELMFILE_EXPERIMENTAL` - enable experimental features, expecting `true` lower case -* `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](https://helmfile.readthedocs.io/en/latest/#environment), it has lower priority than CLI argument `--environment` -* `HELMFILE_TEMPDIR` - specify directory to store temporary files -* `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](https://helmfile.readthedocs.io/en/latest/#version) -* `HELMFILE_GO_YAML_V3` - use *go.yaml.in/yaml/v3* instead of *go.yaml.in/yaml/v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x. -* `HELMFILE_CACHE_HOME` - specify directory to store cached files for remote operations -* `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file -* `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag -* `HELMFILE_RENDER_YAML` - force helmfile.yaml to be rendered as a Go template regardless of file extension, expecting `true` lower case. Useful for migrating from v0 to v1 without renaming files to `.gotmpl` -* `HELMFILE_AWS_SDK_LOG_LEVEL` - configure AWS SDK logging level for vals library. Valid values: `off` (default, secure, case-insensitive), `minimal`, `standard`, `verbose`, or custom comma-separated values like `request,response`. See issue #2270 for details -* `HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP` - enable strict mode for vals secret references. When set to `true` (or any value accepted by Go's `strconv.ParseBool` like `TRUE`, `1`), vals will fail when a referenced key does not exist in the secret map. Invalid values will cause an error when vals is initialized (when secret refs are first evaluated). Default is `false` (when unset or empty) for backward compatibility. See issue #1563 for details - -## CLI Reference - -``` -Declaratively deploy your Kubernetes manifests, Kustomize configs, and Charts as Helm releases in one shot -V1 mode = false -YAML library = go.yaml.in/yaml/v3 - -Usage: - helmfile [command] - -Available Commands: - apply Apply all resources from state file only when there are changes - build Build all resources from state file - cache Cache management - charts DEPRECATED: sync releases from state file (helm upgrade --install) - completion Generate the autocompletion script for the specified shell - delete DEPRECATED: delete releases from state file (helm delete) - deps Update charts based on their requirements - destroy Destroys and then purges releases - diff Diff releases defined in state file - fetch Fetch charts from state file - help Help about any command - init Initialize the helmfile, includes version checking and installation of helm and plug-ins - lint Lint charts from state file (helm lint) - list List releases defined in state file - repos Add chart repositories defined in state file - show-dag It prints a table with 3 columns, GROUP, RELEASE, and DEPENDENCIES. GROUP is the unsigned, monotonically increasing integer starting from 1. All the releases with the same GROUP are deployed concurrently. Everything in GROUP 2 starts being deployed only after everything in GROUP 1 got successfully deployed. RELEASE is the release that belongs to the GROUP. DEPENDENCIES is the list of releases that the RELEASE depends on. It should always be empty for releases in GROUP 1. DEPENDENCIES for a release in GROUP 2 should have some or all dependencies appeared in GROUP 1. It can be "some" because Helmfile simplifies the DAGs of releases into a DAG of groups, so that Helmfile always produce a single DAG for everything written in helmfile.yaml, even when there are technically two or more independent DAGs of releases in it. - status Retrieve status of releases in state file - sync Sync releases defined in state file - template Template releases defined in state file - test Test charts from state file (helm test) - unittest Unit test charts from state file using helm-unittest plugin - version Print the CLI version - write-values Write values files for releases. Similar to `helmfile template`, write values files instead of manifests. - -Flags: - --allow-no-matching-release Do not exit with an error code if the provided selector has no matching releases. - -c, --chart string Set chart. Uses the chart set in release by default, and is available in template as {{ .Chart }} - --color Output with color - --debug Enable verbose output for Helm and set log-level to debug, this disables --quiet/-q effect - --disable-force-update do not force helm repos to update when executing "helm repo add" - --enable-live-output Show live output from the Helm binary Stdout/Stderr into Helmfile own Stdout/Stderr. - It only applies for the Helm CLI commands, Stdout/Stderr for Hooks are still displayed only when it's execution finishes. - -e, --environment string specify the environment name. Overrides "HELMFILE_ENVIRONMENT" OS environment variable when specified. defaults to "default" - -f, --file helmfile.yaml load config from file or directory. defaults to "helmfile.yaml" or "helmfile.yaml.gotmpl" or "helmfile.d" (means "helmfile.d/*.yaml" or "helmfile.d/*.yaml.gotmpl") in this preference. Specify - to load the config from the standard input. - -b, --helm-binary string Path to the helm binary (default "helm") - -h, --help help for helmfile - -i, --interactive Request confirmation before attempting to modify clusters - --kube-context string Set kubectl context. Uses current context by default - -k, --kustomize-binary string Path to the kustomize binary (default "kustomize") - --log-level string Set log level, default info (default "info") - -n, --namespace string Set namespace. Uses the namespace set in the context by default, and is available in templates as {{ .Namespace }} - --no-color Output without color - -q, --quiet Silence output. Equivalent to log-level warn - -l, --selector stringArray Only run using the releases that match labels. Labels can take the form of foo=bar or foo!=bar. - A release must match all labels in a group in order to be used. Multiple groups can be specified at once. - "--selector tier=frontend,tier!=proxy --selector tier=backend" will match all frontend, non-proxy releases AND all backend releases. - The name of a release can be used as a label: "--selector name=myrelease" - --skip-deps skip running "helm repo update" and "helm dependency build" - --state-values-file stringArray specify state values in a YAML file. Used to override .Values within the helmfile template (not values template). - --state-values-set stringArray set state values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template). - --state-values-set-string stringArray set state STRING values on the command line (can specify multiple or separate values with commas: key1=val1,key2=val2). Used to override .Values within the helmfile template (not values template). - --sequential-helmfiles Process helmfile.d files sequentially in alphabetical order instead of in parallel - --strip-args-values-on-exit-error Strip the potential secret values of the helm command args contained in a helmfile error message (default true) - -v, --version version for helmfile - -Use "helmfile [command] --help" for more information about a command. -``` - -**Note:** Each command has its own specific flags. Use `helmfile [command] --help` to see command-specific options. For example, `helmfile sync --help` shows operational flags like `--timeout`, `--wait`, and `--wait-for-jobs`. - -### init - -The `helmfile init` sub-command checks the dependencies required for helmfile operation, such as `helm`, `helm diff plugin`, `helm secrets plugin`, `helm helm-git plugin`, `helm s3 plugin`. When it does not exist or the version is too low, it can be installed automatically. - -### cache - -The `helmfile cache` sub-command is designed for cache management. Go-getter-backed remote file system are cached by `helmfile`. There is no TTL implemented, if you need to update the cached files or directories, you need to clean individually or run a full cleanup with `helmfile cache cleanup` - -#### OCI Chart Cache - -OCI charts are cached in the shared cache directory (`~/.cache/helmfile` by default, or `$HELMFILE_CACHE_HOME`). This cache is shared across all helmfile processes. - -**Cache Behavior:** - -- When a chart exists in the shared cache and is valid, it is reused without re-downloading -- The `--skip-refresh` flag can be used to skip checking for updates to cached charts stored in process-specific temporary directories (it does not affect charts already present in the shared cache) -- When running multiple helmfile processes in parallel (e.g., as an ArgoCD plugin), charts in the shared cache are not refreshed/deleted to prevent race conditions - -**Forcing a Cache Refresh:** - -To force a refresh of cached OCI charts, run: -```bash -helmfile cache cleanup -``` - -This will clear the shared cache, allowing the next helmfile command to re-download charts. - -#### cache info - -Display information about the cache directory. - -#### cache cleanup - -Remove all cached files from the cache directory. - -### sync - -The `helmfile sync` sub-command sync your cluster state as described in your `helmfile`. The default helmfile is `helmfile.yaml`, but any YAML file can be passed by specifying a `--file path/to/your/yaml/file` flag. - -Under the covers, Helmfile executes `helm upgrade --install` for each `release` declared in the manifest, by optionally decrypting [secrets](#secrets) to be consumed as helm chart values. It also updates specified chart repositories and updates the -dependencies of any referenced local charts. - -#### Common sync flags - -* `--timeout SECONDS` - Override the default timeout for all releases in this sync operation. This takes precedence over `helmDefaults.timeout` and per-release `timeout` settings. -* `--wait` - Override the default wait behavior for all releases -* `--wait-for-jobs` - Override the default wait-for-jobs behavior for all releases - -Examples: +### Step 5: Clean up ```bash -# Override timeout for all releases to 10 minutes -helmfile sync --timeout 600 - -# Combine timeout with wait flags -helmfile sync --timeout 900 --wait --wait-for-jobs - -# Target specific releases with custom timeout -helmfile sync --selector tier=backend --timeout 1200 +# Remove everything +helmfile destroy ``` -For Helm 2.9+ you can use a username and password to authenticate to a remote repository. +### Next Steps -### deps +Now that you have the basics, explore these topics: -The `helmfile deps` sub-command locks your helmfile state and local charts dependencies. +**Core concepts** (read in order): +1. [Writing Helmfile](writing-helmfile.md) — patterns and best practices for structuring helmfiles +2. [Values and Merging](values-and-merging.md) — how Helmfile merges values from multiple sources +3. [Environments](environments.md) — manage dev/staging/production with a single helmfile -It basically runs `helm dependency update` on your helmfile state file and all the referenced local charts, so that you get a "lock" file per each helmfile state or local chart. - -All the other `helmfile` sub-commands like `sync` use chart versions recorded in the lock files, so that e.g. untested chart versions won't suddenly get deployed to the production environment. - -For example, the lock file for a helmfile state file named `helmfile.1.yaml` will be `helmfile.1.lock`. The lock file for a local chart would be `requirements.lock`, which is the same as `helm`. - -The lock file can be changed using `lockFilePath` in helm state, which makes it possible to for example have a different lock file per environment via templating. - -It is recommended to version-control all the lock files, so that they can be used in the production deployment pipeline for extra reproducibility. - -To bring in chart updates systematically, it would also be a good idea to run `helmfile deps` regularly, test it, and then update the lock files in the version-control system. - -### diff - -The `helmfile diff` sub-command executes the [helm-diff](https://github.com/databus23/helm-diff) plugin across all of -the charts/releases defined in the manifest. - -To supply the diff functionality Helmfile needs the [helm-diff](https://github.com/databus23/helm-diff) plugin v2.9.0+1 or greater installed. For Helm 2.3+ -you should be able to simply execute `helm plugin install https://github.com/databus23/helm-diff`. For more details -please look at their [documentation](https://github.com/databus23/helm-diff#helm-diff-plugin). - -### apply - -The `helmfile apply` sub-command begins by executing `diff`. If `diff` finds that there is any changes, `sync` is executed. Adding `--interactive` instructs Helmfile to request your confirmation before `sync`. - -An expected use-case of `apply` is to schedule it to run periodically, so that you can auto-fix skews between the desired and the current state of your apps running on Kubernetes clusters. - -### destroy - -The `helmfile destroy` sub-command uninstalls and purges all the releases defined in the manifests. - -`helmfile --interactive destroy` instructs Helmfile to request your confirmation before actually deleting releases. - -`destroy` basically runs `helm uninstall --purge` on all the targeted releases. If you don't want purging, use `helmfile delete` instead. -If `--skip-charts` flag is not set, destroy would prepare all releases, by fetching charts and templating them. - -### delete (DEPRECATED) - -The `helmfile delete` sub-command deletes all the releases defined in the manifests. - -`helmfile --interactive delete` instructs Helmfile to request your confirmation before actually deleting releases. - -Note that `delete` doesn't purge releases. So `helmfile delete && helmfile sync` results in sync failed due to that releases names are not deleted but preserved for future references. If you really want to remove releases for reuse, add `--purge` flag to run it like `helmfile delete --purge`. -If `--skip-charts` flag is not set, destroy would prepare all releases, by fetching charts and templating them. - -### secrets - -The `secrets` parameter in a `helmfile.yaml` causes the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin to be executed to decrypt the file. - -To supply the secret functionality Helmfile needs the `helm secrets` plugin installed. For Helm 2.3+ -you should be able to simply execute `helm plugin install https://github.com/jkroepke/helm-secrets -`. - -### test - -The `helmfile test` sub-command runs a `helm test` against specified releases in the manifest, default to all - -Use `--cleanup` to delete pods upon completion. - -### lint - -The `helmfile lint` sub-command runs a `helm lint` across all of the charts/releases defined in the manifest. Non local charts will be fetched into a temporary folder which will be deleted once the task is completed. - -### unittest - -The `helmfile unittest` sub-command runs `helm unittest` (from the [helm-unittest plugin](https://github.com/helm-unittest/helm-unittest)) on releases that have `unitTests` defined. It automatically generates the final merged values files for each release and passes them to `helm unittest`. - -This requires the `helm-unittest` plugin to be installed. You can install it with: - -```bash -helm plugin install https://github.com/helm-unittest/helm-unittest -``` - -Releases without `unitTests` defined are skipped. Non-local charts will be fetched into a temporary folder which will be deleted once the task is completed. - -Example helmfile configuration: - -```yaml -releases: - - name: my-app - chart: ./charts/my-app - values: - - values.yaml - unitTests: - - tests -``` - -The `unitTests` paths are relative to the chart directory and follow helm-unittest conventions. -If a path does not contain glob characters, it is treated as a directory and `/*_test.yaml` is appended automatically. -You can also specify explicit glob patterns (e.g., `tests/**/*_test.yaml`). - -Running `helmfile unittest` will: - -1. Merge all values files defined for the release -2. Run `helm unittest ./charts/my-app --values --file tests/*_test.yaml` - -You can pass additional flags: - -```bash -# Run with additional values -helmfile unittest --values extra-values.yaml - -# Run with --set overrides -helmfile unittest --set key=value - -# Target specific releases -helmfile unittest --selector name=my-app - -# Fail fast on first test failure -helmfile unittest --fail-fast - -# Enable colored output (Helm 3 only; ignored on Helm 4 due to flag parsing issues) -helmfile unittest --color - -# Enable verbose plugin output -helmfile unittest --debug-plugin - -# Pass extra arguments to helm unittest -helmfile unittest --args "--strict" -``` - -### fetch - -The `helmfile fetch` sub-command downloads or copies local charts to a local directory for debug purpose. The local directory -must be specified with `--output-dir`. - -### list - -The `helmfile list` sub-command lists releases defined in the manifest. Optional `--output` flag accepts `json` to output releases in JSON format. - -If `--skip-charts` flag is not set, list would prepare all releases, by fetching charts and templating them. - -### version - -The `helmfile version` sub-command prints the version of Helmfile.Optional `-o` flag accepts `json` `yaml` `short` to output version in JSON, YAML or short format. - -default it will check for the latest version of Helmfile and print a tip if the current version is not the latest. To disable this behavior, set environment variable `HELMFILE_UPGRADE_NOTICE_DISABLED` to any non-empty value. - -### show-dag - -It prints a table with 3 columns, GROUP, RELEASE, and DEPENDENCIES. - -GROUP is the unsigned, monotonically increasing integer starting from 1. All the releases with the same GROUP are deployed concurrently. Everything in GROUP 2 starts being deployed only after everything in GROUP 1 got successfully deployed. - -RELEASE is the release that belongs to the GROUP. - -DEPENDENCIES is the list of releases that the RELEASE depends on. It should always be empty for releases in GROUP 1. DEPENDENCIES for a release in GROUP 2 should have some or all dependencies appeared in GROUP 1. It can be "some" because Helmfile simplifies the DAGs of releases into a DAG of groups, so that Helmfile always produce a single DAG for everything written in helmfile.yaml, even when there are technically two or more independent DAGs of releases in it. - -## Paths Overview - -Using manifest files in conjunction with command line argument can be a bit confusing. - -A few rules to clear up this ambiguity: - -* Absolute paths are always resolved as absolute paths -* Relative paths referenced *in* the Helmfile manifest itself are relative to that manifest -* Relative paths referenced on the command line are relative to the current working directory the user is in -- Relative paths referenced from within the helmfile loaded from the standard input using `helmfile -f -` are relative to the current working directory - -For additional context, take a look at [paths examples](paths.md). +**Reference material** (look up as needed): +* [Configuration Reference](configuration.md) — complete `helmfile.yaml` schema +* [CLI Reference](cli.md) — all commands and flags +* [Template Functions](templating_funcs.md) — functions available in Go templates ## Labels Overview @@ -909,7 +213,7 @@ The `selector` parameter can be specified multiple times. Each parameter is reso In addition to user supplied labels, the name, the namespace, and the chart are available to be used as selectors. The chart will just be the chart name excluding the repository (Example `stable/filebeat` would be selected using `--selector chart=filebeat`). -`commonLabels` can be used when you want to apply the same label to all releases and use [templating](##Templates) based on that. +`commonLabels` can be used when you want to apply the same label to all releases and use [templating](templating.md) based on that. For instance, you install a number of charts on every customer but need to provide different values file per customer. templates/common.yaml: @@ -944,1047 +248,27 @@ releases: - <<: *cert-manager ``` -## Templates +## Advanced -You can use go's text/template expressions in `helmfile.yaml` and `values.yaml.gotmpl` (templated helm values files). `values.yaml` references will be used verbatim. In other words: +* [Advanced Features](advanced-features.md) - Kubedog, Kustomize, chartify, vals integration +* [Hooks](hooks.md) - Lifecycle hooks (prepare, presync, postsync, cleanup) +* [Secrets](remote-secrets.md) - Remote secrets (vault, SSM, etc.) +* [Shared Configuration Across Teams](shared-configuration-across-teams.md) - Multi-team Helmfile patterns -* for value files ending with `.gotmpl`, template expressions will be rendered -* for plain value files (ending in `.yaml`), content will be used as-is +## Community -In addition to built-in ones, the following custom template functions are available: - -* `readFile` reads the specified local file and generate a golang string -* `readDir` reads the files within provided directory path. (folders are excluded) -* `readDirEntries` Returns a list of [https://pkg.go.dev/os#DirEntry](DirEntry) within provided directory path -* `fromYaml` reads a golang string and generates a map -* `setValueAtPath PATH NEW_VALUE` traverses a golang map, replaces the value at the PATH with NEW_VALUE -* `toYaml` marshals a map into a string -* `get` returns the value of the specified key if present in the `.Values` object, otherwise will return the default value defined in the function - -### Values Files Templates - -You can reference a template of values file in your `helmfile.yaml` like below: - -```yaml -releases: -- name: myapp - chart: mychart - values: - - values.yaml.gotmpl -``` - -Every values file whose file extension is `.gotmpl` is considered as a template file. - -Suppose `values.yaml.gotmpl` was something like: - -```yaml -{{ readFile "values.yaml" | fromYaml | setValueAtPath "foo.bar" "FOO_BAR" | toYaml }} -``` - -And `values.yaml` was: - -```yaml -foo: - bar: "" -``` - -The resulting, temporary values.yaml that is generated from `values.yaml.gotmpl` would become: - -```yaml -foo: - # Notice `setValueAtPath "foo.bar" "FOO_BAR"` in the template above - bar: FOO_BAR -``` - -## Refactoring `helmfile.yaml` with values files templates - -One of expected use-cases of values files templates is to keep `helmfile.yaml` small and concise. - -See the example `helmfile.yaml` below: - -```yaml -releases: - - name: {{ requiredEnv "NAME" }}-vault - namespace: {{ requiredEnv "NAME" }} - chart: roboll/vault-secret-manager - values: - - db: - username: {{ requiredEnv "DB_USERNAME" }} - password: {{ requiredEnv "DB_PASSWORD" }} - set: - - name: proxy.domain - value: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com - - name: proxy.scheme - value: {{ env "SCHEME" | default "https" }} -``` - -The `values` and `set` sections of the config file can be separated out into a template: - -`helmfile.yaml`: - -```yaml -releases: - - name: {{ requiredEnv "NAME" }}-vault - namespace: {{ requiredEnv "NAME" }} - chart: roboll/vault-secret-manager - values: - - values.yaml.gotmpl -``` - -`values.yaml.gotmpl`: - -```yaml -db: - username: {{ requiredEnv "DB_USERNAME" }} - password: {{ requiredEnv "DB_PASSWORD" }} -proxy: - domain: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com - scheme: {{ env "SCHEME" | default "https" }} -``` - -## Environment - -When you want to customize the contents of `helmfile.yaml` or `values.yaml` files per environment, use this feature. - -You can define as many environments as you want under `environments` in `helmfile.yaml`. -`environments` section should be separated from `releases` with `---`. - -The environment name defaults to `default`, that is, `helmfile sync` implies the `default` environment. -The selected environment name can be referenced from `helmfile.yaml` and `values.yaml.gotmpl` by `{{ .Environment.Name }}`. - -If you want to specify a non-default environment, provide a `--environment NAME` flag to `helmfile` like `helmfile --environment production sync`. - -The below example shows how to define a production-only release: - -```yaml -environments: - default: - production: - ---- - -releases: -- name: newrelic-agent - installed: {{ eq .Environment.Name "production" | toYaml }} - # snip -- name: myapp - # snip -``` - -### Environment Values -Helmfile supports 3 values languages : -- Straight yaml -- Go templates to generate straight yaml -- HCL - -Environment Values allows you to inject a set of values specific to the selected environment, into `values.yaml` templates. -Use it to inject common values from the environment to multiple values files, to make your configuration DRY. - -Suppose you have three files `helmfile.yaml`, `production.yaml` and `values.yaml.gotmpl`: - -`helmfile.yaml` - -```yaml -environments: - production: - values: - - production.yaml - ---- - -releases: -- name: myapp - values: - - values.yaml.gotmpl -``` - -`production.yaml` - -```yaml -domain: prod.example.com -releaseName: prod -``` - -`values.yaml.gotmpl` - -```yaml -domain: {{ .Values | get "domain" "dev.example.com" }} -``` - -`helmfile sync` installs `myapp` with the value `domain=dev.example.com`, -whereas `helmfile --environment production sync` installs the app with the value `domain=prod.example.com`. - -For even more flexibility, you can now use values declared in the `environments:` section in other parts of your helmfiles: - -consider: -`default.yaml` - -```yaml -domain: dev.example.com -releaseName: dev -``` - -```yaml -environments: - default: - values: - - default.yaml - production: - values: - - production.yaml # bare .yaml file, content will be used verbatim - - other.yaml.gotmpl # template directives with potential side-effects like `exec` and `readFile` will be honoured - ---- - -releases: -- name: myapp-{{ .Values.releaseName }} # release name will be one of `dev` or `prod` depending on selected environment - values: - - values.yaml.gotmpl -- name: production-specific-release - # this release would be installed only if selected environment is `production` - installed: {{ eq .Values.releaseName "prod" | toYaml }} - ... -``` - -#### HCL specifications - -Since Helmfile v0.164.0, HCL language is supported for environment values only. -HCL values supports interpolations and sharing values across files - -* Only `.hcl` suffixed files will be interpreted as is -* Helmfile supports 2 different blocks: `values` and `locals` -* `values` block is a shared block where all values are accessible everywhere in all loaded files -* `locals` block can't reference external values apart from the ones in the block itself, and where its defined values are only accessible in its local file -* Only values in `values` blocks are made available to the final root `.Values` (e.g : ` values { myvar = "var" }` is accessed through `{{ .Values.myvar }}`) -* There can only be 1 `locals` block per file -* Helmfile hcl `values` are referenced using the `hv` accessor. -* Helmfile hcl `locals` are referenced using the `local` accessor. -* When the same key is defined multiple times across imported `.hcl` files in `values` blocks, values from later files override those from earlier files (last file loaded wins). Map values are merged per key, while list values are replaced as a whole (i.e. not deep-merged). Mixed-types overrides (e.g. bool -> string) are supported (latest value/type wins). -* All cty [standard library functions](`https://pkg.go.dev/github.com/zclconf/go-cty@v1.14.3/cty/function/stdlib`) are available and custom functions could be created in the future - -Consider the following example : - -```terraform -# values1.hcl -locals { - hostname = "host1" -} -values { - domain = "DEV.EXAMPLE.COM" - hostnameV1 = "${local.hostname}.${lower(hv.domain)}" # "host1.dev.example.com" -} -``` -```terraform -# values2.hcl -locals { - hostname = "host2" -} - -values { - hostnameV2 = "${local.hostname}.${hv.domain}" # "host2.DEV.EXAMPLE.COM" -} -``` -#### Note on Environment.Values vs Values - -The `{{ .Values.foo }}` syntax is the recommended way of using environment values. - -Prior to this [pull request](https://github.com/roboll/helmfile/pull/647), environment values were made available through the `{{ .Environment.Values.foo }}` syntax. -This is still working but is **deprecated** and the new `{{ .Values.foo }}` syntax should be used instead. - -You can read more infos about the feature proposal [here](https://github.com/roboll/helmfile/issues/640). - -### Environment Secrets - -Environment Secrets *(not to be confused with Kubernetes Secrets)* are encrypted versions of `Environment Values`. -You can list any number of `secrets.yaml` files created using `helm secrets` or `sops`, so that -Helmfile could automatically decrypt and merge the secrets into the environment values. - -First you must have the [helm-secrets](https://github.com/jkroepke/helm-secrets) plugin installed along with a -`.sops.yaml` file to configure the method of encryption (this can be in the same directory as your helmfile or -in the subdirectory containing your secrets files). - -Then suppose you have a secret `foo.bar` defined in `environments/production/secrets.yaml`: - -```yaml -foo.bar: "mysupersecretstring" -``` - -You can then encrypt it with `helm secrets enc environments/production/secrets.yaml` - -Then reference that encrypted file in `helmfile.yaml`: - -```yaml -environments: - production: - secrets: - - environments/production/secrets.yaml - ---- - -releases: -- name: myapp - chart: mychart - values: - - values.yaml.gotmpl -``` - -Then the environment secret `foo.bar` can be referenced by the below template expression in your `values.yaml.gotmpl`: - -```yaml -{{ .Values.foo.bar }} -``` - -#### Loading remote Environment secrets files - -Since Helmfile v0.149.0, you can use `go-getter`-style URLs to refer to remote secrets files, the same way as in values files: -```yaml -environments: - staging: - secrets: - - git::https://{{ env "GITHUB_PAT" }}@github.com/org/repo.git@/environments/staging.secret.yaml?ref=main - - http://$HOSTNAME/artifactory/example-repo-local/test.tgz@environments/staging.secret.yaml - production: - secrets: - - git::https://{{ env "GITHUB_PAT" }}@github.com/org/repo.git@/environments/production.secret.yaml?ref=main - - http://$HOSTNAME/artifactory/example-repo-local/test.tgz@environments/production.secret.yaml -``` - -### Loading remote Environment values files - -Since Helmfile v0.118.8, you can use `go-getter`-style URLs to refer to remote values files: - -```yaml -environments: - cluster-azure-us-west: - values: - - git::https://git.company.org/helmfiles/global/azure.yaml?ref=master - - git::https://git.company.org/helmfiles/global/us-west.yaml?ref=master - - git::https://gitlab.com/org/repository-name.git@/config/config.test.yaml?ref=main # Public Gilab Repo - cluster-gcp-europe-west: - values: - - git::https://git.company.org/helmfiles/global/gcp.yaml?ref=master - - git::https://git.company.org/helmfiles/global/europe-west.yaml?ref=master - - git::https://ci:{{ env "CI_JOB_TOKEN" }}@gitlab.com/org/repository-name.git@/config.dev.yaml?ref={{ env "APP_COMMIT_SHA" }} # Private Gitlab Repo - staging: - values: - - git::https://{{ env "GITHUB_PAT" }}@github.com/[$GITHUB_ORGorGITHUB_USER]/repository-name.git@/values.dev.yaml?ref=main #Github Private repo - - http://$HOSTNAME/artifactory/example-repo-local/test.tgz@values.yaml #Artifactory url ---- - -releases: - - ... -``` - -Since Helmfile v0.158.0, support more protocols, such as: s3, https, http -``` -values: - - s3::https://helm-s3-values-example.s3.us-east-2.amazonaws.com/values.yaml - - s3://helm-s3-values-example/subdir/values.yaml - - https://john:doe@helm-s3-values-example.s3.us-east-2.amazonaws.com/values.yaml - - http://helm-s3-values-example.s3.us-east-2.amazonaws.com/values.yaml -``` - -For more information about the supported protocols see: [go-getter Protocol-Specific Options](https://github.com/hashicorp/go-getter#protocol-specific-options-1). - -This is particularly useful when you co-locate helmfiles within your project repo but want to reuse the definitions in a global repo. - -### Environment values precedence -With the introduction of HCL, a new value precedence was introduced over environment values. -Here is the order of precedence from least to greatest (the last one overrides all others) -1. `yaml` / `yaml.gotmpl` -2. `hcl` -3. `yaml` secrets - -Example: - ---- - -```yaml -# values1.yaml -domain: "dev.example.com" -``` - -```terraform -# values2.hcl -values { - domain = "overdev.example.com" - env = "dev" - willBeOverridden = "override_me" -} -``` - -```terraform -# values3.hcl -values { - env = "local" -} -``` - -```yaml -# secrets.yml (assuming this one has been encrypted) -willBeOverridden: overridden -``` - -``` -# helmfile.yaml.gotmpl -environments: - default: - values: - - values1.yaml - - values2.hcl - - values3.hcl - secrets: - - secrets.yml ---- -releases: -- name: random-release - [...] - values: - domain: "{{ .Values.domain }}" # == "overdev.example.com" - env: "{{ .Values.env }}" # == "local" - willBeOverridden: "{{ .Values.willBeOverridden }}" # == "overridden" -``` -## DAG-aware installation/deletion ordering with `needs` - -`needs` controls the order of the installation/deletion of the release: - -```yaml -releases: -- name: somerelease - needs: - - [[KUBECONTEXT/]NAMESPACE/]anotherelease -``` - -Be aware that you have to specify the kubecontext and namespace name if you configured one for the release(s). - -All the releases listed under `needs` are installed before(or deleted after) the release itself. - -For the following example, `helmfile [sync|apply]` installs releases in this order: - -1. logging -2. servicemesh -3. myapp1 and myapp2 - -```yaml - - name: myapp1 - chart: charts/myapp - needs: - - servicemesh - - logging - - name: myapp2 - chart: charts/myapp - needs: - - servicemesh - - logging - - name: servicemesh - chart: charts/istio - needs: - - logging - - name: logging - chart: charts/fluentd -``` - -Note that all the releases in a same group is installed concurrently. That is, myapp1 and myapp2 are installed concurrently. - -On `helmfile [delete|destroy]`, deletions happen in the reverse order. - -That is, `myapp1` and `myapp2` are deleted first, then `servicemesh`, and finally `logging`. - -### Selectors and `needs` - -When using selectors/labels, `needs` are ignored by default. This behaviour can be overruled with a few parameters: - -| Parameter | default | Description | -|---|---|---| -| `--skip-needs` | `true` | `needs` are ignored (default behavior). | -| `--include-needs` | `false` | The direct `needs` of the selected release(s) will be included. | -| `--include-transitive-needs` | `false` | The direct and transitive `needs` of the selected release(s) will be included. | - -Let's look at an example to illustrate how the different parameters work: - -```yaml -releases: -- name: serviceA - chart: my/chart - needs: - - serviceB -- name: serviceB - chart: your/chart - needs: - - serviceC -- name: serviceC - chart: her/chart -- name: serviceD - chart: his/chart -``` - -| Command | Included Releases Order | Explanation | -|---|---|---| -| `helmfile -l name=serviceA sync` | - `serviceA` | By default no needs are included. | -| `helmfile -l name=serviceA sync --include-needs` | - `serviceB`
- `serviceA` | `serviceB` is now part of the release as it is a direct need of `serviceA`. | -| `helmfile -l name=serviceA sync --include-transitive-needs` | - `serviceC`
- `serviceB`
- `serviceA` | `serviceC` is now also part of the release as it is a direct need of `serviceB` and therefore a transitive need of `serviceA`. | - -Note that `--include-transitive-needs` will override any potential exclusions done by selectors or conditions. So even if you explicitly exclude a release via a selector it will still be part of the deployment in case it is a direct or transitive need of any of the specified releases. - -## Separating helmfile.yaml into multiple independent files - -Once your `helmfile.yaml` got to contain too many releases, -split it into multiple yaml files. - -Recommended granularity of helmfile.yaml files is "per microservice" or "per team". -And there are two ways to organize your files. - -* Single directory -* Glob patterns - -### Single directory - -`helmfile -f path/to/directory` loads and runs all the yaml files under the specified directory, each file as an independent helmfile.yaml. -The default helmfile directory is `helmfile.d`, that is, -in case helmfile is unable to locate `helmfile.yaml`, it tries to locate `helmfile.d/*.yaml`. - -By default, multiple files in `helmfile.d` are processed in **parallel** for better performance. If you need files to be processed **sequentially in alphabetical order** (e.g., for dependency ordering where databases must be deployed before applications), use the `--sequential-helmfiles` flag. - -For example, you can use a `-.yaml` naming convention to control the sync order when using `--sequential-helmfiles`: - -* `helmfile.d`/ - * `00-database.yaml` - * `01-backend.yaml` - * `02-frontend.yaml` - -```bash -# Process files sequentially in alphabetical order -helmfile --sequential-helmfiles sync -``` - -> **Note:** When processing multiple helmfile.d files, both parallel and sequential modes resolve paths without changing the process working directory, so relative environment variables like `KUBECONFIG` work correctly. - -### Glob patterns - -In case you want more control over how multiple `helmfile.yaml` files are organized, use `helmfiles:` configuration key in the `helmfile.yaml`: - -Suppose you have multiple microservices organized in a Git repository that looks like: - -* `myteam/` (sometimes it is equivalent to a k8s ns, that is `kube-system` for `clusterops` team) - * `apps/` - * `filebeat/` - * `helmfile.yaml` (no `charts/` exists because it depends on the stable/filebeat chart hosted on the official helm charts repository) - * `README.md` (each app managed by my team has a dedicated README maintained by the owners of the app) - * `metricbeat/` - * `helmfile.yaml` - * `README.md` - * `elastalert-operator/` - * `helmfile.yaml` - * `README.md` - * `charts/` - * `elastalert-operator/` - * `` - -The benefits of this structure is that you can run `git diff` to locate in which directory=microservice a git commit has changes. -It allows your CI system to run a workflow for the changed microservice only. - -A downside of this is that you don't have an obvious way to sync all microservices at once. That is, you have to run: - -```bash -for d in apps/*; do helmfile -f $d diff; if [ $? -eq 2 ]; then helmfile -f $d sync; fi; done -``` - -At this point, you'll start writing a `Makefile` under `myteam/` so that `make sync-all` will do the job. - -It does work, but you can rely on the Helmfile feature instead. - -Put `myteam/helmfile.yaml` that looks like: - -```yaml -helmfiles: -- apps/*/helmfile.yaml -``` - -So that you can get rid of the `Makefile` and the bash snippet. -Just run `helmfile sync` inside `myteam/`, and you are done. - -All the files are sorted alphabetically per group = array item inside `helmfiles:`, so that you have granular control over ordering, too. - -#### selectors - -When composing helmfiles you can use selectors from the command line as well as explicit selectors inside the parent helmfile to filter the releases to be used. - -```yaml -helmfiles: -- apps/*/helmfile.yaml -- path: apps/a-helmfile.yaml - selectors: # list of selectors - - name=prometheus - - tier=frontend -- path: apps/b-helmfile.yaml # no selector, so all releases are used - selectors: [] -- path: apps/c-helmfile.yaml # parent selector to be used or cli selector for the initial helmfile - selectorsInherited: true -``` - -* When a subhelmfile has explicit `selectors`, those selectors determine which releases from that subhelmfile are considered; parent and CLI selectors are not combined with them for release filtering. -* When CLI selectors are provided (e.g. `helmfile -l name=b sync`) and a subhelmfile has explicit selectors that are provably incompatible with them (same key, different value), that subhelmfile may be **skipped entirely** without loading or rendering it. For example, with `-l name=b`, a subhelmfile with `selectors: [name=a]` will be skipped since no release could match both. This optimization does not apply when `selectorsInherited: true` is set or when no CLI selectors are provided. Use `--debug` to see log messages about skipped subhelmfiles. -* When not selector is specified there are 2 modes for the selector inheritance because we would like to change the current inheritance behavior (see [issue #344](https://github.com/roboll/helmfile/issues/344) ). - * Legacy mode, sub-helmfiles without selectors inherit selectors from their parent helmfile. The initial helmfiles inherit from the command line selectors. - * explicit mode, sub-helmfile without selectors do not inherit from their parent or the CLI selector. If you want them to inherit from their parent selector then use `selectorsInherited: true`. To enable this explicit mode you need to set the following environment variable `HELMFILE_EXPERIMENTAL=explicit-selector-inheritance` (see [experimental](#experimental-features)). -* Using `selector: []` will select all releases regardless of the parent selector or cli for the initial helmfile -* using `selectorsInherited: true` make the sub-helmfile selects releases with the parent selector or the cli for the initial helmfile. You cannot specify an explicit selector while using `selectorsInherited: true` - -## Importing values from any source - -The `exec` template function that is available in `values.yaml.gotmpl` is useful for importing values from any source -that is accessible by running a command: - -A usual usage of `exec` would look like this: - -```yaml -mysetting: | -{{ exec "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }} -``` - -Or even with a pipeline: - -```yaml -mysetting: | -{{ yourinput | exec "./mycmd-consume-stdin" (list "arg1" "arg2") | indent 2 }} -``` - -The possibility is endless. Try importing values from your golang app, bash script, jsonnet, or anything! - -Then `envExec` same as `exec`, but it can receive a dict as the envs. - -A usual usage of `envExec` would look like this: - -```yaml -mysetting: | -{{ envExec (dict "envkey" "envValue") "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }} -``` - -## Hooks - -A Helmfile hook is a per-release extension point that is composed of: - -* `events` -* `command` -* `args` -* `showlogs` - -Helmfile triggers various `events` while it is running. -Once `events` are triggered, associated `hooks` are executed, by running the `command` with `args`. The standard output of the `command` will be displayed if `showlogs` is set and it's value is `true`. - -Hooks exec order follows the order of definition in the helmfile state. - -Currently supported `events` are: - -* `prepare` -* `preapply` -* `presync` -* `preuninstall` -* `postuninstall` -* `postsync` -* `cleanup` - -Hooks associated to `prepare` events are triggered after each release in your helmfile is loaded from YAML, before execution. -`prepare` hooks are triggered on the release as long as it is not excluded by the helmfile selector(e.g. `helmfile -l key=value`). - -Hooks associated to `presync` events are triggered before each release is synced (installed or upgraded) on the cluster. -This is the ideal event to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`. - -`preapply` hooks are triggered before a release is uninstalled, installed, or upgraded as part of `helmfile apply`. -This is the ideal event to hook into when you are going to use `helmfile apply` for every kind of change. Note that preapply hooks will only run if at least one release has changes to apply. Be sure to make each `preapply` hook command idempotent. Otherwise, rerunning `helmfile apply` on a transient failure may end up either breaking your cluster, or the hook that runs for the second time will never succeed. - -`preuninstall` hooks are triggered immediately before a release is uninstalled as part of `helmfile apply`, `helmfile sync`, `helmfile delete`, and `helmfile destroy`. - -`postuninstall` hooks are triggered immediately after successful uninstall of a release while running `helmfile apply`, `helmfile sync`, `helmfile delete`, `helmfile destroy`. - -`postsync` hooks are triggered after each release is synced (installed or upgraded) on the cluster, regardless if the sync was successful or not. -This is the ideal place to execute any commands that may mutate the cluster state as it will not be run for read-only operations like `lint`, `diff` or `template`. - -`cleanup` hooks are triggered after each release is processed. -This is the counterpart to `prepare`, as any release on which `prepare` has been triggered gets `cleanup` triggered as well. - -The following is an example hook that just prints the contextual information provided to hook: - -```yaml -releases: -- name: myapp - chart: mychart - # *snip* - hooks: - - events: ["prepare", "cleanup"] - showlogs: true - command: "echo" - args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\ -"] -``` - -Let's say you ran `helmfile --environment prod sync`, the above hook results in executing: - -``` -echo {{Environment.Name}} {{.Release.Name}} {{.HelmfileCommand}} -``` - -Whereas the template expressions are executed thus the command becomes: - -``` -echo prod myapp sync -``` - -Now, replace `echo` with any command you like, and rewrite `args` that actually conforms to the command, so that you can integrate any command that does: - -* templating -* linting -* testing - -Hooks expose additional template expressions: - -`.Event.Name` is the name of the hook event. - -`.Event.Error` is the error generated by a failed release, exposed for `postsync` hooks only when a release fails, otherwise its value is `nil`. - -You can use the hooks event expressions to send notifications to platforms such as `Slack`, `MS Teams`, etc. - -The following example passes arguments to a script which sends a notification: - -```yaml -releases: -- name: myapp - chart: mychart - # *snip* - hooks: - - events: - - presync - - postsync - showlogs: true - command: notify.sh - args: - - --event - - '{{`{{ .Event.Name }}`}}' - - --status - - '{{`{{ if .Event.Error }}failure{{ else }}success{{ end }}`}}' - - --environment - - '{{`{{ .Environment.Name }}`}}' - - --namespace - - '{{`{{ .Release.Namespace }}`}}' - - --release - - '{{`{{ .Release.Name }}`}}' -``` - -For templating, imagine that you created a hook that generates a helm chart on-the-fly by running an external tool like ksonnet, kustomize, or your own template engine. -It will allow you to write your helm releases with any language you like, while still leveraging goodies provided by helm. - -### Hooks, Kubectl and Environments - -Hooks can also be used in combination with small tasks using `kubectl` directly, -e.g.: in order to install a custom storage class. - -In the following example, a specific release depends on a custom storage class. -Further, all enviroments have a default kube context configured where releases are deployed into. -The `.Environment.KubeContext` is used in order to apply / remove the YAML to the correct context depending on the environment. - -`environments.yaml`: - -```yaml -environments: - dev: - values: - - ../values/default.yaml - - ../values/dev.yaml - kubeContext: dev-cluster - prod: - values: - - ../values/default.yaml - - ../values/prod.yaml - kubeContext: prod-cluster -``` - -`helmfile.yaml`: - -```yaml -bases: - - ./environments.yaml - ---- -releases: - - name: myService - namespace: my-ns - installed: true - chart: mychart - version: "1.2.3" - values: - - ../services/my-service/values.yaml.gotmpl - hooks: - - events: ["presync"] - showlogs: true - command: "kubectl" - args: - - "apply" - - "-f" - - "./custom-storage-class.yaml" - - "--context" - - "{{`{{.Environment.KubeContext}}`}}" - - events: ["postuninstall"] - showlogs: true - command: "kubectl" - args: - - "delete" - - "-f" - - "./custom-storage-class.yaml" - - "--context" - - "{{`{{.Environment.KubeContext}}`}}" -``` - -### Global Hooks - -In contrast to the per release hooks mentioned above these are run only once at the very beginning and end of the execution of a helmfile command and only the `prepare` and `cleanup` hooks are available respectively. - -They use the same syntax as per release hooks, but at the top level of your helmfile: - -```yaml -hooks: -- events: ["prepare", "cleanup"] - showlogs: true - command: "echo" - args: ["{{`{{.Environment.Name}}`}}", "{{`{{.HelmfileCommand}}`}}\ -"] -``` - -### Helmfile + Kustomize - -Do you prefer `kustomize` to write and organize your Kubernetes apps, but still want to leverage helm's useful features -like rollback, history, and so on? This section is for you! - -The combination of `hooks` and [helmify-kustomize](https://gist.github.com/mumoshu/f9d0bd98e0eb77f636f79fc2fb130690) -enables you to integrate [kustomize](https://github.com/kubernetes-sigs/kustomize) into Helmfile. - -That is, you can use `kustomize` to build a local helm chart from a kustomize overlay. - -Let's assume you have a kustomize project named `foo-kustomize` like this: - -``` -foo-kustomize/ -├── base -│   ├── configMap.yaml -│   ├── deployment.yaml -│   ├── kustomization.yaml -│   └── service.yaml -└── overlays - ├── default - │   ├── kustomization.yaml - │   └── map.yaml - ├── production - │   ├── deployment.yaml - │   └── kustomization.yaml - └── staging - ├── kustomization.yaml - └── map.yaml - -5 directories, 10 files -``` - -Write `helmfile.yaml`: - -```yaml -- name: kustomize - chart: ./foo - hooks: - - events: ["prepare", "cleanup"] - command: "../helmify" - args: ["{{`{{if eq .Event.Name \"prepare\"}}build{{else}}clean{{end}}`}}", "{{`{{.Release.Ch\ -art}}`}}", "{{`{{.Environment.Name}}`}}"] -``` - -Run `helmfile --environment staging sync` and see it results in helmfile running `kustomize build foo-kustomize/overlays/staging > foo/templates/all.yaml`. - -Voilà! You can mix helm releases that are backed by remote charts, local charts, and even kustomize overlays. - -## Guides - -Use the [Helmfile Best Practices Guide](writing-helmfile.md) to write advanced helmfiles that feature: - -* Default values -* Layering - -We also have dedicated documentation on the following topics which might interest you: - -* [Shared Configurations Across Teams](shared-configuration-across-teams.md) - -Or join our friendly slack community in the [`#helmfile`](https://slack.sweetops.com) channel to ask questions and get help. Check out our [slack archive](https://archive.sweetops.com/helmfile/) for good examples of how others are using it. - -## Using .env files - -Helmfile itself doesn't have an ability to load .env files. But you can write some bash script to achieve the goal: - -```console -set -a; . .env; set +a; helmfile sync -``` - -Please see #203 for more context. - -## Running Helmfile interactively - -`helmfile --interactive [apply|destroy|delete|sync]` requests confirmation from you before actually modifying your cluster. - -Use it when you're running `helmfile` manually on your local machine or a kind of secure administrative hosts. - -For your local use-case, aliasing it like `alias hi='helmfile --interactive'` would be convenient. - -Another way to use it is to set the environment variable `HELMFILE_INTERACTIVE=true` to enable the interactive mode by default. -Anything other than `true` will disable the interactive mode. The precedence has the `--interactive` flag. - -## Running Helmfile without an Internet connection - -Once you download all required charts into your machine, you can run `helmfile sync --skip-deps` to deploy your apps. -With the `--skip-deps` option, you can skip running "helm repo update" and "helm dependency build". +Join our friendly slack community in the [`#helmfile`](https://slack.sweetops.com) channel to ask questions and get help. Check out our [slack archive](https://archive.sweetops.com/helmfile/) for good examples of how others are using it. ## Experimental Features Some experimental features may be available for testing in perspective of being (or not) included in a future release. Those features are set using the environment variable `HELMFILE_EXPERIMENTAL`. Here is the current experimental feature : -* `explicit-selector-inheritance` : remove today implicit cli selectors inheritance for composed helmfiles, see [composition selector](#selectors) +* `explicit-selector-inheritance` : remove today implicit cli selectors inheritance for composed helmfiles, see [composition selector](releases.md#selectors) If you want to enable all experimental features set the env var to `HELMFILE_EXPERIMENTAL=true` -## `bash` and `zsh` completion - -helmfile completion --help - ## Examples For more examples, see the [examples/README.md](https://github.com/helmfile/helmfile/blob/master/examples/README.md) or the [`helmfile`](https://github.com/cloudposse/helmfiles/tree/master/releases) distribution by [Cloud Posse](https://github.com/cloudposse/). -## Integrations - -* [renovate](https://github.com/renovatebot/renovate) automates chart version updates. See [this PR for more information](https://github.com/renovatebot/renovate/pull/5257). - * For updating container image tags and git tags embedded within helmfile.yaml and values, you can use [renovate's regexManager](https://docs.renovatebot.com/modules/manager/regex/). Please see [this comment in the renovate repository](https://github.com/renovatebot/renovate/issues/6130#issuecomment-624061289) for more information. -* [ArgoCD Integration](#argocd-integration) -* [Azure ACR Integration](#azure-acr-integration) - -### ArgoCD Integration - -Use [ArgoCD](https://argoproj.github.io/argo-cd/) with `helmfile template` for GitOps. - -ArgoCD has support for kustomize/manifests/helm chart by itself. Why bother with Helmfile? - -The reasons may vary: - -1. You do want to manage applications with ArgoCD, while letting Helmfile manage infrastructure-related components like Calico/Cilium/WeaveNet, Linkerd/Istio, and ArgoCD itself. - -* This way, any application deployed by ArgoCD has access to all the infrastructure. -* Of course, you can use ArgoCD's [Sync Waves and Phases](https://argoproj.github.io/argo-cd/user-guide/sync-waves/) for ordering the infrastructure and application installations. But it may be difficult to separate the concern between the infrastructure and apps and annotate K8s resources consistently when you have different teams for managing infra and apps. - -2. You want to review the exact K8s manifests being applied on pull-request time, before ArgoCD syncs. - -* This is often better than using a kind of `HelmRelease` custom resources that obfuscates exactly what manifests are being applied, which makes reviewing harder. - -3. Use Helmfile as the single-pane of glass for all the K8s resources deployed to your cluster(s). - -* Helmfile can reduce repetition in K8s manifests across ArgoCD application - -For 1, you run `helmfile apply` on CI to deploy ArgoCD and the infrastructure components. - -> helmfile config for this phase often reside within the same directory as your Terraform project. So connecting the two with [terraform-provider-helmfile](https://github.com/mumoshu/terraform-provider-helmfile) may be helpful - -For 2, another app-centric CI or bot should render/commit manifests by running: - -``` -helmfile template --output-dir-template $(pwd)/gitops//{{.Release.Name}} -cd gitops -git add . -git commit -m 'some message' -git push origin $BRANCH -``` - -> Note that `$(pwd)` is necessary when `helmfile.yaml` has one or more sub-helmfiles in nested directories, -> because setting a relative file path in `--output-dir` or `--output-dir-template` results in each sub-helmfile render -> to the directory relative to the specified path. - -so that they can be deployed by Argo CD as usual. - -The CI or bot can optionally submit a PR to be review by human, running: - -``` -hub pull-request -b main -l gitops -m 'some description' -``` - -Recommendations: - -* Do create ArgoCD `Application` custom resource per Helm/Helmfile release, each point to respective sub-directory generated by `helmfile template --output-dir-template` -* If you don't directly push it to the main Git branch and instead go through a pull-request, do lint rendered manifests on your CI, so that you can catch easy mistakes earlier/before ArgoCD finally deploys it -* See [this ArgoCD issue](https://github.com/argoproj/argo-cd/issues/2143#issuecomment-570478329) for why you may want this, and see [this helmfile issue](https://github.com/roboll/helmfile/pull/1357) for how `--output-dir-template` works. - -### Azure ACR Integration - -Azure offers helm repository [support for Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos) as a preview feature. - -To use this you must first `az login` and then `az acr helm repo add -n `. This will extract a token for the given ACR and configure `helm` to use it, e.g. `helm repo update` should work straight away. - -To use `helmfile` with ACR, on the other hand, you must either include a username/password in the repository definition for the ACR in your `helmfile.yaml` or use the `--skip-deps` switch, e.g. `helmfile template --skip-deps`. - -An ACR repository definition in `helmfile.yaml` looks like this: - -```yaml -repositories: - - name: - url: https://.azurecr.io/helm/v1/repo -``` - -## OCI Registries - -In order to use OCI chart registries firstly they must be marked in the repository list as OCI enabled, e.g. - -```yaml -repositories: - - name: myOCIRegistry - url: myregistry.azurecr.io - oci: true -``` - -It is important not to include a scheme for the URL as helm requires that these are not present for OCI registries - -Secondly the credentials for the OCI registry can either be specified within `helmfile.yaml` similar to - -```yaml -repositories: - - name: myOCIRegistry - url: myregistry.azurecr.io - oci: true - username: spongebob - password: squarepants -``` - -or for CI scenarios these can be sourced from the environment with the format `_USERNAME` and ``, e.g. - -```shell -export MYOCIREGISTRY_USERNAME=spongebob -export MYOCIREGISTRY_PASSWORD=squarepants -``` - -If `` contains hyphens, the environment variable to be read is the hyphen replaced by an underscore., e.g. - -```yaml -repositories: - - name: my-oci-registry - url: myregistry.azurecr.io - oci: true -``` - -```shell -export MY_OCI_REGISTRY_USERNAME=spongebob -export MY_OCI_REGISTRY_PASSWORD=squarepants -``` - -### OCI Chart Caching - -OCI charts are automatically cached in the shared cache directory (`~/.cache/helmfile` by default, or the directory specified by `HELMFILE_CACHE_HOME`). This improves performance by avoiding redundant downloads. - -**Multi-Process Safety:** When running multiple helmfile processes in parallel (e.g., as an ArgoCD plugin), charts in the shared cache are not deleted or refreshed to prevent race conditions where one process might delete a chart that another is using. To force a cache refresh, run `helmfile cache cleanup` first. - -See the [cache](#cache) section for more details on cache management. - -## Attribution - -We use: - -* [semtag](https://github.com/pnikosis/semtag) for automated semver tagging. I greatly appreciate the author(pnikosis)'s effort on creating it and their kindness to share it! diff --git a/docs/integrations.md b/docs/integrations.md new file mode 100644 index 00000000..db7c6ad7 --- /dev/null +++ b/docs/integrations.md @@ -0,0 +1,136 @@ +# Integrations + +## Integrations + +* [renovate](https://github.com/renovatebot/renovate) automates chart version updates. See [this PR for more information](https://github.com/renovatebot/renovate/pull/5257). + * For updating container image tags and git tags embedded within helmfile.yaml and values, you can use [renovate's regexManager](https://docs.renovatebot.com/modules/manager/regex/). Please see [this comment in the renovate repository](https://github.com/renovatebot/renovate/issues/6130#issuecomment-624061289) for more information. +* [ArgoCD Integration](#argocd-integration) +* [Azure ACR Integration](#azure-acr-integration) + +### ArgoCD Integration + +Use [ArgoCD](https://argoproj.github.io/argo-cd/) with `helmfile template` for GitOps. + +ArgoCD has support for kustomize/manifests/helm chart by itself. Why bother with Helmfile? + +The reasons may vary: + +1. You do want to manage applications with ArgoCD, while letting Helmfile manage infrastructure-related components like Calico/Cilium/WeaveNet, Linkerd/Istio, and ArgoCD itself. + +* This way, any application deployed by ArgoCD has access to all the infrastructure. +* Of course, you can use ArgoCD's [Sync Waves and Phases](https://argoproj.github.io/argo-cd/user-guide/sync-waves/) for ordering the infrastructure and application installations. But it may be difficult to separate the concern between the infrastructure and apps and annotate K8s resources consistently when you have different teams for managing infra and apps. + +2. You want to review the exact K8s manifests being applied on pull-request time, before ArgoCD syncs. + +* This is often better than using a kind of `HelmRelease` custom resources that obfuscates exactly what manifests are being applied, which makes reviewing harder. + +3. Use Helmfile as the single-pane of glass for all the K8s resources deployed to your cluster(s). + +* Helmfile can reduce repetition in K8s manifests across ArgoCD application + +For 1, you run `helmfile apply` on CI to deploy ArgoCD and the infrastructure components. + +> helmfile config for this phase often reside within the same directory as your Terraform project. So connecting the two with [terraform-provider-helmfile](https://github.com/mumoshu/terraform-provider-helmfile) may be helpful + +For 2, another app-centric CI or bot should render/commit manifests by running: + +``` +helmfile template --output-dir-template $(pwd)/gitops//{{.Release.Name}} +cd gitops +git add . +git commit -m 'some message' +git push origin $BRANCH +``` + +> Note that `$(pwd)` is necessary when `helmfile.yaml` has one or more sub-helmfiles in nested directories, +> because setting a relative file path in `--output-dir` or `--output-dir-template` results in each sub-helmfile render +> to the directory relative to the specified path. + +so that they can be deployed by Argo CD as usual. + +The CI or bot can optionally submit a PR to be review by human, running: + +``` +hub pull-request -b main -l gitops -m 'some description' +``` + +Recommendations: + +* Do create ArgoCD `Application` custom resource per Helm/Helmfile release, each point to respective sub-directory generated by `helmfile template --output-dir-template` +* If you don't directly push it to the main Git branch and instead go through a pull-request, do lint rendered manifests on your CI, so that you can catch easy mistakes earlier/before ArgoCD finally deploys it +* See [this ArgoCD issue](https://github.com/argoproj/argo-cd/issues/2143#issuecomment-570478329) for why you may want this, and see [this helmfile issue](https://github.com/roboll/helmfile/pull/1357) for how `--output-dir-template` works. + +### Azure ACR Integration + +Azure offers helm repository [support for Azure Container Registry](https://docs.microsoft.com/en-us/azure/container-registry/container-registry-helm-repos) as a preview feature. + +To use this you must first `az login` and then `az acr helm repo add -n `. This will extract a token for the given ACR and configure `helm` to use it, e.g. `helm repo update` should work straight away. + +To use `helmfile` with ACR, on the other hand, you must either include a username/password in the repository definition for the ACR in your `helmfile.yaml` or use the `--skip-deps` switch, e.g. `helmfile template --skip-deps`. + +An ACR repository definition in `helmfile.yaml` looks like this: + +```yaml +repositories: + - name: + url: https://.azurecr.io/helm/v1/repo +``` + +## OCI Registries + +In order to use OCI chart registries firstly they must be marked in the repository list as OCI enabled, e.g. + +```yaml +repositories: + - name: myOCIRegistry + url: myregistry.azurecr.io + oci: true +``` + +It is important not to include a scheme for the URL as helm requires that these are not present for OCI registries + +Secondly the credentials for the OCI registry can either be specified within `helmfile.yaml` similar to + +```yaml +repositories: + - name: myOCIRegistry + url: myregistry.azurecr.io + oci: true + username: spongebob + password: squarepants +``` + +or for CI scenarios these can be sourced from the environment with the format `_USERNAME` and ``, e.g. + +```shell +export MYOCIREGISTRY_USERNAME=spongebob +export MYOCIREGISTRY_PASSWORD=squarepants +``` + +If `` contains hyphens, the environment variable to be read is the hyphen replaced by an underscore., e.g. + +```yaml +repositories: + - name: my-oci-registry + url: myregistry.azurecr.io + oci: true +``` + +```shell +export MY_OCI_REGISTRY_USERNAME=spongebob +export MY_OCI_REGISTRY_PASSWORD=squarepants +``` + +### OCI Chart Caching + +OCI charts are automatically cached in the shared cache directory (`~/.cache/helmfile` by default, or the directory specified by `HELMFILE_CACHE_HOME`). This improves performance by avoiding redundant downloads. + +**Multi-Process Safety:** When running multiple helmfile processes in parallel (e.g., as an ArgoCD plugin), charts in the shared cache are not deleted or refreshed to prevent race conditions where one process might delete a chart that another is using. To force a cache refresh, run `helmfile cache cleanup` first. + +See the [cache](cli.md#cache) section for more details on cache management. + +## Attribution + +We use: + +* [semtag](https://github.com/pnikosis/semtag) for automated semver tagging. I greatly appreciate the author(pnikosis)'s effort on creating it and their kindness to share it! diff --git a/docs/releases.md b/docs/releases.md new file mode 100644 index 00000000..614c7d3b --- /dev/null +++ b/docs/releases.md @@ -0,0 +1,188 @@ +# Releases & DAG + +## DAG-aware installation/deletion ordering with `needs` + +`needs` controls the order of the installation/deletion of the release: + +```yaml +releases: +- name: somerelease + needs: + - [[KUBECONTEXT/]NAMESPACE/]anotherelease +``` + +Be aware that you have to specify the kubecontext and namespace name if you configured one for the release(s). + +All the releases listed under `needs` are installed before(or deleted after) the release itself. + +For the following example, `helmfile [sync|apply]` installs releases in this order: + +1. logging +2. servicemesh +3. myapp1 and myapp2 + +```yaml + - name: myapp1 + chart: charts/myapp + needs: + - servicemesh + - logging + - name: myapp2 + chart: charts/myapp + needs: + - servicemesh + - logging + - name: servicemesh + chart: charts/istio + needs: + - logging + - name: logging + chart: charts/fluentd +``` + +Note that all the releases in a same group is installed concurrently. That is, myapp1 and myapp2 are installed concurrently. + +On `helmfile [delete|destroy]`, deletions happen in the reverse order. + +That is, `myapp1` and `myapp2` are deleted first, then `servicemesh`, and finally `logging`. + +### Selectors and `needs` + +When using selectors/labels, `needs` are ignored by default. This behaviour can be overruled with a few parameters: + +| Parameter | default | Description | +|---|---|---| +| `--skip-needs` | `true` | `needs` are ignored (default behavior). | +| `--include-needs` | `false` | The direct `needs` of the selected release(s) will be included. | +| `--include-transitive-needs` | `false` | The direct and transitive `needs` of the selected release(s) will be included. | + +Let's look at an example to illustrate how the different parameters work: + +```yaml +releases: +- name: serviceA + chart: my/chart + needs: + - serviceB +- name: serviceB + chart: your/chart + needs: + - serviceC +- name: serviceC + chart: her/chart +- name: serviceD + chart: his/chart +``` + +| Command | Included Releases Order | Explanation | +|---|---|---| +| `helmfile -l name=serviceA sync` | - `serviceA` | By default no needs are included. | +| `helmfile -l name=serviceA sync --include-needs` | - `serviceB`
- `serviceA` | `serviceB` is now part of the release as it is a direct need of `serviceA`. | +| `helmfile -l name=serviceA sync --include-transitive-needs` | - `serviceC`
- `serviceB`
- `serviceA` | `serviceC` is now also part of the release as it is a direct need of `serviceB` and therefore a transitive need of `serviceA`. | + +Note that `--include-transitive-needs` will override any potential exclusions done by selectors or conditions. So even if you explicitly exclude a release via a selector it will still be part of the deployment in case it is a direct or transitive need of any of the specified releases. + +## Separating helmfile.yaml into multiple independent files + +Once your `helmfile.yaml` got to contain too many releases, +split it into multiple yaml files. + +Recommended granularity of helmfile.yaml files is "per microservice" or "per team". +And there are two ways to organize your files. + +* Single directory +* Glob patterns + +### Single directory + +`helmfile -f path/to/directory` loads and runs all the yaml files under the specified directory, each file as an independent helmfile.yaml. +The default helmfile directory is `helmfile.d`, that is, +in case helmfile is unable to locate `helmfile.yaml`, it tries to locate `helmfile.d/*.yaml`. + +By default, multiple files in `helmfile.d` are processed in **parallel** for better performance. If you need files to be processed **sequentially in alphabetical order** (e.g., for dependency ordering where databases must be deployed before applications), use the `--sequential-helmfiles` flag. + +For example, you can use a `-.yaml` naming convention to control the sync order when using `--sequential-helmfiles`: + +* `helmfile.d`/ + * `00-database.yaml` + * `01-backend.yaml` + * `02-frontend.yaml` + +```bash +# Process files sequentially in alphabetical order +helmfile --sequential-helmfiles sync +``` + +> **Note:** When processing multiple helmfile.d files, both parallel and sequential modes resolve paths without changing the process working directory, so relative environment variables like `KUBECONFIG` work correctly. + +### Glob patterns + +In case you want more control over how multiple `helmfile.yaml` files are organized, use `helmfiles:` configuration key in the `helmfile.yaml`: + +Suppose you have multiple microservices organized in a Git repository that looks like: + +* `myteam/` (sometimes it is equivalent to a k8s ns, that is `kube-system` for `clusterops` team) + * `apps/` + * `filebeat/` + * `helmfile.yaml` (no `charts/` exists because it depends on the stable/filebeat chart hosted on the official helm charts repository) + * `README.md` (each app managed by my team has a dedicated README maintained by the owners of the app) + * `metricbeat/` + * `helmfile.yaml` + * `README.md` + * `elastalert-operator/` + * `helmfile.yaml` + * `README.md` + * `charts/` + * `elastalert-operator/` + * `` + +The benefits of this structure is that you can run `git diff` to locate in which directory=microservice a git commit has changes. +It allows your CI system to run a workflow for the changed microservice only. + +A downside of this is that you don't have an obvious way to sync all microservices at once. That is, you have to run: + +```bash +for d in apps/*; do helmfile -f $d diff; if [ $? -eq 2 ]; then helmfile -f $d sync; fi; done +``` + +At this point, you'll start writing a `Makefile` under `myteam/` so that `make sync-all` will do the job. + +It does work, but you can rely on the Helmfile feature instead. + +Put `myteam/helmfile.yaml` that looks like: + +```yaml +helmfiles: +- apps/*/helmfile.yaml +``` + +So that you can get rid of the `Makefile` and the bash snippet. +Just run `helmfile sync` inside `myteam/`, and you are done. + +All the files are sorted alphabetically per group = array item inside `helmfiles:`, so that you have granular control over ordering, too. + +#### selectors + +When composing helmfiles you can use selectors from the command line as well as explicit selectors inside the parent helmfile to filter the releases to be used. + +```yaml +helmfiles: +- apps/*/helmfile.yaml +- path: apps/a-helmfile.yaml + selectors: # list of selectors + - name=prometheus + - tier=frontend +- path: apps/b-helmfile.yaml # no selector, so all releases are used + selectors: [] +- path: apps/c-helmfile.yaml # parent selector to be used or cli selector for the initial helmfile + selectorsInherited: true +``` + +* When a subhelmfile has explicit `selectors`, those selectors determine which releases from that subhelmfile are considered; parent and CLI selectors are not combined with them for release filtering. +* When CLI selectors are provided (e.g. `helmfile -l name=b sync`) and a subhelmfile has explicit selectors that are provably incompatible with them (same key, different value), that subhelmfile may be **skipped entirely** without loading or rendering it. For example, with `-l name=b`, a subhelmfile with `selectors: [name=a]` will be skipped since no release could match both. This optimization does not apply when `selectorsInherited: true` is set or when no CLI selectors are provided. Use `--debug` to see log messages about skipped subhelmfiles. +* When not selector is specified there are 2 modes for the selector inheritance because we would like to change the current inheritance behavior (see [issue #344](https://github.com/roboll/helmfile/issues/344) ). + * Legacy mode, sub-helmfiles without selectors inherit selectors from their parent helmfile. The initial helmfiles inherit from the command line selectors. + * explicit mode, sub-helmfile without selectors do not inherit from their parent or the CLI selector. If you want them to inherit from their parent selector then use `selectorsInherited: true`. To enable this explicit mode you need to set the following environment variable `HELMFILE_EXPERIMENTAL=explicit-selector-inheritance` (see [experimental](experimental-features.md)). +* Using `selector: []` will select all releases regardless of the parent selector or cli for the initial helmfile +* using `selectorsInherited: true` make the sub-helmfile selects releases with the parent selector or the cli for the initial helmfile. You cannot specify an explicit selector while using `selectorsInherited: true` + diff --git a/docs/templating.md b/docs/templating.md new file mode 100644 index 00000000..d994c8f1 --- /dev/null +++ b/docs/templating.md @@ -0,0 +1,236 @@ +# Templating + +Helmfile uses [Go templates](https://godoc.org/text/template) for templating your helmfile.yaml. While go ships several built-in functions, we have added all of the functions in the [Sprig library](https://godoc.org/github.com/Masterminds/sprig). + +We also added the following functions: + +* [`env`](templating_funcs.md#env) +* [`requiredEnv`](templating_funcs.md#requiredenv) +* [`exec`](templating_funcs.md#exec) +* [`envExec`](templating_funcs.md#envexec) +* [`readFile`](templating_funcs.md#readfile) +* [`readDir`](templating_funcs.md#readdir) +* [`readDirEntries`](templating_funcs.md#readdirentries) +* [`toYaml`](templating_funcs.md#toyaml) +* [`fromYaml`](templating_funcs.md#fromyaml) +* [`setValueAtPath`](templating_funcs.md#setvalueatpath) +* [`get`](templating_funcs.md#get) (Sprig's original `get` is available as `sprigGet`) +* [`getOrNil`](templating_funcs.md#getornil) +* [`tpl`](templating_funcs.md#tpl) +* [`required`](templating_funcs.md#required) +* [`fetchSecretValue`](templating_funcs.md#fetchsecretvalue) +* [`expandSecretRefs`](templating_funcs.md#expandsecretrefs) +* [`include`](templating_funcs.md#include) + +More details on each function can be found at the ["Template Functions" page in our documentation](templating_funcs.md). + +## Using environment variables + +Environment variables can be used in most places for templating the helmfile. Currently this is supported for `name`, `namespace`, `value` (in set), `values` and `url` (in repositories). + +Examples: + +```yaml +repositories: +- name: your-private-git-repo-hosted-charts + url: https://{{ requiredEnv "GITHUB_TOKEN"}}@raw.githubusercontent.com/kmzfs/helm-repo-in-github/master/ +``` + +```yaml +releases: + - name: {{ requiredEnv "NAME" }}-vault + namespace: {{ requiredEnv "NAME" }} + chart: roboll/vault-secret-manager + values: + - db: + username: {{ requiredEnv "DB_USERNAME" }} + password: {{ requiredEnv "DB_PASSWORD" }} + set: + - name: proxy.domain + value: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com + - name: proxy.scheme + value: {{ env "SCHEME" | default "https" }} +``` + +### Note + +If you wish to treat your enviroment variables as strings always, even if they are boolean or numeric values you can use `{{ env "ENV_NAME" | quote }}` or `"{{ env "ENV_NAME" }}"`. These approaches also work with `requiredEnv`. + +### Useful internal Helmfile environment variables + +Helmfile uses some OS environment variables to override default behaviour: + +* `HELMFILE_DISABLE_INSECURE_FEATURES` - disable insecure features, expecting `true` lower case +* `HELMFILE_DISABLE_RUNNER_UNIQUE_ID` - disable unique logging ID, expecting any non-empty value +* `HELMFILE_SKIP_INSECURE_TEMPLATE_FUNCTIONS` - disable insecure template functions, expecting `true` lower case +* `HELMFILE_USE_HELM_STATUS_TO_CHECK_RELEASE_EXISTENCE` - expecting non-empty value to use `helm status` to check release existence, instead of `helm list` which is the default behaviour +* `HELMFILE_EXPERIMENTAL` - enable experimental features, expecting `true` lower case +* `HELMFILE_ENVIRONMENT` - specify [Helmfile environment](environments.md), it has lower priority than CLI argument `--environment` +* `HELMFILE_TEMPDIR` - specify directory to store temporary files +* `HELMFILE_UPGRADE_NOTICE_DISABLED` - expecting any non-empty value to skip the check for the latest version of Helmfile in [helmfile version](cli.md#version) +* `HELMFILE_GO_YAML_V3` - use *go.yaml.in/yaml/v3* instead of *go.yaml.in/yaml/v2*. It's `false` by default in Helmfile v0.x, and `true` in Helmfile v1.x. +* `HELMFILE_CACHE_HOME` - specify directory to store cached files for remote operations +* `HELMFILE_FILE_PATH` - specify the path to the helmfile.yaml file +* `HELMFILE_INTERACTIVE` - enable interactive mode, expecting `true` lower case. The same as `--interactive` CLI flag +* `HELMFILE_RENDER_YAML` - force helmfile.yaml to be rendered as a Go template regardless of file extension, expecting `true` lower case. Useful for migrating from v0 to v1 without renaming files to `.gotmpl` +* `HELMFILE_AWS_SDK_LOG_LEVEL` - configure AWS SDK logging level for vals library. Valid values: `off` (default, secure, case-insensitive), `minimal`, `standard`, `verbose`, or custom comma-separated values like `request,response`. See issue #2270 for details +* `HELMFILE_VALS_FAIL_ON_MISSING_KEY_IN_MAP` - enable strict mode for vals secret references. When set to `true` (or any value accepted by Go's `strconv.ParseBool` like `TRUE`, `1`), vals will fail when a referenced key does not exist in the secret map. Invalid values will cause an error when vals is initialized (when secret refs are first evaluated). Default is `false` (when unset or empty) for backward compatibility. See issue #1563 for details + + +## Templates + +You can use go's text/template expressions in `helmfile.yaml` and `values.yaml.gotmpl` (templated helm values files). `values.yaml` references will be used verbatim. In other words: + +* for value files ending with `.gotmpl`, template expressions will be rendered +* for plain value files (ending in `.yaml`), content will be used as-is + +In addition to built-in ones, the following custom template functions are available: + +* `readFile` reads the specified local file and generate a golang string +* `readDir` reads the files within provided directory path. (folders are excluded) +* `readDirEntries` Returns a list of [https://pkg.go.dev/os#DirEntry](DirEntry) within provided directory path +* `fromYaml` reads a golang string and generates a map +* `setValueAtPath PATH NEW_VALUE` traverses a golang map, replaces the value at the PATH with NEW_VALUE +* `toYaml` marshals a map into a string +* `get` returns the value of the specified key if present in the `.Values` object, otherwise will return the default value defined in the function + +### Values Files Templates + +You can reference a template of values file in your `helmfile.yaml` like below: + +```yaml +releases: +- name: myapp + chart: mychart + values: + - values.yaml.gotmpl +``` + +Every values file whose file extension is `.gotmpl` is considered as a template file. + +Suppose `values.yaml.gotmpl` was something like: + +```yaml +{{ readFile "values.yaml" | fromYaml | setValueAtPath "foo.bar" "FOO_BAR" | toYaml }} +``` + +And `values.yaml` was: + +```yaml +foo: + bar: "" +``` + +The resulting, temporary values.yaml that is generated from `values.yaml.gotmpl` would become: + +```yaml +foo: + # Notice `setValueAtPath "foo.bar" "FOO_BAR"` in the template above + bar: FOO_BAR +``` + +## Refactoring `helmfile.yaml` with values files templates + +One of expected use-cases of values files templates is to keep `helmfile.yaml` small and concise. + +See the example `helmfile.yaml` below: + +```yaml +releases: + - name: {{ requiredEnv "NAME" }}-vault + namespace: {{ requiredEnv "NAME" }} + chart: roboll/vault-secret-manager + values: + - db: + username: {{ requiredEnv "DB_USERNAME" }} + password: {{ requiredEnv "DB_PASSWORD" }} + set: + - name: proxy.domain + value: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com + - name: proxy.scheme + value: {{ env "SCHEME" | default "https" }} +``` + +The `values` and `set` sections of the config file can be separated out into a template: + +`helmfile.yaml`: + +```yaml +releases: + - name: {{ requiredEnv "NAME" }}-vault + namespace: {{ requiredEnv "NAME" }} + chart: roboll/vault-secret-manager + values: + - values.yaml.gotmpl +``` + +`values.yaml.gotmpl`: + +```yaml +db: + username: {{ requiredEnv "DB_USERNAME" }} + password: {{ requiredEnv "DB_PASSWORD" }} +proxy: + domain: {{ requiredEnv "PLATFORM_ID" }}.my-domain.com + scheme: {{ env "SCHEME" | default "https" }} +``` + +## Importing values from any source + +The `exec` template function that is available in `values.yaml.gotmpl` is useful for importing values from any source +that is accessible by running a command: + +A usual usage of `exec` would look like this: + +```yaml +mysetting: | +{{ exec "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }} +``` + +Or even with a pipeline: + +```yaml +mysetting: | +{{ yourinput | exec "./mycmd-consume-stdin" (list "arg1" "arg2") | indent 2 }} +``` + +The possibility is endless. Try importing values from your golang app, bash script, jsonnet, or anything! + +Then `envExec` same as `exec`, but it can receive a dict as the envs. + +A usual usage of `envExec` would look like this: + +```yaml +mysetting: | +{{ envExec (dict "envkey" "envValue") "./mycmd" (list "arg1" "arg2" "--flag1") | indent 2 }} +``` + +## Using .env files + +Helmfile itself doesn't have an ability to load .env files. But you can write some bash script to achieve the goal: + +```console +set -a; . .env; set +a; helmfile sync +``` + +Please see #203 for more context. + +## Running Helmfile interactively + +`helmfile --interactive [apply|destroy|delete|sync]` requests confirmation from you before actually modifying your cluster. + +Use it when you're running `helmfile` manually on your local machine or a kind of secure administrative hosts. + +For your local use-case, aliasing it like `alias hi='helmfile --interactive'` would be convenient. + +Another way to use it is to set the environment variable `HELMFILE_INTERACTIVE=true` to enable the interactive mode by default. +Anything other than `true` will disable the interactive mode. The precedence has the `--interactive` flag. + +## Running Helmfile without an Internet connection + +Once you download all required charts into your machine, you can run `helmfile sync --skip-deps` to deploy your apps. +With the `--skip-deps` option, you can skip running "helm repo update" and "helm dependency build". + +## `bash` and `zsh` completion + +helmfile completion --help diff --git a/docs/writing-helmfile.md b/docs/writing-helmfile.md index 96c499b8..e0bc2f8c 100644 --- a/docs/writing-helmfile.md +++ b/docs/writing-helmfile.md @@ -1,6 +1,6 @@ -# The Helmfile Best Practices Guide +# Writing Helmfile -This guide covers the Helmfile's considered patterns for writing advanced helmfiles. It focuses on how helmfile should be structured and executed. +This guide covers patterns and best practices for writing helmfiles. It focuses on how helmfile should be structured and executed. **Before diving into advanced patterns, we strongly recommend reading [Values Merging and Data Flow](values-and-merging.md)** to understand how Helmfile merges values from various sources. This foundational knowledge is essential for writing effective helmfiles. diff --git a/mkdocs.yml b/mkdocs.yml index b4c92617..36cf6e08 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -16,17 +16,24 @@ docs_dir: docs nav: - Home: index.md - Getting Started: - - Paths Overview: paths.md - - Templating Funcs: templating_funcs.md - - HCL Funcs: hcl_funcs.md - - Built-in Objects: builtin-objects.md - - Core Concepts: + - Writing Helmfile: writing-helmfile.md - Values and Merging: values-and-merging.md - - Advanced Features: - - Best Practices Guide: writing-helmfile.md + - Environments: environments.md + - Releases & DAG: releases.md + - Configuration: + - helmfile.yaml Reference: configuration.md + - Templating: templating.md + - Template Functions: templating_funcs.md + - Built-in Objects: builtin-objects.md + - HCL Functions: hcl_funcs.md + - Paths Overview: paths.md + - CLI Reference: cli.md + - Advanced: - Advanced Features: advanced-features.md + - Hooks: hooks.md - Secrets: remote-secrets.md - - Shared Configuration Across Teams: shared-configuration-across-teams.md + - Shared Configuration: shared-configuration-across-teams.md + - Integrations: integrations.md - Experimental Features: experimental-features.md - About: - Users: users.md From 7cc5fe03584bddd93f7d1f3cf9f49dc449b9f41e Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Sun, 3 May 2026 20:59:03 +0800 Subject: [PATCH 15/28] docs: deduplicate Technical Details sections in values-and-merging.md (#2575) Signed-off-by: yxxhero --- docs/values-and-merging.md | 165 +++++++++---------------------------- 1 file changed, 38 insertions(+), 127 deletions(-) diff --git a/docs/values-and-merging.md b/docs/values-and-merging.md index 8c7e6832..5b39d80f 100644 --- a/docs/values-and-merging.md +++ b/docs/values-and-merging.md @@ -412,133 +412,6 @@ Here's the complete data flow when running `helmfile sync`: - Defaults & Values: `ArrayMergeStrategySparse` (auto-detect nil values) - CLIOverrides: `ArrayMergeStrategyMerge` (always element-by-element) -## Technical Details - -### Secret Handling Intern - -Helmfile processes secrets in a special way: - -**Non-HCL secrets (.yaml, .yaml.gotmpl):** -1. Decrypted using helm-secrets plugin -2. Parsed into values immediately (during load phase) -3. Stored separately from regular values -4. **Mrged last** (highest priority) after all environment values are loaded - -**HCL secrets (.hcl):** -1. Decrypted using helm-secrets plugin -2. Decrypted file paths added to values file list -3. Processed in step 2 (HCL loading phase) -4. Can reference values from other HCL files (using `hv.` accessor) - -This separation allows: -- Secrets to override regular values without being re-decrypted multiple times -- HCL secrets to participate in HCL's cross-file referencing system - -### Multiple Helmfiles and State files - -When using multi-part helmfiles (multiple YAML documents separated by `---`): - -```yaml -# Part 1: base.yaml -helmDefaults: - wait: true - timeout: 300 ---- -# Part 2: environments.yaml -environments: - default: - production: -``` - -Each part is processed in order: - and the results are merged with later parts taking precedence. - -## Technical Details - -### Environment Structure Internals - -The `Environment` struct has three key fields that affect merging: - -```go -type Environment struct { - Name string - KubeContext string - Values map[string]any // Environment values + secrets - Defaults map[string]any // Root-level values: block - CLIOverrides map[string]any // CLI --state-values-set -} -``` - -### Final Merge Process (GetMergedValues) - -When you access `.Values` in templates, Helmfile calls `GetMergedValues()` which merges in this order: - -```go -func (e *Environment) GetMergedValues() (map[string]any, error) { - vals := map[string]any{} - vals = maputil.MergeMaps(vals, e.Defaults) // 1. Defaults (root-level values:) - vals = maputil.MergeMaps(vals, e.Values) // 2. Values (environment values + secrets) - vals = maputil.MergeMaps(vals, e.CLIOverrides, // 3. CLIOverrides (highest priority) - maputil.MergeOptions{ArrayStrategy: maputil.ArrayMergeStrategyMerge}) - return vals, nil -} -``` - -**Important:** CLIOverrides uses `ArrayMergeStrategyMerge` (element-by-element merging), while Defaults and Values use the default strategy (sparse auto-detection). - -### Merging Library: mergo - -Helmfile uses the [mergo](https://github.com/imdario/mergo) library for deep merging with these key features: - -1. **Deep merge for maps**: Nested maps are merged recursively -2. **WithOverride option**: Later values override earlier values -3. **Type-safe**: Preserves value types during merge - -Example from code: -```go -// In loadEnvValues() -if err := mergo.Merge(&valuesVals, &secretVals, mergo.WithOverride); err != nil { - return nil, err -} -``` - -### Array Merge Strategies Implementation - -The `maputil.MergeMaps` function supports three array merge strategies: - -```go -type ArrayMergeStrategy int - -const ( - ArrayMergeStrategySparse ArrayMergeStrategy = iota // Auto-detect based on nil values - ArrayMergeStrategyReplace ArrayMergeStrategy = iota // Always replace arrays - ArrayMergeStrategyMerge ArrayMergeStrategy = iota // Always merge element-by-element -) -``` - -**Sparse Strategy (Default for most cases):** -```go -func mergeSlices(base, override []any, strategy ArrayMergeStrategy) []any { - if strategy == ArrayMergeStrategySparse { - isSparse := false - for _, v := range override { - if v == nil { - isSparse = true - break - } - } - if !isSparse { - return override // Replace entirely - } - // Otherwise merge element-by-element - } -} -``` - -This means: -- `[1, 2, 3]` merged with `[4, 5]` → result: `[4, 5]` (replaced, no nils) -- `[null, 2]` merged with `[1, 2, 3]` → result: `[1, 2, 3]` (merged, has nil) - ## Common Patterns ### Pattern 1: Global Defaults with Environment Overrides @@ -633,6 +506,44 @@ environments: ## Technical Implementation Details +### Secret Handling + +Helmfile processes secrets in a special way: + +**Non-HCL secrets (.yaml, .yaml.gotmpl):** +1. Decrypted using helm-secrets plugin +2. Parsed into values immediately (during load phase) +3. Stored separately from regular values +4. **Merged last** (highest priority) after all environment values are loaded + +**HCL secrets (.hcl):** +1. Decrypted using helm-secrets plugin +2. Decrypted file paths added to values file list +3. Processed in step 2 (HCL loading phase) +4. Can reference values from other HCL files (using `hv.` accessor) + +This separation allows: +- Secrets to override regular values without being re-decrypted multiple times +- HCL secrets to participate in HCL's cross-file referencing system + +### Multiple Helmfiles and State files + +When using multi-part helmfiles (multiple YAML documents separated by `---`): + +```yaml +# Part 1: base.yaml +helmDefaults: + wait: true + timeout: 300 +--- +# Part 2: environments.yaml +environments: + default: + production: +``` + +Each part is processed in order and the results are merged with later parts taking precedence. + ### Environment Structure The `Environment` struct is the core data structure that holds all values: From 420cc3ba9ceac36c384e85e76badabe97777dc50 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Mon, 4 May 2026 14:20:03 +0800 Subject: [PATCH 16/28] fix: add trackFailOnError option to control kubedog exit code (#2576) * fix: add trackFailOnError option to control kubedog exit code behavior When kubedog release tracking fails (e.g. pod ImagePullBackOff), helmfile exits with code 0 instead of a non-zero exit code. Add a trackFailOnError configuration option (default: false) that when set to true, propagates kubedog tracking failures to the exit code. The option is available as: - Per-release YAML: trackFailOnError: true - CLI flag: --track-fail-on-error (sync and apply commands) Extract trackReleaseIfEnabled helper to consolidate kubedog tracking logic from two duplicated call sites into a single maintainable method. Fixes #2507 Signed-off-by: yxxhero * fix: add //go:build ignore to server.go to fix go test CI failure The test/integration/test-cases/issue-2103/input/server.go is a package main helper binary used by the issue-2103 integration test. When go test -coverprofile runs on this package, it fails with "go: no such tool covdata" in the CI environment. Adding //go:build ignore excludes the file from go list ./... (and therefore from PKGS in the Makefile), while still allowing the integration test to build it explicitly via file path: go build -o server ./path/to/server.go Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/8a7000af-72b7-48f8-8a82-24813b5df341 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: update TestGenerateID expected hashes after adding TrackFailOnError field Adding TrackFailOnError *bool to ReleaseSpec changed the spew serialization of the struct, which changed the FNV-32a hash values produced by generateValuesID. Update temp_test.go with the new expected hash strings. Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/caa86cd9-73d1-4894-b745-fd70c0811fd6 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Signed-off-by: yxxhero Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> --- cmd/apply.go | 1 + cmd/sync.go | 1 + pkg/app/app.go | 2 ++ pkg/app/app_test.go | 5 ++++ pkg/app/config.go | 2 ++ pkg/config/apply.go | 7 +++++ pkg/config/sync.go | 7 +++++ pkg/state/helmx.go | 26 +++++++++++++++++++ pkg/state/state.go | 18 +++++++------ pkg/state/temp_test.go | 12 ++++----- .../test-cases/issue-2103/input/server.go | 5 ++++ 11 files changed, 72 insertions(+), 14 deletions(-) diff --git a/cmd/apply.go b/cmd/apply.go index c79e97cb..5f7da506 100644 --- a/cmd/apply.go +++ b/cmd/apply.go @@ -72,6 +72,7 @@ func NewApplyCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.StringVar(&applyOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default), 'helm-legacy' (Helm v4 only), or 'kubedog'") f.IntVar(&applyOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`) f.BoolVar(&applyOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking") + f.BoolVar(&applyOptions.TrackFailOnError, "track-fail-on-error", false, "Fail with non-zero exit code when kubedog tracking fails") f.StringVar(&applyOptions.Description, "description", "", `Set description for all releases. If set, overridesdescriptions in helmfile.yaml. Will be passed to "helm upgrade --description"`) return cmd diff --git a/cmd/sync.go b/cmd/sync.go index 5d180103..4b8985ca 100644 --- a/cmd/sync.go +++ b/cmd/sync.go @@ -57,6 +57,7 @@ func NewSyncCmd(globalCfg *config.GlobalImpl) *cobra.Command { f.StringVar(&syncOptions.TrackMode, "track-mode", "", "Track mode for releases: 'helm' (default), 'helm-legacy' (Helm v4 only), or 'kubedog'") f.IntVar(&syncOptions.TrackTimeout, "track-timeout", 0, `Timeout in seconds for kubedog tracking (0 to use default 300s timeout)`) f.BoolVar(&syncOptions.TrackLogs, "track-logs", false, "Enable log streaming with kubedog tracking") + f.BoolVar(&syncOptions.TrackFailOnError, "track-fail-on-error", false, "Fail with non-zero exit code when kubedog tracking fails") f.StringVar(&syncOptions.Description, "description", "", `Set description for all releases. If set, overrides descriptions in helmfile.yaml. Will be passed to "helm upgrade --description"`) return cmd diff --git a/pkg/app/app.go b/pkg/app/app.go index 6b9738c6..b752a799 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1891,6 +1891,7 @@ Do you really want to apply? TrackMode: c.TrackMode(), TrackTimeout: c.TrackTimeout(), TrackLogs: c.TrackLogs(), + TrackFailOnError: c.TrackFailOnError(), Description: c.Description(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) @@ -2361,6 +2362,7 @@ Do you really want to sync? TrackMode: c.TrackMode(), TrackTimeout: c.TrackTimeout(), TrackLogs: c.TrackLogs(), + TrackFailOnError: c.TrackFailOnError(), Description: c.Description(), } return subst.SyncReleases(&affectedReleases, helm, c.Values(), c.Concurrency(), syncOpts) diff --git a/pkg/app/app_test.go b/pkg/app/app_test.go index bcc996f3..03582cad 100644 --- a/pkg/app/app_test.go +++ b/pkg/app/app_test.go @@ -2534,6 +2534,7 @@ type applyConfig struct { trackMode string trackTimeout int trackLogs bool + trackFailOnError bool // template-only options includeCRDs, skipTests bool @@ -2760,6 +2761,10 @@ func (a applyConfig) TrackLogs() bool { return a.trackLogs } +func (a applyConfig) TrackFailOnError() bool { + return a.trackFailOnError +} + func (a applyConfig) Description() string { return "" } diff --git a/pkg/app/config.go b/pkg/app/config.go index aa7cfa5b..560be510 100644 --- a/pkg/app/config.go +++ b/pkg/app/config.go @@ -91,6 +91,7 @@ type ApplyConfigProvider interface { TrackMode() string TrackTimeout() int TrackLogs() bool + TrackFailOnError() bool Description() string @@ -130,6 +131,7 @@ type SyncConfigProvider interface { TrackMode() string TrackTimeout() int TrackLogs() bool + TrackFailOnError() bool Description() string diff --git a/pkg/config/apply.go b/pkg/config/apply.go index 8580bba2..bc940b9e 100644 --- a/pkg/config/apply.go +++ b/pkg/config/apply.go @@ -88,6 +88,8 @@ type ApplyOptions struct { TrackTimeout int // TrackLogs enables log streaming with kubedog TrackLogs bool + // TrackFailOnError controls whether kubedog tracking failures cause a non-zero exit code + TrackFailOnError bool // Description is the description that will be passed to helm upgrade --description Description string } @@ -316,6 +318,11 @@ func (a *ApplyImpl) TrackLogs() bool { return a.ApplyOptions.TrackLogs } +// TrackFailOnError returns whether kubedog tracking failures should cause a non-zero exit code. +func (a *ApplyImpl) TrackFailOnError() bool { + return a.ApplyOptions.TrackFailOnError +} + // Description returns the description. func (a *ApplyImpl) Description() string { return a.ApplyOptions.Description diff --git a/pkg/config/sync.go b/pkg/config/sync.go index 8124108b..a838cc1f 100644 --- a/pkg/config/sync.go +++ b/pkg/config/sync.go @@ -59,6 +59,8 @@ type SyncOptions struct { TrackTimeout int // TrackLogs enables log streaming with kubedog TrackLogs bool + // TrackFailOnError controls whether kubedog tracking failures cause a non-zero exit code + TrackFailOnError bool // Description is the description that will be passed to helm upgrade --description Description string } @@ -216,6 +218,11 @@ func (t *SyncImpl) TrackLogs() bool { return t.SyncOptions.TrackLogs } +// TrackFailOnError returns whether kubedog tracking failures should cause a non-zero exit code. +func (t *SyncImpl) TrackFailOnError() bool { + return t.SyncOptions.TrackFailOnError +} + // Description returns the description. func (t *SyncImpl) Description() string { return t.SyncOptions.Description diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index ada74c88..ca118921 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -197,6 +197,32 @@ func (st *HelmState) shouldUseKubedog(release *ReleaseSpec, ops *SyncOpts) bool return st.getTrackMode(release, ops) == string(kubedog.TrackModeKubedog) } +func (st *HelmState) shouldFailOnTrackError(release *ReleaseSpec, ops *SyncOpts) bool { + if release.TrackFailOnError != nil { + return *release.TrackFailOnError + } + if ops != nil { + return ops.TrackFailOnError + } + return false +} + +// trackReleaseIfEnabled performs kubedog tracking for a release if trackMode is "kubedog". +// It returns a ReleaseError if tracking fails and shouldFailOnTrackError is true. +// The caller is responsible for mutating affectedReleases when needed. +func (st *HelmState) trackReleaseIfEnabled(ctx context.Context, release *ReleaseSpec, helm helmexec.Interface, opts *SyncOpts) *ReleaseError { + if !st.shouldUseKubedog(release, opts) { + return nil + } + if trackErr := st.trackWithKubedog(ctx, release, helm, opts); trackErr != nil { + st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr) + if st.shouldFailOnTrackError(release, opts) { + return newReleaseFailedError(release, trackErr) + } + } + return nil +} + func (st *HelmState) getTrackMode(release *ReleaseSpec, ops *SyncOpts) string { trackMode := release.TrackMode if trackMode == "" && ops != nil && ops.TrackMode != "" { diff --git a/pkg/state/state.go b/pkg/state/state.go index 594680e5..106d4a66 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -478,6 +478,8 @@ type ReleaseSpec struct { KubedogQPS *float32 `yaml:"kubedogQPS,omitempty"` // KubedogBurst specifies the burst for kubedog kubernetes client KubedogBurst *int `yaml:"kubedogBurst,omitempty"` + // TrackFailOnError controls whether kubedog tracking failures cause a non-zero exit code + TrackFailOnError *bool `yaml:"trackFailOnError,omitempty"` } // TrackResourceSpec specifies a resource to track @@ -912,6 +914,7 @@ type SyncOpts struct { TrackMode string TrackTimeout int TrackLogs bool + TrackFailOnError bool Description string } @@ -1138,10 +1141,8 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme } } else if release.UpdateStrategy == UpdateStrategyReinstallIfForbidden { relErr = st.performSyncOrReinstallOfRelease(affectedReleases, helm, context, release, chart, m, flags...) - if relErr == nil && st.shouldUseKubedog(release, opts) { - if trackErr := st.trackWithKubedog(gocontext.Background(), release, helm, opts); trackErr != nil { - st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr) - } + if relErr == nil { + relErr = st.trackReleaseIfEnabled(gocontext.Background(), release, helm, opts) } } else { if err := helm.SyncRelease(context, release.Name, chart, release.Namespace, flags...); err != nil { @@ -1160,10 +1161,11 @@ func (st *HelmState) SyncReleases(affectedReleases *AffectedReleases, helm helme release.installedVersion = installedVersion } - if st.shouldUseKubedog(release, opts) { - if trackErr := st.trackWithKubedog(gocontext.Background(), release, helm, opts); trackErr != nil { - st.logger.Warnf("kubedog tracking failed for release %s: %v", release.Name, trackErr) - } + if trackErr := st.trackReleaseIfEnabled(gocontext.Background(), release, helm, opts); trackErr != nil { + m.Lock() + affectedReleases.Failed = append(affectedReleases.Failed, release) + m.Unlock() + relErr = trackErr } } } diff --git a/pkg/state/temp_test.go b/pkg/state/temp_test.go index 7ea16939..9ca7339e 100644 --- a/pkg/state/temp_test.go +++ b/pkg/state/temp_test.go @@ -38,39 +38,39 @@ func TestGenerateID(t *testing.T) { run(testcase{ subject: "baseline", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, - want: "foo-values-6ccb848dcd", + want: "foo-values-7f6f8d74dd", }) run(testcase{ subject: "different bytes content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: []byte(`{"k":"v"}`), - want: "foo-values-5bcbbc4c85", + want: "foo-values-5fc74c864c", }) run(testcase{ subject: "different map content", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw"}, data: map[string]any{"k": "v"}, - want: "foo-values-7c6468f955", + want: "foo-values-77df88dd65", }) run(testcase{ subject: "different chart", release: ReleaseSpec{Name: "foo", Chart: "stable/envoy"}, - want: "foo-values-8645f5847f", + want: "foo-values-77c96457f7", }) run(testcase{ subject: "different name", release: ReleaseSpec{Name: "bar", Chart: "incubator/raw"}, - want: "bar-values-54bd8c865", + want: "bar-values-6695f7ff4c", }) run(testcase{ subject: "specific ns", release: ReleaseSpec{Name: "foo", Chart: "incubator/raw", Namespace: "myns"}, - want: "myns-foo-values-b4849b445", + want: "myns-foo-values-9b9484d4c", }) for id, n := range ids { diff --git a/test/integration/test-cases/issue-2103/input/server.go b/test/integration/test-cases/issue-2103/input/server.go index fc34fd5a..0860d96a 100644 --- a/test/integration/test-cases/issue-2103/input/server.go +++ b/test/integration/test-cases/issue-2103/input/server.go @@ -1,5 +1,10 @@ // server.go is a small HTTP server used by the issue-2103 integration test. // It serves different YAML content based on the "ref" query parameter. +// It is excluded from normal `go test ./...` runs; the integration test builds it +// explicitly via its file path. + +//go:build ignore + package main import ( From 41d815aa5b3f1d5b8277543e827973cff81f0534 Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Mon, 4 May 2026 14:20:17 +0800 Subject: [PATCH 17/28] docs: update helmfile skill to reflect v1.1 documentation (#2577) docs: update helmfile skill to reflect v1.1 documentation (#2576) - Add Helmfile v1.0/v1.1 status and Helm 4 support - Expand release configuration fields with tracking, templating, and ownership options - Add new CLI commands: fetch, unittest, show-dag, write-values, build, cache, create - Add values merging and data flow precedence documentation - Expand hooks with preapply, preuninstall, postuninstall events and kubectlApply - Add kubedog resource tracking (helm/helm-legacy/kubedog modes) - Add JSON patches and template partials documentation - Complete remote secrets (vals) with fetchSecretValue and expandSecretRefs - Add comprehensive repository configuration (OCI, TLS, GPG verify) - Update metadata version to 1.1.0 Signed-off-by: yxxhero --- skills/helmfile/README.md | 24 ++- skills/helmfile/SKILL.md | 373 ++++++++++++++++++++++++++++++---- skills/helmfile/metadata.json | 6 +- 3 files changed, 354 insertions(+), 49 deletions(-) diff --git a/skills/helmfile/README.md b/skills/helmfile/README.md index 1cc1b001..38092227 100644 --- a/skills/helmfile/README.md +++ b/skills/helmfile/README.md @@ -1,6 +1,6 @@ # Helmfile Agent Skill -Expert guidance for Helmfile, a declarative spec for deploying Helm charts to Kubernetes. +Expert guidance for Helmfile v1.1, a declarative spec for deploying Helm charts to Kubernetes. ## Installation @@ -32,14 +32,17 @@ cp -r skills/helmfile ~/.agents/skills/ ## What This Skill Covers -- **Configuration Structure**: Basic helmfile.yaml format and release configuration -- **CLI Commands**: sync, apply, diff, destroy, template, and more -- **Templating**: Built-in objects, template functions, values files templates -- **Environments**: Multi-environment setup and conditional releases +- **Status**: Helmfile v1.0/v1.1 released, supports Helm 3.x and Helm 4.x +- **Configuration Structure**: Full helmfile.yaml reference with all release fields +- **CLI Commands**: sync, apply, diff, destroy, template, fetch, unittest, show-dag, write-values, and more +- **Templating**: Built-in objects, template functions (env, exec, readFile, fetchSecretValue, expandSecretRefs), partials +- **Environments**: Multi-environment setup, HCL values, conditional releases +- **Values Merging**: Data flow and precedence (bases -> root values -> env values -> HCL -> secrets -> CLI overrides) - **Layering**: Bases, release templates, nested helmfiles -- **Advanced Features**: Kustomize integration, strategic merge patches, transformers, chart dependencies, remote secrets -- **Best Practices**: Directory structure, DRY configuration, labels filtering -- **Troubleshooting**: Common issues and solutions +- **Hooks**: Lifecycle hooks (prepare, preapply, presync, preuninstall, postuninstall, postsync, cleanup) with kubectlApply +- **Advanced Features**: Kubedog resource tracking, Kustomize integration, strategic merge patches, JSON patches, transformers, chart dependencies, remote secrets (vals) +- **Best Practices**: Directory structure, DRY configuration, labels filtering, missing keys handling +- **Troubleshooting**: Common issues and solutions, Helm 4 compatibility ## Usage @@ -49,9 +52,14 @@ Once installed, simply ask your AI agent questions about Helmfile: - "How do I set up multi-environment deployments?" - "Explain release templates and layering" - "Help me troubleshoot a Helmfile sync issue" +- "How do I use kubedog for resource tracking?" +- "Set up hooks for CRD installation before sync" +- "How do I use vals for remote secrets?" ## References - [Helmfile Documentation](https://helmfile.readthedocs.io) - [Helmfile GitHub](https://github.com/helmfile/helmfile) - [Helm Documentation](https://helm.sh) +- [vals - Secret References](https://github.com/helmfile/vals) +- [kubedog - Resource Tracking](https://github.com/werf/kubedog) diff --git a/skills/helmfile/SKILL.md b/skills/helmfile/SKILL.md index 44d70e56..11ac06cb 100644 --- a/skills/helmfile/SKILL.md +++ b/skills/helmfile/SKILL.md @@ -7,6 +7,10 @@ description: Expert guidance for Helmfile declarative Helm chart deployment You are an expert in Helmfile, a declarative spec for deploying Helm charts to Kubernetes clusters. +## Status + +Helmfile v1.0 and v1.1 have been released (May 2025). We recommend upgrading directly to v1.1 if you are still using v0.x. Helmfile supports both Helm 3.x and Helm 4.x. + ## What is Helmfile Helmfile is a declarative configuration tool that manages Helm releases. It allows you to: @@ -22,6 +26,24 @@ Helmfile is a declarative configuration tool that manages Helm releases. It allo ## Configuration Structure +### Quick Reference + +A `helmfile.yaml` has these top-level sections: + +| Section | Purpose | +|---------|---------| +| `repositories` | Helm chart repositories to use | +| `releases` | The Helm releases to deploy (core of helmfile) | +| `helmDefaults` | Default Helm options for all releases | +| `environments` | Environment-specific values (dev, staging, prod) | +| `helmfiles` | Include other helmfile.yaml files (nesting) | +| `bases` | Shared base files merged before this helmfile | +| `values` | Default values available in templates | +| `commonLabels` | Labels applied to all releases | +| `templates` | Reusable release templates | +| `hooks` | Global lifecycle hooks | +| `apiVersions` / `kubeVersion` | Kubernetes version capabilities | + ### Basic helmfile.yaml ```yaml repositories: @@ -37,21 +59,65 @@ releases: - values.yaml ``` +### Repository Configuration +```yaml +repositories: + - name: stable + url: https://charts.helm.sh/stable + # Git-based repository + - name: polaris + url: git+https://github.com/reactiveops/polaris@deploy/helm?ref=master + # OCI registry with auth + - name: roboll + url: roboll.io/charts + certFile: optional_client_cert + keyFile: optional_client_key + username: optional_username + password: optional_password + oci: true + passCredentials: true + verify: true + keyring: path/to/keyring.gpg + # Self-signed certificate + - name: insecure + url: https://charts.example.com + caFile: optional_ca_file +``` + ### Release Configuration Fields -| Field | Description | -|-------|-------------| -| `name` | Release name | -| `namespace` | Target namespace | -| `chart` | Chart reference (repo/chart or local path) | -| `version` | Semver constraint | -| `values` | Values files or inline values | -| `set`/`setString` | Override specific values | -| `secrets` | Encrypted values files (requires helm-secrets plugin) | -| `installed` | Set false to uninstall on sync | -| `wait` | Wait for resources to be ready | -| `timeout` | Operation timeout in seconds | -| `kubeContext` | Kubernetes context to use | -| `labels` | Key-value pairs for filtering | +| Field | Type | Default | Description | +|-------|------|---------|-------------| +| `name` | string | | Release name | +| `namespace` | string | | Target namespace | +| `chart` | string | | Chart reference (repo/chart or local path) | +| `version` | string | | Semver constraint | +| `values` | list | | Values files or inline values | +| `set`/`setString` | list | | Override specific values | +| `secrets` | list | | Encrypted values files (requires helm-secrets plugin) | +| `installed` | bool | | Set false to uninstall on sync | +| `condition` | string | | Values lookup key for filtering releases | +| `wait` | bool | false | Wait for resources to be ready | +| `waitForJobs` | bool | false | Wait until all Jobs have completed | +| `timeout` | int | 300 | Operation timeout in seconds | +| `kubeContext` | string | | Kubernetes context to use | +| `labels` | map | | Key-value pairs for filtering | +| `createNamespace` | bool | true | Automatically create release namespace | +| `missingFileHandler` | string | | "Error" or "Warn" for missing files | +| `missingFileHandlerConfig` | map | | Additional missing file handler config | +| `valuesTemplate` | list | | Like `values` but template expressions rendered before passing to Helm | +| `setTemplate` | list | | Like `set` but template expressions rendered before passing to Helm | +| `apiVersions` | list | | Per-release API versions | +| `kubeVersion` | string | | Per-release kube version | +| `valuesPathPrefix` | string | | Prefix for values file paths | +| `verifyTemplate` | string | | Templated verify flag | +| `waitTemplate` | string | | Templated wait flag | +| `installedTemplate` | string | | Templated installed flag | +| `adopt` | list | | Resources to adopt (passes `--adopt` to Helm) | +| `forceGoGetter` | bool | false | Force go-getter URL parsing for chart field | +| `forceNamespace` | string | | Force namespace on all K8s resources | +| `skipRefresh` | bool | false | Per-release skip for `helm dependency up` | +| `disableAutoDetectedKubeVersionForDiff` | bool | false | Disable auto-detected kubeVersion for diff | +| `takeOwnership` | bool | false | Take ownership of existing resources | ### Helm Defaults ```yaml @@ -63,6 +129,28 @@ helmDefaults: force: false atomic: true cleanupOnFail: false + verify: false + keyring: path/to/keyring.gpg + skipSchemaValidation: false + waitForJobs: true + recreatePods: false + historyMax: 10 + devel: false + skipDeps: false + reuseValues: false + enableDNS: false + skipCRDs: false + skipRefresh: false + forceConflicts: false + takeOwnership: false + trackMode: "" + disableAutoDetectedKubeVersionForDiff: false + args: + - "--set k=v" + diffArgs: + - "--suppress-secrets" + syncArgs: + - "--labels=app.kubernetes.io/managed-by=helmfile" ``` ## CLI Commands @@ -79,6 +167,15 @@ helmfile lint # Lint charts helmfile test # Run helm tests helmfile list # List releases helmfile deps # Lock dependencies +helmfile repos # Add chart repositories +helmfile fetch # Fetch charts from state file +helmfile status # Retrieve status of releases +helmfile build # Build all resources from state file +helmfile write-values # Write values files (like template but for values) +helmfile unittest # Unit test charts using helm-unittest plugin +helmfile show-dag # Show release dependency graph (GROUP, RELEASE, DEPENDENCIES) +helmfile cache # Cache management +helmfile create # Create a helmfile deployment project scaffold ``` ### Common Flags @@ -91,29 +188,90 @@ helmfile deps # Lock dependencies | `--kube-context` | Kubernetes context | | `--interactive` | Confirm before changes | | `--skip-deps` | Skip dependency updates | +| `--allow-no-matching-release` | Don't error if selector has no matches | +| `-c, --chart` | Set chart (available in template as {{ .Chart }}) | +| `--color` | Output with color | +| `--debug` | Enable verbose output | +| `--state-values-set` | Override state values from CLI | +| `--state-values-file` | Override state values from file | +| `--track-mode` | Resource tracking mode (helm, helm-legacy, kubedog) | +| `--track-timeout` | Tracking timeout in seconds | +| `--track-logs` | Enable real-time log streaming | + +### Fetch Command (Air-gapped Environments) +```bash +helmfile fetch --output-dir ./charts --write-output +``` +| Flag | Default | Description | +|------|---------|-------------| +| `--output-dir` | temp dir | Directory to store charts | +| `--output-dir-template` | default template | Go template for output dir (`.OutputDir`, `.ChartName`, `.Release.*`, `.Environment.*`) | +| `--write-output` | false | Write helmfile.yaml with updated chart paths to stdout | +| `--concurrency` | 0 | Max concurrent helm processes | + +### Show DAG +```bash +helmfile show-dag +``` +Prints a table with GROUP, RELEASE, and DEPENDENCIES. Releases in the same GROUP are deployed concurrently. GROUP 2 starts only after GROUP 1 completes. + +### Unit Tests +```bash +helmfile unittest # Requires helm-unittest plugin +``` +```yaml +releases: + - name: my-app + chart: ./charts/my-app + values: + - values.yaml + unitTests: + - tests # Relative to chart dir, /*_test.yaml appended +``` ## Templating ### Built-in Objects - `.Environment.Name` - Current environment name - `.Environment.KubeContext` - Environment's kube context -- `.Values` / `.StateValues` - Environment values +- `.Values` / `.StateValues` - Environment values (`.StateValues` is an alias) - `.Release.Name` - Release name - `.Release.Namespace` - Release namespace - `.Release.Labels` - Release labels - `.Namespace` - Target namespace +- `.Chart` - Chart set via `--chart` flag +- `.HelmfileCommand` - The helmfile command being run + +### Helmfile .Values vs Helm .Values +Helmfile uses the same `.Values` name as Helm. To distinguish, use `.StateValues` for Helmfile's values: +```yaml +app: + project: {{.Environment.Name}}-{{.StateValues.project}} + +{{` +extraEnvVars: +- name: APP_PROJECT + value: {{.Values.app.project}} +`}} +``` ### Template Functions | Function | Description | |----------|-------------| | `env "VAR"` | Get optional env var (returns empty if unset) | | `requiredEnv "VAR"` | Get required env var (fails if unset) | -| `exec "cmd" (list "args")` | Execute command | +| `exec "cmd" (list "args")` | Execute command, return stdout | +| `envExec (dict "k" "v") "cmd" (list "args")` | Execute command with custom env vars | | `readFile "path"` | Read file contents | +| `readDir "path"` | List file paths in directory | +| `readDirEntries "path"` | List all entries including folders | +| `isFile "path"` | Check if file exists | +| `isDir "path"` | Check if directory exists | | `toYaml` / `fromYaml` | YAML conversion | | `get .Values "key" default` | Get nested value with default | | `required "msg" value` | Fail if value is empty | -| `fetchSecretValue "ref"` | Fetch secret from vals backend | +| `fetchSecretValue "ref"` | Fetch single secret from vals backend | +| `expandSecretRefs` | Fetch map of secrets from vals refs | | `tpl "{{ .Value.key }}" .` | Render template string | ### Values Files Templates @@ -125,6 +283,15 @@ db: password: {{ requiredEnv "DB_PASSWORD" }} ``` +### Template Partials +Files matching `_*.tpl` in the same directory are auto-loaded as helpers: +``` +{{- define "myapp.labels" -}} +app: myapp +env: {{ .Environment.Name }} +{{- end -}} +``` + ## Environments ### Environment Configuration @@ -133,12 +300,18 @@ environments: default: values: - environments/default/values.yaml + - environments/default/values.hcl + - myChartVer: 1.0.0-dev production: values: - environments/production/values.yaml + - myChartVer: 1.0.0 + - vault: + enabled: false secrets: - environments/production/secrets.yaml kubeContext: prod-cluster + missingFileHandler: Error ``` ### Using Environments @@ -155,6 +328,31 @@ releases: chart: stable/prometheus ``` +## Values Merging and Data Flow + +Values are merged in this order (lowest to highest priority): + +``` +┌─────────────────────────────────────────────────────────────────┐ +│ VALUES MERGING ORDER │ +├─────────────────────────────────────────────────────────────────┤ +│ 1. Base files (from `bases:`) │ +│ 2. Root-level `values:` block (Defaults) │ +│ 3. Environment values (yaml/yaml.gotmpl) │ +│ 4. Environment values (HCL, including HCL secrets) │ +│ 5. Environment secrets (non-HCL, decrypted) │ +│ 6. CLI overrides (--state-values-set, --state-values-file) │ +└─────────────────────────────────────────────────────────────────┘ +``` + +**Later values override earlier values** at the map level (deep merge). Arrays use smart merging (sparse auto-detection by default). + +```bash +# CLI overrides (highest priority) +helmfile --state-values-set image.tag=v2.0.0 sync +helmfile --state-values-file overrides.yaml sync +``` + ## Layering and Inheritance ### Bases (Layering) @@ -188,8 +386,81 @@ helmfiles: - {{ toYaml .Values | nindent 4 }} ``` +## Hooks + +### Hook Events +| Event | Description | +|-------|-------------| +| `prepare` | After release loaded from YAML, before execution | +| `preapply` | Before uninstall/install/upgrade during `apply` (only if changes exist) | +| `presync` | Before each release is synced (installed or upgraded) | +| `preuninstall` | Immediately before a release is uninstalled | +| `postuninstall` | After successful uninstall of a release | +| `postsync` | After each release is synced, regardless of success | +| `cleanup` | After each release is processed (counterpart to `prepare`) | + +### Hook Configuration +```yaml +releases: + - name: myapp + chart: mychart + hooks: + - events: ["prepare", "cleanup"] + showlogs: true + command: "echo" + args: ["{{`{{.Environment.Name}}`}}", "{{`{{.Release.Name}}`}}"] + - events: ["presync"] + showlogs: true + command: "kubectl" + args: ["apply", "-f", "crds.yaml"] + - events: ["postsync"] + showlogs: true + command: "kubectl" + args: ["rollout", "status", "deployment/myapp"] +``` + +### kubectlApply Hook +Alternative to `command`/`args`, directly apply manifests: +```yaml +hooks: + - events: ["presync"] + kubectlApply: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: my-config + data: + key: value +``` + ## Advanced Features +### Resource Tracking with Kubedog +```yaml +releases: + - name: myapp + chart: ./charts/myapp + trackMode: kubedog + trackTimeout: 300 + trackLogs: true + trackKinds: + - Deployment + - StatefulSet + skipKinds: + - ConfigMap + trackResources: + - kind: Deployment + name: myapp-deployment + namespace: default +``` + +**Track Modes:** +| Mode | Description | +|------|-------------| +| `helm` (default) | Uses Helm's built-in `--wait` | +| `helm-legacy` | Uses Helm v4's `--wait=legacy` for compatibility | +| `kubedog` | Advanced tracking with detailed feedback | + ### Kustomize Integration Deploy kustomizations as Helm releases: ```yaml @@ -208,13 +479,36 @@ releases: releases: - name: raw1 chart: incubator/raw + values: + - resources: + - apiVersion: v1 + kind: ConfigMap + metadata: + name: raw1 + data: + foo: FOO strategicMergePatches: - apiVersion: v1 kind: ConfigMap metadata: name: raw1 data: - extra: value + bar: BAR +``` + +### JSON Patches +```yaml +releases: + - name: myapp + chart: mychart + jsonPatches: + - target: + version: v1 + kind: ConfigMap + name: myconfig + patch: + - op: remove + path: /data/old-key ``` ### Transformers @@ -246,13 +540,30 @@ releases: ### Remote Secrets (vals) ```yaml +# Single key releases: - name: app values: - db: - password: ref+awssecrets://my-secret/db-password + password: {{ .Values.db.password | fetchSecretValue | quote }} + +# Multiple keys +environments: + default: + values: + - service: + password: ref+vault://svc/#pass + login: ref+vault://svc/#login ``` +```yaml +# values.yaml.gotmpl +service: +{{ .Values.service | expandSecretRefs | toYaml | nindent 2 }} +``` + +Supported backends: Vault, AWS SSM, AWS Secrets Manager, GCP Secret Manager, Azure Key Vault, and more via [vals](https://github.com/helmfile/vals). + ## Best Practices ### Directory Structure @@ -277,6 +588,7 @@ releases: 2. Use `bases` for shared configuration 3. Use `environments` for environment-specific values 4. Use `.gotmpl` files for templated values +5. Use `_*.tpl` partials for shared template logic ### Missing Keys Handling ```yaml @@ -327,13 +639,6 @@ releases: - replicaCount: {{ .Values.replicas }} ``` -### Git-based Charts -```yaml -repositories: - - name: polaris - url: git+https://github.com/reactiveops/polaris@deploy/helm?ref=master -``` - ### OCI Charts ```yaml repositories: @@ -347,18 +652,6 @@ releases: version: 1.0.0 ``` -### Hooks -```yaml -releases: - - name: crds - chart: ./crds - hooks: - - events: ["presync"] - showlogs: true - command: "kubectl" - args: ["apply", "-f", "crds.yaml"] -``` - ## Troubleshooting ### Debug Template Rendering @@ -378,6 +671,7 @@ helmfile list 2. **Chart not found**: Run `helmfile deps` or check repository config 3. **Diff plugin missing**: Install with `helm plugin install https://github.com/databus23/helm-diff` 4. **Secrets not decrypting**: Install helm-secrets plugin +5. **Helm v4 compatibility**: Use `trackMode: helm-legacy` for charts with broken `livenessProbe` configs ## Environment Variables | Variable | Description | @@ -399,3 +693,6 @@ Invoke this skill when: - Implementing best practices for Helm chart management - Configuring remote secrets with vals - Using advanced features like strategic merge patches and transformers +- Setting up resource tracking with kubedog +- Managing Helm 4 compatibility +- Writing hooks for lifecycle management diff --git a/skills/helmfile/metadata.json b/skills/helmfile/metadata.json index c3a96e0a..9d296a40 100644 --- a/skills/helmfile/metadata.json +++ b/skills/helmfile/metadata.json @@ -1,8 +1,8 @@ { - "version": "1.0.0", + "version": "1.1.0", "organization": "Helmfile", - "date": "February 2026", - "abstract": "Comprehensive guide for Helmfile, a declarative spec for deploying Helm charts to Kubernetes. Covers configuration structure, CLI commands, templating, environments, layering, release templates, Kustomize integration, strategic merge patches, transformers, chart dependencies, remote secrets, and best practices. Designed for AI agents working with Helmfile configurations and Kubernetes deployments.", + "date": "May 2026", + "abstract": "Comprehensive guide for Helmfile v1.1, a declarative spec for deploying Helm charts to Kubernetes. Covers configuration structure, CLI commands, templating, environments, values merging and data flow, layering, release templates, hooks (prepare, presync, postsync, cleanup), Kustomize integration, strategic merge patches, JSON patches, transformers, chart dependencies, remote secrets (vals), resource tracking with kubedog, Helm 4 support, and best practices. Designed for AI agents working with Helmfile configurations and Kubernetes deployments.", "references": [ "https://helmfile.readthedocs.io", "https://github.com/helmfile/helmfile", From c6d0310029b22b6b6cf9a1300ef8f548ea245896 Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 7 May 2026 11:07:20 +0200 Subject: [PATCH 18/28] fix(state): resolve OCI repo prefix in ad-hoc release dependencies (#2579) When a release `dependencies[].chart` is given as `/` and the matching `repositories:` entry has `oci: true`, helmfile now rewrites it to `oci:///` before passing it to chartify. Without this, chartify's lookup falls into its `helm repo list` branch, which never finds OCI repos because helm 3+ does not register OCI registries as named repos (they live in the `helm registry login` state instead). The user-visible failure was: failed reading adhoc dependencies: no helm list entry found for repository "". please `helm repo add` it! Explicit `oci://` URLs already worked through chartify's OCI branch; this change makes the `/` form behave the same way. Non-OCI repo prefixes, unknown prefixes, single-segment names, and explicit `oci://` URLs all pass through unchanged. A debug log records each rewrite at the call site for easier troubleshooting. Fixes #1756. Signed-off-by: Dominik Schmidt --- pkg/state/helmx.go | 3 ++ pkg/state/state.go | 22 +++++++++++++ pkg/state/state_test.go | 71 +++++++++++++++++++++++++++++++++++++++++ 3 files changed, 96 insertions(+) diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index ca118921..0a75df98 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -427,6 +427,9 @@ func (st *HelmState) PrepareChartify(helm helmexec.Interface, release *ReleaseSp if err != nil { return nil, clean, err } + } else if rewritten, ok := st.resolveOCIAdhocDepChart(d.Chart); ok { + st.logger.Debugf("ad-hoc dependency %q rewritten to %q (matched OCI repo entry)", d.Chart, rewritten) + chart = rewritten } c.Opts.AdhocChartDependencies = append(c.Opts.AdhocChartDependencies, chartify.ChartDependency{ diff --git a/pkg/state/state.go b/pkg/state/state.go index 106d4a66..cec8f890 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -1392,6 +1392,28 @@ func (st *HelmState) GetRepositoryAndNameFromChartName(chartName string) (*Repos return nil, chartName } +// resolveOCIAdhocDepChart rewrites a release `dependencies[].chart` value that +// uses the named-repo prefix form ("repoName/chartName") into a full oci:// URL +// when the prefix matches a `repositories:` entry with `oci: true`. It returns +// (rewritten, true) on a hit and ("", false) otherwise. +// +// This avoids the chartify path that does `helm repo list` to look up the +// repository URL: that lookup never finds OCI repos because helm 3+ does not +// register OCI registries as named repos (it uses `helm registry login` +// instead). By the time chartify sees an `oci://` URL it already takes the +// correct branch, so rewriting here is enough to make the named-repo form +// behave the same as the explicit URL form. +func (st *HelmState) resolveOCIAdhocDepChart(chart string) (string, bool) { + if strings.HasPrefix(chart, "oci://") { + return "", false + } + repo, name := st.GetRepositoryAndNameFromChartName(chart) + if repo == nil || !repo.OCI { + return "", false + } + return "oci://" + strings.TrimSuffix(repo.URL, "/") + "/" + name, true +} + var rwMutexMap sync.Map // getNamedRWMutex retrieves or creates a sync.RWMutex for a given name. diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 8e6f2ba9..9f1038df 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -6039,3 +6039,74 @@ func TestHelmState_getKubeContext(t *testing.T) { }) } } + +// resolveOCIAdhocDepChart should rewrite a release `dependencies[].chart` value +// that uses the named-repo prefix form into a full oci:// URL whenever the +// matching `repositories:` entry has `oci: true`. All other inputs must pass +// through unchanged so we never disturb existing behavior. +func TestResolveOCIAdhocDepChart(t *testing.T) { + state := &HelmState{ + ReleaseSetSpec: ReleaseSetSpec{ + Repositories: []RepositorySpec{ + {Name: "ociregistry", URL: "registry.example.com:5000/charts", OCI: true}, + {Name: "ociregistry-trailing", URL: "registry.example.com:5000/charts/", OCI: true}, + {Name: "stable", URL: "https://charts.helm.sh/stable"}, + }, + }, + } + + tests := []struct { + name string + chart string + wantOK bool + wantChart string + }{ + { + name: "named OCI repo prefix is rewritten to oci:// URL", + chart: "ociregistry/redis", + wantOK: true, + wantChart: "oci://registry.example.com:5000/charts/redis", + }, + { + name: "trailing slash on repo URL does not produce a double slash", + chart: "ociregistry-trailing/redis", + wantOK: true, + wantChart: "oci://registry.example.com:5000/charts/redis", + }, + { + name: "non-OCI repo prefix is left alone for chartify's helm-repo-list path", + chart: "stable/nginx", + wantOK: false, + }, + { + name: "explicit oci:// URL is left alone (already in chartify's OCI branch)", + chart: "oci://registry.example.com:5000/charts/redis", + wantOK: false, + }, + { + name: "unknown repo prefix is left alone", + chart: "unknownrepo/something", + wantOK: false, + }, + { + name: "single-segment chart (no slash) is left alone", + chart: "localchart", + wantOK: false, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, ok := state.resolveOCIAdhocDepChart(tt.chart) + if ok != tt.wantOK { + t.Errorf("ok: want %v, got %v", tt.wantOK, ok) + } + if tt.wantOK && got != tt.wantChart { + t.Errorf("rewritten chart: want %q, got %q", tt.wantChart, got) + } + if !tt.wantOK && got != "" { + t.Errorf("expected empty rewrite when ok=false, got %q", got) + } + }) + } +} From 0139304d97ba700cf40d1ff776a6c8e19221205e Mon Sep 17 00:00:00 2001 From: Dominik Schmidt Date: Thu, 7 May 2026 15:50:05 +0200 Subject: [PATCH 19/28] feat(state): add mergeStrategy: fallback for first-file-wins env values (#2578) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat(state): add mergeStrategy field to EnvironmentSpec Introduces a per-environment mergeStrategy with valid values "override" (default, current behavior) and "fallback". This commit only adds the field, the constants, and a parse-time validator; the loader still ignores the value, so behavior is unchanged. Subsequent commits thread the value through the values loader and implement the fallback semantics. Signed-off-by: Dominik Schmidt * refactor(state): thread mergeStrategy through values loader Adds a mergeStrategy string parameter to LoadEnvironmentValues, loadValuesEntries, and mapMerge so the value can flow from EnvironmentSpec down to the merge call site. Behavior is unchanged in this commit; mapMerge ignores the strategy and the next commit implements the fallback semantics. Top-level state.DefaultValues and the --state-values-file/-set loaders are passed an empty strategy ("") since they have no per-environment spec to consult and stay on the default override behavior. Signed-off-by: Dominik Schmidt * feat(state): implement fallback merge strategy Adds a hand-rolled fallbackDeepMerge that, unlike mergo, preserves keys present in the destination even when their value is the zero value (false, 0, "", nil, empty list/map). mapMerge dispatches to it when mergeStrategy == "fallback"; "override" and the empty default keep using mergo with WithOverride so existing behaviour is unchanged. Validation lives at the entry of LoadEnvironmentValues so a single chokepoint guards the field. Invalid values produce an error naming both the offending value and the valid options. Tests cover: first-file-wins precedence, gap filling, deep nested merge, three-file chains, explicit zero-value preservation (the case naïve mergo gets wrong), explicit nil preservation, inline map entries, override regression, default-equals-override equivalence, and invalid-strategy errors. Signed-off-by: Dominik Schmidt * feat(state): expose prior-file values in fallback template context Under mergeStrategy: fallback, .gotmpl values files can now reference values from earlier files in the same `values:` list via .Values (e.g. `service.domain: "service.{{ .Values.cluster.domain }}"`). The accumulated result is layered under env.GetMergedValues so env defaults, env values, and CLI overrides still win on overlap. Override mode keeps the historical template context — unchanged — so this is strictly opt-in via the mergeStrategy field. Together with the precedence flip from the previous commit, this lets users replace the brittle two-stage `merged-values.yaml.gotmpl` workaround with native helmfile syntax. Tests cover the headline cross-file template reference case and pin the override-mode contract that prior-file values stay invisible. Signed-off-by: Dominik Schmidt * docs: document mergeStrategy and fallback semantics Adds a new section to values-and-merging.md describing the override vs fallback strategies, the explicit-zero-value preservation guarantee, and the cross-file template reference behavior. Adds a brief pointer to environments.md so users land on the new field from the environment values discussion. Signed-off-by: Dominik Schmidt * refactor(state): reuse maputil.MergeMaps for fallback merge Replaces the hand-rolled fallbackDeepMerge with a single call to maputil.MergeMaps, swapping its arguments so the accumulated dest wins over the new src file. Same first-file-wins semantic, fewer lines, and the fallback path now inherits the same slice merge strategies the rest of helmfile already uses. The one observable behavior shift is for explicit nil values: under fallback, nil in an earlier file no longer 'wins' over a non-nil value in a later file — instead it falls through (matching MergeMaps' rule that nil from the override side only fills missing keys). This is internally consistent: nil-overwrites is an mergo.WithOverride quirk that lives only in the override path. The renamed test NilFallsThroughToFallback pins the new behavior with a comment referencing the contrast with override mode (Issue1154). Signed-off-by: Dominik Schmidt --------- Signed-off-by: Dominik Schmidt --- docs/environments.md | 15 + docs/values-and-merging.md | 48 +++ pkg/app/desired_state_file_loader.go | 4 +- pkg/state/create.go | 33 +- pkg/state/create_test.go | 93 ++++++ pkg/state/environment.go | 13 + pkg/state/envvals_loader.go | 89 +++-- pkg/state/envvals_loader_test.go | 306 +++++++++++++++++- pkg/state/state.go | 6 + pkg/state/testdata/mergestrategy/chain_a.yaml | 4 + pkg/state/testdata/mergestrategy/chain_b.yaml | 4 + pkg/state/testdata/mergestrategy/chain_c.yaml | 3 + pkg/state/testdata/mergestrategy/default.yaml | 2 + .../testdata/mergestrategy/fallback.yaml | 5 + .../mergestrategy/fallback.yaml.gotmpl | 3 + pkg/state/testdata/mergestrategy/glob_a.yaml | 2 + .../testdata/mergestrategy/glob_b.yaml.gotmpl | 2 + .../testdata/mergestrategy/nil_default.yaml | 1 + .../testdata/mergestrategy/nil_fallback.yaml | 1 + .../testdata/mergestrategy/zero_default.yaml | 4 + .../testdata/mergestrategy/zero_fallback.yaml | 6 + 21 files changed, 597 insertions(+), 47 deletions(-) create mode 100644 pkg/state/testdata/mergestrategy/chain_a.yaml create mode 100644 pkg/state/testdata/mergestrategy/chain_b.yaml create mode 100644 pkg/state/testdata/mergestrategy/chain_c.yaml create mode 100644 pkg/state/testdata/mergestrategy/default.yaml create mode 100644 pkg/state/testdata/mergestrategy/fallback.yaml create mode 100644 pkg/state/testdata/mergestrategy/fallback.yaml.gotmpl create mode 100644 pkg/state/testdata/mergestrategy/glob_a.yaml create mode 100644 pkg/state/testdata/mergestrategy/glob_b.yaml.gotmpl create mode 100644 pkg/state/testdata/mergestrategy/nil_default.yaml create mode 100644 pkg/state/testdata/mergestrategy/nil_fallback.yaml create mode 100644 pkg/state/testdata/mergestrategy/zero_default.yaml create mode 100644 pkg/state/testdata/mergestrategy/zero_fallback.yaml diff --git a/docs/environments.md b/docs/environments.md index 41eb7476..cdc7eee6 100644 --- a/docs/environments.md +++ b/docs/environments.md @@ -104,6 +104,21 @@ releases: ... ``` +#### Merge strategy + +By default, when several files are listed under `values:`, later files override earlier files. Set `mergeStrategy: fallback` on the environment to flip the precedence so earlier files win and later files only fill missing keys: + +```yaml +environments: + production: + mergeStrategy: fallback + values: + - cluster-specific.yaml # wins on every key it defines + - shared-defaults.yaml.gotmpl +``` + +Under `fallback`, explicit non-nil values in the earlier file (including zero values like `false`, `0`, `""`, and empty list) are preserved against any later file, while maps are deep-merged so later files may still add nested keys. A later `.gotmpl` file can also reference values from earlier files via `.Values`. See [Merge Strategy: override vs fallback](values-and-merging.md#4a-merge-strategy-override-vs-fallback) for the full semantics, including how explicit `null` is handled. + #### HCL specifications Since Helmfile v0.164.0, HCL language is supported for environment values only. diff --git a/docs/values-and-merging.md b/docs/values-and-merging.md index 5b39d80f..9237a8c3 100644 --- a/docs/values-and-merging.md +++ b/docs/values-and-merging.md @@ -116,6 +116,54 @@ environments: - secrets.yaml # Merged last (step 3) - highest priority ``` +### 4a. Merge Strategy: `override` vs `fallback` + +By default, when an environment lists multiple files under `values:`, **later files override earlier files** (the historical helmfile behavior, equivalent to `mergeStrategy: override`). + +You can flip this per environment so that **earlier files take precedence** and later files only fill in missing keys: + +```yaml +environments: + production: + mergeStrategy: fallback + values: + - cluster-specific.yaml # wins on every key it defines + - shared-defaults.yaml # only fills gaps +``` + +Under `fallback`: + +- An explicit non-nil value in an earlier file is preserved against any later file, including the zero values `false`, `0`, `""`, and empty list. An explicit `enabled: false` in `cluster-specific.yaml` is *not* silently overwritten by `enabled: true` from `shared-defaults.yaml`. +- Maps are deep-merged. An earlier map does not block later files from adding nested keys it didn't set; only the keys an earlier file explicitly defines win on conflict. +- An explicit `null` in an earlier file falls through to a later file's value, matching how `MergeMaps` treats nil from the override side elsewhere in helmfile. +- Within a single `values:` entry that expands to multiple files (e.g. via a glob), the **first** file in the expansion wins. +- A later `.gotmpl` values file can reference values from earlier files via `.Values`, so derived defaults work natively: + + ```yaml + # cluster-specific.yaml + cluster: + domain: prod.example.com + ``` + + ```yaml + # shared-defaults.yaml.gotmpl + service: + domain: "service.{{ .Values.cluster.domain }}" + ``` + + → `service.domain: service.prod.example.com`. Under `mergeStrategy: override` this cross-file template reference is not available. + +Valid values: `override` (default) and `fallback`. Any other value is rejected at load time. + +#### Interaction with `.hcl` values files + +`.hcl` files are evaluated as a single unit so that HCL `locals` and `values` blocks can reference each other across files. Helmfile collects every `.hcl` entry from the `values:` list, renders them together, and merges the combined result after the YAML pass. Two consequences worth knowing: + +- `mergeStrategy` does not reshuffle the position of HCL within the list. HCL's combined output is always merged after the YAML pass. Under `override` it overrides YAML on conflicts (the historical behavior); under `fallback` it fills gaps only. +- Among multiple `.hcl` files, the precedence is HCL's own (last-file-wins within HCL), independent of `mergeStrategy`. The strategy applies at the YAML-vs-HCL boundary, not within HCL. + +If you need first-file-wins precedence between specific HCL files, restructure them into one HCL file (or split the values into YAML). + ### 5. CLI Overrides The highest priority values come from CLI flags: diff --git a/pkg/app/desired_state_file_loader.go b/pkg/app/desired_state_file_loader.go index 8ffe3942..b57a029b 100644 --- a/pkg/app/desired_state_file_loader.go +++ b/pkg/app/desired_state_file_loader.go @@ -69,7 +69,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e // --state-values-file: loaded into Values so arrays replace (not merge) if len(fileArgs) > 0 { - fileVals, err := envld.LoadEnvironmentValues(&handler, fileArgs, environment.New(ld.env), ld.env) + fileVals, err := envld.LoadEnvironmentValues(&handler, fileArgs, environment.New(ld.env), ld.env, "") if err != nil { return nil, err } @@ -78,7 +78,7 @@ func (ld *desiredStateLoader) Load(f string, opts LoadOpts) (*state.HelmState, e // --state-values-set: loaded into CLIOverrides so arrays merge element-by-element if len(setArgs) > 0 { - setVals, err := envld.LoadEnvironmentValues(&handler, setArgs, environment.New(ld.env), ld.env) + setVals, err := envld.LoadEnvironmentValues(&handler, setArgs, environment.New(ld.env), ld.env, "") if err != nil { return nil, err } diff --git a/pkg/state/create.go b/pkg/state/create.go index 9058e9a7..7384596a 100644 --- a/pkg/state/create.go +++ b/pkg/state/create.go @@ -169,7 +169,7 @@ func (c *StateCreator) LoadEnvValues(target *HelmState, env string, failOnMissin return nil, &StateLoadError{fmt.Sprintf("failed to read %s", state.FilePath), err} } - newDefaults, err := state.loadValuesEntries(nil, state.DefaultValues, c.remote, ctxEnv, env) + newDefaults, err := state.loadValuesEntries(nil, state.DefaultValues, c.remote, ctxEnv, env, "") if err != nil { return nil, err } @@ -237,17 +237,12 @@ func mergeEnvironments(dst, src map[string]EnvironmentSpec) { for envName, srcEnv := range src { if dstEnv, exists := dst[envName]; exists { - // Environment exists in both - merge the Values slices - mergedValues := append([]any{}, dstEnv.Values...) - mergedValues = append(mergedValues, srcEnv.Values...) - // Merge Secrets slices mergedSecrets := append([]string{}, dstEnv.Secrets...) mergedSecrets = append(mergedSecrets, srcEnv.Secrets...) // Create merged environment merged := EnvironmentSpec{ - Values: mergedValues, Secrets: mergedSecrets, } @@ -272,6 +267,26 @@ func mergeEnvironments(dst, src map[string]EnvironmentSpec) { merged.MissingFileHandlerConfig = dstEnv.MissingFileHandlerConfig } + // Override MergeStrategy if src has it + if srcEnv.MergeStrategy != "" { + merged.MergeStrategy = srcEnv.MergeStrategy + } else { + merged.MergeStrategy = dstEnv.MergeStrategy + } + + // Concatenate Values so the later layer (src, e.g. main helmfile) + // always takes precedence over the earlier one (dst, e.g. a base + // helmfile). Under override the natural append order achieves that + // (last-file-wins). Under fallback we have to prepend src so that + // "first-file-wins" still resolves to "later layer wins". + if merged.MergeStrategy == MergeStrategyFallback { + mergedValues := append([]any{}, srcEnv.Values...) + merged.Values = append(mergedValues, dstEnv.Values...) + } else { + mergedValues := append([]any{}, dstEnv.Values...) + merged.Values = append(mergedValues, srcEnv.Values...) + } + dst[envName] = merged } else { // Environment only exists in src - just copy it @@ -394,7 +409,7 @@ func (c *StateCreator) loadEnvValues(st *HelmState, name string, failOnMissingEn if err != nil { return nil, err } - valuesVals, err = st.loadValuesEntries(envSpec.MissingFileHandler, envValuesEntries, c.remote, loadValuesEntriesEnv, name) + valuesVals, err = st.loadValuesEntries(envSpec.MissingFileHandler, envValuesEntries, c.remote, loadValuesEntriesEnv, name, envSpec.MergeStrategy) if err != nil { return nil, err } @@ -550,13 +565,13 @@ func (c *StateCreator) scatterGatherEnvSecretFiles(st *HelmState, envSecretFiles return decryptedFilesKeeper, nil } -func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []any, remote *remote.Remote, ctxEnv *environment.Environment, envName string) (map[string]any, error) { +func (st *HelmState) loadValuesEntries(missingFileHandler *string, entries []any, remote *remote.Remote, ctxEnv *environment.Environment, envName string, mergeStrategy string) (map[string]any, error) { var envVals map[string]any valuesEntries := append([]any{}, entries...) ld := NewEnvironmentValuesLoader(st.storage(), st.fs, st.logger, remote) var err error - envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv, envName) + envVals, err = ld.LoadEnvironmentValues(missingFileHandler, valuesEntries, ctxEnv, envName, mergeStrategy) if err != nil { return nil, err } diff --git a/pkg/state/create_test.go b/pkg/state/create_test.go index aa6b704e..0d28753c 100644 --- a/pkg/state/create_test.go +++ b/pkg/state/create_test.go @@ -932,3 +932,96 @@ releases: }) } } + +// mergeEnvironments must keep the established "later layer wins over earlier +// layer" semantics even when the resolved strategy is fallback. Under override +// the natural append order (dst then src) achieves that because last-wins +// chooses src; under fallback we have to prepend src so that first-wins still +// resolves to src. Without this adjustment, a base helmfile's values would +// silently override values declared in the main helmfile that includes it. +func TestMergeEnvironments_LaterLayerWinsRegardlessOfStrategy(t *testing.T) { + tests := []struct { + name string + strategy string + wantOrdering []any + }{ + { + name: "override appends src after dst (last wins)", + strategy: MergeStrategyOverride, + wantOrdering: []any{"base.yaml", "main.yaml"}, + }, + { + name: "empty strategy defaults to override semantics", + strategy: "", + wantOrdering: []any{"base.yaml", "main.yaml"}, + }, + { + name: "fallback prepends src (first wins) so main still beats base", + strategy: MergeStrategyFallback, + wantOrdering: []any{"main.yaml", "base.yaml"}, + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst := map[string]EnvironmentSpec{ + "prod": {Values: []any{"base.yaml"}, MergeStrategy: tt.strategy}, + } + src := map[string]EnvironmentSpec{ + "prod": {Values: []any{"main.yaml"}, MergeStrategy: tt.strategy}, + } + mergeEnvironments(dst, src) + got := dst["prod"].Values + if !reflect.DeepEqual(got, tt.wantOrdering) { + t.Errorf("Values ordering: want %v, got %v", tt.wantOrdering, got) + } + }) + } +} + +// mergeEnvironments must preserve MergeStrategy when layering multiple +// helmfiles (bases). Without this, an environment that opted into +// fallback in a base helmfile would silently fall back to the default +// override behavior in any helmfile that re-declares the environment. +func TestMergeEnvironments_PreservesMergeStrategy(t *testing.T) { + tests := []struct { + name string + dst EnvironmentSpec + src EnvironmentSpec + expected string + }{ + { + name: "src declares fallback, dst empty", + dst: EnvironmentSpec{}, + src: EnvironmentSpec{MergeStrategy: MergeStrategyFallback}, + expected: MergeStrategyFallback, + }, + { + name: "dst declares fallback, src empty preserves dst", + dst: EnvironmentSpec{MergeStrategy: MergeStrategyFallback}, + src: EnvironmentSpec{}, + expected: MergeStrategyFallback, + }, + { + name: "src override wins over dst fallback", + dst: EnvironmentSpec{MergeStrategy: MergeStrategyFallback}, + src: EnvironmentSpec{MergeStrategy: MergeStrategyOverride}, + expected: MergeStrategyOverride, + }, + { + name: "both empty stays empty", + dst: EnvironmentSpec{}, + src: EnvironmentSpec{}, + expected: "", + }, + } + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + dst := map[string]EnvironmentSpec{"prod": tt.dst} + src := map[string]EnvironmentSpec{"prod": tt.src} + mergeEnvironments(dst, src) + if got := dst["prod"].MergeStrategy; got != tt.expected { + t.Errorf("MergeStrategy after merge: want %q, got %q", tt.expected, got) + } + }) + } +} diff --git a/pkg/state/environment.go b/pkg/state/environment.go index c5c78876..91d997ab 100644 --- a/pkg/state/environment.go +++ b/pkg/state/environment.go @@ -15,4 +15,17 @@ type EnvironmentSpec struct { MissingFileHandler *string `yaml:"missingFileHandler,omitempty"` // MissingFileHandlerConfig is composed of various settings for the MissingFileHandler MissingFileHandlerConfig *MissingFileHandlerConfig `yaml:"missingFileHandlerConfig,omitempty"` + + // MergeStrategy controls precedence when multiple values files are listed under `values`. + // + // "override" (default): later files override earlier files (the historical helmfile behavior). + // "fallback": earlier files take precedence; later files only fill gaps. + // + // Under the "fallback" strategy, an explicit non-nil value in an earlier file (including + // the zero values false, 0, "", and empty list) is preserved against any later file. Maps + // are deep-merged, so an earlier map does not block later files from adding nested keys. + // An explicit null in an earlier file falls through to a later file's value (matching how + // helmfile's MergeMaps treats nil from the override side elsewhere). Subsequent .gotmpl + // values files can also reference values from earlier files via .Values. + MergeStrategy string `yaml:"mergeStrategy,omitempty"` } diff --git a/pkg/state/envvals_loader.go b/pkg/state/envvals_loader.go index 8764025b..048154a8 100644 --- a/pkg/state/envvals_loader.go +++ b/pkg/state/envvals_loader.go @@ -36,7 +36,13 @@ 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, mergeStrategy string) (map[string]any, error) { + switch mergeStrategy { + case "", MergeStrategyOverride, MergeStrategyFallback: + default: + return nil, fmt.Errorf("environment %q: invalid mergeStrategy %q (must be %q or %q)", + envName, mergeStrategy, MergeStrategyOverride, MergeStrategyFallback) + } var ( result = map[string]any{} hclLoader = hcllang.NewHCLLoader(ld.fs, ld.logger) @@ -44,8 +50,6 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str ) for _, entry := range valuesEntries { - maps := []any{} - switch strOrMap := entry.(type) { case string: files, skipped, err := ld.storage.resolveFile(missingFileHandler, "environment values", entry.(string)) @@ -64,37 +68,56 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str } if strings.HasSuffix(f, ".hcl") { hclLoader.AddFile(f) - } else { - // Use merged values (Defaults + Values + CLIOverrides) for template rendering - // so that CLI values are accessible via .Values in environment value files. - mergedVals, err := env.GetMergedValues() - if err != nil { - return nil, fmt.Errorf("failed to get merged values for environment file \"%s\": %v", f, err) + continue + } + // Use merged values (Defaults + Values + CLIOverrides) for template rendering + // so that CLI values are accessible via .Values in environment value files. + mergedVals, err := env.GetMergedValues() + if err != nil { + return nil, fmt.Errorf("failed to get merged values for environment file \"%s\": %v", f, err) + } + // Under fallback strategy, also expose values accumulated from earlier files + // in this same `values:` list, including earlier files in this same glob + // expansion, so a later .gotmpl can reference them via .Values (e.g. + // `{{ .Values.cluster.domain }}`). Env CLI overrides and values still win, + // layered on top with WithOverride. + if mergeStrategy == MergeStrategyFallback && len(result) > 0 { + enriched := map[string]any{} + if err := mergo.Merge(&enriched, result); err != nil { + return nil, fmt.Errorf("failed to build template context for \"%s\": %v", f, err) } - tmplData := NewEnvironmentTemplateData(env, "", mergedVals) - r := tmpl.NewFileRenderer(ld.fs, filepath.Dir(f), tmplData) - bytes, err := r.RenderToBytes(f) - if err != nil { - return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", f, err) + if err := mergo.Merge(&enriched, mergedVals, mergo.WithOverride); err != nil { + return nil, fmt.Errorf("failed to build template context for \"%s\": %v", f, err) } - m := map[string]any{} - if err := yaml.Unmarshal(bytes, &m); err != nil { - return nil, fmt.Errorf("failed to load environment values file \"%s\": %v\n\nOffending YAML:\n%s", f, err, bytes) - } - maps = append(maps, m) - ld.logger.Debugf("envvals_loader: loaded %s:%v", strOrMap, m) + mergedVals = enriched + } + tmplData := NewEnvironmentTemplateData(env, "", mergedVals) + r := tmpl.NewFileRenderer(ld.fs, filepath.Dir(f), tmplData) + bytes, err := r.RenderToBytes(f) + if err != nil { + return nil, fmt.Errorf("failed to load environment values file \"%s\": %v", f, err) + } + m := map[string]any{} + if err := yaml.Unmarshal(bytes, &m); err != nil { + return nil, fmt.Errorf("failed to load environment values file \"%s\": %v\n\nOffending YAML:\n%s", f, err, bytes) + } + ld.logger.Debugf("envvals_loader: loaded %s:%v", strOrMap, m) + // Merge each file into result immediately so subsequent files in the same + // entry's expansion (e.g. a glob) can see prior files' values via .Values + // when rendered as templates. + result, err = mapMerge(result, []any{m}, mergeStrategy) + if err != nil { + return nil, err } } case map[any]any, map[string]any: - maps = append(maps, strOrMap) + result, err = mapMerge(result, []any{strOrMap}, mergeStrategy) + if err != nil { + return nil, err + } default: return nil, fmt.Errorf("unexpected type of value: value=%v, type=%T", strOrMap, strOrMap) } - - result, err = mapMerge(result, maps) - if err != nil { - return nil, err - } } maps := []any{} if hclLoader.Length() > 0 { @@ -104,14 +127,14 @@ func (ld *EnvironmentValuesLoader) LoadEnvironmentValues(missingFileHandler *str } maps = append(maps, m) } - result, err = mapMerge(result, maps) + result, err = mapMerge(result, maps, mergeStrategy) if err != nil { return nil, err } return result, nil } -func mapMerge(dest map[string]any, maps []any) (map[string]any, error) { +func mapMerge(dest map[string]any, maps []any, mergeStrategy string) (map[string]any, error) { for _, m := range maps { // All the nested map key should be string. Otherwise we get strange errors due to that // mergo or reflect is unable to merge map[any]any with map[string]any or vice versa. @@ -120,6 +143,16 @@ func mapMerge(dest map[string]any, maps []any) (map[string]any, error) { if err != nil { return nil, err } + if mergeStrategy == MergeStrategyFallback { + // First-file-wins: the new file is the base and the + // accumulator overlays it, so keys already accumulated keep + // their value while keys only present in the new file fill + // in. MergeMaps is used instead of mergo because mergo's + // isEmptyValue rule would silently let a later fallback's + // `enabled: true` clobber an explicit `enabled: false`. + dest = maputil.MergeMaps(vals, dest) + continue + } if err := mergo.Merge(&dest, &vals, mergo.WithOverride); err != nil { return nil, fmt.Errorf("failed to merge %v: %v", m, err) } diff --git a/pkg/state/envvals_loader_test.go b/pkg/state/envvals_loader_test.go index 764ada78..5902ebe6 100644 --- a/pkg/state/envvals_loader_test.go +++ b/pkg/state/envvals_loader_test.go @@ -2,6 +2,7 @@ package state import ( "io" + "strings" "testing" "github.com/google/go-cmp/cmp" @@ -29,7 +30,7 @@ func newLoader() *EnvironmentValuesLoader { func TestEnvValsLoad_SingleValuesFile(t *testing.T) { l := newLoader() - actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.5.yaml"}, nil, "") + actual, err := l.LoadEnvironmentValues(nil, []any{"testdata/values.5.yaml"}, nil, "", "") if err != nil { t.Fatal(err) } @@ -87,7 +88,7 @@ func TestEnvValsLoad_EnvironmentNameFile(t *testing.T) { for _, tt := range tests { 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, "") if err != nil { t.Fatal(err) } @@ -103,7 +104,7 @@ func TestEnvValsLoad_EnvironmentNameFile(t *testing.T) { func TestEnvValsLoad_SingleValuesFileRemote(t *testing.T) { 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, "", "") if err != nil { t.Fatal(err) } @@ -121,7 +122,7 @@ func TestEnvValsLoad_SingleValuesFileRemote(t *testing.T) { func TestEnvValsLoad_OverwriteNilValue_Issue1150(t *testing.T) { 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, "", "") if err != nil { t.Fatal(err) } @@ -143,7 +144,7 @@ func TestEnvValsLoad_OverwriteNilValue_Issue1150(t *testing.T) { func TestEnvValsLoad_OverwriteWithNilValue_Issue1154(t *testing.T) { 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, "", "") if err != nil { t.Fatal(err) } @@ -166,7 +167,7 @@ func TestEnvValsLoad_OverwriteWithNilValue_Issue1154(t *testing.T) { func TestEnvValsLoad_OverwriteEmptyValue_Issue1168(t *testing.T) { 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, "", "") if err != nil { t.Fatal(err) } @@ -191,7 +192,7 @@ func TestEnvValsLoad_OverwriteEmptyValue_Issue1168(t *testing.T) { func TestEnvValsLoad_MultiHCL(t *testing.T) { 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, "", "") if err != nil { t.Fatal(err) } @@ -234,7 +235,7 @@ func TestEnvValsLoad_EnvironmentValues(t *testing.T) { env := environment.New("test") 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, "", "") if err != nil { t.Fatal(err) } @@ -247,3 +248,292 @@ func TestEnvValsLoad_EnvironmentValues(t *testing.T) { t.Error(diff) } } + +// --- mergeStrategy: fallback --- + +// Earlier files take precedence. Same conflicting key in two files → +// the value from default.yaml (loaded first) wins. +func TestEnvValsLoad_FallbackStrategy_EarlierWins(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + cluster := actual["cluster"].(map[string]any) + if got := cluster["domain"]; got != "example.com" { + t.Errorf("cluster.domain: want %q (from default.yaml), got %v", "example.com", got) + } +} + +// Later files only fill keys that are missing from earlier files. +func TestEnvValsLoad_FallbackStrategy_FillsGaps(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + cluster := actual["cluster"].(map[string]any) + if got := cluster["region"]; got != "us-east-1" { + t.Errorf("cluster.region: want %q (from fallback.yaml, missing in default.yaml), got %v", "us-east-1", got) + } + service := actual["service"].(map[string]any) + if got := service["port"]; got != 8080 { + t.Errorf("service.port: want 8080 (from fallback.yaml), got %v", got) + } +} + +// Nested maps merge recursively: top-level cluster is not replaced +// wholesale; both files contribute keys. +func TestEnvValsLoad_FallbackStrategy_DeepMerge(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + expected := map[string]any{ + "cluster": map[string]any{ + "domain": "example.com", // from default (wins) + "region": "us-east-1", // from fallback (gap filled) + }, + "service": map[string]any{ + "port": 8080, // from fallback (gap filled) + }, + } + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("deep merge mismatch (-want +got):\n%s", diff) + } +} + +// First-wins precedence holds across an arbitrarily long chain, not just +// pairwise. Three files exercise the accumulator state across iterations. +func TestEnvValsLoad_FallbackStrategy_ChainedFiles(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{ + "testdata/mergestrategy/chain_a.yaml", + "testdata/mergestrategy/chain_b.yaml", + "testdata/mergestrategy/chain_c.yaml", + }, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + expected := map[string]any{ + "letter": "a", // only in a + "only_a": "from-a", // only in a + "only_b": "from-b", // only in b + "only_c": "from-c", // only in c + "both_ab": "from-a", // a and b → a wins (earlier) + "both_bc": "from-b", // b and c → b wins (earlier) + "all_three": "from-a", // a, b, c → a wins (earliest) + } + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("chain mismatch (-want +got):\n%s", diff) + } +} + +// Explicit zero values in the earlier file MUST be preserved. Without the +// hand-rolled fallbackDeepMerge, mergo's isEmptyValue would silently let +// `enabled: true` from fallback overwrite `enabled: false` from default. +func TestEnvValsLoad_FallbackStrategy_PreservesExplicitZeroValues(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/zero_default.yaml", "testdata/mergestrategy/zero_fallback.yaml"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + expected := map[string]any{ + "enabled": false, + "replicas": 0, + "name": "", + "tags": []any{}, + } + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("explicit zero values not preserved (-want +got):\n%s", diff) + } +} + +// Explicit nil in the earlier file does NOT win under fallback: it falls +// through to the fallback file's value. This matches helmfile's existing +// MergeMaps treatment of nil ("nil from the override side only fills missing +// keys"; here, by argument-swap, nil from the winner is treated as +// "no preference, let the fallback fill it"). Documented as the deliberate +// difference from override mode, where mergo.WithOverride lets nil overwrite +// (see TestEnvValsLoad_OverwriteWithNilValue_Issue1154). +func TestEnvValsLoad_FallbackStrategy_NilFallsThroughToFallback(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/nil_default.yaml", "testdata/mergestrategy/nil_fallback.yaml"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + expected := map[string]any{"value": "from-fallback"} + if diff := cmp.Diff(expected, actual); diff != "" { + t.Errorf("explicit nil should fall through to fallback (-want +got):\n%s", diff) + } +} + +// Inline map entries (not file paths) also honor the fallback strategy. +func TestEnvValsLoad_FallbackStrategy_InlineMapEntry(t *testing.T) { + l := newLoader() + + inline := map[string]any{ + "cluster": map[string]any{"domain": "inline.example"}, + "extra": "from-inline", + } + + actual, err := l.LoadEnvironmentValues(nil, + []any{inline, "testdata/mergestrategy/fallback.yaml"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + cluster := actual["cluster"].(map[string]any) + if got := cluster["domain"]; got != "inline.example" { + t.Errorf("cluster.domain: want inline value to win, got %v", got) + } + if got := actual["extra"]; got != "from-inline" { + t.Errorf("extra: want %q, got %v", "from-inline", got) + } + // fallback.yaml still fills gaps the inline map did not set. + if got := cluster["region"]; got != "us-east-1" { + t.Errorf("cluster.region: want %q from fallback file, got %v", "us-east-1", got) + } +} + +// Regression guard: explicit "override" matches today's behavior +// (last file wins). +func TestEnvValsLoad_OverrideStrategy_PreservesCurrentBehavior(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml"}, + nil, "", MergeStrategyOverride) + if err != nil { + t.Fatal(err) + } + + cluster := actual["cluster"].(map[string]any) + if got := cluster["domain"]; got != "cluster.local" { + t.Errorf("cluster.domain under override: want %q (from fallback.yaml), got %v", "cluster.local", got) + } +} + +// Empty strategy is identical to explicit "override". +func TestEnvValsLoad_DefaultStrategy_MatchesOverride(t *testing.T) { + l := newLoader() + + asDefault, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml"}, + nil, "", "") + if err != nil { + t.Fatal(err) + } + asOverride, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml"}, + nil, "", MergeStrategyOverride) + if err != nil { + t.Fatal(err) + } + if diff := cmp.Diff(asOverride, asDefault); diff != "" { + t.Errorf("default strategy diverges from override (-override +default):\n%s", diff) + } +} + +// Within a single `values:` entry that expands to multiple files (a glob), a +// later .gotmpl in the expansion can reference earlier files in that same +// expansion. Matters because the inner file loop must merge each parsed file +// into the accumulator before rendering the next, not buffer the whole +// expansion and merge once at the end. +func TestEnvValsLoad_FallbackStrategy_GlobTemplateSeesPriorFileInSameExpansion(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/glob_*.yaml*"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + service := actual["service"].(map[string]any) + if got := service["domain"]; got != "service.example.com" { + t.Errorf("service.domain: want %q (templated from sibling glob match), got %v", + "service.example.com", got) + } +} + +// The headline use case: under fallback, a later .gotmpl values file can +// reference values defined by earlier files in the same list via .Values. +// default.yaml sets cluster.domain; fallback.yaml.gotmpl renders +// `service.domain: "service.{{ .Values.cluster.domain }}"`. +func TestEnvValsLoad_FallbackStrategy_TemplateAccessesPriorFile(t *testing.T) { + l := newLoader() + + actual, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml.gotmpl"}, + nil, "", MergeStrategyFallback) + if err != nil { + t.Fatal(err) + } + + service := actual["service"].(map[string]any) + if got := service["domain"]; got != "service.example.com" { + t.Errorf("service.domain: want %q (templated from prior file), got %v", + "service.example.com", got) + } +} + +// Symmetric guard: under override, the same .gotmpl reference does NOT +// see prior files in the same list. Documents the deliberate scoping: +// the cross-file template enrichment is opt-in via mergeStrategy: fallback. +func TestEnvValsLoad_OverrideStrategy_TemplateContextUnchanged(t *testing.T) { + l := newLoader() + + _, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml", "testdata/mergestrategy/fallback.yaml.gotmpl"}, + nil, "", MergeStrategyOverride) + if err == nil { + t.Fatal("expected template render error: under override, .Values.cluster.domain should not resolve to a prior file's value") + } + // The exact error wording is owned by the template renderer; we only + // assert that we got an error rather than a bogus successful render. +} + +// Unknown strategy values produce a clear error that names both the bad +// value and the valid options. +func TestEnvValsLoad_InvalidStrategy_Errors(t *testing.T) { + l := newLoader() + + _, err := l.LoadEnvironmentValues(nil, + []any{"testdata/mergestrategy/default.yaml"}, + nil, "prod", "bogus") + if err == nil { + t.Fatal("expected error for invalid mergeStrategy, got nil") + } + for _, want := range []string{"prod", "bogus", MergeStrategyOverride, MergeStrategyFallback} { + if !strings.Contains(err.Error(), want) { + t.Errorf("error message missing %q: %v", want, err) + } + } +} diff --git a/pkg/state/state.go b/pkg/state/state.go index cec8f890..8a82e85e 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -49,6 +49,12 @@ const ( // Valid enum for updateStrategy values UpdateStrategyReinstallIfForbidden = "reinstallIfForbidden" + + // Valid values for environment mergeStrategy. + // MergeStrategyOverride (default) makes later values files override earlier ones. + // MergeStrategyFallback flips the precedence: earlier files win and later files only fill gaps. + MergeStrategyOverride = "override" + MergeStrategyFallback = "fallback" ) // ReleaseSetSpec is release set spec diff --git a/pkg/state/testdata/mergestrategy/chain_a.yaml b/pkg/state/testdata/mergestrategy/chain_a.yaml new file mode 100644 index 00000000..4c23a0f4 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/chain_a.yaml @@ -0,0 +1,4 @@ +letter: a +only_a: from-a +both_ab: from-a +all_three: from-a diff --git a/pkg/state/testdata/mergestrategy/chain_b.yaml b/pkg/state/testdata/mergestrategy/chain_b.yaml new file mode 100644 index 00000000..19d208f6 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/chain_b.yaml @@ -0,0 +1,4 @@ +only_b: from-b +both_ab: from-b +both_bc: from-b +all_three: from-b diff --git a/pkg/state/testdata/mergestrategy/chain_c.yaml b/pkg/state/testdata/mergestrategy/chain_c.yaml new file mode 100644 index 00000000..2fd3686f --- /dev/null +++ b/pkg/state/testdata/mergestrategy/chain_c.yaml @@ -0,0 +1,3 @@ +only_c: from-c +both_bc: from-c +all_three: from-c diff --git a/pkg/state/testdata/mergestrategy/default.yaml b/pkg/state/testdata/mergestrategy/default.yaml new file mode 100644 index 00000000..4ad18014 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/default.yaml @@ -0,0 +1,2 @@ +cluster: + domain: example.com diff --git a/pkg/state/testdata/mergestrategy/fallback.yaml b/pkg/state/testdata/mergestrategy/fallback.yaml new file mode 100644 index 00000000..8b57825e --- /dev/null +++ b/pkg/state/testdata/mergestrategy/fallback.yaml @@ -0,0 +1,5 @@ +cluster: + domain: cluster.local + region: us-east-1 +service: + port: 8080 diff --git a/pkg/state/testdata/mergestrategy/fallback.yaml.gotmpl b/pkg/state/testdata/mergestrategy/fallback.yaml.gotmpl new file mode 100644 index 00000000..0df72cba --- /dev/null +++ b/pkg/state/testdata/mergestrategy/fallback.yaml.gotmpl @@ -0,0 +1,3 @@ +service: + domain: "service.{{ .Values.cluster.domain }}" + port: 8080 diff --git a/pkg/state/testdata/mergestrategy/glob_a.yaml b/pkg/state/testdata/mergestrategy/glob_a.yaml new file mode 100644 index 00000000..4ad18014 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/glob_a.yaml @@ -0,0 +1,2 @@ +cluster: + domain: example.com diff --git a/pkg/state/testdata/mergestrategy/glob_b.yaml.gotmpl b/pkg/state/testdata/mergestrategy/glob_b.yaml.gotmpl new file mode 100644 index 00000000..2d92987e --- /dev/null +++ b/pkg/state/testdata/mergestrategy/glob_b.yaml.gotmpl @@ -0,0 +1,2 @@ +service: + domain: "service.{{ .Values.cluster.domain }}" diff --git a/pkg/state/testdata/mergestrategy/nil_default.yaml b/pkg/state/testdata/mergestrategy/nil_default.yaml new file mode 100644 index 00000000..6a1a5a05 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/nil_default.yaml @@ -0,0 +1 @@ +value: ~ diff --git a/pkg/state/testdata/mergestrategy/nil_fallback.yaml b/pkg/state/testdata/mergestrategy/nil_fallback.yaml new file mode 100644 index 00000000..5a7504c9 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/nil_fallback.yaml @@ -0,0 +1 @@ +value: from-fallback diff --git a/pkg/state/testdata/mergestrategy/zero_default.yaml b/pkg/state/testdata/mergestrategy/zero_default.yaml new file mode 100644 index 00000000..d1fb7157 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/zero_default.yaml @@ -0,0 +1,4 @@ +enabled: false +replicas: 0 +name: "" +tags: [] diff --git a/pkg/state/testdata/mergestrategy/zero_fallback.yaml b/pkg/state/testdata/mergestrategy/zero_fallback.yaml new file mode 100644 index 00000000..fc39f730 --- /dev/null +++ b/pkg/state/testdata/mergestrategy/zero_fallback.yaml @@ -0,0 +1,6 @@ +enabled: true +replicas: 3 +name: from-fallback +tags: + - a + - b From 897400d64fe2c2c4e6e35a0a4c4433f143dc4d36 Mon Sep 17 00:00:00 2001 From: Niklas Date: Thu, 7 May 2026 15:52:18 +0200 Subject: [PATCH 20/28] Expose internal apis (#2520) * feat: expose WithPreparedCharts and SyncRun This exposes the previously internal functions withPreparedCharts and sync to be used by tooling built on top of helmfile Co-authored-by: Raphael Luba Signed-off-by: Niklas Ott * feat: expose Helm interface and HelmState This adds a function to get the current HelmState Co-authored-by: Raphael Luba Signed-off-by: Niklas Ott * feat: return an error instead of panicking on multiple calls on WithPreparedCharts Signed-off-by: Niklas Ott --------- Signed-off-by: Niklas Ott Co-authored-by: Raphael Luba --- pkg/app/app.go | 32 ++++++++++++++++---------------- pkg/app/run.go | 12 ++++++++++-- 2 files changed, 26 insertions(+), 18 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index b752a799..5042f26f 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -162,7 +162,7 @@ func (a *App) Diff(c DiffConfigProvider) error { includeCRDs := !c.SkipCRDs() - prepErr := run.withPreparedCharts("diff", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("diff", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -234,7 +234,7 @@ func (a *App) Template(c TemplateConfigProvider) error { // https://github.com/helmfile/helmfile/issues/1749 run.helm.SetExtraArgs() - prepErr := run.withPreparedCharts("template", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("template", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -263,7 +263,7 @@ func (a *App) Template(c TemplateConfigProvider) error { func (a *App) WriteValues(c WriteValuesConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { - prepErr := run.withPreparedCharts("write-values", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("write-values", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -315,7 +315,7 @@ func (a *App) Lint(c LintConfigProvider) error { var lintErrs []error // `helm lint` on helm v2 and v3 does not support remote charts, that we need to set `forceDownload=true` here - prepErr := run.withPreparedCharts("lint", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("lint", state.ChartPrepareOptions{ ForceDownload: true, SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), @@ -357,7 +357,7 @@ func (a *App) Unittest(c UnittestConfigProvider) error { var unittestErrs []error // helm unittest needs local charts, so force download - prepErr := run.withPreparedCharts("unittest", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("unittest", state.ChartPrepareOptions{ ForceDownload: true, SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), @@ -436,7 +436,7 @@ func (a *App) Fetch(c FetchConfigProvider) error { defer run.helm.SetEnableLiveOutput(a.EnableLiveOutput) } - prepErr := run.withPreparedCharts("pull", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("pull", state.ChartPrepareOptions{ ForceDownload: true, SkipRefresh: c.SkipRefresh(), SkipRepos: c.SkipRefresh() || c.SkipDeps(), @@ -487,7 +487,7 @@ func (a *App) Sync(c SyncConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { includeCRDs := !c.SkipCRDs() - prepErr := run.withPreparedCharts("sync", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("sync", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -500,7 +500,7 @@ func (a *App) Sync(c SyncConfigProvider) error { Validate: c.Validate(), Concurrency: c.Concurrency(), }, func() []error { - ok, errs = a.sync(run, c) + ok, errs = a.SyncState(run, c) return errs }) @@ -524,7 +524,7 @@ func (a *App) Apply(c ApplyConfigProvider) error { err := a.ForEachState(func(run *Run) (ok bool, errs []error) { includeCRDs := !c.SkipCRDs() - prepErr := run.withPreparedCharts("apply", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("apply", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -570,7 +570,7 @@ func (a *App) Apply(c ApplyConfigProvider) error { func (a *App) Status(c StatusesConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { - err := run.withPreparedCharts("status", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("status", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: c.Concurrency(), @@ -590,7 +590,7 @@ func (a *App) Status(c StatusesConfigProvider) error { func (a *App) Destroy(c DestroyConfigProvider) error { return a.ForEachState(func(run *Run) (ok bool, errs []error) { if !c.SkipCharts() { - err := run.withPreparedCharts("destroy", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("destroy", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -619,7 +619,7 @@ func (a *App) Test(c TestConfigProvider) error { "or set helm.sh/hook-delete-policy\n") } - err := run.withPreparedCharts("test", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("test", state.ChartPrepareOptions{ SkipRepos: c.SkipRefresh() || c.SkipDeps(), SkipRefresh: c.SkipRefresh(), SkipDeps: c.SkipDeps(), @@ -640,7 +640,7 @@ func (a *App) Test(c TestConfigProvider) error { func (a *App) PrintDAGState(c DAGConfigProvider) error { var err error return a.ForEachState(func(run *Run) (ok bool, errs []error) { - err = run.withPreparedCharts("show-dag", state.ChartPrepareOptions{ + err = run.WithPreparedCharts("show-dag", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: 2, @@ -657,7 +657,7 @@ func (a *App) PrintDAGState(c DAGConfigProvider) error { func (a *App) PrintState(c StateConfigProvider) error { return a.ForEachState(func(run *Run) (_ bool, errs []error) { - err := run.withPreparedCharts("build", state.ChartPrepareOptions{ + err := run.WithPreparedCharts("build", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: 2, @@ -730,7 +730,7 @@ func (a *App) ListReleases(c ListConfigProvider) error { var listErr error if !c.SkipCharts() { - prepErr := run.withPreparedCharts("list", state.ChartPrepareOptions{ + prepErr := run.WithPreparedCharts("list", state.ChartPrepareOptions{ SkipRepos: true, SkipDeps: true, Concurrency: 2, @@ -2189,7 +2189,7 @@ func (a *App) status(r *Run, c StatusesConfigProvider) (bool, []error) { return true, errs } -func (a *App) sync(r *Run, c SyncConfigProvider) (bool, []error) { +func (a *App) SyncState(r *Run, c SyncConfigProvider) (bool, []error) { st := r.state helm := r.helm diff --git a/pkg/app/run.go b/pkg/app/run.go index 4c124df2..1b120672 100644 --- a/pkg/app/run.go +++ b/pkg/app/run.go @@ -57,9 +57,9 @@ func (r *Run) prepareChartsIfNeeded(helmfileCommand string, dir string, concurre return releaseToChart, nil } -func (r *Run) withPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func() []error) error { +func (r *Run) WithPreparedCharts(helmfileCommand string, opts state.ChartPrepareOptions, f func() []error) error { if r.ReleaseToChart != nil { - panic("Run.PrepareCharts can be called only once") + return fmt.Errorf("Run.WithPreparedCharts can be called only once") } // Check both CLI options and helmDefaults for skipping repos (issue #2296) @@ -238,3 +238,11 @@ func (r *Run) diff(triggerCleanupEvent bool, detailedExitCode bool, c DiffConfig return &infoMsg, releasesToBeUpdated, releasesToBeDeleted, nil } + +func (r *Run) State() *state.HelmState { + return r.state +} + +func (r *Run) Helm() helmexec.Interface { + return r.helm +} From 07b7fa38862a0b613dac0e975d4b0626e7a6a25d Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 06:59:10 +0800 Subject: [PATCH 21/28] build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 from 1.100.1 to 1.101.0 (#2581) build(deps): bump github.com/aws/aws-sdk-go-v2/service/s3 Bumps [github.com/aws/aws-sdk-go-v2/service/s3](https://github.com/aws/aws-sdk-go-v2) from 1.100.1 to 1.101.0. - [Release notes](https://github.com/aws/aws-sdk-go-v2/releases) - [Commits](https://github.com/aws/aws-sdk-go-v2/compare/service/s3/v1.100.1...service/s3/v1.101.0) --- updated-dependencies: - dependency-name: github.com/aws/aws-sdk-go-v2/service/s3 dependency-version: 1.101.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 2 +- go.sum | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/go.mod b/go.mod index 2e740f82..24955dc9 100644 --- a/go.mod +++ b/go.mod @@ -7,7 +7,7 @@ require ( github.com/Masterminds/semver/v3 v3.5.0 github.com/Masterminds/sprig/v3 v3.3.0 github.com/aws/aws-sdk-go-v2/config v1.32.17 - github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 + github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc github.com/go-test/deep v1.1.1 github.com/gofrs/flock v0.13.0 diff --git a/go.sum b/go.sum index 699e938c..040b6801 100644 --- a/go.sum +++ b/go.sum @@ -180,8 +180,8 @@ github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23 h1:03xatSQO4+AM1 github.com/aws/aws-sdk-go-v2/service/internal/s3shared v1.19.23/go.mod h1:M8l3mwgx5ToK7wot2sBBce/ojzgnPzZXUV445gTSyE8= github.com/aws/aws-sdk-go-v2/service/kms v1.51.0 h1:696UM+NwOrETBCLQJyCAGtVmmZmziBT59yMwgg6Fvrw= github.com/aws/aws-sdk-go-v2/service/kms v1.51.0/go.mod h1:GBO/aaEi47QldDVoqw2CsM2UZQDoqDiFIMJD/ztHPs0= -github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1 h1:mxuT1xE+dI54NW3RkNjP8DUT5HXqbkiAFvfdyDFwE5c= -github.com/aws/aws-sdk-go-v2/service/s3 v1.100.1/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0 h1:etqBTKY581iwLL/H/S2sVgk3C9lAsTJFeXWFDsDcWOU= +github.com/aws/aws-sdk-go-v2/service/s3 v1.101.0/go.mod h1:L2dcoOgS2VSgbPLvpak2NyUPsO1TBN7M45Z4H7DlRc4= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.6 h1:XR42AXidhYs4HwH0I+yElLXVt7zb2hAyNHQJe6Blv7w= github.com/aws/aws-sdk-go-v2/service/secretsmanager v1.41.6/go.mod h1:nOTsSVQlAsgwVRdtZYtECSnsInF8IUhrpnclCPat7Fs= github.com/aws/aws-sdk-go-v2/service/signin v1.0.11 h1:TdJ+HdzOBhU8+iVAOGUTU63VXopcumCOF1paFulHWZc= From 5027e6aa5e0ece36231ca44db88f6c1a7d8c7d1b Mon Sep 17 00:00:00 2001 From: Niklas Date: Fri, 8 May 2026 00:59:26 +0200 Subject: [PATCH 22/28] chore: Deduplicate preparation code of sync and apply (#2523) This commit deduplicates the preparation logic for sync and apply by moving it to a common function. Signed-off-by: Niklas Ott Co-authored-by: Raphael Luba --- pkg/app/app.go | 64 ++++++++++++++++++++++++++------------------------ 1 file changed, 33 insertions(+), 31 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index 5042f26f..097bfed3 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -1708,39 +1708,60 @@ func (a *App) getSelectedReleases(r *Run, includeTransitiveNeeds bool) ([]state. return selected, deduplicated, nil } -func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { +// GetPlannedAndSelectedReleasesWithNeeds returns the planned releases and the selected releases used for planning. +// The planned releases include dependency releases only when includeNeeds is true and skipNeeds is false. +func (a *App) GetPlannedAndSelectedReleasesWithNeeds(r *Run, skipNeeds bool, includeNeeds bool, includeTransitiveNeeds bool) ([]state.ReleaseSpec, []state.ReleaseSpec, error) { st := r.state - helm := r.helm - helm.SetExtraArgs(GetArgs(c.Args(), r.state)...) - - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) + selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, includeTransitiveNeeds) if err != nil { - return false, false, []error{err} + return nil, nil, err } if len(selectedReleases) == 0 { - return false, false, nil + return nil, nil, nil } // This is required when you're trying to deduplicate releases by the selector. // Without this, `PlanReleases` conflates duplicates and return both in `batches`, // even if we provided `SelectedReleases: selectedReleases`. // See https://github.com/roboll/helmfile/issues/1818 for more context. + originalReleases := st.Releases st.Releases = selectedAndNeededReleases + defer func() { + st.Releases = originalReleases + }() - plan, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}) + batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: skipNeeds, IncludeNeeds: includeNeeds, IncludeTransitiveNeeds: includeTransitiveNeeds}) if err != nil { - return false, false, []error{err} + return nil, nil, err } var releasesWithNeeds []state.ReleaseSpec - for _, rs := range plan { + for _, rs := range batches { for _, r := range rs { releasesWithNeeds = append(releasesWithNeeds, r.ReleaseSpec) } } + return releasesWithNeeds, selectedAndNeededReleases, nil +} + +func (a *App) apply(r *Run, c ApplyConfigProvider) (bool, bool, []error) { + st := r.state + helm := r.helm + + helm.SetExtraArgs(GetArgs(c.Args(), r.state)...) + + releasesWithNeeds, selectedAndNeededReleases, err := a.GetPlannedAndSelectedReleasesWithNeeds(r, c.SkipNeeds(), c.IncludeNeeds(), c.IncludeTransitiveNeeds()) + if err != nil { + return false, false, []error{err} + } + + if len(releasesWithNeeds) == 0 { + return false, false, nil + } + // Do build deps and prepare only on selected releases so that we won't waste time // on running various helm commands on unnecessary releases st.Releases = releasesWithNeeds @@ -2193,33 +2214,14 @@ func (a *App) SyncState(r *Run, c SyncConfigProvider) (bool, []error) { st := r.state helm := r.helm - selectedReleases, selectedAndNeededReleases, err := a.getSelectedReleases(r, c.IncludeTransitiveNeeds()) + releasesWithNeeds, selectedAndNeededReleases, err := a.GetPlannedAndSelectedReleasesWithNeeds(r, c.SkipNeeds(), c.IncludeNeeds(), c.IncludeTransitiveNeeds()) if err != nil { return false, []error{err} } - if len(selectedReleases) == 0 { + if len(releasesWithNeeds) == 0 { return false, nil } - // This is required when you're trying to deduplicate releases by the selector. - // Without this, `PlanReleases` conflates duplicates and return both in `batches`, - // even if we provided `SelectedReleases: selectedReleases`. - // See https://github.com/roboll/helmfile/issues/1818 for more context. - st.Releases = selectedAndNeededReleases - - batches, err := st.PlanReleases(state.PlanOptions{Reverse: false, SelectedReleases: selectedReleases, SkipNeeds: c.SkipNeeds(), IncludeNeeds: c.IncludeNeeds(), IncludeTransitiveNeeds: c.IncludeTransitiveNeeds()}) - if err != nil { - return false, []error{err} - } - - var releasesWithNeeds []state.ReleaseSpec - - for _, rs := range batches { - for _, r := range rs { - releasesWithNeeds = append(releasesWithNeeds, r.ReleaseSpec) - } - } - // Do build deps and prepare only on selected releases so that we won't waste time // on running various helm commands on unnecessary releases st.Releases = releasesWithNeeds From 86066cbf9800a4e69f570a3f2e7ef0a56b7313b7 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 8 May 2026 08:11:41 +0800 Subject: [PATCH 23/28] build(deps): bump gitpython from 3.1.47 to 3.1.49 in /docs (#2582) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.47 to 3.1.49. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.47...3.1.49) --- updated-dependencies: - dependency-name: gitpython dependency-version: 3.1.49 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 0974730b..00edb7de 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,7 +2,7 @@ Babel==2.17.0 click==8.1.2 ghp-import==2.0.2 gitdb==4.0.9 -GitPython==3.1.47 +GitPython==3.1.49 importlib-metadata==4.11.3 Jinja2==3.1.6 Markdown==3.8.1 From 997051bba448cc41f80c113388e36335c167c6ce Mon Sep 17 00:00:00 2001 From: Niklas Date: Fri, 8 May 2026 02:12:35 +0200 Subject: [PATCH 24/28] chore: Emit "cleanup" events later to match the behavior in "apply" (#2522) This changes the behaviour of the cleanup event during sync to be triggered right before the function exits and matches the behaviour of apply Signed-off-by: Niklas Ott Co-authored-by: Raphael Luba --- pkg/app/app.go | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/pkg/app/app.go b/pkg/app/app.go index 097bfed3..64f12b68 100644 --- a/pkg/app/app.go +++ b/pkg/app/app.go @@ -2269,13 +2269,6 @@ func (a *App) SyncState(r *Run, c SyncConfigProvider) (bool, []error) { } } - for id := range releasesWithNoChange { - r := releasesWithNoChange[id] - if _, err := st.TriggerCleanupEvent(&r, "sync"); err != nil { - a.Logger.Warnf("warn: %v\n", err) - } - } - names := []string{} for _, r := range releasesToUpdate { names = append(names, fmt.Sprintf(" %s (%s) UPDATED", r.Name, r.Chart)) @@ -2378,6 +2371,13 @@ Do you really want to sync? affectedReleases.DisplayAffectedReleases(c.Logger()) + for id := range releasesWithNoChange { + r := releasesWithNoChange[id] + if _, err := st.TriggerCleanupEvent(&r, "sync"); err != nil { + a.Logger.Warnf("warn: %v\n", err) + } + } + return true, errs } From 4b1707d30d488cc65bcab15d8aa557d66fd85a11 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 11:07:17 +0800 Subject: [PATCH 25/28] build(deps): bump golang.org/x/term from 0.42.0 to 0.43.0 (#2584) Bumps [golang.org/x/term](https://github.com/golang/term) from 0.42.0 to 0.43.0. - [Commits](https://github.com/golang/term/compare/v0.42.0...v0.43.0) --- updated-dependencies: - dependency-name: golang.org/x/term dependency-version: 0.43.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- go.mod | 4 ++-- go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/go.mod b/go.mod index 24955dc9..618907a8 100644 --- a/go.mod +++ b/go.mod @@ -33,7 +33,7 @@ require ( go.yaml.in/yaml/v2 v2.4.4 go.yaml.in/yaml/v3 v3.0.4 golang.org/x/sync v0.20.0 - golang.org/x/term v0.42.0 + golang.org/x/term v0.43.0 gopkg.in/yaml.v3 v3.0.1 helm.sh/helm/v3 v3.20.2 helm.sh/helm/v4 v4.1.4 @@ -105,7 +105,7 @@ require ( go.uber.org/atomic v1.9.0 // indirect golang.org/x/net v0.53.0 // indirect golang.org/x/oauth2 v0.36.0 // indirect - golang.org/x/sys v0.43.0 // indirect + golang.org/x/sys v0.44.0 // indirect golang.org/x/text v0.36.0 // indirect golang.org/x/time v0.15.0 // indirect google.golang.org/api v0.276.0 // indirect diff --git a/go.sum b/go.sum index 040b6801..9b13388c 100644 --- a/go.sum +++ b/go.sum @@ -963,8 +963,8 @@ golang.org/x/sys v0.15.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.17.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.19.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= golang.org/x/sys v0.20.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA= -golang.org/x/sys v0.43.0 h1:Rlag2XtaFTxp19wS8MXlJwTvoh8ArU6ezoyFsMyCTNI= -golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.44.0 h1:ildZl3J4uzeKP07r2F++Op7E9B29JRUy+a27EibtBTQ= +golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/term v0.0.0-20201126162022-7de9c90e9dd1/go.mod h1:bj7SfCRtBDWHUb9snDiAeCFNEtKQo2Wmx5Cou7ajbmo= golang.org/x/term v0.0.0-20210503060354-a79de5458b56/go.mod h1:tfny5GFUkzUvx4ps4ajbZsCe5lw1metzhBm9T3x7oIY= golang.org/x/term v0.0.0-20210927222741-03fcf44c2211/go.mod h1:jbD1KX2456YbFQfuXm/mYQcufACuNUgVhRMnK/tPxf8= @@ -974,8 +974,8 @@ golang.org/x/term v0.15.0/go.mod h1:BDl952bC7+uMoWR75FIrCDx79TPU9oHkTZ9yRbYOrX0= golang.org/x/term v0.17.0/go.mod h1:lLRBjIVuehSbZlaOtGMbcMncT+aqLLLmKrsjNrUguwk= golang.org/x/term v0.19.0/go.mod h1:2CuTdWZ7KHSQwUzKva0cbMg6q2DMI3Mmxp+gKJbskEk= golang.org/x/term v0.20.0/go.mod h1:8UkIAJTvZgivsXaD6/pH6U9ecQzZ45awqEOzuCvwpFY= -golang.org/x/term v0.42.0 h1:UiKe+zDFmJobeJ5ggPwOshJIVt6/Ft0rcfrXZDLWAWY= -golang.org/x/term v0.42.0/go.mod h1:Dq/D+snpsbazcBG5+F9Q1n2rXV8Ma+71xEjTRufARgY= +golang.org/x/term v0.43.0 h1:S4RLU2sB31O/NCl+zFN9Aru9A/Cq2aqKpTZJ6B+DwT4= +golang.org/x/term v0.43.0/go.mod h1:lrhlHNdQJHO+1qVYiHfFKVuVioJIheAc3fBSMFYEIsk= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/text v0.3.2/go.mod h1:bEr9sfX3Q8Zfm5fL9x+3itogRgK3+ptLWKqgva+5dAk= golang.org/x/text v0.3.3/go.mod h1:5Zoc/QRtKVWzQhOtBMvqHzDpF6irO9z98xDceosuGiQ= From 9e6ed5701569ea1b895416493c68656ef822b645 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sat, 9 May 2026 20:46:32 +0800 Subject: [PATCH 26/28] build(deps): bump gitpython from 3.1.49 to 3.1.50 in /docs (#2585) Bumps [gitpython](https://github.com/gitpython-developers/GitPython) from 3.1.49 to 3.1.50. - [Release notes](https://github.com/gitpython-developers/GitPython/releases) - [Changelog](https://github.com/gitpython-developers/GitPython/blob/main/CHANGES) - [Commits](https://github.com/gitpython-developers/GitPython/compare/3.1.49...3.1.50) --- updated-dependencies: - dependency-name: gitpython dependency-version: 3.1.50 dependency-type: direct:production ... Signed-off-by: dependabot[bot] Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- docs/requirements.txt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/requirements.txt b/docs/requirements.txt index 00edb7de..50a13d40 100644 --- a/docs/requirements.txt +++ b/docs/requirements.txt @@ -2,7 +2,7 @@ Babel==2.17.0 click==8.1.2 ghp-import==2.0.2 gitdb==4.0.9 -GitPython==3.1.49 +GitPython==3.1.50 importlib-metadata==4.11.3 Jinja2==3.1.6 Markdown==3.8.1 From c82c61e061f42a80e5ab19eb9379a85d9a31975a Mon Sep 17 00:00:00 2001 From: yxxhero <11087727+yxxhero@users.noreply.github.com> Date: Mon, 11 May 2026 09:05:06 +0800 Subject: [PATCH 27/28] fix: template helmDefaults.postRendererArgs with release data (#2583) PR #1839 introduced template rendering for postRendererArgs, but PR #2510 reverted it while fixing a separate regression. This left helmDefaults-level postRendererArgs containing template expressions (e.g. {{ .Release.Name }}) passed to helm as literal strings instead of being resolved per-release. Add renderPostRendererArgs() that templates helmDefaults.postRendererArgs at flag-generation time using the release's template data, reusing the existing createReleaseTemplateData() helper. Release-level args are already templated by ExecuteTemplateExpressions and CLI args are static, so only the helmDefaults path needs rendering. Fixes #2580 Signed-off-by: opencode Signed-off-by: yxxhero --- pkg/state/helmx.go | 38 ++++++++++++++-- pkg/state/state.go | 17 +++++-- pkg/state/state_test.go | 45 +++++++++++++++++++ test/integration/run.sh | 1 + .../test-cases/postrender-defaults-args.sh | 25 +++++++++++ .../input/echo-args.bash | 12 +++++ .../input/helm-plugin-echo-args/echo-args.sh | 19 ++++++++ .../input/helm-plugin-echo-args/plugin.yaml | 10 +++++ .../input/helmfile.yaml.gotmpl | 30 +++++++++++++ 9 files changed, 190 insertions(+), 7 deletions(-) create mode 100644 test/integration/test-cases/postrender-defaults-args.sh create mode 100755 test/integration/test-cases/postrender-defaults-args/input/echo-args.bash create mode 100755 test/integration/test-cases/postrender-defaults-args/input/helm-plugin-echo-args/echo-args.sh create mode 100644 test/integration/test-cases/postrender-defaults-args/input/helm-plugin-echo-args/plugin.yaml create mode 100644 test/integration/test-cases/postrender-defaults-args/input/helmfile.yaml.gotmpl diff --git a/pkg/state/helmx.go b/pkg/state/helmx.go index 0a75df98..bf7bd46a 100644 --- a/pkg/state/helmx.go +++ b/pkg/state/helmx.go @@ -13,10 +13,12 @@ import ( "github.com/helmfile/chartify" "helm.sh/helm/v4/pkg/storage/driver" + "github.com/helmfile/helmfile/pkg/filesystem" "github.com/helmfile/helmfile/pkg/helmexec" "github.com/helmfile/helmfile/pkg/kubedog" "github.com/helmfile/helmfile/pkg/remote" "github.com/helmfile/helmfile/pkg/resource" + "github.com/helmfile/helmfile/pkg/tmpl" ) type Dependency struct { @@ -115,7 +117,7 @@ func (st *HelmState) appendPostRenderFlags(flags []string, release *ReleaseSpec, } // append post-renderer-args flags to helm flags -func (st *HelmState) appendPostRenderArgsFlags(flags []string, release *ReleaseSpec, postRendererArgs []string) []string { +func (st *HelmState) appendPostRenderArgsFlags(flags []string, release *ReleaseSpec, postRendererArgs []string) ([]string, error) { postRendererArgsFlags := []string{} switch { case len(release.PostRendererArgs) != 0: @@ -123,14 +125,44 @@ func (st *HelmState) appendPostRenderArgsFlags(flags []string, release *ReleaseS case len(postRendererArgs) != 0: postRendererArgsFlags = postRendererArgs case len(st.HelmDefaults.PostRendererArgs) != 0: - postRendererArgsFlags = st.HelmDefaults.PostRendererArgs + rendered, err := st.renderPostRendererArgs(release, st.HelmDefaults.PostRendererArgs) + if err != nil { + return nil, err + } + postRendererArgsFlags = rendered } for _, arg := range postRendererArgsFlags { if arg != "" { flags = append(flags, "--post-renderer-args="+arg) } } - return flags + return flags, nil +} + +func (st *HelmState) renderPostRendererArgs(release *ReleaseSpec, args []string) ([]string, error) { + vals := st.RenderedValues + if vals == nil { + vals = make(map[string]any) + } + + fs := st.fs + if fs == nil { + fs = filesystem.DefaultFileSystem() + } + + tmplData := st.createReleaseTemplateData(release, vals) + renderer := tmpl.NewFileRenderer(fs, st.basePath, tmplData) + + result := make([]string, 0, len(args)) + for _, arg := range args { + rendered, err := renderer.RenderTemplateContentToString([]byte(arg)) + if err != nil { + return nil, fmt.Errorf("failed rendering postRendererArg %q for release %q: %w", arg, release.Name, err) + } + result = append(result, rendered) + } + + return result, nil } // append skip-schema-validation flags to helm flags diff --git a/pkg/state/state.go b/pkg/state/state.go index 8a82e85e..d63f6404 100644 --- a/pkg/state/state.go +++ b/pkg/state/state.go @@ -3627,7 +3627,10 @@ func (st *HelmState) flagsForUpgrade(helm helmexec.Interface, release *ReleaseSp if opt != nil { postRendererArgs = opt.PostRendererArgs } - flags = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) + flags, err = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) + if err != nil { + return nil, nil, err + } skipSchemaValidation := false if opt != nil { @@ -3670,7 +3673,10 @@ func (st *HelmState) flagsForTemplate(helm helmexec.Interface, release *ReleaseS skipSchemaValidation = opt.SkipSchemaValidation } flags = st.appendPostRenderFlags(flags, release, postRenderer, helm) - flags = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) + flags, err := st.appendPostRenderArgsFlags(flags, release, postRendererArgs) + if err != nil { + return nil, nil, err + } flags = st.appendApiVersionsFlags(flags, release, kubeVersion) flags = st.appendChartDownloadFlags(flags, release) flags = st.appendShowOnlyFlags(flags, showOnly) @@ -3793,7 +3799,11 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, if opt != nil { postRendererArgs = opt.PostRendererArgs } - flags = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) + var err error + flags, err = st.appendPostRenderArgsFlags(flags, release, postRendererArgs) + if err != nil { + return nil, nil, err + } skipSchemaValidation := false if opt != nil { @@ -3823,7 +3833,6 @@ func (st *HelmState) flagsForDiff(helm helmexec.Interface, release *ReleaseSpec, takeOwnership = opt.TakeOwnership } - var err error flags, err = st.appendTakeOwnershipFlagsForDiff(flags, release, takeOwnership, pluginsDir) if err != nil { return nil, nil, err diff --git a/pkg/state/state_test.go b/pkg/state/state_test.go index 9f1038df..d188cd57 100644 --- a/pkg/state/state_test.go +++ b/pkg/state/state_test.go @@ -1048,6 +1048,51 @@ func TestHelmState_flagsForUpgrade(t *testing.T) { "--namespace", "test-namespace", }, }, + { + name: "post-renderer-args-helmdefault-templated-with-release-name", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &enable, + PostRendererArgs: []string{"{{ .Release.Name }}", "--chart={{ .Release.Chart }}"}, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &disable, + Name: "my-release", + Namespace: "test-namespace", + CreateNamespace: &disable, + }, + want: []string{ + "--version", "0.1", + "--post-renderer-args=my-release", + "--post-renderer-args=--chart=test/chart", + "--namespace", "test-namespace", + }, + }, + { + name: "post-renderer-args-helmdefault-templated-with-namespace", + defaults: HelmSpec{ + Verify: false, + CreateNamespace: &enable, + PostRendererArgs: []string{"{{ .Release.Namespace }}/{{ .Release.Name }}"}, + }, + version: semver.MustParse("3.10.0"), + release: &ReleaseSpec{ + Chart: "test/chart", + Version: "0.1", + Verify: &disable, + Name: "my-release", + Namespace: "test-namespace", + CreateNamespace: &disable, + }, + want: []string{ + "--version", "0.1", + "--post-renderer-args=test-namespace/my-release", + "--namespace", "test-namespace", + }, + }, { name: "description-from-release", defaults: HelmSpec{ diff --git a/test/integration/run.sh b/test/integration/run.sh index e6b491ec..2637ae0c 100755 --- a/test/integration/run.sh +++ b/test/integration/run.sh @@ -116,6 +116,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes . ${dir}/test-cases/yaml-overwrite.sh . ${dir}/test-cases/chart-needs.sh . ${dir}/test-cases/postrender.sh +. ${dir}/test-cases/postrender-defaults-args.sh . ${dir}/test-cases/issue-2515.sh . ${dir}/test-cases/chartify.sh . ${dir}/test-cases/deps-mr-1011.sh diff --git a/test/integration/test-cases/postrender-defaults-args.sh b/test/integration/test-cases/postrender-defaults-args.sh new file mode 100644 index 00000000..1c1b6699 --- /dev/null +++ b/test/integration/test-cases/postrender-defaults-args.sh @@ -0,0 +1,25 @@ +postrender_defaults_args_case_input_dir="${cases_dir}/postrender-defaults-args/input" +postrender_defaults_args_case_output_dir="${cases_dir}/postrender-defaults-args/output" + +# Helm 4 requires post-renderers to be plugins +if [ "${HELMFILE_HELM4}" = "1" ]; then + info "Installing echo-args post-renderer plugin for Helm 4" + ${helm} plugin uninstall echo-args &>/dev/null || true + ${helm} plugin install ${postrender_defaults_args_case_input_dir}/helm-plugin-echo-args ${PLUGIN_INSTALL_FLAGS} || fail "Failed to install echo-args plugin" +fi + +config_file="helmfile.yaml.gotmpl" +postrender_defaults_args_tmp=$(mktemp -d) + +test_start "postrender-defaults-args template" +info "Running helmfile template with helmDefaults.postRendererArgs containing {{ .Release.Name }}" +${helmfile} -f ${postrender_defaults_args_case_input_dir}/${config_file} template --concurrency 1 &> ${postrender_defaults_args_tmp}/template.out || fail "\"helmfile template\" shouldn't fail" + +info "Verifying that helmDefaults.postRendererArgs were templated with release names" +grep -q "name: rendered-arg-foo" ${postrender_defaults_args_tmp}/template.out || fail "Expected postRendererArg 'foo' for release foo, but not found in output" +grep -q "name: rendered-arg-bar" ${postrender_defaults_args_tmp}/template.out || fail "Expected postRendererArg 'bar' for release bar, but not found in output" + +info "Verifying that literal template expression was NOT passed" +grep -q "rendered-arg-{{ .Release.Name }}" ${postrender_defaults_args_tmp}/template.out && fail "Template expression was NOT rendered (found literal {{ .Release.Name }})" + +test_pass "postrender-defaults-args template" diff --git a/test/integration/test-cases/postrender-defaults-args/input/echo-args.bash b/test/integration/test-cases/postrender-defaults-args/input/echo-args.bash new file mode 100755 index 00000000..41fc557c --- /dev/null +++ b/test/integration/test-cases/postrender-defaults-args/input/echo-args.bash @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +arg=$1 +cat +echo "---" +cat < Date: Fri, 15 May 2026 13:14:02 +0800 Subject: [PATCH 28/28] Bump Helm support to 3.21.0 and 4.2.0 (#2588) * chore: bump pinned helm versions Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b1cfacaa-52d2-46c8-9fc7-67beaca43df0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * fix: align kubernetes module pins for helm bump Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b1cfacaa-52d2-46c8-9fc7-67beaca43df0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * docs: clarify helm and k8s version pins Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b1cfacaa-52d2-46c8-9fc7-67beaca43df0 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: update helm 4 snapshot outputs Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/b430f041-d8fb-407f-af06-070f2d0e9293 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: update helm 4 postrender integration fixture Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/fea792b4-b24c-43a9-a391-1fd52e59f843 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> * test: update helm 4 postrender template fixture Agent-Logs-Url: https://github.com/helmfile/helmfile/sessions/7ca16c9d-e398-46ce-849b-6299214b2b60 Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --------- Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: yxxhero <11087727+yxxhero@users.noreply.github.com> --- .github/workflows/Makefile | 2 +- .github/workflows/ci.yaml | 16 +- Dockerfile | 6 +- Dockerfile.debian-stable-slim | 6 +- Dockerfile.ubuntu | 6 +- go.mod | 36 ++-- go.sum | 156 +++++++++--------- pkg/app/init.go | 2 +- .../snapshot/chart_need/output-helm4.yaml | 33 ++++ .../output-helm4.yaml | 34 ++++ .../snapshot/oci_need/output-helm4.yaml | 27 +++ .../snapshot/postrenderer/output-helm4.yaml | 2 + .../postrender/output/template-result-helm4 | 2 + .../output/template-result-live-helm4 | 2 + 14 files changed, 216 insertions(+), 114 deletions(-) create mode 100644 test/e2e/template/helmfile/testdata/snapshot/chart_need/output-helm4.yaml create mode 100644 test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/output-helm4.yaml create mode 100644 test/e2e/template/helmfile/testdata/snapshot/oci_need/output-helm4.yaml diff --git a/.github/workflows/Makefile b/.github/workflows/Makefile index 731e37a7..47d6d167 100644 --- a/.github/workflows/Makefile +++ b/.github/workflows/Makefile @@ -1,4 +1,4 @@ -HELM_VERSION ?= v4.1.0 +HELM_VERSION ?= v4.2.0 KUSTOMIZE_VERSION ?= v5.8.0 K8S_VERSION ?= v1.34.0 MINIKUBE_VERSION ?= v1.37.0 diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 9dbd0954..b7d4fcbb 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -37,7 +37,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - helm-version: [v3.18.6, v3.20.2, v4.1.4] + helm-version: [v3.18.6, v3.21.0, v4.2.0] steps: - uses: actions/checkout@v6 with: @@ -67,16 +67,16 @@ jobs: env: HELMFILE_HELM4: ${{ startsWith(matrix.helm-version, 'v4') && '1' || '0' }} - name: Archive built binaries - if: matrix.helm-version == 'v4.1.4' + if: matrix.helm-version == 'v4.2.0' run: tar -cvf built-binaries.tar helmfile diff-yamls dyff - uses: actions/upload-artifact@v7 - if: matrix.helm-version == 'v4.1.4' + if: matrix.helm-version == 'v4.2.0' with: name: built-binaries-${{ github.run_id }} path: built-binaries.tar retention-days: 1 - name: Display built binaries - if: matrix.helm-version == 'v4.1.4' + if: matrix.helm-version == 'v4.2.0' run: ls -l helmfile diff-yamls dyff integration_tests: @@ -105,23 +105,23 @@ jobs: plugin-secrets-version: 4.7.4 plugin-diff-version: 3.15.3 extra-helmfile-flags: '--enable-live-output' - - helm-version: v3.20.2 + - helm-version: v3.21.0 kustomize-version: v5.8.0 plugin-secrets-version: 4.7.4 plugin-diff-version: 3.15.3 extra-helmfile-flags: '' - - helm-version: v3.20.2 + - helm-version: v3.21.0 kustomize-version: v5.8.0 plugin-secrets-version: 4.7.4 plugin-diff-version: 3.15.3 extra-helmfile-flags: '--enable-live-output' # Helmfile now supports both Helm 3.x and Helm 4.x - - helm-version: v4.1.4 + - helm-version: v4.2.0 kustomize-version: v5.8.0 plugin-secrets-version: 4.7.4 plugin-diff-version: 3.15.3 extra-helmfile-flags: '' - - helm-version: v4.1.4 + - helm-version: v4.2.0 kustomize-version: v5.8.0 plugin-secrets-version: 4.7.4 plugin-diff-version: 3.15.3 diff --git a/Dockerfile b/Dockerfile index 14814512..dea639d4 100644 --- a/Dockerfile +++ b/Dockerfile @@ -30,7 +30,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ENV HELM_DATA_HOME="${HELM_DATA_HOME}" -ARG HELM_VERSION="v4.1.4" +ARG HELM_VERSION="v4.2.0" ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_BIN="/usr/local/bin/helm" ARG HELM_LOCATION="https://get.helm.sh" @@ -39,8 +39,8 @@ RUN set -x && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ echo Verifying ${HELM_FILENAME}... && \ case ${TARGETPLATFORM} in \ - "linux/amd64") HELM_SHA256="70b2c30a19da4db264dfd68c8a3664e05093a361cefd89572ffb36f8abfa3d09" ;; \ - "linux/arm64") HELM_SHA256="13d03672be289045d2ff00e4e345d61de1c6f21c1257a45955a30e8ae036d8f1" ;; \ + "linux/amd64") HELM_SHA256="97dbeb971be4ac4b27e3839976d9564c0fb35c6f3b1da89dd1e292d236af4096" ;; \ + "linux/arm64") HELM_SHA256="1f8de130dfbd04de64978e7b852a7a547be1404956a366608276d2520b678670" ;; \ esac && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo Extracting ${HELM_FILENAME}... && \ diff --git a/Dockerfile.debian-stable-slim b/Dockerfile.debian-stable-slim index 5a2b703f..8cdd76cc 100644 --- a/Dockerfile.debian-stable-slim +++ b/Dockerfile.debian-stable-slim @@ -38,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ENV HELM_DATA_HOME="${HELM_DATA_HOME}" -ARG HELM_VERSION="v4.1.4" +ARG HELM_VERSION="v4.2.0" ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_BIN="/usr/local/bin/helm" ARG HELM_LOCATION="https://get.helm.sh" @@ -47,8 +47,8 @@ RUN set -x && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ echo Verifying ${HELM_FILENAME}... && \ case ${TARGETPLATFORM} in \ - "linux/amd64") HELM_SHA256="70b2c30a19da4db264dfd68c8a3664e05093a361cefd89572ffb36f8abfa3d09" ;; \ - "linux/arm64") HELM_SHA256="13d03672be289045d2ff00e4e345d61de1c6f21c1257a45955a30e8ae036d8f1" ;; \ + "linux/amd64") HELM_SHA256="97dbeb971be4ac4b27e3839976d9564c0fb35c6f3b1da89dd1e292d236af4096" ;; \ + "linux/arm64") HELM_SHA256="1f8de130dfbd04de64978e7b852a7a547be1404956a366608276d2520b678670" ;; \ esac && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo Extracting ${HELM_FILENAME}... && \ diff --git a/Dockerfile.ubuntu b/Dockerfile.ubuntu index 486736c9..94e4c9c1 100644 --- a/Dockerfile.ubuntu +++ b/Dockerfile.ubuntu @@ -38,7 +38,7 @@ ENV HELM_CONFIG_HOME="${HELM_CONFIG_HOME}" ARG HELM_DATA_HOME="${HOME}/.local/share/helm" ENV HELM_DATA_HOME="${HELM_DATA_HOME}" -ARG HELM_VERSION="v4.1.4" +ARG HELM_VERSION="v4.2.0" ENV HELM_VERSION="${HELM_VERSION}" ENV HELM_BIN="/usr/local/bin/helm" ARG HELM_LOCATION="https://get.helm.sh" @@ -47,8 +47,8 @@ RUN set -x && \ curl --retry 5 --retry-connrefused -LO "${HELM_LOCATION}/${HELM_FILENAME}" && \ echo Verifying ${HELM_FILENAME}... && \ case ${TARGETPLATFORM} in \ - "linux/amd64") HELM_SHA256="70b2c30a19da4db264dfd68c8a3664e05093a361cefd89572ffb36f8abfa3d09" ;; \ - "linux/arm64") HELM_SHA256="13d03672be289045d2ff00e4e345d61de1c6f21c1257a45955a30e8ae036d8f1" ;; \ + "linux/amd64") HELM_SHA256="97dbeb971be4ac4b27e3839976d9564c0fb35c6f3b1da89dd1e292d236af4096" ;; \ + "linux/arm64") HELM_SHA256="1f8de130dfbd04de64978e7b852a7a547be1404956a366608276d2520b678670" ;; \ esac && \ echo "${HELM_SHA256} ${HELM_FILENAME}" | sha256sum -c && \ echo Extracting ${HELM_FILENAME}... && \ diff --git a/go.mod b/go.mod index 618907a8..366266b6 100644 --- a/go.mod +++ b/go.mod @@ -35,15 +35,17 @@ require ( golang.org/x/sync v0.20.0 golang.org/x/term v0.43.0 gopkg.in/yaml.v3 v3.0.1 - helm.sh/helm/v3 v3.20.2 - helm.sh/helm/v4 v4.1.4 + helm.sh/helm/v3 v3.21.0 + helm.sh/helm/v4 v4.2.0 k8s.io/apimachinery v0.36.0 k8s.io/client-go v0.36.0 ) replace ( + // kubedog's flagger dependency still imports k8s.io/api/autoscaling/v2beta2, + // while Helm 4.2.0 requires k8s.io/apimachinery/content.IsPathSegmentName from v0.36+. k8s.io/api => k8s.io/api v0.35.4 - k8s.io/apimachinery => k8s.io/apimachinery v0.35.4 + k8s.io/apimachinery => k8s.io/apimachinery v0.36.0 k8s.io/client-go => k8s.io/client-go v0.35.4 ) @@ -85,8 +87,8 @@ require ( github.com/huandu/xstrings v1.5.0 // indirect github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/itchyny/gojq v0.12.19 // indirect - github.com/klauspost/compress v1.18.0 // indirect - github.com/lib/pq v1.11.2 // indirect + github.com/klauspost/compress v1.18.4 // indirect + github.com/lib/pq v1.12.3 // indirect github.com/mattn/go-colorable v0.1.14 // indirect github.com/mattn/go-runewidth v0.0.19 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect @@ -150,7 +152,7 @@ require ( github.com/GoogleCloudPlatform/opentelemetry-operations-go/internal/resourcemapping v0.55.0 // indirect github.com/MakeNowJust/heredoc v1.0.0 // indirect github.com/Masterminds/squirrel v1.5.4 // indirect - github.com/ProtonMail/go-crypto v1.3.0 // indirect + github.com/ProtonMail/go-crypto v1.4.1 // indirect github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d // indirect github.com/agext/levenshtein v1.2.3 // indirect github.com/antchfx/jsonquery v1.3.7 // indirect @@ -208,7 +210,7 @@ require ( github.com/exponent-io/jsonpath v0.0.0-20210407135951-1de76d718b3f // indirect github.com/extism/go-sdk v1.7.1 // indirect github.com/felixge/httpsnoop v1.0.4 // indirect - github.com/fluxcd/cli-utils v0.37.2-flux.1 // indirect + github.com/fluxcd/cli-utils v1.2.0 // indirect github.com/fluxcd/flagger v1.36.1 // indirect github.com/fxamacker/cbor/v2 v2.9.0 // indirect github.com/getsops/gopgagent v0.0.0-20241224165529-7044f28e491e // indirect @@ -325,35 +327,35 @@ require ( go.opentelemetry.io/auto/sdk v1.2.1 // indirect go.opentelemetry.io/contrib/detectors/gcp v1.39.0 // indirect go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 // indirect - go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 // indirect + go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 // indirect go.opentelemetry.io/otel v1.43.0 // indirect go.opentelemetry.io/otel/metric v1.43.0 // indirect go.opentelemetry.io/otel/sdk v1.43.0 // indirect go.opentelemetry.io/otel/sdk/metric v1.43.0 // indirect go.opentelemetry.io/otel/trace v1.43.0 // indirect - go.opentelemetry.io/proto/otlp v1.9.0 // indirect + go.opentelemetry.io/proto/otlp v1.10.0 // indirect go.uber.org/multierr v1.11.0 // indirect golang.org/x/crypto v0.50.0 // indirect - golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 // indirect + golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 // indirect golang.org/x/mod v0.34.0 // indirect golang.org/x/tools v0.43.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d // indirect gopkg.in/evanphx/json-patch.v4 v4.13.0 // indirect gopkg.in/gookit/color.v1 v1.1.6 // indirect gopkg.in/inf.v0 v0.9.1 // indirect gopkg.in/yaml.v2 v2.4.0 // indirect k8s.io/api v0.36.0 // indirect - k8s.io/apiextensions-apiserver v0.35.1 // indirect - k8s.io/apiserver v0.35.1 // indirect - k8s.io/cli-runtime v0.35.1 // indirect - k8s.io/component-base v0.35.1 // indirect + k8s.io/apiextensions-apiserver v0.36.0 // indirect + k8s.io/apiserver v0.36.0 // indirect + k8s.io/cli-runtime v0.36.0 // indirect + k8s.io/component-base v0.36.0 // indirect k8s.io/klog/v2 v2.140.0 // indirect k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a // indirect - k8s.io/kubectl v0.35.1 // indirect + k8s.io/kubectl v0.36.0 // indirect k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 // indirect oras.land/oras-go/v2 v2.6.0 // indirect - sigs.k8s.io/controller-runtime v0.23.1 // indirect + sigs.k8s.io/controller-runtime v0.24.0 // indirect sigs.k8s.io/kustomize/api v0.21.1 // indirect sigs.k8s.io/kustomize/kyaml v0.21.1 // indirect sigs.k8s.io/randfill v1.0.0 // indirect diff --git a/go.sum b/go.sum index 9b13388c..913fc734 100644 --- a/go.sum +++ b/go.sum @@ -126,8 +126,8 @@ github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2 h1:+vx7roKuyA63n github.com/Netflix/go-expect v0.0.0-20220104043353-73e0943537d2/go.mod h1:HBCaDeC1lPdgDeDbhX8XFpy1jqjK0IBG8W5K+xYqA0w= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5 h1:TngWCqHvy9oXAN6lEVMRuU21PR1EtLVZJmdB18Gu3Rw= github.com/Nvveen/Gotty v0.0.0-20120604004816-cd527374f1e5/go.mod h1:lmUJ/7eu/Q8D7ML55dXQrVaamCz2vxCfdQBasLZfHKk= -github.com/ProtonMail/go-crypto v1.3.0 h1:ILq8+Sf5If5DCpHQp4PbZdS1J7HDFRXz/+xKBiRGFrw= -github.com/ProtonMail/go-crypto v1.3.0/go.mod h1:9whxjD8Rbs29b4XWbB8irEcE8KHMqaR2e7GWU1R+/PE= +github.com/ProtonMail/go-crypto v1.4.1 h1:9RfcZHqEQUvP8RzecWEUafnZVtEvrBVL9BiF67IQOfM= +github.com/ProtonMail/go-crypto v1.4.1/go.mod h1:e1OaTyu5SYVrO9gKOEhTc+5UcXtTUa+P3uLudwcgPqo= github.com/a8m/envsubst v1.4.3 h1:kDF7paGK8QACWYaQo6KtyYBozY2jhQrTuNNuUxQkhJY= github.com/a8m/envsubst v1.4.3/go.mod h1:4jjHWQlZoaXPoLQUb7H2qT4iLkZDdmEQiOUogdUmqVU= github.com/acarl005/stripansi v0.0.0-20180116102854-5a71ef0e047d h1:licZJFw2RwpHMqeKTCYkitsPqHNxTmd4SNR5r94FGM8= @@ -206,8 +206,8 @@ github.com/blang/semver v3.5.1+incompatible h1:cQNTCjp13qL8KC3Nbxr/y2Bqb63oX6wdn github.com/blang/semver v3.5.1+incompatible/go.mod h1:kRBLl5iJ+tD4TcOOxsy/0fnwebNt5EWlYSAyrTnjyyk= github.com/blang/semver/v4 v4.0.0 h1:1PFHFE6yCCTv8C1TeyNNarDzntLi7wMI5i/pzqYIsAM= github.com/blang/semver/v4 v4.0.0/go.mod h1:IbckMUScFkM3pff0VJDNKRiT6TG/YpiHIM2yvyW5YoQ= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0 h1:e+C0SB5R1pu//O4MQ3f9cFuPGoOVeF2fE4Og9otCc70= -github.com/bshuster-repo/logrus-logstash-hook v1.0.0/go.mod h1:zsTqEiSzDgAa/8GZR7E1qaXrhYNDKBYy5/dWPTIflbk= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0 h1:o2FzZifLg+z/DN1OFmzTWzZZx/roaqt8IPZCIVco8r4= +github.com/bshuster-repo/logrus-logstash-hook v1.1.0/go.mod h1:Q2aXOe7rNuPgbBtPCOzYyWDvKX7+FpxE5sRdvcPoui0= github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.3 h1:ZN+IMa753KfX5hd8vVaMixjnqRZ3y8CuJKRKj1xcsSM= @@ -240,8 +240,8 @@ github.com/containerd/log v0.1.0/go.mod h1:VRRf09a7mHDIRezVKTRCrOq78v577GXq3bSa3 github.com/containerd/platforms v0.2.1 h1:zvwtM3rz2YHPQsF2CHYM8+KtB5dvhISiXh5ZpSBQv6A= github.com/containerd/platforms v0.2.1/go.mod h1:XHCb+2/hzowdiut9rkudds9bE5yJ7npe7dG/wG+uFPw= github.com/coreos/go-systemd/v22 v22.3.2/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= -github.com/coreos/go-systemd/v22 v22.5.0 h1:RrqgGjYQKalulkV8NGVIfkXQf6YYmOyiJKk8iXXhfZs= -github.com/coreos/go-systemd/v22 v22.5.0/go.mod h1:Y58oyj3AT4RCenI/lSvhwexgC+NSVTIJ3seZv2GcEnc= +github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= +github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/cpuguy83/go-md2man/v2 v2.0.6/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= github.com/cpuguy83/go-md2man/v2 v2.0.7 h1:zbFlGlXEAKlwXpmvle3d8Oe3YnkKIK4xSRTd3sHPnBo= github.com/cpuguy83/go-md2man/v2 v2.0.7/go.mod h1:oOW0eioCTA6cOiMLiUPZOpcVxMig6NIQQ7OS05n1F4g= @@ -262,20 +262,20 @@ github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/r github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc= github.com/dimchansky/utfbom v1.1.1 h1:vV6w1AhK4VMnhBno/TPVCoK9U/LP0PkLCS9tbxHdi/U= github.com/dimchansky/utfbom v1.1.1/go.mod h1:SxdoEBH5qIqFocHMyGOXVAybYJdr71b1Q/j0mACtrfE= -github.com/distribution/distribution/v3 v3.0.0 h1:q4R8wemdRQDClzoNNStftB2ZAfqOiN6UX90KJc4HjyM= -github.com/distribution/distribution/v3 v3.0.0/go.mod h1:tRNuFoZsUdyRVegq8xGNeds4KLjwLCRin/tTo6i1DhU= +github.com/distribution/distribution/v3 v3.1.1 h1:KUbk7C8CfaLXy8kbf/hGq9cad/wCoLB6dbWH6DMbmX0= +github.com/distribution/distribution/v3 v3.1.1/go.mod h1:d7lXwZpph0bVcOj4Aqn0nMrWHIwRQGdiV5TLeI+/w6Y= github.com/distribution/reference v0.6.0 h1:0IXCQ5g4/QMHHkarYzh5l+u8T3t73zM5QvfrDyIgxBk= github.com/distribution/reference v0.6.0/go.mod h1:BbU0aIcezP1/5jX/8MP0YiH4SdvB5Y4f/wlDRiLyi3E= github.com/dlclark/regexp2 v1.11.0 h1:G/nrcoOa7ZXlpoa/91N3X7mM3r8eIlMBBJZvsz/mxKI= github.com/dlclark/regexp2 v1.11.0/go.mod h1:DHkYz0B9wPfa6wondMfaivmHpzrQ3v9q8cnmRbL6yW8= github.com/docker/cli v29.2.0+incompatible h1:9oBd9+YM7rxjZLfyMGxjraKBKE4/nVyvVfN4qNl9XRM= github.com/docker/cli v29.2.0+incompatible/go.mod h1:JLrzqnKDaYBop7H2jaqPtU4hHvMKP+vjCwu2uszcLI8= -github.com/docker/docker-credential-helpers v0.8.2 h1:bX3YxiGzFP5sOXWc3bTPEXdEaZSeVMrFgOr3T+zrFAo= -github.com/docker/docker-credential-helpers v0.8.2/go.mod h1:P3ci7E3lwkZg6XiHdRKft1KckHiO9a2rNtyFbZ/ry9M= +github.com/docker/docker-credential-helpers v0.9.5 h1:EFNN8DHvaiK8zVqFA2DT6BjXE0GzfLOZ38ggPTKePkY= +github.com/docker/docker-credential-helpers v0.9.5/go.mod h1:v1S+hepowrQXITkEfw6o4+BMbGot02wiKpzWhGUZK6c= github.com/docker/go-connections v0.6.0 h1:LlMG9azAe1TqfR7sO+NJttz1gy6KO7VJBh+pMmjSD94= github.com/docker/go-connections v0.6.0/go.mod h1:AahvXYshr6JgfUJGdDCs2b5EZG/vmaMAntpSFH5BFKE= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c h1:+pKlWGMw7gf6bQ+oDZB4KHQFypsfjYlq/C4rfL7D3g8= -github.com/docker/go-events v0.0.0-20190806004212-e31b211e4f1c/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= +github.com/docker/go-events v0.0.0-20250808211157-605354379745 h1:yOn6Ze6IbYI/KAw2lw/83ELYvZh6hvsygTVkD0dzMC4= +github.com/docker/go-events v0.0.0-20250808211157-605354379745/go.mod h1:Uw6UezgYA44ePAFQYUehOuCzmy5zmg/+nl2ZfMWGkpA= github.com/docker/go-metrics v0.0.1 h1:AgB/0SvBxihN0X8OR4SjsblXkbMvalQ8cjmtKQ2rQV8= github.com/docker/go-metrics v0.0.1/go.mod h1:cG1hvH2utMXtqgqqYE9plW6lDxS3/5ayHzueweSI3Vw= github.com/docker/go-units v0.5.0 h1:69rxXcBk27SvSaaxTtLh/8llcHD8vYHT7WSdRZ/jvr4= @@ -310,8 +310,8 @@ github.com/fatih/color v1.19.0 h1:Zp3PiM21/9Ld6FzSKyL5c/BULoe/ONr9KlbYVOfG8+w= github.com/fatih/color v1.19.0/go.mod h1:zNk67I0ZUT1bEGsSGyCZYZNrHuTkJJB+r6Q9VuMi0LE= github.com/felixge/httpsnoop v1.0.4 h1:NFTV2Zj1bL4mc9sqWACXbQFVBBg2W3GPvqp8/ESS2Wg= github.com/felixge/httpsnoop v1.0.4/go.mod h1:m8KPJKqk1gH5J9DgRY2ASl2lWCfGKXixSwevea8zH2U= -github.com/fluxcd/cli-utils v0.37.2-flux.1 h1:tQ588ghtRN+E+kHq415FddfqA9v4brn/1WWgrP6rQR0= -github.com/fluxcd/cli-utils v0.37.2-flux.1/go.mod h1:LcWSu1NYET8d8U7O326RhEm5JkQXCMK6ITu4G1CT02c= +github.com/fluxcd/cli-utils v1.2.0 h1:1o07pXTMxJ/XJ1GpAbLtjdXwfCUMq4Ku1OcnvJHLohI= +github.com/fluxcd/cli-utils v1.2.0/go.mod h1:d5HdTDdR5sCbsIbgtOQ7x7srKYwYeZORU6CD2yn4j/M= github.com/fluxcd/flagger v1.36.1 h1:X2PumtNwZz9YSGaOtZLFm2zAKLgHhFkbNv8beg7ifyc= github.com/fluxcd/flagger v1.36.1/go.mod h1:qmtLsxheVDTI8XeCaXUxW5UCmfcSKnY9fizG9NmW/Fk= github.com/foxcpp/go-mockdns v1.2.0 h1:omK3OrHRD1IWJz1FuFBCFquhXslXoF17OvBS6JPzZF0= @@ -481,8 +481,8 @@ github.com/goware/prefixer v0.0.0-20160118172347-395022866408 h1:Y9iQJfEqnN3/Nce github.com/goware/prefixer v0.0.0-20160118172347-395022866408/go.mod h1:PE1ycukgRPJ7bJ9a1fdfQ9j8i/cEcRAoLZzbxYpNB/s= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79 h1:+ngKgrYPPJrOjhax5N+uePQ0Fh1Z7PheYoUI/0nzkPA= github.com/gregjones/httpcache v0.0.0-20190611155906-901d90724c79/go.mod h1:FecbI9+v66THATjSRHfNgh1IVFe/9kFxbXtjV0ctIMA= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7 h1:X+2YciYSxvMQK0UZ7sg45ZVabVZBeBuvMkmuI2V3Fak= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.7/go.mod h1:lW34nIZuQ8UDPdkon5fmfp2l3+ZkQ2me/+oecHYLOII= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0 h1:HWRh5R2+9EifMyIHV7ZV+MIZqgz+PMpZ14Jynv3O2Zs= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.28.0/go.mod h1:JfhWUomR1baixubs02l85lZYYOm7LV6om4ceouMv45c= github.com/hashicorp/errwrap v1.0.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= github.com/hashicorp/errwrap v1.1.0 h1:OxrOeh75EUXMY8TBjag2fzXGZ40LB6IKw45YeGUDY2I= github.com/hashicorp/errwrap v1.1.0/go.mod h1:YH+1FKiLXxHSkmPseP+kNlulaMuP3n2brvKWEqk/Jc4= @@ -565,8 +565,8 @@ github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51/go.mod h1:C github.com/keybase/go-keychain v0.0.1 h1:way+bWYa6lDppZoZcgMbYsvC7GxljxrskdNInRtuthU= github.com/keybase/go-keychain v0.0.1/go.mod h1:PdEILRW3i9D8JcdM+FmY6RwkHGnhHxXwkPPMeUgOK1k= github.com/klauspost/compress v1.13.6/go.mod h1:/3/Vjq9QcHkK5uEr5lBEmyoZ1iFhe47etQ6QUkpK6sk= -github.com/klauspost/compress v1.18.0 h1:c/Cqfb0r+Yi+JtIEq73FWXVkRonBlf0CRNYc8Zttxdo= -github.com/klauspost/compress v1.18.0/go.mod h1:2Pp+KzxcywXVXMr50+X0Q/Lsb43OQHYWRCY2AiWywWQ= +github.com/klauspost/compress v1.18.4 h1:RPhnKRAQ4Fh8zU2FY/6ZFDwTVTxgJ/EMydqSTzE9a2c= +github.com/klauspost/compress v1.18.4/go.mod h1:R0h/fSBs8DE4ENlcrlib3PsXS61voFxhIs2DeRhCvJ4= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -579,8 +579,8 @@ github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0 h1:P6pPBnrTSX3DEVR4fDembhR github.com/lann/ps v0.0.0-20150810152359-62de8c46ede0/go.mod h1:vmVJ0l/dxyfGW6FmdpVm2joNMFikkuWg0EoCKLGUMNw= github.com/leodido/go-urn v1.2.0/go.mod h1:+8+nEpDfqqsY+g338gtMEUOtuK+4dEMhiQEgxpxOKII= github.com/lib/pq v1.10.9/go.mod h1:AlVN5x4E4T544tWzH6hKfbfQvm3HdbOxrmggDNAPY9o= -github.com/lib/pq v1.11.2 h1:x6gxUeu39V0BHZiugWe8LXZYZ+Utk7hSJGThs8sdzfs= -github.com/lib/pq v1.11.2/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= +github.com/lib/pq v1.12.3 h1:tTWxr2YLKwIvK90ZXEw8GP7UFHtcbTtty8zsI+YjrfQ= +github.com/lib/pq v1.12.3/go.mod h1:/p+8NSbOcwzAEI7wiMXFlgydTwcgTr3OSKMsD2BitpA= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de h1:9TO3cAIGXtEhnIaL+V+BEER86oLrvS+kWobKpbJuye0= github.com/liggitt/tabwriter v0.0.0-20181228230101-89fcab3d43de/go.mod h1:zAbeS9B/r2mtpb6U+EI2rYA5OAXxsYw6wTamcNW+zcE= github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY= @@ -644,8 +644,8 @@ github.com/oklog/ulid/v2 v2.1.1 h1:suPZ4ARWLOJLegGFiZZ1dFAkqzhMjL3J1TzI+5wHz8s= github.com/oklog/ulid/v2 v2.1.1/go.mod h1:rcEKHmBBKfef9DhnvX7y1HZBYxjXb0cP5ExxNsTT1QQ= github.com/onsi/ginkgo/v2 v2.28.1 h1:S4hj+HbZp40fNKuLUQOYLDgZLwNUVn19N3Atb98NCyI= github.com/onsi/ginkgo/v2 v2.28.1/go.mod h1:CLtbVInNckU3/+gC8LzkGUb9oF+e8W8TdUsxPwvdOgE= -github.com/onsi/gomega v1.39.0 h1:y2ROC3hKFmQZJNFeGAMeHZKkjBL65mIZcvrLQBF9k6Q= -github.com/onsi/gomega v1.39.0/go.mod h1:ZCU1pkQcXDO5Sl9/VVEGlDyp+zm0m1cmeG5TOzLgdh4= +github.com/onsi/gomega v1.39.1 h1:1IJLAad4zjPn2PsnhH70V4DKRFlrCzGBNrNaru+Vf28= +github.com/onsi/gomega v1.39.1/go.mod h1:hL6yVALoTOxeWudERyfppUcZXjMwIMLnuSfruD2lcfg= github.com/openbao/openbao/api/v2 v2.5.1 h1:Br79D6L20SbAa5P7xqENxmvv8LyI4HoKosPy7klhn4o= github.com/openbao/openbao/api/v2 v2.5.1/go.mod h1:Dh5un77tqGgMbmlVEqjqN+8/dMyUohnkaQVg/wXW0Ig= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= @@ -689,8 +689,8 @@ github.com/prometheus/common v0.67.5 h1:pIgK94WWlQt1WLwAC5j2ynLaBRDiinoAb86HZHTU github.com/prometheus/common v0.67.5/go.mod h1:SjE/0MzDEEAyrdr5Gqc6G+sXI67maCxzaT3A2+HqjUw= github.com/prometheus/otlptranslator v1.0.0 h1:s0LJW/iN9dkIH+EnhiD3BlkkP5QVIUVEoIwkU+A6qos= github.com/prometheus/otlptranslator v1.0.0/go.mod h1:vRYWnXvI6aWGpsdY/mOT/cbeVRBlPWtBNDb7kGR3uKM= -github.com/prometheus/procfs v0.19.2 h1:zUMhqEW66Ex7OXIiDkll3tl9a1ZdilUOd/F6ZXw4Vws= -github.com/prometheus/procfs v0.19.2/go.mod h1:M0aotyiemPhBCM0z5w87kL22CxfcH05ZpYlu+b4J7mw= +github.com/prometheus/procfs v0.20.1 h1:XwbrGOIplXW/AU3YhIhLODXMJYyC1isLFfYCsTEycfc= +github.com/prometheus/procfs v0.20.1/go.mod h1:o9EMBZGRyvDrSPH1RqdxhojkuXstoe4UlK79eF5TGGo= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5 h1:EaDatTxkdHG+U3Bk4EUr+DZ7fOGwTfezUiUJMaIcaho= github.com/redis/go-redis/extra/rediscmd/v9 v9.0.5/go.mod h1:fyalQWdtzDBECAQFBJuQe5bzQ02jGd5Qcbgb97Flm7U= github.com/redis/go-redis/extra/redisotel/v9 v9.0.5 h1:EfpWLLCyXw8PSM2/XNJLjI3Pb27yVE+gIAfeqp8LUCc= @@ -820,54 +820,54 @@ go.mongodb.org/mongo-driver v1.17.6 h1:87JUG1wZfWsr6rIz3ZmpH90rL5tea7O3IHuSwHUps go.mongodb.org/mongo-driver v1.17.6/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAccj+rVKqgQ= go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0 h1:I/7S/yWobR3QHFLqHsJ8QOndoiFsj1VgHpQiq43KlUI= -go.opentelemetry.io/contrib/bridges/prometheus v0.65.0/go.mod h1:jPF6gn3y1E+nozCAEQj3c6NZ8KY+tvAgSVfvoOJUFac= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0 h1:dkBzNEAIKADEaFnuESzcXvpd09vxvDZsOjx11gjUqLk= +go.opentelemetry.io/contrib/bridges/prometheus v0.67.0/go.mod h1:Z5RIwRkZgauOIfnG5IpidvLpERjhTninpP1dTG2jTl4= go.opentelemetry.io/contrib/detectors/gcp v1.39.0 h1:kWRNZMsfBHZ+uHjiH4y7Etn2FK26LAGkNFw7RHv1DhE= go.opentelemetry.io/contrib/detectors/gcp v1.39.0/go.mod h1:t/OGqzHBa5v6RHZwrDBJ2OirWc+4q/w2fTbLZwAKjTk= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0 h1:2gApdml7SznX9szEKFjKjM4qGcGSvAybYLBY319XG3g= -go.opentelemetry.io/contrib/exporters/autoexport v0.65.0/go.mod h1:0QqAGlbHXhmPYACG3n5hNzO5DnEqqtg4VcK5pr22RI0= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0 h1:4fnRcNpc6YFtG3zsFw9achKn3XgmxPxuMuqIL5rE8e8= +go.opentelemetry.io/contrib/exporters/autoexport v0.67.0/go.mod h1:qTvIHMFKoxW7HXg02gm6/Wofhq5p3Ib/A/NNt1EoBSQ= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0 h1:yI1/OhfEPy7J9eoa6Sj051C7n5dvpj0QX8g4sRchg04= go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.67.0/go.mod h1:NoUCKYWK+3ecatC4HjkRktREheMeEtrXoQxrqYFeHSc= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0 h1:OyrsyzuttWTSur2qN/Lm0m2a8yqyIjUVBZcxFPuXq2o= -go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0 h1:CqXxU8VOmDefoh0+ztfGaymYbhdB/tT3zs79QaZTNGY= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.68.0/go.mod h1:BuhAPThV8PBHBvg8ZzZ/Ok3idOdhWIodywz2xEcRbJo= go.opentelemetry.io/otel v1.43.0 h1:mYIM03dnh5zfN7HautFE4ieIig9amkNANT+xcVxAj9I= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0 h1:ZVg+kCXxd9LtAaQNKBxAvJ5NpMf7LpvEr4MIZqb0TMQ= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.16.0/go.mod h1:hh0tMeZ75CCXrHd9OXRYxTlCAdxcXioWHFIpYw2rZu8= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0 h1:djrxvDxAe44mJUrKataUbOhCKhR3F8QCyWucO16hTQs= -go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.16.0/go.mod h1:dt3nxpQEiSoKvfTVxp3TUg5fHPLhKtbcnN3Z1I1ePD0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0 h1:NOyNnS19BF2SUDApbOKbDtWZ0IK7b8FJ2uAGdIWOGb0= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.40.0/go.mod h1:VL6EgVikRLcJa9ftukrHu/ZkkhFBSo1lzvdBC9CF1ss= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0 h1:9y5sHvAxWzft1WQ4BwqcvA+IFVUJ1Ya75mSAUnFEVwE= -go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.40.0/go.mod h1:eQqT90eR3X5Dbs1g9YSM30RavwLF725Ris5/XSXWvqE= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0 h1:QKdN8ly8zEMrByybbQgv8cWBcdAarwmIPZ6FThrWXJs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.40.0/go.mod h1:bTdK1nhqF76qiPoCCdyFIV+N/sRHYXYCTQc+3VCi3MI= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0 h1:DvJDOPmSWQHWywQS6lKL+pb8s3gBLOZUtw4N+mavW1I= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.40.0/go.mod h1:EtekO9DEJb4/jRyN4v4Qjc2yA7AtfCBuz2FynRUWTXs= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0 h1:wVZXIWjQSeSmMoxF74LzAnpVQOAFDo3pPji9Y4SOFKc= -go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.40.0/go.mod h1:khvBS2IggMFNwZK/6lEeHg/W57h/IX6J4URh57fuI40= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0 h1:krvC4JMfIOVdEuNPTtQ0ZjCiXrybhv+uOHMfHRmnvVo= -go.opentelemetry.io/otel/exporters/prometheus v0.62.0/go.mod h1:fgOE6FM/swEnsVQCqCnbOfRV4tOnWPg7bVeo4izBuhQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0 h1:ivlbaajBWJqhcCPniDqDJmRwj4lc6sRT+dCAVKNmxlQ= -go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.16.0/go.mod h1:u/G56dEKDDwXNCVLsbSrllB2o8pbtFLUC4HpR66r2dc= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0 h1:Dn8rkudDzY6KV9dr/D/bTUuWgqDf9xe0rr4G2elrn0Y= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.19.0/go.mod h1:gMk9F0xDgyN9M/3Ed5Y1wKcx/9mlU91NXY2SNq7RQuU= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0 h1:HIBTQ3VO5aupLKjC90JgMqpezVXwFuq6Ryjn0/izoag= +go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.19.0/go.mod h1:ji9vId85hMxqfvICA0Jt8JqEdrXaAkcpkI9HPXya0ro= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0 h1:8UQVDcZxOJLtX6gxtDt3vY2WTgvZqMQRzjsqiIHQdkc= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.43.0/go.mod h1:2lmweYCiHYpEjQ/lSJBYhj9jP1zvCvQW4BqL9dnT7FQ= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0 h1:w1K+pCJoPpQifuVpsKamUdn9U0zM3xUziVOqsGksUrY= +go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetrichttp v1.43.0/go.mod h1:HBy4BjzgVE8139ieRI75oXm3EcDN+6GhD88JT1Kjvxg= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0 h1:88Y4s2C8oTui1LGM6bTWkw0ICGcOLCAI5l6zsD1j20k= +go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.43.0/go.mod h1:Vl1/iaggsuRlrHf/hfPJPvVag77kKyvrLeD10kpMl+A= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0 h1:RAE+JPfvEmvy+0LzyUA25/SGawPwIUbZ6u0Wug54sLc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.43.0/go.mod h1:AGmbycVGEsRx9mXMZ75CsOyhSP6MFIcj/6dnG+vhVjk= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0 h1:3iZJKlCZufyRzPzlQhUIWVmfltrXuGyfjREgGP3UUjc= +go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracehttp v1.43.0/go.mod h1:/G+nUPfhq2e+qiXMGxMwumDrP5jtzU+mWN7/sjT2rak= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0 h1:jOveH/b4lU9HT7y+Gfamf18BqlOuz2PWEvs8yM7Q6XE= +go.opentelemetry.io/otel/exporters/prometheus v0.65.0/go.mod h1:i1P8pcumauPtUI4YNopea1dhzEMuEqWP1xoUZDylLHo= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0 h1:GJkybS+crDMdExT/BUNCEgfrmfboztcS6PhvSo88HKM= +go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.19.0/go.mod h1:NuAyxRYIG2lKX3YQkB+83StTxM7s52PUUkRRiC0wnYI= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0 h1:TC+BewnDpeiAmcscXbGMfxkO+mwYUwE/VySwvw88PfA= go.opentelemetry.io/otel/exporters/stdout/stdoutmetric v1.43.0/go.mod h1:J/ZyF4vfPwsSr9xJSPyQ4LqtcTPULFR64KwTikGLe+A= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0 h1:MzfofMZN8ulNqobCmCAVbqVL5syHw+eB2qPRkCMA/fQ= -go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.40.0/go.mod h1:E73G9UFtKRXrxhBsHtG00TB5WxX57lpsQzogDkqBTz8= -go.opentelemetry.io/otel/log v0.16.0 h1:DeuBPqCi6pQwtCK0pO4fvMB5eBq6sNxEnuTs88pjsN4= -go.opentelemetry.io/otel/log v0.16.0/go.mod h1:rWsmqNVTLIA8UnwYVOItjyEZDbKIkMxdQunsIhpUMes= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0 h1:mS47AX77OtFfKG4vtp+84kuGSFZHTyxtXIN269vChY0= +go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.43.0/go.mod h1:PJnsC41lAGncJlPUniSwM81gc80GkgWJWr3cu2nKEtU= +go.opentelemetry.io/otel/log v0.19.0 h1:KUZs/GOsw79TBBMfDWsXS+KZ4g2Ckzksd1ymzsIEbo4= +go.opentelemetry.io/otel/log v0.19.0/go.mod h1:5DQYeGmxVIr4n0/BcJvF4upsraHjg6vudJJpnkL6Ipk= go.opentelemetry.io/otel/metric v1.43.0 h1:d7638QeInOnuwOONPp4JAOGfbCEpYb+K6DVWvdxGzgM= go.opentelemetry.io/otel/metric v1.43.0/go.mod h1:RDnPtIxvqlgO8GRW18W6Z/4P462ldprJtfxHxyKd2PY= go.opentelemetry.io/otel/sdk v1.43.0 h1:pi5mE86i5rTeLXqoF/hhiBtUNcrAGHLKQdhg4h4V9Dg= go.opentelemetry.io/otel/sdk v1.43.0/go.mod h1:P+IkVU3iWukmiit/Yf9AWvpyRDlUeBaRg6Y+C58QHzg= -go.opentelemetry.io/otel/sdk/log v0.16.0 h1:e/b4bdlQwC5fnGtG3dlXUrNOnP7c8YLVSpSfEBIkTnI= -go.opentelemetry.io/otel/sdk/log v0.16.0/go.mod h1:JKfP3T6ycy7QEuv3Hj8oKDy7KItrEkus8XJE6EoSzw4= +go.opentelemetry.io/otel/sdk/log v0.19.0 h1:scYVLqT22D2gqXItnWiocLUKGH9yvkkeql5dBDiXyko= +go.opentelemetry.io/otel/sdk/log v0.19.0/go.mod h1:vFBowwXGLlW9AvpuF7bMgnNI95LiW10szrOdvzBHlAg= go.opentelemetry.io/otel/sdk/metric v1.43.0 h1:S88dyqXjJkuBNLeMcVPRFXpRw2fuwdvfCGLEo89fDkw= go.opentelemetry.io/otel/sdk/metric v1.43.0/go.mod h1:C/RJtwSEJ5hzTiUz5pXF1kILHStzb9zFlIEe85bhj6A= go.opentelemetry.io/otel/trace v1.43.0 h1:BkNrHpup+4k4w+ZZ86CZoHHEkohws8AY+WTX09nk+3A= go.opentelemetry.io/otel/trace v1.43.0/go.mod h1:/QJhyVBUUswCphDVxq+8mld+AvhXZLhe+8WVFxiFff0= -go.opentelemetry.io/proto/otlp v1.9.0 h1:l706jCMITVouPOqEnii2fIAuO3IVGBRPV5ICjceRb/A= -go.opentelemetry.io/proto/otlp v1.9.0/go.mod h1:xE+Cx5E/eEHw+ISFkwPLwCZefwVjY+pqKg1qcK03+/4= +go.opentelemetry.io/proto/otlp v1.10.0 h1:IQRWgT5srOCYfiWnpqUYz9CVmbO8bFmKcwYxpuCSL2g= +go.opentelemetry.io/proto/otlp v1.10.0/go.mod h1:/CV4QoCR/S9yaPj8utp3lvQPoqMtxXdzn7ozvvozVqk= go.szostok.io/version v1.2.0 h1:8eMMdfsonjbibwZRLJ8TnrErY8bThFTQsZYV16mcXms= go.szostok.io/version v1.2.0/go.mod h1:EiU0gPxaXb6MZ+apSN0WgDO6F4JXyC99k9PIXf2k2E8= go.uber.org/atomic v1.9.0 h1:ECmE8Bn/WFTYwEW/bpKD3M8VtR/zQVbavAoalC1PYyE= @@ -898,8 +898,8 @@ golang.org/x/crypto v0.23.0/go.mod h1:CKFgDieR+mRhux2Lsu27y0fO304Db0wZe70UKqHu0v golang.org/x/crypto v0.50.0 h1:zO47/JPrL6vsNkINmLoo/PH1gcxpls50DNogFvB5ZGI= golang.org/x/crypto v0.50.0/go.mod h1:3muZ7vA7PBCE6xgPX7nkzzjiUq87kRItoJQM1Yo8S+Q= golang.org/x/exp v0.0.0-20190121172915-509febef88a4/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56 h1:2dVuKD2vS7b0QIHQbpyTISPd0LeHDbnYEryqj5Q1ug8= -golang.org/x/exp v0.0.0-20240719175910-8a7402abbf56/go.mod h1:M4RDyNAINzryxdtnbRXRL/OHtkFuWGRjvuhBJpk2IlY= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93 h1:fQsdNF2N+/YewlRZiricy4P1iimyPKZ/xwniHj8Q2a0= +golang.org/x/exp v0.0.0-20251219203646-944ab1f22d93/go.mod h1:EPRbTFwzwjXj9NpYyyrvenVh9Y+GFeEvMNh7Xuz7xgU= golang.org/x/lint v0.0.0-20181026193005-c67002cb31c3/go.mod h1:UVdnD1Gm6xHRNCYTkRU2/jEulfH38KcIWyp/GAMgvoE= golang.org/x/lint v0.0.0-20190227174305-5b3e6a55c961/go.mod h1:wehouNa3lNwaWXcvxsM5YxQ5yQlVC4a0KAMCusXpPoU= golang.org/x/lint v0.0.0-20190313153728-d0100b6bd8b3/go.mod h1:6SW0HCj/g11FgYtHlgUYUwCkIfeOF89ocIRzGO/8vkc= @@ -1019,8 +1019,8 @@ google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7 h1:XzmzkmB14QhVhgn google.golang.org/genproto v0.0.0-20260319201613-d00831a3d3e7/go.mod h1:L43LFes82YgSonw6iTXTxXUX1OlULt4AQtkik4ULL/I= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9 h1:VPWxll4HlMw1Vs/qXtN7BvhZqsS9cdAittCNvVENElA= google.golang.org/genproto/googleapis/api v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:7QBABkRtR8z+TEnmXTqIqwJLlzrZKVfAUm7tY3yGv0M= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9 h1:m8qni9SQFH0tJc1X0vmnpw/0t+AImlSvp30sEupozUg= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260401024825-9d38bb4040a9/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d h1:wT2n40TBqFY6wiwazVK9/iTWbsQrgk5ZfCSVFLO9LQA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260406210006-6f92a3bedf2d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= @@ -1054,38 +1054,38 @@ gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C gopkg.in/yaml.v3 v3.0.0-20200605160147-a5ece683394c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= -helm.sh/helm/v3 v3.20.2 h1:binM4rvPx5DcNsa1sIt7UZi55lRbu3pZUFmQkSoRh48= -helm.sh/helm/v3 v3.20.2/go.mod h1:Fl1kBaWCpkUrM6IYXPjQ3bdZQfFrogKArqptvueZ6Ww= -helm.sh/helm/v4 v4.1.4 h1:zwTrNkalG4f7SYigRSdQnYrTj0QEz1qzetzAlYoDVSo= -helm.sh/helm/v4 v4.1.4/go.mod h1:5dSo8rRgn3OTkDAc/k0Ipw5/Q+BlqKIKZwa0XwSiINI= +helm.sh/helm/v3 v3.21.0 h1:9TRbaXQH+BIKLLDYlu++JsyWodS5kBBOLF7C7HY5+cs= +helm.sh/helm/v3 v3.21.0/go.mod h1:5IvU6Ae6ruB/vasVHhnC1IU5RvqFM349vLYS1BiHqeY= +helm.sh/helm/v4 v4.2.0 h1:J+0TmTtPK2NuS6z9Z2WOcIX0nGGJylokEZLt0fi0X4U= +helm.sh/helm/v4 v4.2.0/go.mod h1:sDQRGAct/I/ogTvOX8QqE/8bBWuLH4BHbB3QFL5G3do= honnef.co/go/tools v0.0.0-20190102054323-c2f93a96b099/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= honnef.co/go/tools v0.0.0-20190523083050-ea95bdfd59fc/go.mod h1:rf3lG4BRIbNafJWhAfAdb/ePZxsR/4RtNHQocxwk9r4= k8s.io/api v0.35.4 h1:P7nFYKl5vo9AGUp1Z+Pmd3p2tA7bX2wbFWCvDeRv988= k8s.io/api v0.35.4/go.mod h1:yl4lqySWOgYJJf9RERXKUwE9g2y+CkuwG+xmcOK8wXU= -k8s.io/apiextensions-apiserver v0.35.1 h1:p5vvALkknlOcAqARwjS20kJffgzHqwyQRM8vHLwgU7w= -k8s.io/apiextensions-apiserver v0.35.1/go.mod h1:2CN4fe1GZ3HMe4wBr25qXyJnJyZaquy4nNlNmb3R7AQ= -k8s.io/apimachinery v0.35.4 h1:xtdom9RG7e+yDp71uoXoJDWEE2eOiHgeO4GdBzwWpds= -k8s.io/apimachinery v0.35.4/go.mod h1:NNi1taPOpep0jOj+oRha3mBJPqvi0hGdaV8TCqGQ+cc= -k8s.io/apiserver v0.35.1 h1:potxdhhTL4i6AYAa2QCwtlhtB1eCdWQFvJV6fXgJzxs= -k8s.io/apiserver v0.35.1/go.mod h1:BiL6Dd3A2I/0lBnteXfWmCFobHM39vt5+hJQd7Lbpi4= -k8s.io/cli-runtime v0.35.1 h1:uKcXFe8J7AMAM4Gm2JDK4mp198dBEq2nyeYtO+JfGJE= -k8s.io/cli-runtime v0.35.1/go.mod h1:55/hiXIq1C8qIJ3WBrWxEwDLdHQYhBNRdZOz9f7yvTw= +k8s.io/apiextensions-apiserver v0.36.0 h1:Wt7E8J+VBCbj4FjiBfDTK/neXDDjyJVJc7xfuOHImZ0= +k8s.io/apiextensions-apiserver v0.36.0/go.mod h1:kGDjH0msuiIB3tgsYRV0kS9GqpMYMUsQ3GHv7TApyug= +k8s.io/apimachinery v0.36.0 h1:jZyPzhd5Z+3h9vJLt0z9XdzW9VzNzWAUw+P1xZ9PXtQ= +k8s.io/apimachinery v0.36.0/go.mod h1:FklypaRJt6n5wUIwWXIP6GJlIpUizTgfo1T/As+Tyxc= +k8s.io/apiserver v0.36.0 h1:Jg5OFAENUACByUCg15CmhZAYrr5ZyJ+jodyA1mHl3YE= +k8s.io/apiserver v0.36.0/go.mod h1:mHvwdHf+qKEm+1/hYm756SV+oREOKSPnsjagOpx6Vho= +k8s.io/cli-runtime v0.36.0 h1:HNxciQpQMMOKS0/GiUXcKDyA6J2FDILJj9NmP2BZrTg= +k8s.io/cli-runtime v0.36.0/go.mod h1:KObkknK9Ro5LYX+1RdiKc7C8CvGg4aX+V/Zv+E8WPHA= k8s.io/client-go v0.35.4 h1:DN6fyaGuzK64UvnKO5fOA6ymSjvfGAnCAHAR0C66kD8= k8s.io/client-go v0.35.4/go.mod h1:2Pg9WpsS4NeOpoYTfHHfMxBG8zFMSAUi4O/qoiJC3nY= -k8s.io/component-base v0.35.1 h1:XgvpRf4srp037QWfGBLFsYMUQJkE5yMa94UsJU7pmcE= -k8s.io/component-base v0.35.1/go.mod h1:HI/6jXlwkiOL5zL9bqA3en1Ygv60F03oEpnuU1G56Bs= +k8s.io/component-base v0.36.0 h1:hFjEktssxiJhrK1zfybkH4kJOi8iZuF+mIDCqS5+jRo= +k8s.io/component-base v0.36.0/go.mod h1:JZvIfcNHk+uck+8LhJzhSBtydWXaZNQwX2OdL+Mnwsk= k8s.io/klog/v2 v2.140.0 h1:Tf+J3AH7xnUzZyVVXhTgGhEKnFqye14aadWv7bzXdzc= k8s.io/klog/v2 v2.140.0/go.mod h1:o+/RWfJ6PwpnFn7OyAG3QnO47BFsymfEfrz6XyYSSp0= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a h1:xCeOEAOoGYl2jnJoHkC3hkbPJgdATINPMAxaynU2Ovg= k8s.io/kube-openapi v0.0.0-20260317180543-43fb72c5454a/go.mod h1:uGBT7iTA6c6MvqUvSXIaYZo9ukscABYi2btjhvgKGZ0= -k8s.io/kubectl v0.35.1 h1:zP3Er8C5i1dcAFUMh9Eva0kVvZHptXIn/+8NtRWMxwg= -k8s.io/kubectl v0.35.1/go.mod h1:cQ2uAPs5IO/kx8R5s5J3Ihv3VCYwrx0obCXum0CvnXo= +k8s.io/kubectl v0.36.0 h1:hEGr8NvIm2Wjqs2Xy48Uzmvo6lpHdGKlLyMvau2gTms= +k8s.io/kubectl v0.36.0/go.mod h1:iDe8aV5BEi45W8k+5n71I2pJ/nwE0PHDu+/2cejzYoo= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2 h1:AZYQSJemyQB5eRxqcPky+/7EdBj0xi3g0ZcxxJ7vbWU= k8s.io/utils v0.0.0-20260210185600-b8788abfbbc2/go.mod h1:xDxuJ0whA3d0I4mf/C4ppKHxXynQ+fxnkmQH0vTHnuk= oras.land/oras-go/v2 v2.6.0 h1:X4ELRsiGkrbeox69+9tzTu492FMUu7zJQW6eJU+I2oc= oras.land/oras-go/v2 v2.6.0/go.mod h1:magiQDfG6H1O9APp+rOsvCPcW1GD2MM7vgnKY0Y+u1o= -sigs.k8s.io/controller-runtime v0.23.1 h1:TjJSM80Nf43Mg21+RCy3J70aj/W6KyvDtOlpKf+PupE= -sigs.k8s.io/controller-runtime v0.23.1/go.mod h1:B6COOxKptp+YaUT5q4l6LqUJTRpizbgf9KSRNdQGns0= +sigs.k8s.io/controller-runtime v0.24.0 h1:Ck6N2LdS8Lovy1o25BB4r1xjvLEKUl1s2o9kU+KWDE4= +sigs.k8s.io/controller-runtime v0.24.0/go.mod h1:vFkfY5fGt5xAC/sKb8IBFKgWPNKG9OUG29dR8Y2wImw= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730 h1:IpInykpT6ceI+QxKBbEflcR5EXP7sU1kvOlxwZh5txg= sigs.k8s.io/json v0.0.0-20250730193827-2d320260d730/go.mod h1:mdzfpAEoE6DHQEN0uh9ZbOCuHbLK5wOm7dK4ctXE9Tg= sigs.k8s.io/kustomize/api v0.21.1 h1:lzqbzvz2CSvsjIUZUBNFKtIMsEw7hVLJp0JeSIVmuJs= diff --git a/pkg/app/init.go b/pkg/app/init.go index a3837095..c86a340e 100644 --- a/pkg/app/init.go +++ b/pkg/app/init.go @@ -20,7 +20,7 @@ import ( const ( HelmRequiredVersion = "v3.18.6" // Minimum required version (supports Helm 3.x and 4.x) HelmDiffRecommendedVersion = "v3.15.3" - HelmRecommendedVersion = "v4.1.0" // Recommended to use latest Helm 4 + HelmRecommendedVersion = "v4.2.0" // Recommended Helm 4 version HelmSecretsRecommendedVersion = "v4.7.4" // v4.7.0+ works with both Helm 3 (single plugin) and Helm 4 (split plugin architecture) HelmGitRecommendedVersion = "v1.3.0" HelmS3RecommendedVersion = "v0.16.3" diff --git a/test/e2e/template/helmfile/testdata/snapshot/chart_need/output-helm4.yaml b/test/e2e/template/helmfile/testdata/snapshot/chart_need/output-helm4.yaml new file mode 100644 index 00000000..ce3f9ba5 --- /dev/null +++ b/test/e2e/template/helmfile/testdata/snapshot/chart_need/output-helm4.yaml @@ -0,0 +1,33 @@ +Adding repo myrepo http://localhost:18080/ +"myrepo" has been added to your repositories + +Building dependency release=foo, chart=$WD/temp1/foo +Hang tight while we grab the latest from your chart repositories... +...Successfully got an update from the "myrepo" chart repository +Update Complete. ⎈Happy Helming!⎈ +Saving 1 charts +Downloading raw from repo http://localhost:18080/ +Deleting outdated charts + +Templating release=foo, chart=$WD/temp1/foo +--- +# Source: raw/charts/dep/templates/resources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-2 + namespace: default +data: + bar: BAR + + +--- +# Source: raw/templates/resources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-1 + namespace: default +data: + foo: FOO + diff --git a/test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/output-helm4.yaml b/test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/output-helm4.yaml new file mode 100644 index 00000000..ee97997b --- /dev/null +++ b/test/e2e/template/helmfile/testdata/snapshot/chart_need_enable_live_output/output-helm4.yaml @@ -0,0 +1,34 @@ +Live output is enabled +Adding repo myrepo http://localhost:18081/ +"myrepo" has been added to your repositories + +Building dependency release=foo, chart=$WD/temp1/foo +Hang tight while we grab the latest from your chart repositories... +...Successfully got an update from the "myrepo" chart repository +Update Complete. ⎈Happy Helming!⎈ +Saving 1 charts +Downloading raw from repo http://localhost:18081/ +Deleting outdated charts + +Templating release=foo, chart=$WD/temp1/foo +--- +# Source: raw/charts/dep/templates/resources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-2 + namespace: default +data: + bar: BAR + + +--- +# Source: raw/templates/resources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-1 + namespace: default +data: + foo: FOO + diff --git a/test/e2e/template/helmfile/testdata/snapshot/oci_need/output-helm4.yaml b/test/e2e/template/helmfile/testdata/snapshot/oci_need/output-helm4.yaml new file mode 100644 index 00000000..7825808f --- /dev/null +++ b/test/e2e/template/helmfile/testdata/snapshot/oci_need/output-helm4.yaml @@ -0,0 +1,27 @@ +Building dependency release=foo, chart=$WD/temp1/foo +Saving 1 charts +Downloading raw from repo oci://localhost:$REGISTRY_PORT/myrepo +Deleting outdated charts + +Templating release=foo, chart=$WD/temp1/foo +--- +# Source: raw/charts/dep/templates/resources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-2 + namespace: default +data: + bar: BAR + + +--- +# Source: raw/templates/resources.yaml +apiVersion: v1 +kind: ConfigMap +metadata: + name: foo-1 + namespace: default +data: + foo: FOO + diff --git a/test/e2e/template/helmfile/testdata/snapshot/postrenderer/output-helm4.yaml b/test/e2e/template/helmfile/testdata/snapshot/postrenderer/output-helm4.yaml index 1c75e976..5a255f07 100644 --- a/test/e2e/template/helmfile/testdata/snapshot/postrenderer/output-helm4.yaml +++ b/test/e2e/template/helmfile/testdata/snapshot/postrenderer/output-helm4.yaml @@ -13,6 +13,7 @@ data: two: TWO metadata: name: cm2 + --- # Source: raw/templates/resources.yaml apiVersion: v1 @@ -32,6 +33,7 @@ data: one: ONE metadata: name: cm1 + --- # Source: raw/templates/resources.yaml apiVersion: v1 diff --git a/test/integration/test-cases/postrender/output/template-result-helm4 b/test/integration/test-cases/postrender/output/template-result-helm4 index 6e1a0682..f4b4d4ab 100644 --- a/test/integration/test-cases/postrender/output/template-result-helm4 +++ b/test/integration/test-cases/postrender/output/template-result-helm4 @@ -9,6 +9,7 @@ data: name: cm2 metadata: name: cm2 + --- # Source: raw/templates/resources.yaml apiVersion: v1 @@ -28,6 +29,7 @@ data: name: cm1 metadata: name: cm1 + --- # Source: raw/templates/resources.yaml apiVersion: v1 diff --git a/test/integration/test-cases/postrender/output/template-result-live-helm4 b/test/integration/test-cases/postrender/output/template-result-live-helm4 index b4dda41d..5fa5fc15 100644 --- a/test/integration/test-cases/postrender/output/template-result-live-helm4 +++ b/test/integration/test-cases/postrender/output/template-result-live-helm4 @@ -10,6 +10,7 @@ data: name: cm2 metadata: name: cm2 + --- # Source: raw/templates/resources.yaml apiVersion: v1 @@ -29,6 +30,7 @@ data: name: cm1 metadata: name: cm1 + --- # Source: raw/templates/resources.yaml apiVersion: v1