Commit 776f148
authored
feat(helm): publish the chart to GHCR as a signed OCI artifact (#7563)
* feat(helm): publish the chart to GHCR as a signed OCI artifact
The chart has only ever existed inside the repo, so self-hosters whose
security process requires an officially published artifact could not use
it at all — a chart pulled from a git checkout is not something they can
pin, mirror, or admit.
Publishes helm/sim to oci://ghcr.io/simstudioai/charts/sim on push to
main, signed with Sigstore keyless signing and carrying a SLSA build
provenance attestation, both stored in the registry so they survive a
mirror into an internal registry.
The publish job lives in helm.yml rather than its own publish-*.yml so it
can gate on the existing jobs: nothing ships unless the chart linted,
unit-tested, rendered clean under kubeconform, and installed on kind.
Publishing is idempotent — a version already in the registry is skipped
rather than overwritten, since a published chart version is immutable.
Also pushes helm/artifacthub-repo.yml to the reserved artifacthub.io tag
for the Artifact Hub listing, and switches the chart README and the
Kubernetes docs page off the clone-and-install path.
* fix(helm): fail closed on registry lookup errors and pin documented installs
The publish guard treated every non-zero `helm show chart` exit as proof the
version was unpublished, so a transient 5xx, an expired token, or a DNS blip
would have enabled a push that moves an already-published version tag. Runs
where the version already exists are routine — the path filter also fires on
package.json and workflow edits — so this was reachable.
Now only an explicit `: not found` counts as absent; anything else stops the
job. Verified against the pinned Helm v3.16.4 that an absent version and an
absent repository both report `: not found`, so a first publish still
proceeds, while denied/unauthorized/dial-tcp failures abort instead.
Also pins the two documented install paths that were still reproducible only
by accident: the cloud-specific command paired a pinned chart with values
fetched from the moving main branch, and the README quick start omitted
--version while the same README warns against exactly that.
* chore(helm): harden the chart workflow against zizmor and document cosign v3
Ran the two standard workflow linters over helm.yml. actionlint reported one
shellcheck style issue; zizmor reported 22 findings, including two High.
Both High findings predate this PR: the version gate expanded
github.base_ref straight into a run block. The three Medium findings were
checkouts leaving the token in .git/config. Fixed all of them here rather
than leaving known findings in a file this PR already rewrites -- the gate
only reads history and fetches a public branch, so it never needed the
credential.
The 16 informational findings were mine: every ${{ }} in a run block is
template expansion before the shell sees it, so the documented fix is to
pass values through env and reference them as shell variables. Done for the
whole publish job, which also removes the shellcheck nit.
zizmor now reports zero findings on the file in pedantic mode, with no
suppressions, and actionlint is clean.
Separately, cosign v3 writes the Sigstore protobuf bundle format by default
and cosign v2 cannot read it, so the verification instructions now state the
required version. The in-workflow verify uses the same binary it signs with,
so it could never have caught this.
Re-verified the existence guard's four branches after the env refactor, and
confirmed it also fails closed when the registry login has not happened.
* fix(helm): push Artifact Hub metadata from helm/ so the layer title matches
Dry-ran the publish sequence against a local OCI registry, which surfaced one
divergence from Artifact Hub's documented command: passing a path-qualified
argument to oras records the layer's title annotation as
`helm/artifacthub-repo.yml` rather than the bare `artifacthub-repo.yml` the
docs produce. Artifact Hub selects the layer by media type, so this was
probably harmless, but there is no reason to diverge from the documented
form. Running the step from helm/ reproduces it exactly.
Everything else in the sequence behaved as intended against a real registry:
helm push derives charts/sim:1.9.1 from the chart name and version, the
digest grep extracts it, the metadata lands with both documented media types
under the artifacthub.io tag, the chart and metadata tags coexist without
disturbing each other, and helm pull round-trips to an identical sha256.
Also confirmed the overwrite hazard behind the fail-closed guard is real
rather than theoretical: re-pushing modified content under an existing
version moved the tag, so a pinned consumer would have silently received
different bytes.
* fix(helm): categorise the chart for Artifact Hub
The chart carried `category: developer-tools`, which is a different
convention entirely -- Artifact Hub reads `artifacthub.io/category` and
accepts only one value from a fixed list that has no developer-tools entry.
The listing this PR sets up would therefore have published uncategorised.
Adds `artifacthub.io/category: ai-machine-learning`, the closest fit in that
list, and keeps the bare annotation for whatever else consumes it. Bumps to
1.9.2 per the chart's own gate and moves the documented versions with it.
Deliberately not adding `artifacthub.io/images`: Artifact Hub already
extracts images from a dry-run render, and a hand-listed copy would be a
second source of truth next to the generated images.yaml.
* feat(helm): also publish the chart to a classic HTTP repo
I claimed earlier that OCI-only was defensible in 2026. That was wrong, and
checking rather than reasoning settled it: Bitnami, cert-manager,
ingress-nginx, prometheus-community, Grafana, Argo and external-secrets all
still serve a live index.yaml. Dual-publish is the actual convention, and
external-secrets -- the workflow this one is modelled on -- does both.
Adds a chart-releaser job that maintains index.yaml on a pages branch and
attaches each packaged chart to a GitHub release. It is a separate job from
the OCI publish because it needs contents: write to cut that release, and
that permission has no business sitting in the job that holds the signing
identity.
Three details worth stating:
- mark_as_latest is false. A chart release must never take the "Latest"
badge from the application release it packages.
- Releases are named helm-chart-<version> so they stay distinguishable from
the vX.Y.Z app releases in the same list.
- skip_existing mirrors the OCI job: re-running on a published version is a
no-op rather than an overwrite.
The job no-ops with a warning until a gh-pages branch exists, because
creating that branch and enabling Pages are one-time manual steps no
workflow can perform for itself, and main's CI must not fail on their
absence.
No documentation yet. index.yaml is only reachable once the branch, Pages,
and DNS are in place, and pointing users at a hostname that does not resolve
is the exact failure this whole PR exists to remove.
* docs(helm): document the Helm repository now that charts.sim.ai serves
Held these back until the hostname actually resolved, because shipping an
install command that points at a dead domain is the exact failure this work
exists to remove. It resolves now: DNS is in place, GitHub Pages serves the
branch, and Let's Encrypt has issued the certificate.
Verified end to end rather than assumed:
helm repo add sim https://charts.sim.ai
helm repo update # Successfully got an update
Presents OCI as the preferred path in both places and the repository as the
compatibility path, and says plainly that the signature and provenance are
attached to the OCI artifact only -- `helm repo add` has no equivalent, and
leaving that implicit would let someone believe they had verified something
they had not.
Bumps to 1.9.3 per the chart's own gate, since this touches helm/sim/.
* docs(self-hosting): install from the published chart, not a checkout
The Terraform page still told readers the chart "is not published to a Helm
repository or an OCI registry, so there is no repository to point at", and
had them vendor the repo as a submodule or git clone. That is now false, and
it is the same claim that makes a self-hoster conclude Sim is not packaged
for them, so it is the most important line in the docs to correct.
It now points repository at the OCI registry with a pinned version, notes
the classic repository as the alternative for tooling that does not speak
OCI, and replaces the "because the chart is local, version does nothing"
paragraph -- pinning the chart version is now both possible and the thing
that stops an unplanned apply pulling new migrations. Syntax checked against
the provider docs; the page's existing set_sensitive list form is already v3.
Also switches the two upgrade commands and the env render check off ./helm/sim,
which no longer exists for anyone following the install path.
Deliberately unchanged, because these are not install references:
- The chart README links on the Kubernetes page. The README lives in the
repo; charts.sim.ai serves index.yaml, not documentation.
- The cosign --certificate-identity-regexp. That is a Sigstore OIDC identity
that happens to look like a URL, and rewriting it would break verification.
- The releases page and issue tracker links, which point where they should.
- The git clone in the quick start, which is the Docker Compose path and
genuinely does need the repo.
* docs(helm): audit the chart documentation against the chart
Swept the remaining checkout-era references and audited the Helm docs by
checking every values path they mention against values.yaml rather than
reading them.
That found a real error: the chart has no `copilot.env`. The map is
`copilot.server.env`, so anyone following the env-validation note would have
set a key that silently does nothing. Fixed.
It also flagged `app.envDefaults.FREE_TABLES_LIMIT` and
`app.secrets.existingSecret.keys`, which are NOT bugs -- both appear only in
upgrade notes describing keys older releases shipped and this one removed.
Left exactly as they are; "fixing" them would delete the history an upgrader
needs.
The chart README was the bigger problem. It led with registry install after
the earlier commits, then used `./helm/sim` in eleven subsequent commands --
upgrade, dry-run, examples, parameters, secrets, troubleshooting. Someone
following the documented install had no such path. They now use the OCI
reference with a pinned version, with one note establishing that convention
so the checkout form does not have to be repeated everywhere.
Two related corrections while in there: the example values files are not part
of the packaged chart, so the examples section now fetches one at a release
tag instead of assuming it locally; and values.schema.json cannot be `cat`ed
without a checkout, so it links to the repo or `helm pull --untar`.
Left pointing at GitHub on purpose: the chart README deep links (that is
where the README lives, and an extra hop through the repository root would
be worse), the releases page, the issue tracker, the cosign identity regexp,
and the Docker Compose quick start's git clone.
Bumps to 1.9.4 per the chart's own gate.
* fix(helm): track the app release in appVersion, and enforce it
appVersion sat at v0.8.18 while the app shipped through v0.8.24 -- six
releases. Because the image tags default to Chart.AppVersion, a default
`helm install` deployed the older Sim, and regenerating images.yaml here
moved eleven lines, so the mirror inventory an air-gapped operator works
from named the wrong images too.
Publishing is what makes this serious. Cloning main got you whatever was
there; a published chart version is immutable, so every stale appVersion
would be frozen and installable forever.
It was bumped by hand and nothing checked it, which is why it drifted. The
publish job now refuses to publish when appVersion does not match the latest
GitHub release, comparing against the release API rather than a hardcoded
value so the check cannot go stale itself. Confirmed it fires on exactly the
drift that existed (v0.8.18 vs v0.8.24) and passes now that it is fixed.
Bumps appVersion to v0.8.24, regenerates images.yaml, and takes the chart to
1.9.5.
* fix(helm): fail the appVersion check only when it lags, not on any mismatch
The check compared appVersion to /releases/latest for equality, which races
the release it is meant to protect. Version tags are cut by the main-branch
merge commit that releases them, so on that commit appVersion legitimately
names a release that does not exist yet while ci.yml is still building it.
Equality would have rejected the bump and blocked the very publish it was
for. helm/sim/ci/kind-values.yaml already documents this circularity, and
records it as the reason appVersion went unbumped from chart 1.2.0 to 1.6.3.
Lagging is the failure; being ahead is a normal transient. The comparison is
`sort -V`, so it orders versions rather than strings -- v0.9.9 against
v0.10.0 is exactly where a string compare silently inverts.
Verified all five cases: behind fails, equal passes, ahead passes with a
notice, and both multi-digit orderings resolve correctly.1 parent 5730e62 commit 776f148
9 files changed
Lines changed: 529 additions & 49 deletions
File tree
- .github/workflows
- apps/docs/content/docs/platform/self-hosting
- helm
- sim
Large diffs are not rendered by default.
Lines changed: 1 addition & 1 deletion
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
311 | 311 | | |
312 | 312 | | |
313 | 313 | | |
314 | | - | |
| 314 | + | |
315 | 315 | | |
316 | 316 | | |
317 | 317 | | |
| |||
Lines changed: 60 additions & 9 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
24 | | - | |
25 | | - | |
26 | | - | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
| 27 | + | |
| 28 | + | |
| 29 | + | |
| 30 | + | |
27 | 31 | | |
| 32 | + | |
28 | 33 | | |
29 | 34 | | |
30 | 35 | | |
| |||
34 | 39 | | |
35 | 40 | | |
36 | 41 | | |
37 | | - | |
| 42 | + | |
| 43 | + | |
38 | 44 | | |
39 | 45 | | |
40 | 46 | | |
| |||
56 | 62 | | |
57 | 63 | | |
58 | 64 | | |
| 65 | + | |
| 66 | + | |
| 67 | + | |
| 68 | + | |
| 69 | + | |
| 70 | + | |
| 71 | + | |
| 72 | + | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
| 76 | + | |
| 77 | + | |
| 78 | + | |
| 79 | + | |
| 80 | + | |
| 81 | + | |
| 82 | + | |
| 83 | + | |
| 84 | + | |
| 85 | + | |
| 86 | + | |
| 87 | + | |
| 88 | + | |
| 89 | + | |
| 90 | + | |
| 91 | + | |
| 92 | + | |
| 93 | + | |
| 94 | + | |
| 95 | + | |
| 96 | + | |
| 97 | + | |
| 98 | + | |
| 99 | + | |
| 100 | + | |
| 101 | + | |
| 102 | + | |
59 | 103 | | |
60 | 104 | | |
61 | 105 | | |
62 | 106 | | |
63 | 107 | | |
64 | | - | |
65 | | - | |
| 108 | + | |
| 109 | + | |
| 110 | + | |
| 111 | + | |
| 112 | + | |
| 113 | + | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
66 | 117 | | |
67 | 118 | | |
68 | 119 | | |
| |||
80 | 131 | | |
81 | 132 | | |
82 | 133 | | |
83 | | - | |
| 134 | + | |
84 | 135 | | |
85 | 136 | | |
86 | 137 | | |
| |||
181 | 232 | | |
182 | 233 | | |
183 | 234 | | |
184 | | - | |
185 | | - | |
| 235 | + | |
| 236 | + | |
186 | 237 | | |
187 | 238 | | |
188 | 239 | | |
| |||
Lines changed: 11 additions & 4 deletions
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
139 | 139 | | |
140 | 140 | | |
141 | 141 | | |
142 | | - | |
| 142 | + | |
143 | 143 | | |
144 | 144 | | |
145 | 145 | | |
146 | 146 | | |
147 | 147 | | |
148 | 148 | | |
149 | 149 | | |
150 | | - | |
151 | | - | |
| 150 | + | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
152 | 159 | | |
153 | 160 | | |
154 | 161 | | |
| |||
172 | 179 | | |
173 | 180 | | |
174 | 181 | | |
175 | | - | |
| 182 | + | |
176 | 183 | | |
177 | 184 | | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
156 | 156 | | |
157 | 157 | | |
158 | 158 | | |
159 | | - | |
| 159 | + | |
| 160 | + | |
160 | 161 | | |
161 | 162 | | |
162 | 163 | | |
163 | 164 | | |
164 | 165 | | |
165 | 166 | | |
166 | 167 | | |
167 | | - | |
| 168 | + | |
168 | 169 | | |
169 | 170 | | |
170 | 171 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| 20 | + | |
| 21 | + | |
| 22 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
2 | 2 | | |
3 | 3 | | |
4 | 4 | | |
5 | | - | |
6 | | - | |
| 5 | + | |
| 6 | + | |
7 | 7 | | |
8 | 8 | | |
9 | 9 | | |
| |||
21 | 21 | | |
22 | 22 | | |
23 | 23 | | |
| 24 | + | |
| 25 | + | |
| 26 | + | |
24 | 27 | | |
25 | 28 | | |
26 | 29 | | |
| |||
0 commit comments