Skip to content

ci(helm): fail the chart release if any index URL is off charts.kosli.com - #1219

Merged
mbevc1 merged 2 commits into
mainfrom
helm-index-url-guard
Sep 28, 2026
Merged

mbevc1 merged 2 commits into
mainfrom
helm-index-url-guard

Conversation

@sami-alajrami

Copy link
Copy Markdown
Contributor

Summary

Implements the CI guard from the plan in kosli-dev/server#7025 (issue kosli-dev/server#7024).

helm repo index --merge copies old entries forward unchanged, so stale chart URLs pointing at hosts we no longer own would be republished on every release. This adds a check between "Helm regenerate repo index" and the S3 upload that fails the workflow if any URL in package/index.yaml is not on https://charts.kosli.com/.

Changes

  • scripts/check-helm-index-urls.sh: extracts every chart URL with yq and fails unless all start with https://charts.kosli.com/.
  • .github/workflows/helm-chart.yml: new step running the script before the upload.

Review comment on the plan

The reviewer on kosli-dev/server#7025 pointed out that the proposed one-liner (! yq ... | grep -v ...) discarded yq's exit status and passed silently when extraction produced no output. The script fails closed instead: it captures the yq output, fails if yq errors or is missing, fails if no URLs were found, and only then checks prefixes. No leading !, so the set -e caveat in the plan no longer applies.

Verification

Ran the script locally (yq 4.53.6, the same version preinstalled on ubuntu-latest) against fixtures:

Case Exit
Live charts.kosli.com/index.yaml 0
Index with the old merkely-development.github.io / charts.merkely.com URLs 1, prints the bad URLs
Index with empty entries 1
Malformed YAML 1
Missing file 1
yq not on PATH 1

shellcheck passes. The live index already has no non-kosli URLs, so the S3 fix the plan required before merging is in place and the next chart release will pass this check.

🤖 Generated with Claude Code

….com

`helm repo index --merge` copies old entries forward unchanged, so stale
URLs pointing at hosts we no longer own (kosli-dev/server#7024) would be
republished on every release. Add a fail-closed check between index
regeneration and the S3 upload.

The check is a standalone script rather than an inline `! yq | grep -v`
pipeline so that yq errors, a missing or malformed index, and an index
with no URLs all fail the step instead of passing silently (addresses the
review comment on kosli-dev/server#7025).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@claude

claude Bot commented Sep 28, 2026 •

Copy link
Copy Markdown
Contributor

Claude finished @mbevc1's task in 4m 42s —— View job


PR Review: ci(helm): fail the chart release if any index URL is off charts.kosli.com

  • Static checks (go build, go vet, go mod tidy)
  • Review scripts/check-helm-index-urls.sh
  • Review .github/workflows/helm-chart.yml
  • Post feedback

Static analysis

go build ./..., go vet ./... and go mod tidy all pass, and go.mod/go.sum are unchanged. That's expected, since no Go code changed.

I couldn't run the script or shellcheck myself because this job doesn't allow those commands. The review below comes from reading the code, alongside your fixture table in the PR description.

Review

Workflow (helm-chart.yml:73-74): the check is in the right place. It runs after helm repo index --merge and before the aws s3 cp upload, so a bad index never reaches S3. It calls the script by relative path, and the checkout step makes that path available. ✅

Script: this is solid and handles the concern raised in kosli-dev/server#7025 properly:

  • urls=$(yq …) || { …; exit 1; } keeps yq's exit status. A missing yq, missing file or malformed YAML all fail the step instead of passing silently.
  • An empty result is an error, so an index with no URLs can't pass by accident.
  • The prefix check ^https://charts\.kosli\.com/ escapes the dots and requires the trailing slash, so lookalike hosts such as https://charts.kosli.com.evil.io/… are rejected.
  • grep -v … || true is correct: grep -v exits with 1 when every line matches, which is the success case here, and without || true set -e would abort the script.
  • Bad URLs are printed to stderr, which makes a failure easy to diagnose from the logs.

Minor / optional

  1. Entries with no URLs are skipped (inline comment on L12). The empty check only fails when the whole index has no URLs. A single entry missing urls is ignored. Helm always writes urls, so this is low risk.
  2. Relies on the runner's preinstalled yq. The script uses mikefarah yq v4 syntax. If the runner image ever drops yq, or ships the Python yq instead, the step fails rather than passing. So the failure mode is safe, but release jobs would break until someone installs yq. Pinning yq in the workflow would avoid depending on the runner image, but that's not required.
  3. Tests: the fixture cases in the PR description aren't committed. If this script might change later, a few small fixtures under scripts/testdata/ and a short test script would make them repeatable. Not needed for a 28-line guard, so this is optional.

What's good

  • The script fails closed at every step. It is separate from the workflow YAML, so you can run it locally, and it passes shellcheck according to your PR description.
  • The header comment explains why the check exists and links the issue, without repeating what the code does. That matches the repo's comment guidelines.
  • Checking the live index first, so the next release doesn't fail on day one, was a sensible step.

Verdict: I found no bugs. It's ready to merge; the points above are optional.

@sami-alajrami
sami-alajrami enabled auto-merge (squash) September 28, 2026 09:03
@mbevc1 mbevc1 closed this Sep 28, 2026
auto-merge was automatically disabled September 28, 2026 10:24

Pull request was closed

@mbevc1 mbevc1 reopened this Sep 28, 2026
@mbevc1
mbevc1 enabled auto-merge (squash) September 28, 2026 10:25

index="${1:?usage: $0 <index.yaml>}"

urls=$(yq '.entries[][].urls[]' "$index") || {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Nit (optional): In yq v4, .urls[] on an entry with no urls key (or urls: []) returns nothing, so the entry gets skipped without an error. The empty check on L16 only catches the case where the whole index has no URLs. In practice Helm always writes urls, so this is just about how strictly the script fails closed. If you want it strict, you could also fail when any entry has no URLs:

missing=$(yq '[.entries[][] | select((.urls // []) | length == 0)] | length' "$index")
[ "$missing" = "0" ] || { echo "$missing chart entries have no URLs in $index" >&2; exit 1; }

@mbevc1
mbevc1 merged commit 380acbc into main Sep 28, 2026
28 of 29 checks passed
@mbevc1
mbevc1 deleted the helm-index-url-guard branch September 28, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants