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
69 changes: 66 additions & 3 deletions .github/workflows/chart-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,12 +13,25 @@ name: Helm Chart Release
# No git tags or GitHub Releases are minted — historical
# `chart/<name>/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.
# Auto-discovery remains the default. Callers with a manual service picker can
# pass `only` to force exactly those chart names; this must stay aligned with
# docker-release's `only` input so a targeted release cannot fan out on charts.

on:
workflow_call:
inputs:
only:
description: "Comma-separated chart names to release; empty uses change detection."
required: false
type: string
default: ""
workflow_dispatch:
inputs:
only:
description: "Charts to release: 'all' or comma-separated names."
required: false
type: string
default: "all"

env:
AWS_REGION: us-east-1
Expand All @@ -43,9 +56,35 @@ jobs:

- name: Discover charts needing release
id: set
env:
FORCE: ${{ github.event_name == 'workflow_dispatch' }}
ONLY: ${{ inputs.only }}
run: |
set -euo pipefail
force=${{ github.event_name == 'workflow_dispatch' }}
force="${FORCE:-false}"
scope="${ONLY//[[:space:]]/}"
if [ "$scope" = "all" ]; then
force=true
scope=""
elif [ -n "$ONLY" ] && [ -z "$scope" ]; then
echo "::error::only must be 'all' or a comma-separated chart list"
exit 1
fi

requested_names=()
discovered_names=()
if [ -n "$scope" ]; then
force=true
IFS=',' read -ra candidates <<< "$scope"
for requested_name in "${candidates[@]}"; do
if [[ ! "$requested_name" =~ ^[a-z0-9][a-z0-9-]*$ ]]; then
echo "::error::Invalid chart name '${requested_name}' in only='${ONLY}'"
exit 1
fi
requested_names+=("$requested_name")
done
fi

entries=()

# Per-service IAM push role. All trading services are
Expand Down Expand Up @@ -77,6 +116,15 @@ jobs:
[ -f "${chart_dir}Chart.yaml" ] || continue
name=$(grep '^name:' "${chart_dir}Chart.yaml" | awk '{print $2}')

if [ -n "$scope" ]; then
selected=false
for requested_name in "${requested_names[@]}"; do
[ "$requested_name" = "$name" ] && selected=true
done
[ "$selected" = "true" ] || continue
fi
discovered_names+=("$name")

base=""
if git rev-parse -q --verify "refs/releases/chart/${name}" >/dev/null; then
base="refs/releases/chart/${name}"
Expand All @@ -102,6 +150,21 @@ jobs:
entries+=("{\"name\":\"${name}\",\"dir\":\"${dir}\",\"role\":\"${role}\"}")
done

