Skip to content

ci(release): publish the hosted controller image and chart, pinned by digest - #748

Merged
petr-sandbox merged 4 commits into
mainfrom
work/deploy-pipeline-hosted-controller
Oct 12, 2026
Merged

petr-sandbox merged 4 commits into
mainfrom
work/deploy-pipeline-hosted-controller

Conversation

@petr-sandbox

Copy link
Copy Markdown
Collaborator

Part of the deployment pipeline work (phase 4b), stacked on #745.

What

  • The release builds and publishes switch-hosted-controller (image) and charts/switch-hosted-controller (chart) on every release, RC and dev build, versioned with switch-core.
  • scripts/pin_chart_images.py now knows which chart each image belongs to (CHARTS, IMAGES[*].chart). The hosted chart is pinned through image.repository and image.digest. verify is scoped per chart and rejects another chart's first-party image.
  • The "a version is published once" guard covers both charts (scripts/refuse_published_chart.sh).
  • Dev chart builds move a main tag on dev/charts/*, only while the build is still the tip of main. ArgoCD can follow it for development and resolve it to a digest.
  • PR CI pins and verifies both charts. The dev cleanup also prunes the new packages. The hosted chart's Chart.yaml now uses the same 0.0.0-dev placeholder as the switch chart. Napoleon installs it from source today, so that path still works.
  • artifacts.yaml, RELEASING and CHANGELOG are updated.

Testing

  • pytest core/tests/test_pin_chart_images.py core/tests/test_gen_artifacts.py: all pass.
  • actionlint is clean. The PR CI chart step, run locally, pins, lints and verifies both charts.
  • Not yet run on Actions. The main tag step uses oras tag and has only been checked by reading it.

🤖 Generated with Claude Code

@petr-sandbox petr-sandbox left a comment

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Review notes (AI-assisted, not verified by running the workflows). Two findings are on lines outside the diff:

  • scripts/prune_dev_packages.py ~L103: the moving main tag isn't protected from pruning. doomed() goes by age and position only. If main stops moving (say the hosted push keeps failing after the switch push succeeds), the version it points to ages past 30 days and outside the 20 newest, gets deleted, and the dev ArgoCD Application can no longer resolve main.
  • Stale docs. The switch-release.yml header still describes three images and one chart. RELEASING.md says the hosted chart is pinned "the same way", but its refs are repository@digest with no :<version> tag.

The first two inline comments (partial push, GHCR missing-package response) look most important before merge.

Comment thread .github/workflows/switch-release.yml Outdated
for chart in $(jq -r '.[].chart' "$RUNNER_TEMP/charts.json"); do
# prep refused a published version, but a re-run of this job alone
# skips prep, so ask again right before pushing.
scripts/refuse_published_chart.sh "oci://$REGISTRY/$NAMESPACE/charts/$chart" "$VERSION"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

A failed push leaves the version half-published, with no way to finish it. Each chart is checked and pushed in turn. If charts/switch:X is pushed and the switch-hosted-controller push then fails, re-running the job stops here on switch, because X is already published. The hosted chart is never pushed, release-pins is never recorded, and dev main never moves; the only way out is a new version. Suggest checking every chart before pushing any, and treating "every chart already present" as done.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in 303ea6e. scripts/release_charts.sh publish checks every chart before pushing any. A chart that already has the version is kept only if it renders exactly this build's image refs, and its registry digest is used for the pins. Anything else at that version fails the run. So re-running the failed chart job finishes the version, and "all present with these images" counts as done. I tested it against a local registry: switch pushed first and the hosted push missing → the re-run pushes the hosted chart; everything present → no-op; different images at the same version → fails. A full re-run still stops at prep, since it rebuilds the images.

Comment thread scripts/refuse_published_chart.sh Outdated
echo "::error::$chart:$version is already published. A version is published once; cut a new one."
exit 1
fi
if ! grep -q ': not found$' <<<"$out"; then

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

A brand-new package may never pass this check (unconfirmed). Only helm's : not found counts as unpublished. charts/switch-hosted-controller doesn't exist yet under either <owner>/ or <owner>/dev/, and GHCR can answer a missing repository with denied / unauthorized instead. If it does, this exits 1 and blocks every dev build and release, and the package can never be created. Worth checking what GHCR actually returns for a missing package before merge.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Checked against GHCR: a package that doesn't exist answers : not found, with credentials and anonymously. That covers both charts/switch-hosted-controller and dev/charts/switch-hosted-controller, and a namespace that doesn't exist too. The check now lives in release_charts.sh (status), with that finding noted. Any other answer still fails loudly; I confirmed that with an HTTP/HTTPS mismatch.

Comment thread .github/workflows/pr-ci.yml Outdated
for f in $(jq -r '.[]' <<<"$files"); do args+=(-f "$f"); done
# Linted with the check values: a chart with a values schema
# does not lint without the values it requires.
helm lint "$path" "${args[@]}"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The switch chart is no longer linted with its default values. The bare helm lint deploy/remote/helm/switch was removed, so lint always runs with render-check-values.yaml (plus overlays). A template that fails lint with the defaults but passes with the placeholder secrets now gets through. switch-release.yml made the same change.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed: Chart.lint_defaults. The switch chart is linted on its defaults again, then with each render's values. The hosted chart has a schema that requires values, so it's linted with its render values only. Both workflows go through the same build step.

Comment thread scripts/pin_chart_images.py Outdated
if not re.search(r"@sha256:[0-9a-f]{64}$", ref):
problems.append(f"{ref}: not pinned by digest")
elif ref.startswith(prefix):
elif ref.startswith(prefix) or name in IMAGES:

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

name in IMAGES matches by basename from any registry. A third-party image with a generic name, for example docker.io/envoyproxy/gateway:v1 as a sidecar, would fail verify --chart switch-hosted-controller with "add it to IMAGES". The other-chart check should only match refs from the pinning registry.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed: another chart's image is flagged only when it comes from the pinning registry. Added a test with docker.io/envoyproxy/gateway:v1 in the hosted render.

Comment thread scripts/pin_chart_images.py Outdated
lines, image.values_key, "image", f"{name}:{version}@{digests[name]}"
)
expected: dict[tuple[str, str], str] = {}
if chart == "switch":

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nit: the pin style is picked with a hard-coded chart name. The docstring says adding a chart to CHARTS is the whole change, but a third chart using the <key>.image style would get the repository/digest shape, or fail with "expected one image.repository line". Suggest making the style a field on Chart.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed: Chart.pin_style (tagged / repository-digest). Nothing in pin keys off the chart name anymore.

Comment thread .github/workflows/switch-release.yml Outdated
for f in $(jq -r '.[]' <<<"$files"); do args+=(-f "$f"); done
# Linted with the check values: a chart with a values schema
# does not lint without the values it requires.
helm lint "$path" "${args[@]}"

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nit: this pin, package, lint and verify loop is nearly a copy of pr-ci.yml 280-295, and the two already differ: one renders the .tgz, the other the source tree. Moving it into a script, as was done for refuse_published_chart.sh, would keep PR CI testing what the release runs.

Also, helm lint runs once per render, so the source chart is linted again for every overlay. It lints the source tree, not the packaged .tgz that gets pushed.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed: pin, package, lint and verify moved to scripts/release_charts.sh build, and PR CI runs that same step with placeholder digests. Lint and template now both run on the packaged .tgz. Lint still runs once per value set (defaults, then each render), because with different values it catches different template errors.

Comment thread .github/workflows/switch-release.yml Outdated
uses: actions/checkout@11d5960a326750d5838078e36cf38b85af677262 # v4.4.0
with:
# To ask, before moving the dev `main` tag, whether this is still main's head.
fetch-depth: 0

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Nit: fetch-depth: 0 seems to be needed only for the git ls-remote in the main tag step, which needs no history. A shallow checkout would avoid fetching full history on every chart job.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed: dropped it. git ls-remote needs no history.

@petr-sandbox

Copy link
Copy Markdown
Collaborator Author

Thanks. Both summary points are fixed in 303ea6e:

  • main and pruning: doomed() never deletes a version tagged main, however old it is. There's a test for it.
  • Stale docs: the release workflow header now lists both charts and four images, and covers the re-run behaviour and the main tag. RELEASING now says the hosted chart renders as repository@sha256:… with no tag, and describes the re-run path.

Inline replies cover the rest. Nothing has run on Actions yet; the first merge to main exercises the publish path.

petr-sandbox and others added 3 commits October 12, 2026 12:33
… digest

The hosted controller is built and published beside the switch images on
every release, RC and dev build, and its chart is pinned to its image by
digest the same way the switch chart is. The pinner now knows which chart
each image belongs to, and PR CI checks both charts.

Dev chart builds also move a `main` tag, only while the build is still the
tip of main, so an environment can follow main and resolve it to a digest.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…build with PR CI

- scripts/release_charts.sh packages, lints and checks the charts for both the
  release and PR CI, and publishes them: every chart is checked before any
  push, and a chart already at the version is kept only if it renders this
  build's images, so re-running a failed chart job completes the version.
- The switch chart is linted on its defaults again, and lint and render run
  on the packaged .tgz.
- The pin style is a field on the chart; verify flags another chart's image
  only from the pinning registry.
- The dev cleanup never deletes the build the `main` tag points at.
- No full-history checkout in the chart job; header and RELEASING updated.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Rebasing onto the hosted-controller image main had already started
publishing under a hardcoded matrix entry (image: hosted-controller)
replaced the dynamic include this PR relies on. The pin/digest/release
steps all key off scripts/pin_chart_images.py's IMAGES dict, which
names it switch-hosted-controller — a static matrix using the other
name would have built an image whose digest the pin step could never
find. Also fixes two leftover "hosted-controller" (singular-chart-era)
mentions in RELEASING.md to match.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@petr-sandbox
petr-sandbox force-pushed the work/deploy-pipeline-hosted-controller branch from 303ea6e to 78f6f85 Compare October 12, 2026 10:37
Base automatically changed from work/deploy-pipeline to main October 12, 2026 10:43
…e rebase

Main had independently added its own hosted-controller artifact entry
(published_as: hosted-controller) while this PR was adding its own
(published_as: switch-hosted-controller, the name the release
workflow's pin/digest machinery actually uses). The rebase's line-based
YAML merge kept both, under the same key, which the generator wrote
out as a literal duplicate dict key — exactly what "Registry, generated
modules and declared versions agree" exists to catch. Drops the stale
one and regenerates.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
@petr-sandbox
petr-sandbox merged commit 6cf212d into main Oct 12, 2026
12 checks passed
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.

2 participants