Skip to content

[DEVEX-1535] Add helm-chart-lint + helm-goldens-check composite actions#127

Merged
ronneseth merged 3 commits into
mainfrom
DEVEX-1535-helm-chart-actions
May 24, 2026
Merged

[DEVEX-1535] Add helm-chart-lint + helm-goldens-check composite actions#127
ronneseth merged 3 commits into
mainfrom
DEVEX-1535-helm-chart-actions

Conversation

@ronneseth
Copy link
Copy Markdown
Contributor

Summary

Adds the two central composite actions the per-repo Helm chart convention runbook depends on. Each chart adopting the convention wires them into its PR-triggered CI workflow as the chart-lint + goldens-check merge gates.

This is prerequisite #2 of DEVEX-1535 (covers both prereqs 2a and 2b from the Story).

helm-chart-lint (§3.1)

.github/actions/helm-chart-lint/ — POSIX shell + yq. Enforces all seven rules from the guideline:

# Rule
1 Values file names match ^(values\.yaml|values-(local|stg|qa|prod|dev-ec2|qa-ec2|prod-ec2)\.yaml|Chart\.yaml|values\.schema\.json)$
2 helm template <chart> (no -f, no --set) renders cleanly
3 templates/env-file.yaml forbidden — must be templates/env-configmap.yaml
4 No top-level topo: key in any values file
5 Mandatory .app namespace in values.yaml
6 No top-level non-reserved namespaces (aws:, ec2:, etc. fail)
7 No if .Values.<unapproved-key> render gates in templates

The reserved chart-control key list is configurable via the reserved-chart-control-keys input so charts with legitimately distinct chart-control keys can extend it locally.

helm-goldens-check v1 (§3.2)

.github/actions/helm-goldens-check/ — installs Helm + the helm-unittest plugin and runs:

  • Layer 1 (EKS-Helm path): helm unittest <chart-path> when <chart-path>/tests/ exists. Snapshot diffing is handled by the plugin.
  • Layer 1 (SetupPkg + Helm-EC2 paths): delegated to a chart-committed <goldens-path>/regenerate.sh hook; the action re-runs git diff over <goldens-path> after the hook executes and fails on any change. Path-specific render logic stays with the chart (per guideline: "Layer 2 stays custom — no community tool spans those paths").
  • Layer 2 (cross-path baseline): same delegation pattern via <goldens-path>/regenerate-cross-path-baseline.sh. Action fails on any diff.

skip-layer1 and skip-cross-path inputs let charts opt out during the migration window before they've committed tests/ or a baseline.

Why the goldens-check is a thin wrapper

The runbook generates per-chart goldens during Step 2 (<slug><env>-<path>.env) and Step 8 (cross-path-baseline.txt) and commits them. The action's job is to enforce "diff against committed = empty", not to know how each chart renders its three paths (Pattern S vs R vs D, daemon-set flags, multi-Deployment slugs). Delegating to per-chart regenerate hooks keeps the central action stable while each chart owns its matrix.

A v2 that drives an explicit matrix.yaml and renders all three paths centrally is a follow-up once a few charts have migrated and the shape stabilizes.

Test plan

A new Test helm-chart actions workflow exercises both actions on PR / push to main:

  • lint-good-fixture: lint passes against a convention-compliant fixture chart
  • lint-bad-fixture: lint exits non-zero against an intentionally-unmigrated fixture chart (catches Rules 1, 3, 4, 5, 6, and 7 — verified locally)
  • goldens-good-fixture: goldens-check passes against the good fixture with skip flags engaged
  • Bash scripts pass shellcheck clean
  • Workflow file passes actionlint

Locally verified the linter against the radmin chart (unmigrated): catches every expected violation across all seven rules. Output shape uses ::error:: annotations for GitHub UI surfacing.

JIRA

DEVEX-1535

Made with Cursor

Adds two central composite actions referenced by the per-repo Helm chart
convention runbook.

helm-chart-lint enforces (per guideline §3.1):
  - Values file names match the convention regex (values.yaml,
    values-{local,stg,qa,prod,dev-ec2,qa-ec2,prod-ec2}.yaml, Chart.yaml,
    values.schema.json).
  - helm template ./deployments (no -f, no --set) renders cleanly.
  - templates/env-file.yaml is forbidden — must be templates/env-configmap.yaml.
  - No top-level `topo:` key in any values file.
  - Mandatory `.app` namespace in values.yaml.
  - No non-reserved top-level namespaces (catches .aws, .ec2, etc.).
  - No `if .Values.<unapproved-key>` render gates (per-resource gates only).

helm-goldens-check v1 (per guideline §3.2):
  - Layer 1 EKS-Helm path via `helm unittest <chart>` when tests/ exists.
  - Layer 1 SetupPkg + Helm-EC2 paths via chart-committed regenerate hook
    (golden/regenerate.sh) + post-hook `git diff` enforcement.
  - Layer 2 cross-path baseline via chart-committed regenerate hook
    (golden/regenerate-cross-path-baseline.sh) + post-hook diff.
  - Skip flags for charts that have not yet committed tests/ or baseline.

