Skip to content

chore(main): release wordpress 5.5.9 (#668) #627

chore(main): release wordpress 5.5.9 (#668)

chore(main): release wordpress 5.5.9 (#668) #627

Workflow file for this run

name: Release Charts to OCI Registry
# Triggered by per-chart release tags (e.g. wordpress-v4.5.0) created by
# release-please when a release PR is merged. The chart name + version are parsed
# from the tag; the chart is packaged, pushed to ghcr.io/slybase/charts, signed
# with Cosign and its ArtifactHub metadata is uploaded via ORAS.
#
# PR validation (helm lint/template) lives in pr-chart-validate.yml.
on:
push:
tags:
- "*-v*"
workflow_dispatch:
inputs:
chart:
description: "Chart to publish"
required: true
default: "wg-easy"
type: choice
options:
- wg-easy
- wireguard
- wordpress
version:
description: "Version to publish (defaults to the chart's Chart.yaml version)"
required: false
type: string
dry_run:
description: "Package only — skip push/sign/publish"
required: false
default: true
type: boolean
force:
description: "Force push even if the version already exists"
required: false
default: false
type: boolean
env:
FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true
HELM_VERSION: v3.18.3
ORAS_VERSION: 1.2.0
SYFT_VERSION: 1.45.1
TRIVY_VERSION: 0.71.0
# Tag-triggered releases publish live when this is "true".
# Set to "false" to revert to dry-run mode (Phase 0 safety net).
RELEASE_ENABLED: "true"
jobs:
oci-release:
name: Release to OCI Registry
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
attestations: write
pull-requests: write
steps:
- name: Resolve release context
id: ctx
shell: bash
env:
EVENT_NAME: ${{ github.event_name }}
REF_NAME: ${{ github.ref_name }}
INPUT_CHART: ${{ github.event.inputs.chart }}
INPUT_VERSION: ${{ github.event.inputs.version }}
INPUT_DRY_RUN: ${{ github.event.inputs.dry_run }}
INPUT_FORCE: ${{ github.event.inputs.force }}
run: |
set -euo pipefail
if [ "$EVENT_NAME" = "workflow_dispatch" ]; then
chart_name="$INPUT_CHART"
chart_version="$INPUT_VERSION" # may be empty -> resolved from Chart.yaml later
dry_run="$INPUT_DRY_RUN"
force="$INPUT_FORCE"
checkout_ref="${{ github.ref }}"
else
# Tag push, e.g. "wordpress-v4.5.0" -> name=wordpress version=4.5.0
tag="$REF_NAME"
chart_name="${tag%-v*}"
chart_version="${tag##*-v}"
if [ "$RELEASE_ENABLED" = "true" ]; then dry_run="false"; else dry_run="true"; fi
force="false"
checkout_ref="$tag"
fi
chart_dir="charts/${chart_name}"
{
echo "chart_name=$chart_name"
echo "chart_version=$chart_version"
echo "chart_dir=$chart_dir"
echo "dry_run=$dry_run"
echo "force=$force"
echo "checkout_ref=$checkout_ref"
} >> "$GITHUB_OUTPUT"
echo "Chart: $chart_name"
echo "Version: ${chart_version:-<from Chart.yaml>}"
echo "Dry run: $dry_run (RELEASE_ENABLED=$RELEASE_ENABLED)"
- name: Checkout
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6
with:
ref: ${{ steps.ctx.outputs.checkout_ref }}
fetch-depth: 1
- name: Install Helm
shell: bash
run: |
set -euo pipefail
archive="helm-${HELM_VERSION}-linux-amd64.tar.gz"
curl -fsSLO "https://get.helm.sh/${archive}"
tar -zxf "$archive"
sudo mv linux-amd64/helm /usr/local/bin/helm
rm -rf "$archive" linux-amd64
helm version --short
- name: Resolve chart version from Chart.yaml (if not provided)
id: chart
shell: bash
env:
CHART_DIR: ${{ steps.ctx.outputs.chart_dir }}
CHART_NAME: ${{ steps.ctx.outputs.chart_name }}
CHART_VERSION: ${{ steps.ctx.outputs.chart_version }}
run: |
set -euo pipefail
if [ ! -f "$CHART_DIR/Chart.yaml" ]; then
echo "::error::No Chart.yaml found at $CHART_DIR"
exit 1
fi
file_version=$(helm show chart "$CHART_DIR" | awk '/^version:/ { print $2 }')
chart_version="${CHART_VERSION:-$file_version}"
if [ -z "$chart_version" ]; then
echo "::error::Could not resolve chart version"
exit 1
fi
# On tag pushes the tag version must match Chart.yaml (release-please keeps them in sync).
if [ -n "$CHART_VERSION" ] && [ "$CHART_VERSION" != "$file_version" ]; then
echo "::warning::Tag/input version ($CHART_VERSION) differs from Chart.yaml ($file_version)"
fi
{
echo "chart_dir=$CHART_DIR"
echo "chart_name=$CHART_NAME"
echo "chart_version=$chart_version"
} >> "$GITHUB_OUTPUT"
- name: Install ORAS
if: steps.ctx.outputs.dry_run != 'true'
shell: bash
run: |
set -euo pipefail
curl -fsSLO "https://github.com/oras-project/oras/releases/download/v${ORAS_VERSION}/oras_${ORAS_VERSION}_linux_amd64.tar.gz"
mkdir -p oras-install/
tar -zxf oras_*.tar.gz -C oras-install/
sudo mv oras-install/oras /usr/local/bin/
rm -rf oras_*.tar.gz oras-install/
oras version
- name: Install Cosign
if: steps.ctx.outputs.dry_run != 'true'
uses: sigstore/cosign-installer@6f9f17788090df1f26f669e9d70d6ae9567deba6 # v4.1.2
- name: Generate GitHub App Token
id: app-token
if: steps.ctx.outputs.dry_run != 'true'
uses: actions/create-github-app-token@bcd2ba49218906704ab6c1aa796996da409d3eb1 # v3
with:
client-id: ${{ secrets.SLYBASE_APP_ID }}
private-key: ${{ secrets.SLYBASE_APP_PRIVATE_KEY }}
owner: SlyBase
continue-on-error: true
- name: Resolve registry authentication
id: registry-auth
if: steps.ctx.outputs.dry_run != 'true'
shell: bash
env:
APP_TOKEN: ${{ steps.app-token.outputs.token }}
FALLBACK_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
# GitHub App installation tokens cannot write to organization-owned
# GHCR packages regardless of the App's declared permissions or the
# package's "Manage Actions access" settings — this is a platform
# limitation, not a config gap (see
# https://github.com/orgs/community/discussions/78090 and
# https://github.com/orgs/community/discussions/50180). Always use
# GITHUB_TOKEN for the OCI push; keep APP_TOKEN generation only for
# whatever else in this workflow may still need App identity.
if [ -n "$FALLBACK_TOKEN" ]; then
selected_token="$FALLBACK_TOKEN"
token_source="github"
else
selected_token="$APP_TOKEN"
token_source="app"
fi
echo "::add-mask::$selected_token"
{
echo "token=$selected_token"
echo "source=$token_source"
} >> "$GITHUB_OUTPUT"
- name: Login to GitHub Container Registry
if: steps.ctx.outputs.dry_run != 'true'
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ steps.registry-auth.outputs.token }}
- name: Package chart
id: package
shell: bash
env:
CHART_DIR: ${{ steps.chart.outputs.chart_dir }}
CHART_NAME: ${{ steps.chart.outputs.chart_name }}
CHART_VERSION: ${{ steps.chart.outputs.chart_version }}
run: |
set -euo pipefail
echo "Packaging chart $CHART_NAME version $CHART_VERSION"
mkdir -p packages
helm dependency build "$CHART_DIR"
helm package "$CHART_DIR" --destination ./packages
echo "package_file=./packages/${CHART_NAME}-${CHART_VERSION}.tgz" >> "$GITHUB_OUTPUT"
ls -la ./packages
- name: Install Syft
shell: bash
run: |
set -euo pipefail
curl -sSfL https://raw.githubusercontent.com/anchore/syft/main/install.sh | sh -s -- -b /usr/local/bin "v${SYFT_VERSION}"
syft version
- name: Generate chart SBOM
id: sbom
shell: bash
env:
CHART_NAME: ${{ steps.chart.outputs.chart_name }}
CHART_VERSION: ${{ steps.chart.outputs.chart_version }}
PACKAGE_FILE: ${{ steps.package.outputs.package_file }}
run: |
set -euo pipefail
mkdir -p sbom
sbom_file="sbom/${CHART_NAME}-${CHART_VERSION}.spdx.json"
syft "$PACKAGE_FILE" -o "spdx-json=${sbom_file}"
echo "sbom_file=${sbom_file}" >> "$GITHUB_OUTPUT"
- name: Resolve referenced images
id: images
shell: bash
env:
CHART_DIR: ${{ steps.chart.outputs.chart_dir }}
run: |
set -euo pipefail
python3 - "$CHART_DIR/Chart.yaml" > images.txt <<'EOF'
import sys
import yaml
with open(sys.argv[1]) as f:
chart = yaml.safe_load(f)
images_yaml = (chart.get("annotations") or {}).get("artifacthub.io/images", "")
for image in yaml.safe_load(images_yaml) or []:
print(image["image"])
EOF
echo "Referenced images:"
cat images.txt
- name: Install Trivy
shell: bash
run: |
set -euo pipefail
curl -sSfL https://raw.githubusercontent.com/aquasecurity/trivy/main/contrib/install.sh | sh -s -- -b /usr/local/bin "v${TRIVY_VERSION}"
trivy version
# Upstream images are not built by us, so CRITICAL findings only warn —
# they must not block publishing the chart.
- name: Trivy image scan (referenced images, non-blocking)
shell: bash
run: |
set -euo pipefail
while IFS= read -r image; do
[ -z "$image" ] && continue
echo "::group::trivy image scan: ${image}"
rc=0
trivy image --severity CRITICAL --exit-code 1 --ignore-unfixed --scanners vuln "$image" || rc=$?
echo "::endgroup::"
if [ "$rc" -ne 0 ]; then
echo "::warning title=Trivy CRITICAL findings::${image} has CRITICAL vulnerabilities (upstream image, release not blocked)"
echo "⚠️ Trivy found CRITICAL vulnerabilities in \`${image}\` (upstream image, release not blocked)" >> "$GITHUB_STEP_SUMMARY"
fi
done < images.txt
- name: Dry run summary
if: steps.ctx.outputs.dry_run == 'true'
shell: bash
env:
CHART_NAME: ${{ steps.chart.outputs.chart_name }}
CHART_VERSION: ${{ steps.chart.outputs.chart_version }}
SBOM_FILE: ${{ steps.sbom.outputs.sbom_file }}
run: |
echo "🧪 DRY RUN — chart packaged but NOT published."
echo "Would push: oci://ghcr.io/slybase/charts/${CHART_NAME}:${CHART_VERSION}"
echo "Would sign + attach SBOM (${SBOM_FILE}) + attest provenance + upload ArtifactHub metadata."
echo "Set RELEASE_ENABLED=true (tag push) or dry_run=false (manual) to publish."
- name: Push chart to OCI registry
if: steps.ctx.outputs.dry_run != 'true'
id: push
shell: bash
env:
CHART_NAME: ${{ steps.chart.outputs.chart_name }}
CHART_VERSION: ${{ steps.chart.outputs.chart_version }}
PACKAGE_FILE: ${{ steps.package.outputs.package_file }}
run: |
set -euo pipefail
echo "Pushing to OCI registry: oci://ghcr.io/slybase/charts"
echo "Chart file: $PACKAGE_FILE"
set -x
push_output=$(helm push "$PACKAGE_FILE" "oci://ghcr.io/slybase/charts" 2>&1) || {
echo "Helm push failed with exit code $?"
echo "$push_output"
exit 1
}
set +x
echo "$push_output"
digest=$(echo "$push_output" | grep -o 'sha256:[a-f0-9]\{64\}' | head -1)
if [ -z "$digest" ]; then
echo "Failed to extract digest from push output"
exit 1
fi
chart_ref="ghcr.io/slybase/charts/${CHART_NAME}@${digest}"
{
echo "chart_digest=$digest"
echo "chart_ref=$chart_ref"
} >> "$GITHUB_OUTPUT"
- name: Sign chart with Cosign
if: steps.ctx.outputs.dry_run != 'true' && steps.push.outputs.chart_ref != ''
run: |
chart_ref="${{ steps.push.outputs.chart_ref }}"
cosign sign --yes "$chart_ref"
- name: Verify chart signature
if: steps.ctx.outputs.dry_run != 'true' && steps.push.outputs.chart_ref != ''
run: |
chart_ref="${{ steps.push.outputs.chart_ref }}"
cosign verify "$chart_ref" \
--certificate-oidc-issuer "https://token.actions.githubusercontent.com" \
--certificate-identity-regexp "^https://github.com/${{ github.repository }}/.github/workflows/oci-release.yaml@refs/(tags|heads)/.+$"
- name: Attach chart SBOM as OCI referrer
if: steps.ctx.outputs.dry_run != 'true' && steps.push.outputs.chart_ref != ''
shell: bash
env:
SBOM_FILE: ${{ steps.sbom.outputs.sbom_file }}
run: |
set -euo pipefail
chart_ref="${{ steps.push.outputs.chart_ref }}"
oras attach --artifact-type "application/spdx+json" \
"$chart_ref" \
"${SBOM_FILE}:application/spdx+json"
- name: Generate provenance attestation for chart artifact
if: steps.ctx.outputs.dry_run != 'true' && steps.push.outputs.chart_ref != ''
uses: actions/attest-build-provenance@a2bbfa25375fe432b6a289bc6b6cd05ecd0c4c32 # v4.1.0
with:
subject-name: ghcr.io/slybase/charts/${{ steps.chart.outputs.chart_name }}
subject-digest: ${{ steps.push.outputs.chart_digest }}
push-to-registry: true
- name: Output chart information
if: steps.ctx.outputs.dry_run != 'true'
run: |
chart_name="${{ steps.chart.outputs.chart_name }}"
chart_version="${{ steps.chart.outputs.chart_version }}"
chart_ref="${{ steps.push.outputs.chart_ref }}"
echo "📦 Chart: $chart_name"
echo "🏷️ Version: $chart_version"
echo "📍 Reference: $chart_ref"
echo "✅ Signed and verified with Cosign"
echo "✅ SBOM attached (application/spdx+json) and provenance attested"
echo ""
echo "To use this chart with Helm:"
echo "helm install my-release oci://ghcr.io/slybase/charts/$chart_name --version $chart_version"
- name: Upload ArtifactHub metadata for this chart
if: steps.ctx.outputs.dry_run != 'true'
shell: bash
env:
CHART_NAME: ${{ steps.chart.outputs.chart_name }}
run: |
set -euo pipefail
if [ -f "charts/$CHART_NAME/artifacthub-repo.yml" ]; then
metadata_file="charts/$CHART_NAME/artifacthub-repo.yml"
else
metadata_file="artifacthub-repo.yml"
fi
tmp_config="$(mktemp)"
tmp_manifest="$(mktemp)"
cleanup() {
rm -f "$tmp_config" "$tmp_manifest"
}
trap cleanup EXIT
printf '{}' > "$tmp_config"
oras push \
"ghcr.io/slybase/charts/$CHART_NAME:artifacthub.io" \
--config "$tmp_config:application/vnd.cncf.artifacthub.config.v1+yaml" \
"$metadata_file:application/vnd.cncf.artifacthub.repository-metadata.layer.v1.yaml"
oras manifest fetch "ghcr.io/slybase/charts/$CHART_NAME:artifacthub.io" > "$tmp_manifest"
jq -e '.config.size > 0 and .config.mediaType == "application/vnd.cncf.artifacthub.config.v1+yaml" and (.layers | length) == 1' "$tmp_manifest"
# Only source of "a release actually shipped" notification: pr-auto-assign.yml
# deliberately does not assign release-please PRs, so without this nobody
# would hear about a completed release bump.
- name: Notify merged release PR
if: steps.ctx.outputs.dry_run != 'true'
shell: bash
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
CHART_NAME: ${{ steps.chart.outputs.chart_name }}
CHART_VERSION: ${{ steps.chart.outputs.chart_version }}
run: |
set -euo pipefail
branch="release-please--branches--main--components--${CHART_NAME}"
pr=$(gh pr list --state merged --search "head:${branch}" \
--json number,mergedAt --limit 1 --jq 'sort_by(.mergedAt) | reverse | .[0].number // empty')
if [ -z "$pr" ]; then
echo "::warning::could not find merged release PR for $branch; skipping notify"
exit 0
fi
gh pr comment "$pr" --body "🚀 @slydlake Released **${CHART_NAME} v${CHART_VERSION}** to \`ghcr.io/slybase/charts\`."