Trigger: bp-network-policies:1.0.1 dead-reserved 2026-05-20. The chart had `catalyst.openova.io/no-upstream: "true"` (passing the pre-merge GUARD 1 elevated in PR #2087 / TBD-V35) but was missing `catalyst.openova.io/smoke-render-mode: "default-off"`. Its `enabled: false` master gate rendered 1 line at default values, tripping the post-merge smoke-render guard. By then the version in Chart.yaml was already on main; recovery required a follow-up bump-and-fix PR. Same shape as PR #2087; this PR closes the dual-annotation gap so the second annotation slipping through also fails pre-merge. What this PR does ----------------- - scripts/check-chart-annotations.sh — extended with GUARD 2: For every chart Chart.yaml passed in (default: every platform/*/chart/Chart.yaml + products/*/chart/Chart.yaml under the repo): run `helm template <chart-dir>` at default values. If output is <5 lines AND the chart lacks the smoke-render-mode:default-off annotation, FAIL with operator guidance pointing at docs/BLUEPRINT-AUTHORING.md §11. For charts with non-empty `dependencies:`, run `helm dependency build` first (registry-auth pre-configured by the workflow). GUARD 1 logic preserved unchanged. New env knob: SKIP_SMOKE_RENDER=1 for local dev runs without GHCR pull token; CI never sets this. - .github/workflows/check-chart-annotations.yaml — added: - azure/setup-helm@v4 step (same pin as blueprint-release.yaml) - GHCR helm registry login (read-only, packages: read perm) - timeout raised 5 → 10 min to accommodate helm dep build - docs/BLUEPRINT-AUTHORING.md — Guard table rewritten to show both pre-merge guards (GUARD 1 + GUARD 2) above the post-merge belt-and- braces guards. Validation ---------- Positive tests (local): - bp-network-policies:1.0.2 (both annotations present, 1-line render) → PASS - axon:0.1.0 (no-upstream:true, 277-line render) → PASS - bp-kyverno-policies:1.0.0 (no-upstream:true, 1167-line) → PASS Negative test (local): - Strip smoke-render-mode:default-off from bp-network-policies:1.0.2 → guard fails with exit 1 and the operator-guidance error message pointing at the annotation + BLUEPRINT-AUTHORING.md. The post-merge guard in .github/workflows/blueprint-release.yaml stays in place as belt-and-braces (same logic, same annotation key); pre- merge catches the violation while the version in Chart.yaml is still editable. Refs #2092 (TBD-V38) Refs #2086 (TBD-V35 — sibling GUARD 1 elevation, PR #2087) Refs #2080 (TBD-V34 — bp-continuum dead-reserve) Co-authored-by: hatiyildiz <hati.yildiz@openova.io> Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
180 lines
7.0 KiB
YAML
180 lines
7.0 KiB
YAML
name: Chart-annotations guard (pre-merge hollow-chart check)
|
|
|
|
# PRE-MERGE replica of GUARD 1 + GUARD 2 in
|
|
# .github/workflows/blueprint-release.yaml.
|
|
#
|
|
# Catches hollow-chart violations BEFORE the PR merges:
|
|
#
|
|
# GUARD 1 — Chart.yaml has NO `dependencies:` entry AND no
|
|
# `catalyst.openova.io/no-upstream: "true"` opt-out annotation.
|
|
# (Elevated to pre-merge in PR #2087 / TBD-V35.)
|
|
#
|
|
# GUARD 2 — Default-values `helm template` of the chart produces <5 lines
|
|
# AND the chart lacks the
|
|
# `catalyst.openova.io/smoke-render-mode: default-off` annotation.
|
|
# (Elevated to pre-merge in this PR / TBD-V38.)
|
|
#
|
|
# Without these gates, violations only surface at the post-merge Blueprint
|
|
# Release workflow — by which point the version in Chart.yaml is
|
|
# "dead-reserved" (the merge SHA owns it but no GHCR tag ever publishes)
|
|
# and recovery requires a follow-up version-bump-and-annotate PR.
|
|
#
|
|
# Recurrence history that motivated promoting these guards to pre-merge:
|
|
# GUARD 1:
|
|
# - bp-cert-manager:1.0.0 (issue #181 — guard origin)
|
|
# - bp-crossplane-claims (historical)
|
|
# - bp-kyverno-policies (PR #2023)
|
|
# - bp-continuum:0.1.1 (PR #2072 dead-pinned, fix PR #2081, TBD-V34 / #2080)
|
|
# GUARD 2:
|
|
# - bp-network-policies:1.0.1 (had no-upstream:true but missing
|
|
# smoke-render-mode; dead-reserved 2026-05-20 — required BOTH
|
|
# annotations. The dual-annotation gap motivated this elevation.)
|
|
#
|
|
# Per CLAUDE.md anti-pattern catalogue + Inviolable Principle #13
|
|
# (chart-pin bumps must match a published GHCR tag): every dead-reserved
|
|
# version is a chart-pin lockstep break.
|
|
#
|
|
# Per CLAUDE.md "every workflow MUST be event-driven, NEVER scheduled":
|
|
# this workflow is push-on-merge (belt-and-braces) + pull_request-on-touch.
|
|
# There is no `schedule:` trigger; ad-hoc reruns go through
|
|
# workflow_dispatch.
|
|
#
|
|
# Scoping note — only CHANGED charts are checked in PRs. Pre-existing
|
|
# violations are NOT blocked by this guard until a PR actually touches the
|
|
# chart; the post-merge Blueprint Release workflow continues to fail-loudly
|
|
# on their next publish attempt regardless. This keeps the guard zero-noise
|
|
# for unrelated PRs while still catching every new chart introduction or
|
|
# version-bump that would dead-reserve a tag.
|
|
|
|
on:
|
|
pull_request:
|
|
paths:
|
|
- 'platform/*/chart/Chart.yaml'
|
|
- 'products/*/chart/Chart.yaml'
|
|
- 'scripts/check-chart-annotations.sh'
|
|
- '.github/workflows/check-chart-annotations.yaml'
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'platform/*/chart/Chart.yaml'
|
|
- 'products/*/chart/Chart.yaml'
|
|
- 'scripts/check-chart-annotations.sh'
|
|
- '.github/workflows/check-chart-annotations.yaml'
|
|
workflow_dispatch:
|
|
inputs:
|
|
scope:
|
|
description: 'Scope: changed (PR diff) or all (every chart in the tree)'
|
|
required: false
|
|
type: choice
|
|
default: changed
|
|
options:
|
|
- changed
|
|
- all
|
|
|
|
permissions:
|
|
contents: read
|
|
# GUARD 2 needs to `helm dependency build` against
|
|
# oci://ghcr.io/openova-io/bp-* subcharts. Read-only GHCR pull
|
|
# token is sufficient; the post-merge workflow uses the same scope.
|
|
packages: read
|
|
|
|
jobs:
|
|
check:
|
|
name: Chart-annotations guard
|
|
runs-on: ubuntu-latest
|
|
timeout-minutes: 10
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
# Need both sides of the PR diff to enumerate changed charts.
|
|
# PR runs already get `refs/pull/N/merge` with 2 commits; push
|
|
# runs need a depth >= 2 so HEAD~1 resolves.
|
|
fetch-depth: 2
|
|
|
|
- name: Set up Helm
|
|
# GUARD 2 needs `helm template` (and `helm dependency build` for
|
|
# charts with declared dependencies). Pin matches the post-merge
|
|
# Blueprint Release workflow.
|
|
uses: azure/setup-helm@v4
|
|
with:
|
|
version: v3.18.4
|
|
|
|
- name: Install yq (declared-deps parser)
|
|
run: |
|
|
# Same yq pin as the post-merge Blueprint Release workflow —
|
|
# awk/grep on YAML is fragile and would let a subtly malformed
|
|
# Chart.yaml slip past the guard. Keep the version in sync with
|
|
# .github/workflows/blueprint-release.yaml.
|
|
sudo wget -qO /usr/local/bin/yq \
|
|
https://github.com/mikefarah/yq/releases/download/v4.44.3/yq_linux_amd64
|
|
sudo chmod +x /usr/local/bin/yq
|
|
yq --version
|
|
|
|
- name: Helm registry login (for OCI subchart resolution)
|
|
# `helm dependency build` resolves `oci://ghcr.io/openova-io/bp-*`
|
|
# subcharts; needs an authenticated helm registry login. Read-only
|
|
# GITHUB_TOKEN with `packages: read` (above) is sufficient.
|
|
run: |
|
|
echo "${{ secrets.GITHUB_TOKEN }}" | helm registry login ghcr.io \
|
|
--username "${{ github.actor }}" --password-stdin
|
|
|
|
- name: Detect changed chart manifests
|
|
id: changed
|
|
run: |
|
|
set -euo pipefail
|
|
# workflow_dispatch with scope=all → run over every chart.
|
|
if [ "${{ github.event_name }}" = "workflow_dispatch" ] \
|
|
&& [ "${{ inputs.scope }}" = "all" ]; then
|
|
echo "scope=all" >> "$GITHUB_OUTPUT"
|
|
echo "charts=" >> "$GITHUB_OUTPUT"
|
|
exit 0
|
|
fi
|
|
|
|
# PR runs: compare against the merge base.
|
|
# push-to-main runs: compare against the previous commit.
|
|
if [ "${{ github.event_name }}" = "pull_request" ]; then
|
|
base_sha="${{ github.event.pull_request.base.sha }}"
|
|
head_sha="${{ github.event.pull_request.head.sha }}"
|
|
# actions/checkout@v4 doesn't fetch the base by default for
|
|
# shallow clones; fetch just enough to diff.
|
|
git fetch --no-tags --depth=1 origin "$base_sha" 2>/dev/null || true
|
|
range="${base_sha}...${head_sha}"
|
|
else
|
|
range="HEAD~1...HEAD"
|
|
fi
|
|
|
|
echo "Diffing range: $range"
|
|
changed=$(git diff --name-only "$range" 2>/dev/null \
|
|
| grep -E '^(platform|products)/[^/]+/chart/Chart\.yaml$' \
|
|
| sort -u || true)
|
|
echo "Changed Chart.yaml files:"
|
|
echo "$changed"
|
|
|
|
# Multi-line outputs need the EOF-heredoc form.
|
|
{
|
|
echo "scope=changed"
|
|
echo "charts<<EOF"
|
|
echo "$changed"
|
|
echo "EOF"
|
|
} >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Run hollow-chart guards (GUARD 1 + GUARD 2)
|
|
run: |
|
|
set -euo pipefail
|
|
if [ "${{ steps.changed.outputs.scope }}" = "all" ]; then
|
|
echo "Scope: all (workflow_dispatch override)"
|
|
bash scripts/check-chart-annotations.sh
|
|
exit $?
|
|
fi
|
|
|
|
# Scope: changed. Empty list = no chart manifests touched → skip.
|
|
charts="${{ steps.changed.outputs.charts }}"
|
|
if [ -z "$charts" ]; then
|
|
echo "No Chart.yaml files changed in this PR — guard skipped."
|
|
exit 0
|
|
fi
|
|
|
|
# shellcheck disable=SC2086
|
|
echo "$charts" | xargs -r bash scripts/check-chart-annotations.sh
|