Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
{{- include "nvcf-container-cache.validateServiceType" . -}}
{{- include "nvcf-container-cache.validateServiceType" . }}
apiVersion: v1
kind: Service
metadata:
Expand Down
56 changes: 56 additions & 0 deletions deploy/helm/container-cache/tests/render-apiversion-test.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
#!/usr/bin/env bash
# SPDX-FileCopyrightText: Copyright (c) 2026 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Every rendered document must carry apiVersion and kind. A trailing "-}}" on
# a template action placed right after the license header swallows the
# newline and glues "apiVersion: v1" onto the last comment line; helm lint
# accepts the result and ArgoCD then fails the sync with "groupVersion
# shouldn't be empty". Run from the chart subtree:
# bash tests/render-apiversion-test.sh
set -euo pipefail
CHART_DIR="$(cd "$(dirname "$0")/.." && pwd)/deploy"
TMP="$(mktemp -d)"
trap 'rm -rf "$TMP"' EXIT
fail() { echo "FAIL: $*" >&2; exit 1; }

check() { # $1 label, remaining args: helm --set flags
local label="$1"; shift
helm template t "$CHART_DIR" "$@" > "$TMP/$label.yaml" 2>/dev/null || fail "$label: helm template failed"
python3 - "$TMP/$label.yaml" "$label" <<'PY'
import re, sys, yaml
path, label = sys.argv[1], sys.argv[2]
text = open(path).read()
# Raw documents, so a failure can point at the comment line the field was
# trimmed onto. A comment that merely mentions "kind:" in a valid document is
# not an error.
raw_docs = [d for d in re.split(r"^---[ \t]*$", text, flags=re.M)]
bad = []
n = 0
for raw in raw_docs:
body = [l for l in raw.splitlines() if l.strip() and not l.lstrip().startswith("#")]
if not body:
continue # separator gap, or a template that rendered only its "# Source:" header under these values
d = yaml.safe_load(raw)
if not isinstance(d, dict):
bad.append(("non-mapping document", body[0][:80]))
continue
n += 1
for field in ("apiVersion", "kind"):
if not d.get(field):
glued = next((l.strip() for l in raw.splitlines() if l.lstrip().startswith("#") and f"{field}:" in l), None)
bad.append((d.get("kind"), (d.get("metadata") or {}).get("name"), f"missing {field}", f"glued onto comment: {glued}" if glued else ""))
if n == 0:
bad.append(("no documents rendered", ""))
if bad:
print(f"FAIL: {label}: {bad}", file=sys.stderr)
sys.exit(1)
print(f"{label}: {n} documents, all carry apiVersion and kind")
PY
}

check default
check consistent-hash --set consistentHashRouting.enabled=true --set replicaCount=3
check pvc --set persistentVolumeClaim.storageClassName=nvcf-cc-sc
check pdb --set podDisruptionBudget.enabled=true --set podDisruptionBudget.minAvailable=1
echo "PASS: apiVersion render tests"
61 changes: 60 additions & 1 deletion tools/ci/check-helm-charts
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ chart_map=(
"api-keys-colocated api-keys-colocated/api-keys"
"cassandra cassandra/helm"
"cloud-functions cloud-functions/nvcf-api"
"container-cache container-cache/deploy"
"ess encrypted-secret-store/ess-api"
"gateway-routes-vanity gateway-routes/chart"
"grpc-proxy grpc-proxy/grpc-proxy"
Expand Down Expand Up @@ -170,6 +171,64 @@ run_step() { # run_step <label> <command...> -> 0 on success, prints output on
return 0
}

# helm template and helm lint accept a document whose apiVersion has been
# swallowed into a preceding comment (a template action ending in "-}}" right
# after the license header does this), because the line is valid YAML. The
# API server and ArgoCD then reject the object ("groupVersion shouldn't be
# empty"). Check every rendered document for a top-level apiVersion and kind.
check_rendered_objects() { # check_rendered_objects <stem> <rendered file>
local stem="$1" rendered="$2"
# Document-aware: a comment that merely mentions "kind:" is fine. Only when
# the same document lacks the top-level field is the comment line reported,
# as the likely place the field was trimmed onto.
awk -v stem="${stem}" '
function flush() {
if (content) {
if (!hasapi) report("apiVersion", gluedapi)
if (!haskind) report("kind", gluedkind)
}
content = 0; hasapi = 0; haskind = 0; gluedapi = 0; gluedkind = 0; name = "?"; n++
}
function report(field, glued) {
bad++
if (glued) {
printf "%s: rendered document %d (%s) has no top-level %s; line %d has %s rendered onto a comment line (a template action ending in -}} trimmed the newline)\n", stem, n, name, field, glued, field > "/dev/stderr"
} else {
printf "%s: rendered document %d (%s) has no top-level %s\n", stem, n, name, field > "/dev/stderr"
}
}
BEGIN { n = 1; name = "?" }
/^---/ { flush(); next }
/^[ \t]*#/ {
if ($0 ~ /apiVersion:/ && !gluedapi) gluedapi = NR
if ($0 ~ /kind:/ && !gluedkind) gluedkind = NR
next
}
/^[ \t]*$/ { next }
{ content = 1 }
/^apiVersion:/ { hasapi = 1 }
/^kind:/ { haskind = 1 }
/^ name:/ && name == "?" { name = $2 }
END {
flush()
exit (bad ? 1 : 0)
}
' "${rendered}"
}

