From 5eba2fc4c9cffd0923a0a69516ac3aaf03bdc5d8 Mon Sep 17 00:00:00 2001 From: Pedro Date: Tue, 14 Jul 2026 17:44:06 +0200 Subject: [PATCH] feat(chart-release): add targeted chart scope (pinpredict/trading#2242) --- .github/workflows/chart-release.yml | 69 ++++++++++++++++++++++++++-- .github/workflows/docker-release.yml | 13 +++++- README.md | 8 +++- 3 files changed, 83 insertions(+), 7 deletions(-) diff --git a/.github/workflows/chart-release.yml b/.github/workflows/chart-release.yml index 70f2556..ec5b2af 100644 --- a/.github/workflows/chart-release.yml +++ b/.github/workflows/chart-release.yml @@ -13,12 +13,25 @@ name: Helm Chart Release # 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. +# 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 @@ -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 @@ -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}" @@ -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" diff --git a/.github/workflows/docker-release.yml b/.github/workflows/docker-release.yml index 065b184..44c98be 100644 --- a/.github/workflows/docker-release.yml +++ b/.github/workflows/docker-release.yml @@ -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: "" @@ -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 / diff --git a/README.md b/README.md index 8358ae9..b2d2235 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` | 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/` 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/` 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. | +| `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/` 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/` 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. Optional `only` accepts comma-separated chart names for a targeted manual release. | | `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`. | @@ -141,6 +141,7 @@ 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: @@ -148,6 +149,9 @@ jobs: 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 ```