diff --git a/.github/workflows/chart-release.yml b/.github/workflows/chart-release.yml index a75fe30..70f2556 100644 --- a/.github/workflows/chart-release.yml +++ b/.github/workflows/chart-release.yml @@ -8,9 +8,10 @@ name: Helm Chart Release # tag as fallback) unless workflow_dispatch forced a release. # - Resolves the next version from the ECR OCI repo (highest X.Y.Z # tag + 1 — the artifact store is the version record, -# platform-gitops#1201), packages, pushes, advances the marker ref, -# mints the legacy git tag, contributes to one aggregated GitHub -# Release. +# platform-gitops#1201), packages, pushes (plus the X.Y.Z- +# provenance alias), advances the marker ref, and notifies Dispatch. +# No git tags or GitHub Releases are minted — historical +# `chart//X.Y.Z` tags remain as the frozen fallback baseline. # # Caller has no inputs; auto-discovery handles everything. Each repo's # `ci.yml` decides whether to call this workflow. @@ -156,9 +157,7 @@ jobs: set -euo pipefail # The ECR OCI repo is the version record: next version is a - # patch bump of the highest X.Y.Z tag already pushed. Git tags - # are a legacy mirror, still minted below for deploy - # correlation but no longer read (platform-gitops#1201). A + # patch bump of the highest X.Y.Z tag already pushed. A # missing repo or an empty tag list both mean first release. latest=$(aws ecr describe-images \ --repository-name "charts/${NAME}" \ @@ -184,7 +183,6 @@ jobs: patch=$((patch + 1)) done version="${major}.${minor}.${patch}" - tag="chart/${NAME}/${version}" # Stamp the resolved version into Chart.yaml before packaging sed -i "s/^version:.*/version: ${version}/" "${DIR}/Chart.yaml" @@ -221,25 +219,6 @@ jobs: >/dev/null } || echo "::warning::sha alias tag failed for charts/${NAME}:${version}-${SHORT_SHA}" - # Tag immediately after successful push (retry up to 3 times). - # If permanent failure the chart sits in ECR with no git tag — - # the next run's recovery loop will reclaim it. - git tag "$tag" - tag_pushed=false - for attempt in 1 2 3; do - if git push origin "$tag"; then - tag_pushed=true - break - fi - echo "::warning::Git tag push attempt ${attempt}/3 failed for ${tag}" - sleep 2 - done - - if [ "$tag_pushed" != true ]; then - echo "::error::Git tag push failed after 3 attempts for ${tag} — chart already in ECR!" - exit 1 - fi - # Advance the per-chart release marker — one mutable ref, not # a tag, so it is invisible to Kargo's tag enumeration and the # GitHub create-webhook. The prepare job diffs against it to @@ -247,14 +226,11 @@ jobs: git push --force origin "${GITHUB_SHA}:refs/releases/chart/${NAME}" \ || echo "::warning::Marker ref push failed for refs/releases/chart/${NAME} — next run falls back to the legacy tag baseline" - mkdir -p release-info - echo "${NAME} ${version}" > "release-info/${NAME}.txt" echo "version=${version}" >> "$GITHUB_OUTPUT" - # Direct publish signal to Dispatch (platform-gitops#1201) — the - # replacement for the create-webhook on the legacy git tag. Never - # fails the release; the EventBridge ECR-push backstop covers a - # missed notification. + # The publish signal (platform-gitops#1201). Never fails the + # release; the EventBridge ECR-push backstop covers a missed + # notification. - name: Notify Dispatch if: steps.release.outcome == 'success' uses: pinpredict/.github/actions/notify-dispatch@main @@ -264,53 +240,15 @@ jobs: version: ${{ steps.release.outputs.version }} secret: ${{ secrets.CI_WEBHOOK_SECRET }} - - name: Upload release info - if: steps.release.outcome == 'success' - uses: actions/upload-artifact@v7 - with: - name: chart-release-info-${{ matrix.name }} - path: release-info/${{ matrix.name }}.txt - retention-days: 1 - + # Kept as the aggregate fail-gate (stable check name for branch + # protection). The GitHub release it used to create was keyed off the + # legacy chart/* git tag; Dispatch's Slack cards carry the per-service + # changelog now (platform-gitops#1201). release-summary: needs: package if: ${{ !cancelled() && needs.package.result != 'skipped' }} runs-on: ubuntu-latest - permissions: - contents: write - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v6 - - - name: Download chart-release-info artifacts - uses: actions/download-artifact@v7 - with: - path: release-info - pattern: chart-release-info-* - merge-multiple: true - - - name: Create aggregated GitHub release - run: | - set -euo pipefail - shopt -s nullglob - released=() - for f in release-info/*.txt; do - released+=("$(cat "$f")") - done - - if [ ${#released[@]} -eq 0 ]; then - echo "No charts were released." - exit 0 - fi - - title="Charts: $(IFS=', '; echo "${released[*]}")" - first_name="${released[0]%% *}" - first_ver="${released[0]##* }" - gh release create "chart/${first_name}/${first_ver}" \ - --generate-notes \ - --title "$title" - - name: Fail if any package failed if: needs.package.result != 'success' run: | diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index eca3350..54a8771 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -21,11 +21,10 @@ name: Docker Release # store is the version record (platform-gitops#1201). Each successful # build pushes the image, advances the `refs/releases/image/` # marker ref (the change-detection baseline for discover-services), -# creates an immutable git tag `image//X.Y.Z`, and contributes to -# a single aggregated GitHub Release at the end of the run. Dispatch -# still correlates on the per-service git tags via the `create` webhook; -# the tags and Releases go away once Dispatch is notified directly -# (#1201 dependency 2). +# and notifies Dispatch directly (signed POST via notify-dispatch; +# the EventBridge ECR-push backstop covers a missed call). No git tags +# or GitHub Releases are minted — the historical `image//X.Y.Z` +# tags remain in repos as the frozen fallback baseline only. on: workflow_call: @@ -119,7 +118,6 @@ jobs: BUILD_ARGS: ${{ matrix.build_args }} ECR_REGISTRY: ${{ steps.ecr-login.outputs.registry }} SHORT_SHA: ${{ github.sha }} - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} # Empty unless private-modules minted a token above; gates the # BuildKit --secret so it's a no-op for every other caller. GH_PRIVATE_TOKEN: ${{ steps.private-module-token.outputs.token }} @@ -129,10 +127,8 @@ jobs: # The ECR repo is the version record: next version is a patch # bump of the highest X.Y.Z tag already pushed (the - - # sibling tags are filtered out). Git tags are a legacy mirror, - # still minted below for deploy correlation but no longer read - # (platform-gitops#1201). A missing repo or an empty tag list - # both mean first release. + # sibling tags are filtered out). A missing repo or an empty + # tag list both mean first release. latest=$(aws ecr describe-images \ --repository-name "${ECR_REPO}" \ --filter tagStatus=TAGGED \ @@ -157,7 +153,6 @@ jobs: patch=$((patch + 1)) done version="${major}.${minor}.${patch}" - tag="image/${NAME}/${version}" echo "Building ${ECR_REPO} v${version} from ${DOCKERFILE}${TARGET:+ (target=${TARGET})}" @@ -204,25 +199,6 @@ jobs: docker buildx build "${buildx_args[@]}" . - # Tag immediately after successful push (retry up to 3 times). - # If the tag push fails permanently the image is in ECR with no - # git tag — the next run's recovery loop will reclaim it. - git tag "$tag" - tag_pushed=false - for attempt in 1 2 3; do - if git push origin "$tag"; then - tag_pushed=true - break - fi - echo "::warning::Git tag push attempt ${attempt}/3 failed for ${tag}" - sleep 2 - done - - if [ "$tag_pushed" != true ]; then - echo "::error::Git tag push failed after 3 attempts for ${tag} — image already in ECR!" - exit 1 - fi - # Advance the per-service release marker — one mutable ref, # not a tag, so it is invisible to Kargo's tag enumeration and # the GitHub create-webhook. discover-services diffs against @@ -230,14 +206,11 @@ jobs: git push --force origin "${GITHUB_SHA}:refs/releases/image/${NAME}" \ || echo "::warning::Marker ref push failed for refs/releases/image/${NAME} — next run falls back to the legacy tag baseline" - mkdir -p release-info - echo "${NAME} ${version}" > "release-info/${NAME}.txt" echo "version=${version}" >> "$GITHUB_OUTPUT" - # Direct publish signal to Dispatch (platform-gitops#1201) — the - # replacement for the create-webhook on the legacy git tag. Never - # fails the release; the EventBridge ECR-push backstop covers a - # missed notification. + # The publish signal (platform-gitops#1201). Never fails the + # release; the EventBridge ECR-push backstop covers a missed + # notification. - name: Notify Dispatch if: steps.release.outcome == 'success' uses: pinpredict/.github/actions/notify-dispatch@main @@ -247,53 +220,15 @@ jobs: version: ${{ steps.release.outputs.version }} secret: ${{ secrets.CI_WEBHOOK_SECRET }} - - name: Upload release info - if: steps.release.outcome == 'success' - uses: actions/upload-artifact@v7 - with: - name: release-info-${{ matrix.name }} - path: release-info/${{ matrix.name }}.txt - retention-days: 1 - + # Kept as the aggregate fail-gate (stable check name for branch + # protection). The GitHub release it used to create was keyed off the + # legacy image/* git tag; Dispatch's Slack cards carry the per-service + # changelog now (platform-gitops#1201). release-summary: needs: build if: ${{ !cancelled() && needs.build.result != 'skipped' }} runs-on: ubuntu-latest - permissions: - contents: write - env: - GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} steps: - - uses: actions/checkout@v6 - - - name: Download release-info artifacts - uses: actions/download-artifact@v7 - with: - path: release-info - pattern: release-info-* - merge-multiple: true - - - name: Create aggregated GitHub release - run: | - set -euo pipefail - shopt -s nullglob - released=() - for f in release-info/*.txt; do - released+=("$(cat "$f")") - done - - if [ ${#released[@]} -eq 0 ]; then - echo "No services were released." - exit 0 - fi - - title="Release: $(IFS=', '; echo "${released[*]}")" - first_name="${released[0]%% *}" - first_ver="${released[0]##* }" - gh release create "image/${first_name}/${first_ver}" \ - --generate-notes \ - --title "$title" - - name: Fail if any build failed if: needs.build.result != 'success' run: | diff --git a/AGENTS.md b/AGENTS.md index 55b567b..1521335 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -38,11 +38,10 @@ Both workflows use a three-shot retry pattern (try / sleep 30 / retry / sleep 60 - **Release marker refs**: `refs/releases/image/` and `refs/releases/chart/` — one mutable ref per service, force-advanced to the released SHA on every successful push. They are the change-detection baseline for `discover-services` and chart-release's prepare job (legacy `image|chart//X.Y.Z` tag is the fallback until a service releases once with the marker in place). Not tags, so they don't feed Kargo's tag enumeration or the GitHub `create` webhook. Readers must fetch them explicitly (`+refs/releases/*:refs/releases/*`). - **Dispatch publish notification**: after each successful push, both release workflows call `actions/notify-dispatch` — a signed POST (service, version, full SHA, run URL) through the public webhook-forwarder `/dispatch/ci` route, HMAC'd with the org `CI_WEBHOOK_SECRET` (reaches reusable workflows via `secrets: inherit`). Warn-only on failure: the EventBridge ECR-push backstop covers a missed call. - **Chart sha alias**: chart-release aliases the pushed OCI chart as `X.Y.Z-` (via `aws ecr put-image` on the same manifest — allowed under tag immutability) so the ECR-push backstop can recover commit provenance for charts, mirroring the image tag pair. Semver-prerelease form, so Kargo chart Warehouses ignore it. -- Per-service image tag: `image//X.Y.Z` (immutable git tag, pushed after successful ECR push). **Legacy mirror** — the direct notification above supersedes the `create`-webhook correlation; the tags are removed in #1201's final step once the new signals are verified. -- Per-chart tag: `chart//X.Y.Z` — same legacy status. +- **No `image/*` / `chart/*` git tags and no GitHub Releases are minted** (platform-gitops#1201). The historical tags remain in service repos as the *frozen* fallback change-detection baseline — do not prune them until every service has released once with marker refs (the readers fall back to them). Do not reintroduce tag minting: it spams Kargo's git tag enumeration and the GitHub `create` webhook. - Per-service config tag (Kargo freight for `-config` Warehouse): `vX.Y.Z+` — semver **build metadata** form (`+`), not pre-release (`-`). The `+` form passes `semverConstraint: ">=0.0.0"` cleanly; the pre-release form would require `>=0.0.0-0`. **This tag family stays** — it is genuine Kargo freight. -Pinpoint/Dispatch (our deploy correlator) hooks the GitHub `create` webhook on `image//*` tags — keep the tag format stable until the direct CI→Dispatch notification lands. +Dispatch (our deploy correlator) learns about publishes from the notify-dispatch POST plus the EventBridge ECR-push backstop; its legacy `create`-webhook handler remains but no longer receives artifact tags. ### `tag-config.yml` → platform-gitops dispatch diff --git a/README.md b/README.md index 714f02e..9ec0522 100644 --- a/README.md +++ b/README.md @@ -10,8 +10,8 @@ Why `.github` and not a dedicated `github-actions` repo: `.github` is *the* GitH | File | Purpose | |---|---| -| `docker-release.yml` | Matrix-based image build + push to ECR. Version = highest `X.Y.Z` tag in the ECR repo + 1 (ECR is the version record — platform-gitops#1201); advances the `refs/releases/image/` marker ref, mints the legacy `image//X.Y.Z` git tag (Dispatch correlation, being phased out), aggregated GitHub Release. Caller passes a `matrix` input in the standard `{include:[...]}` shape. Optional `private-modules: true` mints a short-lived read-only `pinpredict-argocd` App token and exposes it to the build as BuildKit secret `id=gh_token` (`RUN --mount=type=secret,id=gh_token …`) — for Dockerfiles that fetch a private pinpredict module (e.g. `github.com/pinpredict/ppkit`) instead of vendoring it. Default false. | -| `chart-release.yml` | Auto-discovers `charts/*/`, skips charts unchanged since their `refs/releases/chart/` marker ref, resolves the next version from the ECR OCI repo, packages, pushes, advances the marker, mints the legacy `chart//X.Y.Z` tag. No caller inputs. | +| `docker-release.yml` | Matrix-based image build + push to ECR. Version = highest `X.Y.Z` tag in the ECR repo + 1 (ECR is the version record — platform-gitops#1201); advances the `refs/releases/image/` marker ref and notifies Dispatch. No git tags or GitHub Releases. Caller passes a `matrix` input in the standard `{include:[...]}` shape. Optional `private-modules: true` mints a short-lived read-only `pinpredict-argocd` App token and exposes it to the build as BuildKit secret `id=gh_token` (`RUN --mount=type=secret,id=gh_token …`) — for Dockerfiles that fetch a private pinpredict module (e.g. `github.com/pinpredict/ppkit`) instead of vendoring it. Default false. | +| `chart-release.yml` | Auto-discovers `charts/*/`, skips charts unchanged since their `refs/releases/chart/` marker ref, resolves the next version from the ECR OCI repo, packages, pushes (+ `X.Y.Z-` provenance alias), advances the marker, notifies Dispatch. No git tags or GitHub Releases. No caller inputs. | | `tag-config.yml` | Tags merges to main that touch `.platform/services/.yaml` with `vX.Y.Z+` (per-service Kargo `-config` Warehouse freight), then dispatches `service-config-tag` to platform-gitops so missing pointer files get seeded. | | `actionlint.yml` | Lints GitHub Actions workflow YAML with [`actionlint`](https://github.com/rhysd/actionlint) at a pinned version. Self-runs on this repo when PRs/pushes touch `.github/workflows/**` or `actions/**/action.yml`; callers reuse it via `uses: pinpredict/.github/.github/workflows/actionlint.yml@main`. |