diff --git a/.github/workflows/artifacthub-verify.yml b/.github/workflows/artifacthub-verify.yml index 2edda85..eb34290 100644 --- a/.github/workflows/artifacthub-verify.yml +++ b/.github/workflows/artifacthub-verify.yml @@ -59,6 +59,6 @@ jobs: AH_API_KEY_SECRET: ${{ secrets.AH_API_KEY_SECRET }} EXPECTED_ARTIFACTHUB_REPOSITORY_URL: oci://ghcr.io/keiailab/charts/mongodb-operator EXPECTED_CHART_VERSION: ${{ github.event.inputs.tag || github.ref_name }} - ARTIFACTHUB_SMOKE_ATTEMPTS: "30" + ARTIFACTHUB_SMOKE_ATTEMPTS: "90" ARTIFACTHUB_SMOKE_SLEEP_SECONDS: "30" run: bash hack/artifacthub_smoke.sh diff --git a/.github/workflows/helm-publish.yml b/.github/workflows/helm-publish.yml index d6404c4..1638a3b 100644 --- a/.github/workflows/helm-publish.yml +++ b/.github/workflows/helm-publish.yml @@ -76,26 +76,61 @@ jobs: env: GHCR_USER: ${{ github.actor }} GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }} + HELM_GPG_FINGERPRINT: F1A6893583E632A757FF6767F3CC8C6AEC9CEB08 + HELM_SIGNING_PRIVATE_KEY: ${{ secrets.HELM_SIGNING_PRIVATE_KEY }} + HELM_SIGNING_PASSPHRASE: ${{ secrets.HELM_SIGNING_PASSPHRASE }} + WORKFLOW_EVENT_NAME: ${{ github.event_name }} run: | set -euo pipefail VERSION=$(grep '^version:' charts/mongodb-operator/Chart.yaml | awk '{print $2}' | tr -d '"') APP_VERSION=$(grep '^appVersion:' charts/mongodb-operator/Chart.yaml | awk '{print $2}' | tr -d '"') CHART_REF="oci://ghcr.io/keiailab/charts/mongodb-operator" + if [ -z "${HELM_SIGNING_PRIVATE_KEY:-}" ]; then + echo "::error::HELM_SIGNING_PRIVATE_KEY secret is required so Artifact Hub marks OCI charts as signed" + exit 1 + fi echo "$GHCR_TOKEN" | helm registry login ghcr.io -u "$GHCR_USER" --password-stdin + cleanup() { + helm registry logout ghcr.io || true + rm -rf "${SIGNING_GNUPGHOME:-}" out + } + trap cleanup EXIT if helm show chart "$CHART_REF" --version "$VERSION" > /tmp/existing-chart.yaml 2>/tmp/existing-chart.err; then EXISTING_VERSION=$(awk -F': *' '/^version:/ {gsub(/"/, "", $2); print $2; exit}' /tmp/existing-chart.yaml) EXISTING_APP_VERSION=$(awk -F': *' '/^appVersion:/ {gsub(/"/, "", $2); print $2; exit}' /tmp/existing-chart.yaml) - if [ "$EXISTING_VERSION" = "$VERSION" ] && [ "$EXISTING_APP_VERSION" = "$APP_VERSION" ]; then - echo "✓ OCI chart ${VERSION} already exists with appVersion ${APP_VERSION}; skipping push" - helm registry logout ghcr.io || true + if [ "$EXISTING_VERSION" != "$VERSION" ] || [ "$EXISTING_APP_VERSION" != "$APP_VERSION" ]; then + echo "::error::OCI chart ${VERSION} exists with unexpected metadata: version=${EXISTING_VERSION}, appVersion=${EXISTING_APP_VERSION}" + exit 1 + fi + if [ "$WORKFLOW_EVENT_NAME" != "workflow_dispatch" ]; then + echo "✓ OCI chart ${VERSION} already exists with appVersion ${APP_VERSION}; skipping duplicate ${WORKFLOW_EVENT_NAME} publish" exit 0 fi - echo "::error::OCI chart ${VERSION} exists with unexpected metadata: version=${EXISTING_VERSION}, appVersion=${EXISTING_APP_VERSION}" + echo "↻ OCI chart ${VERSION} already exists; workflow_dispatch will republish signed provenance" + fi + SIGNING_GNUPGHOME="$(mktemp -d)" + chmod 700 "$SIGNING_GNUPGHOME" + printf '%s\n' "$HELM_SIGNING_PRIVATE_KEY" > "$SIGNING_GNUPGHOME/private.asc" + gpg --batch --homedir "$SIGNING_GNUPGHOME" --import "$SIGNING_GNUPGHOME/private.asc" >/dev/null + KEY_LISTING="$(gpg --batch --homedir "$SIGNING_GNUPGHOME" --with-colons --list-secret-keys --fingerprint "$HELM_GPG_FINGERPRINT")" + FOUND_FINGERPRINT="$(awk -F: '$1 == "fpr" { print $10; exit }' <<< "$KEY_LISTING")" + SIGNING_KEY_UID="$(awk -F: '$1 == "uid" { print $10; exit }' <<< "$KEY_LISTING")" + if [ "$FOUND_FINGERPRINT" != "$HELM_GPG_FINGERPRINT" ] || [ -z "$SIGNING_KEY_UID" ]; then + echo "::error::Helm signing key fingerprint mismatch or missing UID" exit 1 fi + gpg --batch --homedir "$SIGNING_GNUPGHOME" --yes --export-secret-keys "$HELM_GPG_FINGERPRINT" > "$SIGNING_GNUPGHOME/secring.gpg" + gpg --batch --homedir "$SIGNING_GNUPGHOME" --yes --export "$HELM_GPG_FINGERPRINT" > "$SIGNING_GNUPGHOME/pubring.gpg" + chmod 600 "$SIGNING_GNUPGHOME/secring.gpg" "$SIGNING_GNUPGHOME/pubring.gpg" + SIGN_ARGS=(--sign --key "$SIGNING_KEY_UID" --keyring "$SIGNING_GNUPGHOME/secring.gpg") + if [ -n "${HELM_SIGNING_PASSPHRASE:-}" ]; then + printf '%s' "$HELM_SIGNING_PASSPHRASE" > "$SIGNING_GNUPGHOME/passphrase.txt" + SIGN_ARGS+=(--passphrase-file "$SIGNING_GNUPGHOME/passphrase.txt") + fi mkdir -p out - helm package charts/mongodb-operator -d out/ + helm package "${SIGN_ARGS[@]}" charts/mongodb-operator -d out/ + helm verify "out/mongodb-operator-${VERSION}.tgz" --keyring "$SIGNING_GNUPGHOME/pubring.gpg" + test -s "out/mongodb-operator-${VERSION}.tgz.prov" helm push "out/mongodb-operator-${VERSION}.tgz" oci://ghcr.io/keiailab/charts helm show chart "$CHART_REF" --version "$VERSION" >/dev/null - helm registry logout ghcr.io || true - echo "✓ pushed chart ${VERSION} to oci://ghcr.io/keiailab/charts/mongodb-operator" + echo "✓ pushed signed chart ${VERSION} to oci://ghcr.io/keiailab/charts/mongodb-operator" diff --git a/hack/artifacthub_smoke_test.sh b/hack/artifacthub_smoke_test.sh index 7f0d8c0..0de7042 100755 --- a/hack/artifacthub_smoke_test.sh +++ b/hack/artifacthub_smoke_test.sh @@ -21,6 +21,10 @@ case "$1 $2" in printf 'keiailab-mongodb-operator/mongodb-operator 0.3.0-alpha.16 0.3.0-alpha.16\n' exit 0 ;; + "show chart") + printf 'version: 1.12.4\nappVersion: 1.12.2\n' + exit 0 + ;; esac echo "unexpected helm call: $*" >&2 exit 99 @@ -64,14 +68,14 @@ case "$url" in ;; */repositories/search*) if [[ "${ARTIFACTHUB_TEST_CASE:-missing}" == "registered" ]]; then - printf '[{"repository_id":"repo-id","name":"keiailab-mongodb-operator","url":"https://keiailab.github.io/mongodb-operator","last_tracking_errors":null}]' >"$out" + printf '[{"repository_id":"repo-id","name":"keiailab-mongodb-operator","url":"oci://ghcr.io/keiailab/charts/mongodb-operator","last_tracking_errors":null}]' >"$out" else printf '[]' >"$out" fi ;; - */packages/helm/keiailab-mongodb-operator/mongodb-operator) + */packages/helm/keiailab-mongodb-operator/mongodb-operator/1.12.4) if [[ "${ARTIFACTHUB_TEST_CASE:-missing}" == "registered" ]]; then - printf '{"name":"mongodb-operator"}' >"$out" + printf '{"name":"mongodb-operator","version":"1.12.4","app_version":"1.12.2","signed":true,"repository":{"url":"oci://ghcr.io/keiailab/charts/mongodb-operator"}}' >"$out" else exit 22 fi @@ -91,6 +95,10 @@ export ARTIFACTHUB_API_URL="https://artifacthub.test/api/v1" export ARTIFACTHUB_ORG="keiailab" export ARTIFACTHUB_REPOSITORY_NAME="keiailab-mongodb-operator" export ARTIFACTHUB_PACKAGE_NAME="mongodb-operator" +export EXPECTED_ARTIFACTHUB_REPOSITORY_URL="oci://ghcr.io/keiailab/charts/mongodb-operator" +export EXPECTED_CHART_VERSION="1.12.4" +export EXPECTED_APP_VERSION="1.12.2" +export ARTIFACTHUB_SMOKE_SLEEP_SECONDS="0" export HELM_REPO_URL="https://keiailab.github.io/mongodb-operator" if ARTIFACTHUB_TEST_CASE=missing bash "$repo_root/hack/artifacthub_smoke.sh" >"$tmpdir/missing.out" 2>&1; then