if [ -n "$scope" ]; then
missing=()
for requested_name in "${requested_names[@]}"; do
found=false
for discovered_name in "${discovered_names[@]-}"; do
[ "$requested_name" = "$discovered_name" ] && found=true
done
[ "$found" = "true" ] || missing+=("$requested_name")
done
if [ ${#missing[@]} -gt 0 ]; then
echo "::error::Requested chart(s) not found: $(IFS=,; echo "${missing[*]}")"
exit 1
fi
fi

if [ ${#entries[@]} -eq 0 ]; then
echo "matrix={\"include\":[]}" >> "$GITHUB_OUTPUT"
echo "any=false" >> "$GITHUB_OUTPUT"
Expand Down
13 changes: 11 additions & 2 deletions .github/workflows/docker-release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ on:
type: boolean
default: false
only:
description: "Comma-separated image ids to release (stevedore --only); empty releases per change detection. Feeds workflow_dispatch service pickers."
description: "Comma-separated image ids to release (stevedore --only), 'all' to force every image, or empty for change detection. Feeds workflow_dispatch service pickers."
required: false
type: string
default: ""
Expand Down Expand Up @@ -137,10 +137,19 @@ jobs:
ONLY: ${{ inputs.only }}
run: |
set -euo pipefail
only="${ONLY//[[:space:]]/}"
if [ "$only" = "all" ]; then
only=$(yq -r '[.images[].id] | join(",")' .stevedore.yaml)
[ -n "$only" ] || { echo "::error::No image ids found in .stevedore.yaml"; exit 1; }
elif [ -n "$ONLY" ] && [ -z "$only" ]; then
echo "::error::only must be 'all' or a comma-separated image list"
exit 1
fi

args="--parallel ${PARALLEL}"
[ "${NO_PUSH}" = "true" ] && args="${args} --no-push"
[ -n "${CHANGED_SINCE}" ] && args="${args} --changed-since ${CHANGED_SINCE}"
[ -n "${ONLY}" ] && args="${args} --only ${ONLY}"
[ -n "$only" ] && args="${args} --only ${only}"
echo "value=${args}" >> "$GITHUB_OUTPUT"

# buildx's type=gha cache backend reads ACTIONS_CACHE_URL /
Expand Down
8 changes: 6 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,8 @@ Why `.github` and not a dedicated `github-actions` repo: `.github` is *the* GitH

| File | Purpose |
|---|---|
| `docker-release.yml` | Image build + release via [stevedore](https://github.com/blairham/stevedore), driven by the caller repo's `.stevedore.yaml` — **no `matrix` input**. Single job by design (pinpredict/.github#39): one runner + one BuildKit, so a build-once Dockerfile compiles once for every image. Version = highest `X.Y.Z` tag in the ECR repo + 1 (ECR is the version record — platform-gitops#1201, resolved by stevedore); advances the `refs/releases/image/<id>` marker refs. No git tags or GitHub Releases. Exports `STEVEDORE_CACHE_FROM/TO` (one `type=gha` scope) for configs that opt into layer caching. Notifies Dispatch once per **pushed** image from stevedore's release summary (`no-push` builds notify nothing). Optional `private-modules: true` mints a short-lived read-only `pinpredict-argocd` App token and exposes it as `GH_PRIVATE_TOKEN`, which the caller's `.stevedore.yaml` wires to a BuildKit `--secret` (`secrets: [{id: gh_token, env: GH_PRIVATE_TOKEN}]`) so a Dockerfile can `go mod download` a private pinpredict module (e.g. `github.com/pinpredict/ppkit`) without vendoring — the Docker analogue of `setup-go`'s `private-modules`; default false. Existing callers are pinned to `@pre-stevedore` (the frozen matrix implementation, which carried the same `private-modules` BuildKit-secret input) — see the tagging exception below. |
| `chart-release.yml` | Auto-discovers `charts/*/`, skips charts unchanged since their `refs/releases/chart/<name>` marker ref, resolves the next version from the ECR OCI repo, packages, pushes (+ `X.Y.Z-<sha7>` provenance alias), advances the marker, notifies Dispatch. No git tags or GitHub Releases. No caller inputs. |
| `docker-release.yml` | Image build + release via [stevedore](https://github.com/blairham/stevedore), driven by the caller repo's `.stevedore.yaml` — **no `matrix` input**. Single job by design (pinpredict/.github#39): one runner + one BuildKit, so a build-once Dockerfile compiles once for every image. Version = highest `X.Y.Z` tag in the ECR repo + 1 (ECR is the version record — platform-gitops#1201, resolved by stevedore); advances the `refs/releases/image/<id>` marker refs. No git tags or GitHub Releases. Optional `only` accepts comma-separated image ids or `all` for manual release pickers. Exports `STEVEDORE_CACHE_FROM/TO` (one `type=gha` scope) for configs that opt into layer caching. Notifies Dispatch once per **pushed** image from stevedore's release summary (`no-push` builds notify nothing). Optional `private-modules: true` mints a short-lived read-only `pinpredict-argocd` App token and exposes it as `GH_PRIVATE_TOKEN`, which the caller's `.stevedore.yaml` wires to a BuildKit `--secret` (`secrets: [{id: gh_token, env: GH_PRIVATE_TOKEN}]`) so a Dockerfile can `go mod download` a private pinpredict module (e.g. `github.com/pinpredict/ppkit`) without vendoring — the Docker analogue of `setup-go`'s `private-modules`; default false. Existing callers are pinned to `@pre-stevedore` (the frozen matrix implementation, which carried the same `private-modules` BuildKit-secret input) — see the tagging exception below. |
| `chart-release.yml` | Auto-discovers `charts/*/`, skips charts unchanged since their `refs/releases/chart/<name>` marker ref, resolves the next version from the ECR OCI repo, packages, pushes (+ `X.Y.Z-<sha7>` provenance alias), advances the marker, notifies Dispatch. No git tags or GitHub Releases. Optional `only` accepts comma-separated chart names for a targeted manual release. |
| `tag-config.yml` | Tags merges to main that touch `.platform/services/<svc>.yaml` with `vX.Y.Z+<svc>` (per-service Kargo `<svc>-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`. |

Expand Down Expand Up @@ -141,13 +141,17 @@ jobs:
uses: pinpredict/.github/.github/workflows/docker-release.yml@main
with:
changed-since: ${{ github.event.before }}
only: ${{ github.event_name == 'workflow_dispatch' && inputs.services || '' }}
secrets: inherit

chart-release:
needs: detect
if: needs.detect.outputs.charts_changed == 'true'
permissions: { id-token: write, contents: write }
uses: pinpredict/.github/.github/workflows/chart-release.yml@main
with:
# Keep this expression identical to docker-release's `only` value.
only: ${{ github.event_name == 'workflow_dispatch' && inputs.services || '' }}
secrets: inherit
```

Expand Down