Skip to content

Commit 29932dd

Browse files
authored
test(tests): add a render smoke check over every app chart (#4018)
## What this PR does I added an offline render check for application charts because helm-unittest suites can leave templates outside their `templates:` lists unchecked. It runs in `make unit-tests` with fixtures for fresh, configured and wildcard certificate states. The fixtures cover injected cluster and namespace values, with checks for PostgreSQL scheduling, tenant OIDC groups, shared services and Harbor certificate settings. The two charts that require instance-type lookup are skipped only while every fixture fails with the expected lookup error. I also added ClickHouse tests for generating the backup API password and preserving an existing one. The render check uses empty lookup results and checks YAML rendering only. Closes #4057 ### Validation I ran `make unit-tests` and `make test-controllers`. All 16 render-matrix tests passed under bats and the shell runner with dash. Deliberately broken rendering, fixture validation and password generation or reuse made the tests fail. ### Downstream repositories - [x] No downstream repository is affected by this change ### Release note ```release-note NONE ``` <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit * **Tests** * Added automated validation that charts render successfully across fresh, configured, and wildcard cluster states, including checks for conditional resources and scheduling settings. * Added coverage confirming ClickHouse backup API passwords are retained when a Secret already exists. * Expanded checks for invalid fixtures, skipped charts, and consistent rendered output. * **Documentation** * Documented the render scenarios and how fixture values represent cluster configuration. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
2 parents 56ceb3b + 5dc4489 commit 29932dd

9 files changed

Lines changed: 577 additions & 0 deletions

File tree

‎hack/check-render-matrix.bats‎

Lines changed: 274 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,274 @@
1+
#!/usr/bin/env bats
2+
3+
# Shared by bats and cozytest.sh: inspect exit codes directly, without bats-only
4+
# helpers. Scratch directories stay available if an assertion fails.
5+
6+
make_render_chart() {
7+
mkdir -p "$1/templates"
8+
printf 'apiVersion: v2\nname: render-test\nversion: 0.0.0\n' > "$1/Chart.yaml"
9+
printf 'apiVersion: v1\nkind: ConfigMap\nmetadata:\n name: render-test\n' > "$1/templates/object.yaml"
10+
}
11+
12+
expect_render_failure() {
13+
if output=$(dash hack/check-render-matrix.sh "$@" 2>&1); then
14+
echo "expected render failure, got: $output" >&2
15+
return 1
16+
fi
17+
printf '%s\n' "$output"
18+
}
19+
20+
@test "rendered plus skipped accounts for every app chart" {
21+
total=$(find packages/apps -mindepth 1 -maxdepth 1 -type d | wc -l | tr -d ' ')
22+
output=$(dash hack/check-render-matrix.sh)
23+
summary=$(printf '%s\n' "$output" | grep '^check-render-matrix:')
24+
rendered=$(printf '%s\n' "$summary" | sed 's/.*: \([0-9]*\) rendered.*/\1/')
25+
skipped=$(printf '%s\n' "$summary" | sed 's/.*, \([0-9]*\) skipped.*/\1/')
26+
[ "$rendered" -gt 0 ]
27+
[ "$((rendered + skipped))" -eq "$total" ]
28+
}
29+
30+
@test "render matrix preserves multiple chart paths containing spaces" {
31+
tmp=$(mktemp -d)
32+
make_render_chart "$tmp/space in parent/first"
33+
make_render_chart "$tmp/space in parent/second"
34+
output=$(dash hack/check-render-matrix.sh "$tmp/space in parent/first/" "$tmp/space in parent/second")
35+
printf '%s\n' "$output" | grep -Fx 'check-render-matrix: 2 rendered, 0 skipped'
36+
rm -rf "$tmp"
37+
}
38+
39+
@test "render matrix refuses empty discovery and ignores nested fixtures" {
40+
tmp=$(mktemp -d)
41+
mkdir -p "$tmp/hack/testdata/render-fixtures/validator"
42+
cp hack/check-render-matrix.sh "$tmp/hack/"
43+
cp hack/testdata/render-fixtures/fresh.yaml "$tmp/hack/testdata/render-fixtures/validator/nested.yaml"
44+
output=$(cd "$tmp" && expect_render_failure)
45+
printf '%s\n' "$output" | grep -F 'found no charts under packages/apps'
46+
make_render_chart "$tmp/packages/apps/example"
47+
output=$(cd "$tmp" && expect_render_failure)
48+
printf '%s\n' "$output" | grep -F 'no fixtures under hack/testdata/render-fixtures'
49+
rm -rf "$tmp"
50+
}
51+
52+
@test "render matrix reports a broken chart and still checks the next chart" {
53+
tmp=$(mktemp -d)
54+
make_render_chart "$tmp/broken"
55+
make_render_chart "$tmp/valid"
56+
printf '{{ required "deliberately broken" .Values.nope }}\n' > "$tmp/broken/templates/object.yaml"
57+
output=$(expect_render_failure "$tmp/broken" "$tmp/valid")
58+
for fixture in hack/testdata/render-fixtures/*.yaml; do
59+
state=$(basename "$fixture" .yaml)
60+
printf '%s\n' "$output" | grep -Fx "FAIL broken ($state)"
61+
done
62+
printf '%s\n' "$output" | grep -F 'deliberately broken'
63+
printf '%s\n' "$output" | grep -Fx 'check-render-matrix: 1 rendered, 0 skipped'
64+
rm -rf "$tmp"
65+
}
66+
67+
@test "render matrix fails on malformed YAML" {
68+
tmp=$(mktemp -d)
69+
make_render_chart "$tmp/badyaml"
70+
printf 'a: b\n c: d\n' > "$tmp/badyaml/templates/object.yaml"
71+
output=$(expect_render_failure "$tmp/badyaml")
72+
printf '%s\n' "$output" | grep -F 'FAIL badyaml'
73+
printf '%s\n' "$output" | grep -F 'YAML parse error'
74+
rm -rf "$tmp"
75+
}
76+
77+
@test "render matrix does not count a nonexistent chart as rendered" {
78+
tmp=$(mktemp -d)
79+
output=$(expect_render_failure "$tmp/missing")
80+
printf '%s\n' "$output" | grep -F 'FAIL missing'
81+
printf '%s\n' "$output" | grep -Fx 'check-render-matrix: 0 rendered, 0 skipped'
82+
rm -rf "$tmp"
83+
}
84+
85+
@test "render matrix rejects skips that now render or fail for another reason" {
86+
tmp=$(mktemp -d)
87+
for name in vm-instance kubernetes-nodes; do
88+
make_render_chart "$tmp/$name"
89+
output=$(expect_render_failure "$tmp/$name")
90+
printf '%s\n' "$output" | grep -F 'chart now renders; remove its skip'
91+
printf '{{ fail "unrelated render failure" }}\n' > "$tmp/$name/templates/object.yaml"
92+
output=$(expect_render_failure "$tmp/$name")
93+
printf '%s\n' "$output" | grep -F 'unrelated render failure'
94+
printf '%s\n' "$output" | grep -Fx 'check-render-matrix: 0 rendered, 0 skipped'
95+
done
96+
rm -rf "$tmp"
97+
}
98+
99+
@test "render matrix counts documented lookup failures as skipped" {
100+
tmp=$(mktemp -d)
101+
make_render_chart "$tmp/vm-instance"
102+
make_render_chart "$tmp/kubernetes-nodes"
103+
printf '%s\n' '{{ fail "Specified instanceType does not exist in the cluster: u1.medium" }}' > "$tmp/vm-instance/templates/object.yaml"
104+
printf '%s\n' '{{ fail `specified instanceType "u1.medium" not found in cluster` }}' > "$tmp/kubernetes-nodes/templates/object.yaml"
105+
output=$(dash hack/check-render-matrix.sh "$tmp/vm-instance" "$tmp/kubernetes-nodes")
106+
printf '%s\n' "$output" | grep -F 'SKIP vm-instance ('
107+
printf '%s\n' "$output" | grep -F 'SKIP kubernetes-nodes ('
108+
printf '%s\n' "$output" | grep -Fx 'check-render-matrix: 0 rendered, 2 skipped'
109+
rm -rf "$tmp"
110+
}
111+
112+
@test "fixtures match the injected value types" {
113+
for fixture in hack/testdata/render-fixtures/*.yaml; do
114+
helm template fixture-check hack/testdata/render-fixtures/validator \
115+
--set-file "fixture=$fixture"
116+
done
117+
}
118+
119+
@test "fixture validation rejects wrong scalar and scheduling types" {
120+
tmp=$(mktemp -d)
121+
for value in true 12 null '[]' '{}'; do
122+
printf '_cluster:\n oidc-enabled: %s\n_namespace: {host: "example.org"}\n' "$value" > "$tmp/value.yaml"
123+
if output=$(helm template fixture-check hack/testdata/render-fixtures/validator \
124+
--set-file "fixture=$tmp/value.yaml" 2>&1); then
125+
echo "accepted non-string oidc-enabled: $value" >&2
126+
exit 1
127+
fi
128+
printf '%s\n' "$output" | grep -F '_cluster.oidc-enabled must be a string'
129+
done
130+
for value in true 12 null '[]' '{}'; do
131+
printf '_cluster:\n scheduling:\n globalAppTopologySpreadConstraints: %s\n dedicatedNodesForWindowsVMs: "false"\n_namespace: {host: "example.org"}\n' "$value" > "$tmp/value.yaml"
132+
if output=$(helm template fixture-check hack/testdata/render-fixtures/validator \
133+
--set-file "fixture=$tmp/value.yaml" 2>&1); then
134+
echo "accepted non-string scheduling constraint: $value" >&2
135+
exit 1
136+
fi
137+
printf '%s\n' "$output" | grep -F '_cluster.scheduling.globalAppTopologySpreadConstraints must be a string'
138+
done
139+
rm -rf "$tmp"
140+
}
141+
142+
@test "fixture validation rejects malformed maps and missing scheduling keys" {
143+
tmp=$(mktemp -d)
144+
for variant in malformed-yaml empty-namespace branding-scalar namespace-map scheduling-scalar missing-scheduling missing-constraint missing-windows; do
145+
cluster_extra=''
146+
scheduling='scheduling:'
147+
namespace='host: "example.org"'
148+
constraint='globalAppTopologySpreadConstraints: ""'
149+
windows='dedicatedNodesForWindowsVMs: "false"'
150+
case "$variant" in
151+
malformed-yaml) constraint='['; windows=''; expected='error converting YAML to JSON' ;;
152+
empty-namespace) namespace=''; expected='_namespace must be a non-empty map' ;;
153+
branding-scalar) cluster_extra='branding: true'; expected='_cluster.branding must be a map' ;;
154+
namespace-map) namespace='branding: {}'; expected='_namespace.branding must be a string' ;;
155+
scheduling-scalar) constraint='true'; windows=''; expected='_cluster.scheduling must be a map' ;;
156+
missing-scheduling) cluster_extra='oidc-enabled: "false"'; scheduling=''; constraint=''; windows=''; expected='_cluster.scheduling must be a map' ;;
157+
missing-constraint) constraint=''; expected='_cluster.scheduling.globalAppTopologySpreadConstraints is missing' ;;
158+
missing-windows) windows=''; expected='_cluster.scheduling.dedicatedNodesForWindowsVMs is missing' ;;
159+
esac
160+
printf '_cluster:\n %s\n %s\n %s\n %s\n_namespace: {%s}\n' \
161+
"$cluster_extra" "$scheduling" "$constraint" "$windows" "$namespace" > "$tmp/value.yaml"
162+
if output=$(helm template fixture-check hack/testdata/render-fixtures/validator \
163+
--set-file "fixture=$tmp/value.yaml" 2>&1); then
164+
echo "accepted invalid fixture: $variant" >&2
165+
exit 1
166+
fi
167+
printf '%s\n' "$output" | grep -F "$expected"
168+
done
169+
rm -rf "$tmp"
170+
}
171+
172+
@test "configured scheduling reaches the postgres manifest" {
173+
tmp=$(mktemp -d)
174+
for state in fresh configured wildcard; do
175+
helm template postgres-render-check packages/apps/postgres -n tenant-test \
176+
-f "hack/testdata/render-fixtures/$state.yaml" > "$tmp/$state.yaml"
177+
done
178+
if grep -q '^ topologySpreadConstraints:' "$tmp/fresh.yaml"; then
179+
echo 'fresh unexpectedly enables topology spread' >&2
180+
exit 1
181+
fi
182+
for state in configured wildcard; do
183+
grep -A 7 '^ topologySpreadConstraints:' "$tmp/$state.yaml" > "$tmp/constraints"
184+
grep -F 'maxSkew: 1' "$tmp/constraints"
185+
grep -F 'topologyKey: topology.kubernetes.io/zone' "$tmp/constraints"
186+
grep -F 'cnpg.io/cluster: postgres-render-check' "$tmp/constraints"
187+
done
188+
rm -rf "$tmp"
189+
}
190+
191+
@test "render matrix reaches capability-gated OIDC templates" {
192+
tmp=$(mktemp -d)
193+
make_render_chart "$tmp/tenant"
194+
cat > "$tmp/tenant/templates/object.yaml" <<'EOF'
195+
{{- if and (eq (index .Values._cluster "oidc-enabled") "true") (.Capabilities.APIVersions.Has "v1.edp.epam.com/v1") }}
196+
{{- fail "OIDC branch rendered" }}
197+
{{- end }}
198+
EOF
199+
output=$(expect_render_failure "$tmp/tenant")
200+
printf '%s\n' "$output" | grep -Fx 'FAIL tenant (configured)'
201+
printf '%s\n' "$output" | grep -Fx 'FAIL tenant (wildcard)'
202+
printf '%s\n' "$output" | grep -F 'OIDC branch rendered'
203+
if printf '%s\n' "$output" | grep -Fq 'FAIL tenant (fresh)'; then
204+
echo 'OIDC branch enabled for fresh fixture' >&2
205+
exit 1
206+
fi
207+
rm -rf "$tmp"
208+
}
209+
210+
@test "fixture states enable OIDC groups and shared-service resources" {
211+
tmp=$(mktemp -d)
212+
for state in fresh configured wildcard; do
213+
helm template tenant-check packages/apps/tenant -n tenant-test \
214+
--api-versions v1.edp.epam.com/v1 \
215+
-f "hack/testdata/render-fixtures/$state.yaml" > "$tmp/tenant.yaml"
216+
helm template kubernetes-check packages/apps/kubernetes -n tenant-test \
217+
-f "hack/testdata/render-fixtures/$state.yaml" > "$tmp/kubernetes.yaml"
218+
groups=$(awk '/^kind: KeycloakRealmGroup$/ {n++} END {print n+0}' "$tmp/tenant.yaml")
219+
metrics=$(awk '/^ name: kubernetes-check-metrics-server$/ {n++} END {print n+0}' "$tmp/kubernetes.yaml")
220+
case "$state" in
221+
fresh) [ "$groups" -eq 0 ]; [ "$metrics" -eq 0 ] ;;
222+
*) [ "$groups" -eq 4 ]; [ "$metrics" -eq 1 ] ;;
223+
esac
224+
done
225+
rm -rf "$tmp"
226+
}
227+
228+
@test "certificate fixtures exercise per-host ACME and wildcard ingress" {
229+
tmp=$(mktemp -d)
230+
for state in configured wildcard; do
231+
helm template harbor-check packages/apps/harbor -n tenant-test \
232+
-f "hack/testdata/render-fixtures/$state.yaml" \
233+
--show-only templates/ingress.yaml > "$tmp/$state.yaml"
234+
grep -Fx 'kind: Ingress' "$tmp/$state.yaml"
235+
grep -F 'harbor-check.example.org' "$tmp/$state.yaml"
236+
done
237+
grep -F 'acme.cert-manager.io/http01-ingress-ingressclassname: tenant-test' "$tmp/configured.yaml"
238+
grep -F 'cert-manager.io/cluster-issuer: letsencrypt-prod' "$tmp/configured.yaml"
239+
grep -F 'secretName: harbor-check-ingress-tls' "$tmp/configured.yaml"
240+
grep -Fx ' tls:' "$tmp/wildcard.yaml"
241+
if grep -Eq '^[[:space:]]+(acme.cert-manager.io/|cert-manager.io/cluster-issuer:|secretName:)' "$tmp/wildcard.yaml"; then
242+
echo 'wildcard ingress unexpectedly requests a per-host certificate' >&2
243+
exit 1
244+
fi
245+
rm -rf "$tmp"
246+
}
247+
248+
@test "every pair of fixture states produces different deterministic manifests" {
249+
tmp=$(mktemp -d)
250+
count=0
251+
for fixture in hack/testdata/render-fixtures/*.yaml; do
252+
state=$(basename "$fixture" .yaml)
253+
count=$((count + 1))
254+
# This compares whole states; the tests above assert specific branches.
255+
# Render twice so randomness cannot masquerade as state coverage.
256+
for run in first second; do
257+
helm template tenant-check packages/apps/tenant -n tenant-test \
258+
--api-versions v1.edp.epam.com/v1 -f "$fixture" > "$tmp/$state-$run.yaml"
259+
done
260+
cmp "$tmp/$state-first.yaml" "$tmp/$state-second.yaml"
261+
cp "$tmp/$state-first.yaml" "$tmp/$state.manifests"
262+
done
263+
[ "$count" -ge 3 ]
264+
for a in "$tmp"/*.manifests; do
265+
for b in "$tmp"/*.manifests; do
266+
[ "$a" != "$b" ] || continue
267+
if cmp -s "$a" "$b"; then
268+
echo "fixture states produce identical manifests: $a and $b" >&2
269+
exit 1
270+
fi
271+
done
272+
done
273+
rm -rf "$tmp"
274+
}

‎hack/check-render-matrix.sh‎

Lines changed: 87 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,87 @@
1+
#!/bin/sh
2+
# Render app charts on their defaults plus injected cluster/namespace values.
3+
# helm-unittest renders only templates selected by a suite; this smoke check
4+
# also reaches templates with no assertions. It checks rendering, not Kubernetes
5+
# schema validity or runtime behavior. See testdata/render-fixtures/README.md.
6+
# Usage (from the repository root): hack/check-render-matrix.sh [chart-dir ...]
7+
set -eu
8+
9+
FIXTURE_DIR="hack/testdata/render-fixtures"
10+
11+
# Empty lookup results are the only accepted reason to skip a chart. Match the
12+
# actual diagnostic, so another error cannot keep an obsolete skip alive.
13+
skip_reason() {
14+
case "$1" in
15+
vm-instance) echo 'Specified instanceType does not exist in the cluster: u1.medium' ;;
16+
kubernetes-nodes) echo 'specified instanceType "u1.medium" not found in cluster' ;;
17+
*) echo "" ;;
18+
esac
19+
}
20+
21+
render_chart() {
22+
chart=$1 fixture=$2
23+
name=$(basename "$chart")
24+
case "$name" in
25+
tenant) set -- tenant-sub --api-versions v1.edp.epam.com/v1 ;;
26+
# Satisfy the parent-cluster and release-name contract before the lookup.
27+
kubernetes-nodes) set -- kubernetes-nodes-render-check --set-string cluster=render ;;
28+
*) set -- "$name-render-check" ;;
29+
esac
30+
helm template "$@" "$chart" -n tenant-test -f "$fixture"
31+
}
32+
33+
if [ "$#" -eq 0 ]; then
34+
set -- packages/apps/*/
35+
if [ ! -d "$1" ]; then
36+
echo "check-render-matrix: found no charts under packages/apps" >&2
37+
exit 1
38+
fi
39+
fi
40+
41+
# Check exactly the files the render loop will consume, not nested fixtures.
42+
fixtures=0
43+
for fixture in "$FIXTURE_DIR"/*.yaml; do
44+
[ -f "$fixture" ] || continue
45+
fixtures=$((fixtures + 1))
46+
done
47+
if [ "$fixtures" -eq 0 ]; then
48+
echo "check-render-matrix: no fixtures under $FIXTURE_DIR" >&2
49+
exit 1
50+
fi
51+
52+
errors=$(mktemp)
53+
trap 'rm -f "$errors"' EXIT
54+
rc=0 rendered=0 skipped=0
55+
for dir in "$@"; do
56+
name=$(basename "$dir")
57+
reason=$(skip_reason "$name")
58+
chart_failed=0
59+
for fixture in "$FIXTURE_DIR"/*.yaml; do
60+
[ -f "$fixture" ] || continue
61+
state=$(basename "$fixture" .yaml)
62+
if render_chart "$dir" "$fixture" >/dev/null 2>"$errors"; then
63+
if [ -z "$reason" ]; then
64+
continue
65+
fi
66+
echo "FAIL $name ($state): chart now renders; remove its skip"
67+
else
68+
if [ -n "$reason" ] && grep -Fq "$reason" "$errors"; then
69+
continue
70+
fi
71+
echo "FAIL $name ($state)"
72+
sed -n '/^level=INFO/d; s/^/ /; p' "$errors"
73+
fi
74+
rc=1
75+
chart_failed=1
76+
done
77+
[ "$chart_failed" -eq 0 ] || continue
78+
if [ -n "$reason" ]; then
79+
echo "SKIP $name ($reason)"
80+
skipped=$((skipped + 1))
81+
else
82+
rendered=$((rendered + 1))
83+
fi
84+
done
85+
86+
echo "check-render-matrix: $rendered rendered, $skipped skipped"
87+
exit "$rc"
Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
# Render fixtures
2+
3+
`hack/check-render-matrix.sh` renders every app chart with its defaults plus these injected values. helm-unittest renders the templates selected by each suite, so a chart with passing unit tests can still have an untested template that fails to render. This sweep checks that Helm accepts the rendered YAML; it does not validate Kubernetes schemas or runtime behavior.
4+
5+
The source of `_cluster` is `packages/core/platform/templates/apps.yaml`; `_namespace` comes from `packages/apps/tenant/templates/namespace.yaml`. These fixtures model selected install states, not a complete copy of every platform setting. Top-level scalar values in these maps are strings, including booleans. Charts such as tenant compare `oidc-enabled` with `"true"`, so a YAML boolean has the wrong type.
6+
7+
`scheduling` and `branding` are maps copied with `toYaml`, which preserves their nested types. The scheduling keys read by app charts have a more specific contract: `globalAppTopologySpreadConstraints` is a string containing YAML (or an empty string), and `dedicatedNodesForWindowsVMs` is compared with the string `"true"`. The configured and wildcard fixtures supply a topology constraint; fresh leaves it empty. The embedded YAML retains numeric fields such as `maxSkew`.
8+
9+
`_namespace.<service>` contains the namespace providing the shared service, or an empty string when unavailable. A non-empty value enables the service-dependent templates in the Kubernetes chart. These states use ingress rather than Gateway API, so `gateway` stays empty: a non-empty gateway would disable Harbor Ingress and leave the certificate modes untested.
10+
11+
| File | State |
12+
|---|---|
13+
| `fresh.yaml` | OIDC off, no certificates or shared services, no topology constraint |
14+
| `configured.yaml` | OIDC on, per-host ACME via http01, shared services and topology constraint |
15+
| `wildcard.yaml` | OIDC on, dns01 with a platform-issued wildcard certificate, shared services and topology constraint |
16+
17+
Each chart is attempted under every fixture. `vm-instance` and `kubernetes-nodes` require a `VirtualMachineClusterInstancetype` lookup on their defaults. The sweep accepts their skip only while every fixture fails with the recorded lookup diagnostic; a successful render or a different error fails the sweep. For kubernetes-nodes it supplies a parent cluster and matching release name first. For tenant it declares the Keycloak API with `--api-versions v1.edp.epam.com/v1`, allowing OIDC-enabled fixtures to render the realm groups.
18+
19+
`lookup` returns an empty result during this check. Tests for existing objects belong in chart-specific helm-unittest suites using `kubernetesProvider`; `packages/apps/clickhouse/tests/backup_api_password_persistence_test.yaml` covers both password generation and preservation that way. Presets, replica counts and other app feature toggles are outside this sweep.
20+
21+
The BATS suite checks fixture types with the small Helm chart in `validator/`, using `--set-file` so explicit nulls are not removed by Helm's values merge. Separate assertions check PostgreSQL topology constraints, tenant OIDC groups, the Kubernetes metrics-server HelmRelease and Harbor per-host ACME versus wildcard TLS. Pairwise comparison of complete tenant renders detects duplicate fixture states, and a second render of each state checks that randomness cannot satisfy that comparison. The branch assertions are needed because tenant also copies injected values into a Secret, so different output alone does not prove a conditional resource was rendered.
22+
23+
When adding an injected value read by an app, update the relevant states and add an assertion for the branch it exercises. Review all read forms, including `index`, `get` and `dig`, before deciding a value is unused:
24+
25+
```sh
26+
grep -rn -e '_cluster' -e '_namespace' packages/apps/*/templates/
27+
```

0 commit comments

Comments
 (0)