# Lint a dmwd-io Helm chart: helm lint + kubeconform + the house rules from SKILL.md.
# lint_chart.sh <chart-dir> [values-file ...]
# scripts/lint_chart.sh ./chart values.yaml values-prod.yaml
# Exit code is non-zero if any check fails. Missing `helm`/`kubeconform` on PATH
# degrades that check to a skipped warning rather than a hard failure, so the
# house-rule checks (which need no external tools) still run in a bare CI shell.
if [[ -z "$CHART_DIR" || ! -d "$CHART_DIR" ]]; then
echo "Usage: $0 <chart-dir> [values-file ...]" >&2
warn() { printf '⚠️ %s\n' "$1"; }
error() { printf '❌ %s\n' "$1"; fail=1; }
ok() { printf '✅ %s\n' "$1"; }
for f in "${VALUES_FILES[@]}"; do
values_args+=(--values "$f")
if command -v helm >/dev/null 2>&1; then
if helm lint "$CHART_DIR" "${values_args[@]}"; then
error "helm lint reported errors/warnings"
warn "helm not on PATH — skipped"
echo "== helm template | kubeconform -strict =="
if command -v helm >/dev/null 2>&1 && command -v kubeconform >/dev/null 2>&1; then
trap 'rm -f "$rendered"' EXIT
if helm template release "$CHART_DIR" "${values_args[@]}" > "$rendered" 2>/tmp/helm_template_err; then
if kubeconform -strict -summary -ignore-missing-schemas < "$rendered"; then
ok "kubeconform -strict clean"
error "kubeconform found schema violations"
error "helm template failed:"
cat /tmp/helm_template_err >&2
warn "helm and/or kubeconform not on PATH — skipped"
echo "== house rule: CRDs are never Helm-managed (ADR-028) =="
if [[ -d "$CHART_DIR/crds" ]]; then
if grep -rqiE 'installCRDs|crds\.(enabled|install)' "$CHART_DIR"/values*.yaml 2>/dev/null; then
ok "chart ships crds/ and an opt-out key is present in values — confirm it is set to false/disabled"
error "chart ships a crds/ directory but no installCRDs/crds.enabled/crds.install opt-out key was found in any values file — CRDs must be applied by a separate kubectl task, not Helm"
ok "no in-chart crds/ directory"
echo "== house rule: pinned versions, no 'latest' =="
version_hits=$(grep -rniE '(\bversion:\s*["'"'"']?latest["'"'"']?|\btag:\s*["'"'"']?latest["'"'"']?)' \
"$CHART_DIR/Chart.yaml" "$CHART_DIR"/values*.yaml 2>/dev/null || true)
if [[ -n "$version_hits" ]]; then
error "found a floating 'latest' version/tag — pin an exact version instead:"
ok "no 'latest' version/tag found in Chart.yaml or values files"
echo "== house rule: no plaintext secrets in values =="
secret_hits=$(grep -rniE '(password|secret|token|apikey|api_key)\s*:\s*["'"'"']?[A-Za-z0-9]' \
"$CHART_DIR"/values*.yaml 2>/dev/null | grep -viE 'existingSecret|secretName|secretKeyRef|ExternalSecret|valueFrom|secretRef' || true)
if [[ -n "$secret_hits" ]]; then
error "found what looks like a plaintext secret value — use 1Password Connect / external-secrets references instead:"
ok "no plaintext-looking secret values found"
if [[ "$fail" -eq 0 ]]; then
echo "All checks passed."
echo "One or more checks failed — see ❌ lines above."