Includes good-chart and bad-chart fixtures plus a smoke-test workflow that
proves the linter passes on convention-compliant charts and fails (with
expected violations) on unmigrated ones.

Refs: DEVEX-1535
Co-authored-by: Cursor <cursoragent@cursor.com>
@ronneseth ronneseth requested a review from a team as a code owner May 24, 2026 17:26
@ronneseth ronneseth requested a review from aspencer May 24, 2026 17:26
@cursor
Copy link
Copy Markdown

cursor Bot commented May 24, 2026

PR Summary

Low Risk
Changes are limited to GitHub Actions and bash lint/golden scripts; they do not alter runtime deployments until each repo opts in via its own workflow.

Overview
Adds two reusable GitHub composite actions so repos adopting the org Helm chart convention can wire PR merge gates without duplicating logic.

helm-chart-lint installs Helm and yq, then runs lint.sh against a chart path (default ./deployments). It enforces seven guideline rules: allowed values filenames, clean helm template with default values.yaml, ban on templates/env-file.yaml, no top-level topo:, required .app map, top-level keys limited to a configurable reserved set (always including app), and template render gates only on approved .Values.* roots (including else if / else with forms).

helm-goldens-check runs Layer 1 via helm unittest when tests/ exists (optional skip-layer1), optional per-chart regenerate.sh with git diff on the goldens dir, and Layer 2 cross-path baseline via regenerate-cross-path-baseline.sh / cross-path-baseline.txt (optional skip-cross-path).

A new Test helm-chart actions workflow exercises compliant and intentionally bad fixture charts, including a regression check that Rule 7 flags else if .Values.aws, and an end-to-end helm-unittest install/run job.

Reviewed by Cursor Bugbot for commit c4efbbe. Bugbot is set up for automated code reviews on this repo. Configure here.

Comment thread .github/actions/helm-chart-lint/action.yml
Comment thread .github/actions/helm-chart-lint/scripts/lint.sh Outdated
… RESERVED_KEYS

Two BugBot findings on PR #127:

1. The `reserved-chart-control-keys` input is documented as "in addition to
   `app`", but the script treated the input as the complete allowed-key set.
   A caller overriding the input without restating `app` would get a
   self-contradicting failure (Rule 5 requires `app:`, Rule 6 then flags it
   as non-reserved). Prepend `app` unconditionally to RESERVED_KEYS — the
   prepend is idempotent vs. the default and matches the documented contract.

2. `grep -v '^$'` in two pipelines returns exit 1 when all input lines are
   empty. Combined with `set -euo pipefail`, that silently kills the script
   with no `::error::` annotation. Replace with `{ grep -v '^$' || true; }`
   so Rule 6 / Rule 7 fall through and fire loudly per key, which is the
   useful failure mode.

Refs: DEVEX-1535
Co-authored-by: Cursor <cursoragent@cursor.com>
Copy link
Copy Markdown

@cursor cursor Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 2 potential issues.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit 558eeea. Configure here.

Comment thread .github/actions/helm-chart-lint/scripts/lint.sh Outdated
Comment thread .github/actions/helm-goldens-check/action.yml Outdated
… real version

Two BugBot findings on PR #127 commit 558eeea:

1. (Medium) Rule 7's regex only matched `{{ if .Values.X }}` and
   `{{ with .Values.X }}` — `{{- else if .Values.X }}` is a standard
   Go-template/Helm pattern that silently bypassed the check. Same gap in
   the inner extraction regex. Extend both regexes to also match an
   optional `else ` prefix so `else if` and `else with` are caught.

2. (High) `helm-unittest-version` default was `0.6.2` — that tag never
   existed (the 0.6.x series only released v0.6.0), and the value was
   missing the `v` prefix helm-unittest's git tags use. The bug was masked
   in CI because every existing goldens-check job used `skip-layer1: true`.
   Pin to `v1.0.3` (mature stable line, released 2025-10-05).

Regression guards:
* bad-chart fixture's env-file.yaml now contains an `else if .Values.aws`
  branch. The lint-bad-fixture CI job asserts the `.Values.aws` annotation
  appears in lint output — if the Rule 7 regex narrows again, CI fails.
* good-chart fixture ships a minimal `tests/deployment_test.yaml`. A new
  goldens-installs-helm-unittest job runs the action with
  `skip-layer1: false`, exercising the full plugin install + run path. If
  the default version ever points at a non-existent tag again,
  `helm plugin install --version <X>` fails fast in CI.

Refs: DEVEX-1535
Co-authored-by: Cursor <cursoragent@cursor.com>
@ronneseth ronneseth merged commit 6434635 into main May 24, 2026
6 checks passed
@ronneseth ronneseth deleted the DEVEX-1535-helm-chart-actions branch May 24, 2026 18:11
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant