mirror of
https://github.com/helmfile/helmfile.git
synced 2026-09-30 06:12:09 +02:00
feat: add inherits: for sub-helmfile config inheritance (#2680)
* feat: add `inherits:` for sub-helmfile config inheritance
Add an opt-in `inherits:` field to `helmfiles:` entries so a sub-helmfile
can inherit specific configuration categories from its parent:
helmfiles:
- path: myapp.yaml
inherits: [repositories, environments]
Allowed values: repositories, helmDefaults, commonLabels, apiVersions,
kubeVersion, templates, environments. Child values win; parent fills gaps
(consistent with `bases:`). This directly fixes #1495, where a repository
declared in the parent was unavailable to sub-helmfiles, producing a
confusing "repo not found" error.
Implementation notes:
- The 6 pure fields (repositories, helmDefaults, commonLabels, apiVersions,
kubeVersion, templates) are merged post-load via MergeInherited; verified
all are consumed post-load (ExecuteTemplates/converge), never at parse.
- environments is injected pre-load as ctxEnv, because RenderedValues is
baked at load time; the parent's resolved values become the base and the
child's own environments: block overrides per key.
- helmDefaults uses a *HelmSpec pointer (value type is non-comparable) with
a no-override mergo merge, so a child that omits helmDefaults inherits the
parent's fully.
- A footgun warning (WarnUninheritedRepos) suggests
`inherits: [repositories]` when a release references a repo the parent
declares but the child lacks.
- Unknown inherits keys are rejected at parse time with the allowed set.
Inheritance is opt-in and fully backward compatible: empty (the default)
preserves the historical independent-sub-helmfile behavior.
Fixes #1495
Signed-off-by: yxxhero <aiopsclub@163.com>
* fix: address review — deep-copy inherited config and fix bases: doc link
- BuildInheritedConfig now deep-copies the pure fields via a YAML round-trip
(Env via environment.DeepCopy) so the returned config never aliases the
parent state's slices/maps, matching its doc comment. Now returns an error
to surface round-trip failures; the call site in processNestedHelmfiles is
updated. Added TestBuildInheritedConfig_PureFieldsAreDeepCopied to lock
in the no-aliasing guarantee.
- Fix the broken `bases:` anchor (#) in shared-configuration-across-teams.md
to point to writing-helmfile.md#layering-state-files.
Signed-off-by: yxxhero <aiopsclub@163.com>
* fix: address review — reject inherits without path and document helmDefaults caveat
Signed-off-by: yxxhero <aiopsclub@163.com>
* fix: address review — make AllowedInherits immutable and clarify effective-repo wording
Signed-off-by: yxxhero <aiopsclub@163.com>
---------
Signed-off-by: yxxhero <aiopsclub@163.com>
This commit is contained in:
@@ -109,6 +109,7 @@ ${kubectl} create namespace ${test_ns} || fail "Could not create namespace ${tes
|
||||
. ${dir}/test-cases/helmfile-double-fetch.sh
|
||||
. ${dir}/test-cases/skip-diff-output.sh
|
||||
. ${dir}/test-cases/v1-subhelmfile-multi-bases-with-array-values.sh
|
||||
. ${dir}/test-cases/inherits-subhelmfile.sh
|
||||
. ${dir}/test-cases/kustomized-fetch.sh
|
||||
. ${dir}/test-cases/issue-2503-kustomize-fetch.sh
|
||||
. ${dir}/test-cases/regression.sh
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
inherits_input_dir="${cases_dir}/inherits-subhelmfile/input"
|
||||
inherits_output_tmp=$(mktemp -d)
|
||||
|
||||
# Run `helmfile template` on an input file; assert it succeeds and that the
|
||||
# rendered output contains the given pattern.
|
||||
expect_template_ok() {
|
||||
local desc="$1" file="$2" pattern="$3"
|
||||
info "${desc}"
|
||||
code=0
|
||||
${helmfile} -f ${inherits_input_dir}/${file} template &> ${inherits_output_tmp}/out.log || code=$?
|
||||
if [ ${code} -ne 0 ]; then
|
||||
cat ${inherits_output_tmp}/out.log
|
||||
fail "${desc}: template should have succeeded, exit=${code}"
|
||||
fi
|
||||
grep -q "${pattern}" ${inherits_output_tmp}/out.log || { cat ${inherits_output_tmp}/out.log; fail "${desc}: expected '${pattern}' in output"; }
|
||||
info "${desc}: OK"
|
||||
}
|
||||
|
||||
# --- Scenario 1: repositories (the issue #1495 regression) -------------------
|
||||
# Uses the remote incubator/raw chart so that real repository registration is
|
||||
# exercised — the one thing only an integration test can cover.
|
||||
test_start "inherits: repositories (issue #1495)"
|
||||
${helm} repo remove incubator 2>/dev/null || true
|
||||
expect_template_ok \
|
||||
"sub-helmfile resolves chart via repository inherited from parent" \
|
||||
"helmfile-inherits.yaml" \
|
||||
"kind: ConfigMap"
|
||||
test_pass "inherits: repositories (issue #1495)"
|
||||
|
||||
# --- Scenario 2: environments (resolved values flow to the sub-helmfile) -----
|
||||
# Uses the local raw chart (no repository needed) to isolate environments
|
||||
# inheritance. The parent's resolved value must appear in the rendered output.
|
||||
test_start "inherits: environments"
|
||||
expect_template_ok \
|
||||
"sub-helmfile renders the parent's resolved environment value" \
|
||||
"helmfile-env.yaml" \
|
||||
"from-parent-env"
|
||||
test_pass "inherits: environments"
|
||||
|
||||
# --- Scenario 3: transitive inheritance (parent -> child -> grandchild) ------
|
||||
# The env value is inherited across two hops and must reach the grandchild's
|
||||
# rendered output. Also uses the local raw chart.
|
||||
test_start "inherits: transitive (parent -> child -> grandchild)"
|
||||
expect_template_ok \
|
||||
"grandchild renders value transitively inherited across two levels" \
|
||||
"helmfile-transitive.yaml" \
|
||||
"from-parent-env"
|
||||
test_pass "inherits: transitive (parent -> child -> grandchild)"
|
||||
|
||||
# --- Scenario 4: validation (unknown key rejected at parse time) -------------
|
||||
test_start "inherits: rejects unknown key at parse time"
|
||||
info "Expecting parse error for unknown inherits key"
|
||||
code=0
|
||||
${helmfile} -f ${inherits_input_dir}/helmfile-bad-key.yaml template &> ${inherits_output_tmp}/bad.log || code=$?
|
||||
if [ ${code} -eq 0 ]; then
|
||||
cat ${inherits_output_tmp}/bad.log
|
||||
fail "template should have failed for an unknown inherits key, but exited 0"
|
||||
fi
|
||||
grep -q "invalid inherits entry" ${inherits_output_tmp}/bad.log || { cat ${inherits_output_tmp}/bad.log; fail "expected 'invalid inherits entry' in the error"; }
|
||||
grep -q "bogusKey" ${inherits_output_tmp}/bad.log || { cat ${inherits_output_tmp}/bad.log; fail "expected the offending key 'bogusKey' in the error"; }
|
||||
info "unknown key rejected as expected (exit=${code})"
|
||||
test_pass "inherits: rejects unknown key at parse time"
|
||||
|
||||
# Cleanup so the registered repo does not leak into subsequent tests.
|
||||
${helm} repo remove incubator 2>/dev/null || true
|
||||
rm -rf ${inherits_output_tmp}
|
||||
@@ -0,0 +1,18 @@
|
||||
# Sub-helmfile (templated) that consumes the parent's resolved environment
|
||||
# value via .Values. This only resolves because the parent declared
|
||||
# `inherits: [environments]`, which passes the parent's resolved env as the
|
||||
# rendering context (see twoPassRenderTemplateToYaml). Uses the local raw chart
|
||||
# so no repository or network is required.
|
||||
releases:
|
||||
- name: env-inherit-test
|
||||
chart: ../../../charts/raw
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: env-inherit-cm
|
||||
namespace: default
|
||||
data:
|
||||
inherited: {{ .Values.inheritedvalue }}
|
||||
@@ -0,0 +1,6 @@
|
||||
# Middle sub-helmfile: inherits environment values from the parent, then passes
|
||||
# them further down to its own sub-helmfile. It has no releases of its own.
|
||||
helmfiles:
|
||||
- path: grandchild-transitive.yaml.gotmpl
|
||||
inherits:
|
||||
- environments
|
||||
@@ -0,0 +1,15 @@
|
||||
# Sub-helmfile that references a repository ("incubator") it does NOT declare.
|
||||
# It only resolves when the parent passes the repository down via inherits:.
|
||||
releases:
|
||||
- name: inherits-test
|
||||
chart: incubator/raw
|
||||
version: 0.2.3
|
||||
values:
|
||||
- resources:
|
||||
- apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: inherits-test-cm
|
||||
namespace: default
|
||||
data:
|
||||
source: inherited-repository
|
||||
@@ -0,0 +1 @@
|
||||
inheritedvalue: from-parent-env
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
# Leaf sub-helmfile (templated): consumes an environment value that was
|
||||
# transitively inherited across two levels (parent -> middle -> here). Uses the
|
||||
# local raw chart so no repository or network is required.
|
||||
releases:
|
||||
- name: transitive-test
|
||||
chart: ../../../charts/raw
|
||||
values:
|
||||
- templates:
|
||||
- |
|
||||
apiVersion: v1
|
||||
kind: ConfigMap
|
||||
metadata:
|
||||
name: transitive-cm
|
||||
namespace: default
|
||||
data:
|
||||
hop-count: "3"
|
||||
source: {{ .Values.inheritedvalue }}
|
||||
@@ -0,0 +1,12 @@
|
||||
repositories:
|
||||
- name: incubator
|
||||
url: https://charts.helm.sh/incubator
|
||||
|
||||
# An unknown inherits key must be rejected at parse time with a clear error,
|
||||
# rather than silently doing nothing. The parse fails before any chart is
|
||||
# fetched, so this scenario needs no network.
|
||||
helmfiles:
|
||||
- path: child.yaml
|
||||
inherits:
|
||||
- repositories
|
||||
- bogusKey
|
||||
@@ -0,0 +1,12 @@
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- env-values.yaml
|
||||
|
||||
# The sub-helmfile inherits the parent's resolved environment values. It uses a
|
||||
# LOCAL chart (no repository needed) so this scenario isolates the environments
|
||||
# inheritance from the repositories scenario.
|
||||
helmfiles:
|
||||
- path: child-env.yaml.gotmpl
|
||||
inherits:
|
||||
- environments
|
||||
@@ -0,0 +1,11 @@
|
||||
repositories:
|
||||
- name: incubator
|
||||
url: https://charts.helm.sh/incubator
|
||||
|
||||
# The sub-helmfile opts into inheriting the parent's repository, so the
|
||||
# release in child.yaml (chart: incubator/raw) can resolve "incubator" even
|
||||
# though child.yaml does not declare any repositories itself. See issue #1495.
|
||||
helmfiles:
|
||||
- path: child.yaml
|
||||
inherits:
|
||||
- repositories
|
||||
@@ -0,0 +1,10 @@
|
||||
environments:
|
||||
default:
|
||||
values:
|
||||
- env-values.yaml
|
||||
|
||||
# Parent passes its resolved environment values to the middle sub-helmfile.
|
||||
helmfiles:
|
||||
- path: child-transitive.yaml
|
||||
inherits:
|
||||
- environments
|
||||
@@ -0,0 +1 @@
|
||||
https://github.com/helmfile/helmfile/issues/1495
|
||||
Reference in New Issue
Block a user