Skip to content

Commit 7594b40

Browse files
committed
ci(helm): branch-driven publish with PR pre-flight version check
Restructures the chart publish workflow: - Adds a release channel: pushes to release branch publish as <chart-yaml-version>-release.<short-sha>, hidden from default `helm install` (clients must pass --devel). Master keeps publishing the on-disk version verbatim as stable. - PRs touching deploy/helm/** now run a pre-flight version check that fails if the on-disk Chart.yaml version was already published as a stable release. Catches "I forgot to bump the version" before merge. - Single combined job (was two): version-collision check uses curl HEAD against the public chart URL, no AWS credentials needed, so it applies identically on PRs (including from forks) and on push. - index.yaml is regenerated from current bucket state on every publish rather than --merge appended. This decouples cleanup from publish: any S3 lifecycle rule that expires release-channel tarballs is automatically reflected in the next published index. - values.schema.json is now uploaded alongside the chart on stable publishes, enabling IDE schema validation via `# yaml-language-server: $schema=https://helm.appsmith.com/values.schema.json`. - index.yaml gets short Cache-Control (max-age=60) so `helm repo update` picks up new versions promptly even behind a CDN. - Bumps Helm to v4.1.4 (was v3.6.3) and setup-helm to v4 (was v1). - Switches HELM_S3_BUCKET / HELM_REPO_URL from secrets to repository variables so the validate-version path works on fork PRs. Naming matches what each value actually represents (bucket name vs. URL). - Drops workflow_dispatch (re-runs from the Actions UI cover the rare manual republish case) and the unused `helm repo add bitnami` step (helm dep build resolves dependencies via Chart.yaml URLs).
1 parent 46fa344 commit 7594b40

1 file changed

Lines changed: 114 additions & 31 deletions

File tree

.github/workflows/helm-release.yml

Lines changed: 114 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,37 @@
11
name: Helm Charts Publish
22

33
# Package & publish new version for Helm chart to S3 bucket.
4+
#
5+
# Channels (single repo, branch-driven):
6+
# - master -> stable release. Uses the version in Chart.yaml verbatim.
7+
# Fails if that version was already published.
8+
# - release -> pre-release. Uses "<chart-yaml-version>-release.<short-sha>"
9+
# so every commit produces a unique chart. Pre-release suffix
10+
# hides these from default `helm install` (clients must pass
11+
# --devel to install). S3 lifecycle (configured on the bucket,
12+
# not in this workflow) prunes -release.* tarballs after N days.
13+
#
14+
# Pull requests touching deploy/helm/** also run, but skip the publish
15+
# steps. The version-collision check still fires, so a PR that forgets to
16+
# bump Chart.yaml's version (when the on-disk version was already
17+
# published) fails CI before merge.
18+
#
19+
# Variables (set as repository variables, not secrets — both are public
20+
# values and we want them readable by fork PRs):
21+
# - vars.HELM_S3_BUCKET bucket name for `aws s3 ...` operations
22+
# - vars.HELM_REPO_URL full URL (incl. scheme) clients fetch charts from
423

524
on:
625
push:
726
branches:
827
- master
28+
- release
929
paths:
1030
- "deploy/helm/**"
1131

12-
workflow_dispatch:
32+
pull_request:
33+
paths:
34+
- "deploy/helm/**"
1335

1436
jobs:
1537
publish:
@@ -25,50 +47,111 @@ jobs:
2547
uses: actions/checkout@v4
2648

2749
- name: Setup Helm
28-
uses: azure/setup-helm@v1
50+
uses: azure/setup-helm@v4
2951
with:
30-
version: v3.6.3
52+
version: v4.1.4
3153

32-
- name: Get the chart version
54+
- name: Compute chart version
3355
id: chart-version
3456
run: |
35-
set -o xtrace
36-
cat "${{ github.workspace }}/deploy/helm/Chart.yaml"
37-
chart_version="$(awk '$1 == "version:" {print $2}' "${{ github.workspace }}/deploy/helm/Chart.yaml" | head -1 )"
38-
echo "version=$chart_version" >> $GITHUB_OUTPUT
39-
# Scan the S3 bucket with the Helm version retrieved from Chart.yaml
40-
# If an archive with this version already exists in the bucket, fail immediately.
41-
- name: Check chart version is not already published
42-
env:
43-
AWS_ACCESS_KEY_ID: "${{ secrets.HELM_AWS_ACCESS_KEY_ID }}"
44-
AWS_SECRET_ACCESS_KEY: "${{ secrets.HELM_AWS_SECRET_ACCESS_KEY }}"
45-
AWS_EC2_METADATA_DISABLED: true
46-
run: |
47-
set -o xtrace
48-
chart_version="${{ steps.chart-version.outputs.version }}"
49-
if [[ -z $chart_version ]]; then
50-
echo "Empty chart version from Chart.yaml. Exiting." >&2
57+
set -o pipefail
58+
base="$(yq -r '.version' Chart.yaml)"
59+
if [[ -z "$base" || "$base" == "null" ]]; then
60+
echo "::error::Empty 'version:' in deploy/helm/Chart.yaml."
5161
exit 1
5262
fi
53-
archive="appsmith-$chart_version.tgz"
54-
if [[ "$archive" == "$(aws s3api list-objects --bucket '${{ secrets.HELM_S3_BUCKET }}' --prefix "$archive" --query 'Contents[0].Key' --output text)" ]]; then
55-
echo "$archive is already present in the Helm bucket. Please change the version number in 'Chart.yaml' file." >&2
63+
# Suffix only on the release branch. PRs (ref=refs/pull/...) and
64+
# master both fall through to the base version + stable channel
65+
# (the collision check below catches anyone who forgot to bump).
66+
if [[ "${{ github.ref }}" == "refs/heads/release" ]]; then
67+
sha="$(git rev-parse --short HEAD)"
68+
version="${base}-release.${sha}"
69+
channel="release"
70+
else
71+
version="${base}"
72+
channel="stable"
73+
fi
74+
echo "base=${base}" >> "$GITHUB_OUTPUT"
75+
echo "version=${version}" >> "$GITHUB_OUTPUT"
76+
echo "channel=${channel}" >> "$GITHUB_OUTPUT"
77+
78+
# Stable releases are immutable — fail if a tarball at this version
79+
# already exists at the public chart URL. Skipped for the release
80+
# channel because pre-release versions are SHA-suffixed and never
81+
# collide with already-published tarballs.
82+
#
83+
# HEAD against the public URL needs no credentials, so this check
84+
# works identically on PRs (incl. from forks) and on push events.
85+
- name: Check chart version is not already published
86+
if: steps.chart-version.outputs.channel == 'stable'
87+
run: |
88+
base="${{ steps.chart-version.outputs.base }}"
89+
tarball_url="${{ vars.HELM_REPO_URL }}/appsmith-${base}.tgz"
90+
if curl -fsIL -o /dev/null "${tarball_url}"; then
91+
echo "::error::Chart version ${base} is already published at ${tarball_url}."
92+
echo "::error::Bump 'version:' in deploy/helm/Chart.yaml before merging."
5693
exit 1
5794
fi
95+
echo "OK: chart version ${base} is not yet published."
96+
5897
- name: Publish Helm
98+
# Only publish on push (or manual dispatch) against master or release.
99+
# PRs and dispatches against feature branches run the version-collision
100+
# check above and stop there.
101+
if: github.ref == 'refs/heads/master' || github.ref == 'refs/heads/release'
59102
env:
60103
AWS_ACCESS_KEY_ID: "${{ secrets.HELM_AWS_ACCESS_KEY_ID }}"
61-
AWS_SECRET_ACCESS_KEY: "${{ secrets.HELM_AWS_SECRET_ACCESS_KEY }}"
62104
# Not really sure why this is needed, but without it, we see the error:
63105
# <botocore.awsrequest.AWSRequest object at 0x7fde607adac0>
64106
# Error: Process completed with exit code 255.
107+
AWS_SECRET_ACCESS_KEY: "${{ secrets.HELM_AWS_SECRET_ACCESS_KEY }}"
65108
AWS_EC2_METADATA_DISABLED: true
66109
run: |
67-
echo "Publishing new Helm chart version"
68-
helm repo add bitnami https://charts.bitnami.com/bitnami
110+
set -o xtrace
111+
version="${{ steps.chart-version.outputs.version }}"
112+
channel="${{ steps.chart-version.outputs.channel }}"
113+
tarball="appsmith-${version}.tgz"
114+
115+
echo "Publishing chart version ${version} to channel=${channel}"
116+
69117
helm dep build
70-
helm package .
71-
aws s3 cp s3://${{ secrets.HELM_S3_BUCKET }}/index.yaml .
72-
helm repo index . --url https://${{ secrets.HELM_S3_BUCKET }} --merge index.yaml
73-
aws s3 cp appsmith-${{ steps.chart-version.outputs.version }}.tgz s3://${{ secrets.HELM_S3_BUCKET }}
74-
aws s3 cp index.yaml s3://${{ secrets.HELM_S3_BUCKET }}
118+
119+
# Override the on-disk Chart.yaml version for release-channel builds.
120+
helm package . --version "${version}"
121+
122+
# Upload the tarball. Tag release-channel objects so an S3 lifecycle
123+
# rule can expire them after N days; stable objects stay forever.
124+
if [[ "${channel}" == "release" ]]; then
125+
aws s3 cp "${tarball}" "s3://${{ vars.HELM_S3_BUCKET }}/" \
126+
--tagging "channel=release"
127+
else
128+
aws s3 cp "${tarball}" "s3://${{ vars.HELM_S3_BUCKET }}/"
129+
fi
130+
131+
# Publish the bare values.schema.json on stable releases only.
132+
# The schema is also bundled inside every chart .tgz (that's
133+
# what Helm itself validates against on install). The bare URL
134+
# exists for IDEs that fetch via the `# yaml-language-server:
135+
# $schema=...` directive — keeping it tied to stable means
136+
# IDE schema reflects what default `helm install` actually
137+
# delivers, not whatever release-tip happens to ship.
138+
if [[ "${channel}" == "stable" ]]; then
139+
aws s3 cp values.schema.json "s3://${{ vars.HELM_S3_BUCKET }}/values.schema.json" \
140+
--cache-control "public, max-age=60"
141+
fi
142+
143+
# Regenerate index.yaml from current bucket state rather than
144+
# appending via --merge. This way, when the lifecycle rule deletes
145+
# expired -release.* tarballs, the next publish naturally drops
146+
# them from the index — no separate cleanup workflow needed.
147+
mkdir -p /tmp/reindex
148+
aws s3 sync "s3://${{ vars.HELM_S3_BUCKET }}/" /tmp/reindex/ \
149+
--exclude '*' --include '*.tgz'
150+
helm repo index /tmp/reindex/ --url "${{ vars.HELM_REPO_URL }}"
151+
152+
# Short cache-control on index.yaml so `helm repo update` sees
153+
# new versions promptly even if the bucket is fronted by a CDN.
154+
# Tarballs are content-addressed (digest in index) so they can
155+
# cache aggressively — leave their default cache-control alone.
156+
aws s3 cp /tmp/reindex/index.yaml "s3://${{ vars.HELM_S3_BUCKET }}/index.yaml" \
157+
--cache-control "public, max-age=60"

0 commit comments

Comments
 (0)