Publish Helm Charts (OCI) #8
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: Publish Helm Charts (OCI) | |
| on: | |
| release: | |
| types: [published] | |
| concurrency: | |
| group: publish-oci | |
| cancel-in-progress: false | |
| permissions: {} | |
| env: | |
| REGISTRY: ghcr.io | |
| REGISTRY_PATH: ghcr.io/countly/helm | |
| FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true | |
| jobs: | |
| lint: | |
| name: Lint Charts | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 5 | |
| permissions: | |
| contents: read | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install Helm | |
| uses: azure/setup-helm@v4 | |
| with: | |
| version: v3.17.0 | |
| - name: Lint all charts | |
| run: | | |
| for chart in charts/*/; do | |
| echo "Linting ${chart}..." | |
| helm lint "${chart}" --strict | |
| done | |
| publish: | |
| name: Publish and Sign Charts | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| needs: lint | |
| permissions: | |
| contents: write | |
| packages: write | |
| id-token: write | |
| steps: | |
| - uses: actions/checkout@v5 | |
| - name: Install Helm | |
| uses: azure/setup-helm@v4 | |
| with: | |
| version: v3.17.0 | |
| - name: Install Cosign | |
| uses: sigstore/cosign-installer@v4.1.0 | |
| - name: Install Syft | |
| uses: anchore/sbom-action/download-syft@v0 | |
| - name: Login to GHCR | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| GH_ACTOR: ${{ github.actor }} | |
| run: | | |
| echo "${GITHUB_TOKEN}" | helm registry login ${REGISTRY} -u "${GH_ACTOR}" --password-stdin | |
| echo "${GITHUB_TOKEN}" | cosign login ${REGISTRY} -u "${GH_ACTOR}" --password-stdin | |
| - name: Package, push, sign, and attach SBOM | |
| env: | |
| RELEASE_TAG: ${{ github.event.release.tag_name }} | |
| run: | | |
| mkdir -p /tmp/helm-packages | |
| # Derive chart version from release tag (strip leading 'v' if present) | |
| chart_version="${RELEASE_TAG#v}" | |
| if [ -z "${chart_version}" ]; then | |
| echo "ERROR: Could not derive version from release tag '${RELEASE_TAG}'" | |
| exit 1 | |
| fi | |
| echo "Publishing charts at version: ${chart_version}" | |
| for chart_dir in charts/*/; do | |
| chart_name=$(basename "${chart_dir}") | |
| pkg="/tmp/helm-packages/${chart_name}-${chart_version}.tgz" | |
| echo "::group::${chart_name}:${chart_version}" | |
| # Package with version from release tag (includes prerelease suffix) | |
| helm package "${chart_dir}" --version "${chart_version}" --destination /tmp/helm-packages/ | |
| # Push with retry (transient GHCR failures) | |
| max_retries=3 | |
| push_output="" | |
| push_success=false | |
| for attempt in $(seq 1 $max_retries); do | |
| push_output=$(helm push "${pkg}" "oci://${REGISTRY_PATH}" 2>&1) && { push_success=true; break; } | |
| echo "Push attempt ${attempt}/${max_retries} failed, retrying in 5s..." | |
| sleep 5 | |
| done | |
| if [ "${push_success}" != "true" ]; then | |
| echo "ERROR: helm push failed for ${chart_name} after ${max_retries} attempts" | |
| echo "${push_output}" | |
| exit 1 | |
| fi | |
| echo "${push_output}" | |
| digest=$(echo "${push_output}" | grep -oE 'sha256:[a-f0-9]{64}' | tail -1) | |
| if [ -z "${digest}" ]; then | |
| echo "ERROR: Could not extract digest for ${chart_name}" | |
| exit 1 | |
| fi | |
| ref="${REGISTRY_PATH}/${chart_name}@${digest}" | |
| echo "Digest: ${digest}" | |
| # Sign the OCI artifact (keyless via Sigstore OIDC) | |
| echo "Signing ${chart_name}..." | |
| cosign sign --yes "${ref}" | |
| # Generate SBOM (CycloneDX JSON) | |
| sbom_file="/tmp/helm-packages/${chart_name}-${chart_version}.sbom.cdx.json" | |
| echo "Generating SBOM for ${chart_name}..." | |
| syft dir:"${chart_dir}" -o cyclonedx-json="${sbom_file}" | |
| # Attest SBOM as signed in-toto attestation (replaces deprecated cosign attach sbom) | |
| cosign attest --yes --predicate "${sbom_file}" --type cyclonedx "${ref}" | |
| echo "::endgroup::" | |
| done | |
| - name: Summary and release notes | |
| env: | |
| GH_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| RELEASE_TAG: ${{ github.event.release.tag_name }} | |
| run: | | |
| chart_version="${RELEASE_TAG#v}" | |
| # Build chart listing | |
| chart_list="" | |
| for chart_dir in charts/*/; do | |
| chart_name=$(basename "${chart_dir}") | |
| chart_list="${chart_list} | |
| - \`oci://${REGISTRY_PATH}/${chart_name}:${chart_version}\` — signed + SBOM" | |
| done | |
| # Write job summary | |
| { | |
| echo "## Published Charts" | |
| echo "" | |
| echo "All charts are signed with Cosign (keyless/Sigstore) and include SBOM attestations." | |
| echo "${chart_list}" | |
| } >> "$GITHUB_STEP_SUMMARY" | |
| # Append OCI chart links to release notes | |
| existing_body=$(gh release view "${RELEASE_TAG}" --json body -q '.body') | |
| { | |
| echo "${existing_body}" | |
| echo "" | |
| echo "## OCI Charts" | |
| echo "" | |
| echo '```bash' | |
| echo "helm pull oci://${REGISTRY_PATH}/<chart-name> --version <version>" | |
| echo '```' | |
| echo "${chart_list}" | |
| } | gh release edit "${RELEASE_TAG}" --notes-file - |