Files
sure/.github/workflows/helm-publish.yml
Jeff 7017b6340e fix(helm): normalize appVersion to strip leading v (#2050) (#2156)
* fix(helm): normalize appVersion to strip leading v (#2050)

Releases triggered on a tag like `v0.7.1-rc.1` end up writing
`appVersion: "v0.7.1-rc.1"` into Chart.yaml / the published
index.yaml, but the Docker image is pushed to GHCR without the leading
`v` (`ghcr.io/we-promise/sure:0.7.1-rc.1`). Flux CD / any consumer
that pulls the chart then fails with `ImagePullBackoff` against
`v0.7.1-rc.1` (a tag that doesn't exist).

`normalize_version` is already applied to `CHART_VERSION`; route the
two tag-derived `APP_VERSION` paths through the same helper so the
appVersion matches the published image tag.

Closes #2050

* chore(ci): bind helm-publish version inputs to step env (#2050)

@coderabbitai (zizmor) flagged that the version-resolve step expanded
${{ inputs.chart_version }} and ${{ inputs.app_version }} directly
into bash, which is a template-injection vector — a malicious caller
of this reusable workflow could inject shell via an input like
'; rm -rf … #'.

Bind both inputs to step env (CHART_VERSION_INPUT,
APP_VERSION_INPUT) and reference them as shell variables in the
conditionals. Behaviour is unchanged; the values just arrive through
the env table instead of the runner's template pass.

---------

Co-authored-by: jeffrey701 <jeffrey701@users.noreply.github.com>
2026-06-03 11:53:15 +02:00

175 lines
6.0 KiB
YAML

name: Helm Publish
on:
workflow_call:
inputs:
chart_version:
description: Chart semver version (v-prefix allowed)
required: false
type: string
app_version:
description: App version value for Chart.yaml appVersion
required: false
type: string
update_gh_pages:
description: Whether to publish packaged chart to gh-pages index
required: false
type: boolean
default: true
permissions:
contents: write
jobs:
publish:
if: github.repository == 'we-promise/sure'
runs-on: ubuntu-latest
outputs:
chart_version: ${{ steps.version.outputs.chart_version }}
app_version: ${{ steps.version.outputs.app_version }}
steps:
- name: Checkout
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
persist-credentials: false
fetch-depth: 0
- name: Install Helm
uses: azure/setup-helm@dda3372f752e03dde6b3237bc9431cdc2f7a02a2 # v5.0.0
- name: Resolve chart and app versions
id: version
shell: bash
# Bind workflow inputs to env so the values arrive as shell variables
# instead of being interpolated verbatim by the `${{ }}` runner pass.
# zizmor flags the direct expansion as a template-injection risk.
env:
CHART_VERSION_INPUT: ${{ inputs.chart_version }}
APP_VERSION_INPUT: ${{ inputs.app_version }}
run: |
set -euo pipefail
normalize_version() {
local raw="$1"
echo "${raw#v}"
}
if [ -n "$CHART_VERSION_INPUT" ]; then
CHART_VERSION="$(normalize_version "$CHART_VERSION_INPUT")"
elif [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" == v* ]]; then
CHART_VERSION="$(normalize_version "${GITHUB_REF_NAME}")"
else
CHART_VERSION="0.0.0-nightly.$(date -u +'%Y%m%d.%H%M%S')"
fi
# Normalize APP_VERSION the same way CHART_VERSION is — appVersion
# must match the OCI image tag in GHCR, which is published without a
# leading `v`. Without this, a release on tag `v0.7.1-rc.1` writes
# `appVersion: "v0.7.1-rc.1"` into Chart.yaml / index.yaml, and Helm
# then fails to pull `ghcr.io/we-promise/sure:v0.7.1-rc.1` (the real
# tag is `0.7.1-rc.1`). See #2050.
if [ -n "$APP_VERSION_INPUT" ]; then
APP_VERSION="$(normalize_version "$APP_VERSION_INPUT")"
elif [[ "${GITHUB_REF_TYPE}" == "tag" && "${GITHUB_REF_NAME}" == v* ]]; then
APP_VERSION="$(normalize_version "${GITHUB_REF_NAME}")"
else
APP_VERSION="${CHART_VERSION}"
fi
echo "chart_version=${CHART_VERSION}" >> "$GITHUB_OUTPUT"
echo "app_version=${APP_VERSION}" >> "$GITHUB_OUTPUT"
- name: Update Chart.yaml version
shell: bash
run: |
set -euo pipefail
sed -i -E "s/^version:.*/version: ${{ steps.version.outputs.chart_version }}/" charts/sure/Chart.yaml
sed -i -E "s/^appVersion:.*/appVersion: \"${{ steps.version.outputs.app_version }}\"/" charts/sure/Chart.yaml
- name: Add Helm repositories
run: |
helm repo add cloudnative-pg https://cloudnative-pg.github.io/charts
helm repo add ot-helm https://ot-container-kit.github.io/helm-charts
helm repo update
- name: Build dependencies
run: helm dependency build charts/sure
- name: Package chart
run: |
mkdir -p .cr-release-packages
helm package charts/sure -d .cr-release-packages
- name: Upload packaged chart artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6.0.0
with:
name: helm-chart-package
path: .cr-release-packages/*.tgz
include-hidden-files: true
if-no-files-found: error
retention-days: 7
- name: Checkout gh-pages
if: ${{ inputs.update_gh_pages }}
uses: actions/checkout@93cb6efe18208431cddfb8368fd83d5badbf9bfd # v5.0.1
with:
ref: gh-pages
path: gh-pages
- name: Update index and push
if: ${{ inputs.update_gh_pages }}
env:
GIT_USER_NAME: ${{ github.actor }}
GIT_USER_EMAIL: ${{ github.actor }}@users.noreply.github.com
run: |
set -euo pipefail
CHART_VERSION="${{ steps.version.outputs.chart_version }}"
MAX_ATTEMPTS=5
cp .cr-release-packages/*.tgz gh-pages/
cd gh-pages
git config user.name "$GIT_USER_NAME"
git config user.email "$GIT_USER_EMAIL"
index_and_commit() {
if [ -f index.yaml ]; then
helm repo index . --url https://we-promise.github.io/sure --merge index.yaml
else
helm repo index . --url https://we-promise.github.io/sure
fi
git add .
if git diff --cached --quiet; then
echo "No Helm chart updates to publish."
return 1
fi
git commit -m "Publish chart ${CHART_VERSION}"
}
index_and_commit || exit 0
for attempt in $(seq 1 "$MAX_ATTEMPTS"); do
echo "Push attempt ${attempt}/${MAX_ATTEMPTS}..."
if git push; then
echo "Chart ${CHART_VERSION} published successfully."
exit 0
fi
if [ "$attempt" -eq "$MAX_ATTEMPTS" ]; then
echo "::error::Failed to push after ${MAX_ATTEMPTS} attempts"
exit 1
fi
backoff=$(( attempt * 2 ))
echo "Push failed; retrying in ${backoff}s after rebase..."
sleep "$backoff"
git fetch origin gh-pages
git rebase origin/gh-pages
git reset HEAD~1 --soft 2>/dev/null || true
index_and_commit || { echo "No changes after rebase."; exit 0; }
done