Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 31 additions & 0 deletions .github/workflows/chart-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -233,6 +233,23 @@ jobs:
helm package "${DIR}"
helm push "${NAME}-${version}.tgz" "oci://${ECR_REGISTRY}/charts"

# Alias the pushed chart with a sha-suffixed tag. Dispatch's
# ECR-push backstop recovers commit provenance only from an
# X.Y.Z-<sha7> tag (platform-gitops#1201); a plain-version
# event carries no commit and is ignored. Repo immutability
# blocks re-pushing existing tags but allows aliasing the
# same manifest under a new one. Warn-only: without the
# alias the direct CI notification below still covers the
# release.
SHORT_SHA="${GITHUB_SHA:0:7}"
manifest=$(aws ecr batch-get-image --repository-name "charts/${NAME}" \
--image-ids imageTag="${version}" \
--query 'images[0].imageManifest' --output text)
aws ecr put-image --repository-name "charts/${NAME}" \
--image-tag "${version}-${SHORT_SHA}" \
--image-manifest "$manifest" >/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.
Expand Down Expand Up @@ -261,6 +278,20 @@ jobs:

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.
- name: Notify Dispatch
if: steps.release.outcome == 'success'
uses: pinpredict/.github/actions/notify-dispatch@main
with:
kind: chart
service: ${{ matrix.name }}
version: ${{ steps.release.outputs.version }}
secret: ${{ secrets.CI_WEBHOOK_SECRET }}

- name: Upload release info
if: steps.release.outcome == 'success'
Expand Down
14 changes: 14 additions & 0 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -226,6 +226,20 @@ jobs:

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.
- name: Notify Dispatch
if: steps.release.outcome == 'success'
uses: pinpredict/.github/actions/notify-dispatch@main
with:
kind: image
service: ${{ matrix.name }}
version: ${{ steps.release.outputs.version }}
secret: ${{ secrets.CI_WEBHOOK_SECRET }}

- name: Upload release info
if: steps.release.outcome == 'success'
Expand Down
4 changes: 3 additions & 1 deletion AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,9 @@ Both workflows use a three-shot retry pattern (try / sleep 30 / retry / sleep 60

- **ECR is the version record** (platform-gitops#1201): both release workflows resolve the next version as the highest strict-`X.Y.Z` tag in the service's ECR repo (image or `charts/<name>` OCI) plus one, then probe-and-bump past any existing candidate (ECR tags are immutable). Git tags are never read for versioning.
- **Release marker refs**: `refs/releases/image/<name>` and `refs/releases/chart/<name>` — 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/<name>/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/*`).
- Per-service image tag: `image/<name>/X.Y.Z` (immutable git tag, pushed after successful ECR push). **Legacy mirror** — still minted only because Dispatch correlates on the `create` webhook; goes away once Dispatch is notified directly (#1201 dependency 2).
- **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-<sha7>` (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/<name>/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/<name>/X.Y.Z` — same legacy status.
- Per-service config tag (Kargo freight for `<svc>-config` Warehouse): `vX.Y.Z+<svc>` — 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.

Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ Why `.github` and not a dedicated `github-actions` repo: `.github` is *the* GitH

| Action | Purpose |
|---|---|
| `notify-dispatch` | POSTs a signed publish notification (service, version, SHA, run URL) to Dispatch's public `/dispatch/ci` route after a successful ECR push. Called by both release workflows; warn-only on failure (the EventBridge ECR-push backstop covers a missed call). |
| `discover-services` | Reads `.platform/services/*.yaml` and emits a docker matrix of services whose docker-relevant files changed since their last release (baseline = `refs/releases/image/<name>` marker ref; legacy `image/<name>/*` tag fallback). Also emits `charts_changed`. |
| `validate-platform-service` | Pre-merge static + render check for added/modified `.platform/services/*.yaml`. Renders each via `charts/service-template` for every env in `environments[]` with all `renderXxx` flags forced on; verifies `repositories.chart` resolves to a real `charts/<x>/Chart.yaml`. Closes the gap from platform-gitops#544 — every dis-opticodds-props-streamer failure mode would have failed CI here. |
| `validate-reusable-inputs` | Cross-repo input validation for callers of `pinpredict/.github` reusable workflows. Diffs every `with:` block against the referenced workflow's `on.workflow_call.inputs` map; fails on unknown keys or missing-required keys. Closes the gap left by stock `actionlint`, which can't fetch remote reusable workflows (platform-gitops#1045). Runs automatically as a sibling job in `actionlint.yml`, so any consumer that already `uses:` that reusable workflow inherits it. |
Expand Down
63 changes: 63 additions & 0 deletions actions/notify-dispatch/action.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
name: notify-dispatch
description: >-
Notify Dispatch that an artifact was published (platform-gitops#1201).
POSTs a signed publish notification through the public webhook-forwarder
/dispatch/ci route so the deploy flow's publish stage fills without the
legacy image/*+chart/* git-tag webhook. Never fails the release: a missed
notification is covered by the EventBridge ECR-push backstop.

inputs:
kind:
description: '"image" or "chart"'
required: true
service:
description: Service / chart name (the tag's middle segment)
required: true
version:
description: Released version (X.Y.Z)
required: true
secret:
description: >-
HMAC secret (org Actions secret CI_WEBHOOK_SECRET, reaches reusable
workflows via `secrets: inherit`). Empty skips the notification with
a warning rather than failing — the ECR-push backstop still covers
deploy tracking.
required: true
url:
description: Public /dispatch/ci endpoint (webhook-forwarder API Gateway, dev)
required: false
default: "https://gd7tapt7j8.execute-api.us-east-1.amazonaws.com/dispatch/ci"

runs:
using: composite
steps:
- name: Notify Dispatch
shell: bash
env:
KIND: ${{ inputs.kind }}
SERVICE: ${{ inputs.service }}
VERSION: ${{ inputs.version }}
SECRET: ${{ inputs.secret }}
URL: ${{ inputs.url }}
RUN_URL: ${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}
run: |
set -euo pipefail
if [ -z "${SECRET}" ]; then
echo "::warning::CI_WEBHOOK_SECRET not available — skipping Dispatch notification (ECR-push backstop covers deploy tracking)"
exit 0
fi

body=$(printf '{"repo":"%s","kind":"%s","service":"%s","version":"%s","sha":"%s","run_url":"%s"}' \
"${GITHUB_REPOSITORY}" "${KIND}" "${SERVICE}" "${VERSION}" "${GITHUB_SHA}" "${RUN_URL}")
sig=$(printf '%s' "$body" | openssl dgst -sha256 -hmac "${SECRET}" | awk '{print $NF}')

status=$(curl -sS -o /tmp/dispatch-resp.json -w '%{http_code}' -X POST "${URL}" \
-H "Content-Type: application/json" \
-H "X-Dispatch-Signature: ${sig}" \
--data "$body" || echo "000")

if [ "$status" -ge 200 ] && [ "$status" -lt 300 ]; then
echo "Dispatch notified: ${KIND} ${SERVICE} ${VERSION}"
else
echo "::warning::Dispatch notification returned ${status}: $(cat /tmp/dispatch-resp.json 2>/dev/null || true) — ECR-push backstop covers this release"
fi