render_and_check() { # render_and_check <stem> <chart dir> <values file>
local stem="$1" chart_dir="$2" values_file="$3" rendered
rendered="$(mktemp)"
if ! helm template "${stem}" "${chart_dir}" -f "${values_file}" >"${rendered}"; then
rm -f "${rendered}"
return 1
fi
check_rendered_objects "${stem}" "${rendered}"
local rc=$?
rm -f "${rendered}"
return "${rc}"
}

failed=()

for stem in "${stems[@]}"; do
Expand Down Expand Up @@ -198,7 +257,7 @@ for stem in "${stems[@]}"; do
run_step "${stem}: helm lint" \
helm lint "${chart_dir}" -f "${values_file}" || chart_failed=1
run_step "${stem}: helm template" \
helm template "${stem}" "${chart_dir}" -f "${values_file}" || chart_failed=1
render_and_check "${stem}" "${chart_dir}" "${values_file}" || chart_failed=1
fi

if [ "${chart_failed}" -eq 0 ]; then
Expand Down
8 changes: 8 additions & 0 deletions tools/ci/helm-validate-values/container-cache.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
# CI-only values for helm lint/template validation.
# Exercise the optional surfaces so their templates render in CI too.
replicaCount: 3
consistentHashRouting:
enabled: true
podDisruptionBudget:
enabled: true
minAvailable: 1
52 changes: 52 additions & 0 deletions tools/ci/test-check-helm-charts
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,58 @@ run_with_fake_helm() { # run_with_fake_helm -> sets $out and $rc
rm -rf "${stub_dir}"
}

# A rendered document whose apiVersion was swallowed into a comment must fail
# the chart, naming the file line. helm itself accepts this output, which is
# how the container-cache Service shipped without an apiVersion in 0.29.0.
make_fake_glued_helm() { # make_fake_glued_helm -> directory holding a helm stub, printed to stdout
local dir; dir="$(mktemp -d)"
cat >"${dir}/helm" <<'STUB'
#!/usr/bin/env bash
# Stub helm: template emits one good document and one whose apiVersion was
# trimmed onto the preceding comment line.
if [ "${1:-}" = "template" ]; then
printf -- '---\n# Source: x/templates/a.yaml\napiVersion: v1\nkind: ConfigMap\nmetadata:\n name: good\n'
printf -- '---\n# Source: x/templates/b.yaml\n# limitations under the License.apiVersion: v1\nkind: Service\nmetadata:\n name: glued\n'
fi
exit 0
STUB
chmod +x "${dir}/helm"
printf '%s' "${dir}"
}

make_fake_headless_helm() { # make_fake_headless_helm -> stub whose template emits a document with no apiVersion at all
local dir; dir="$(mktemp -d)"
cat >"${dir}/helm" <<'STUB'
#!/usr/bin/env bash
if [ "${1:-}" = "template" ]; then
printf -- '---\n# Source: x/templates/a.yaml\nkind: Service\nmetadata:\n name: headless\nspec: {}\n'
fi
exit 0
STUB
chmod +x "${dir}/helm"
printf '%s' "${dir}"
}

make_fake_tree
stub_dir="$(make_fake_glued_helm)"
set +e
out="$(PATH="${stub_dir}:${PATH}" bash "${script}" --values-dir "${fake_values_dir}" --chart-root "${fake_chart_root}" 2>&1)"
rc=$?
set -e
rm -rf "${stub_dir}"
expect "apiVersion glued onto a comment fails" 1 "rendered onto a comment line"
rm -rf "${fake_values_dir}" "${fake_chart_root}"

make_fake_tree
stub_dir="$(make_fake_headless_helm)"
set +e
out="$(PATH="${stub_dir}:${PATH}" bash "${script}" --values-dir "${fake_values_dir}" --chart-root "${fake_chart_root}" 2>&1)"
rc=$?
set -e
rm -rf "${stub_dir}"
expect "document without apiVersion fails" 1 "has no top-level apiVersion"
rm -rf "${fake_values_dir}" "${fake_chart_root}"

# A charts/ directory created by the run is removed entirely.
make_fake_tree
printf 'dependencies:\n - name: openbao\n' >>"${fake_chart_root}/openbao/helm/Chart.yaml"
Expand Down
Loading