build-push: workflow_dispatch input only to rebuild a single image leg

Gitea cannot re-run one job and a full run takes ~1h. Add a plan job that
turns the optional `only` input (e.g. cac:php85) into one filtered matrix per
image family; image jobs take their matrix from it and are skipped when empty.
Push events / empty input plan the full set as before. Unknown values fail
the plan job. Tests added to multiarch-workflow.test.py.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0185ukXjXyosSkunDsdWz3mb
This commit is contained in:
shadowdaoandClaude Sonnet 5.5 committed 2026-11-12 07:52:13 -08:00
1 parent 4e657304c5
commit b5bb46740d
3 files changed
+277 -6

No files matched your search

+84
View File
@@ -0,0 +1,84 @@
#!/usr/bin/env bash
## plan-legs.sh - decide which image legs of build-push.yaml run.
##
## Env:
## ONLY workflow_dispatch input "only". Empty/blank (and every push
## event) = build everything, exactly as before.
## GITHUB_OUTPUT file the outputs are appended to (set by the runner).
##
## ONLY is a comma list of tokens (spaces ignored):
## cac:php85 cac-fpm:php84 cac-litespeed:php83 cac-lsphp:php82 one leg
## cac | cac-fpm | cac-litespeed | cac-lsphp every PHP version of that image
## shared-httpd | shared-ols that image
## Any other token is an error (exit 2, nothing is written): a typo must fail
## the plan job loudly, never silently build nothing.
##
## Outputs (matrix families = JSON array of phpver, [] = skip the job;
## single-leg images = true|false):
## cac cac_fpm cac_litespeed cac_lsphp shared_httpd shared_ols
set -euo pipefail
MATRIX_FAMILIES=(cac cac-fpm cac-litespeed cac-lsphp)
SINGLE_FAMILIES=(shared-httpd shared-ols)
ALL_PHPVER=(82 83 84 85)
die() { echo "plan-legs: $*" >&2; exit 2; }
only=${ONLY-}
only=${only//[[:space:]]/}
declare -A want_ver want_single
want_all=false
if [ -z "$only" ]; then
want_all=true
else
n=0
IFS=',' read -r -a toks <<< "$only"
for t in "${toks[@]}"; do
[ -n "$t" ] || continue
n=$((n + 1))
fam=${t%%:*}
ver=""
if [ "$fam" != "$t" ]; then
ver=${t#*:}
[ -n "$ver" ] || die "empty PHP version in '$t'"
fi
if [[ " ${MATRIX_FAMILIES[*]} " == *" $fam "* ]]; then
if [ -z "$ver" ]; then
for v in "${ALL_PHPVER[@]}"; do want_ver["$fam:$v"]=1; done
elif [[ "$ver" =~ ^php(8[2-5])$ ]]; then
want_ver["$fam:${BASH_REMATCH[1]}"]=1
else
die "unknown PHP version in '$t' (want ${fam}:php82 .. ${fam}:php85)"
fi
elif [[ " ${SINGLE_FAMILIES[*]} " == *" $fam "* ]]; then
[ "$fam" = "$t" ] || die "'$t': $fam has a single leg, use plain '$fam'"
want_single["$fam"]=1
else
die "unknown image '$t' (known: ${MATRIX_FAMILIES[*]} ${SINGLE_FAMILIES[*]})"
fi
done
[ "$n" -gt 0 ] || die "'only' has no usable entries: '$only'"
fi
matrix_json() { # family
local fam=$1 v out=""
for v in "${ALL_PHPVER[@]}"; do
if $want_all || [ -n "${want_ver["$fam:$v"]-}" ]; then out+="${out:+,}$v"; fi
done
printf '[%s]' "$out"
}
single_json() { # family
if $want_all || [ -n "${want_single["$1"]-}" ]; then echo true; else echo false; fi
}
out=${GITHUB_OUTPUT:-/dev/stdout}
{
echo "cac=$(matrix_json cac)"
echo "cac_fpm=$(matrix_json cac-fpm)"
echo "cac_litespeed=$(matrix_json cac-litespeed)"
echo "cac_lsphp=$(matrix_json cac-lsphp)"
echo "shared_httpd=$(single_json shared-httpd)"
echo "shared_ols=$(single_json shared-ols)"
} >> "$out"
+55 -5
View File
@@ -1,10 +1,22 @@
name: Cloud Apache Container
run-name: ${{ gitea.actor }} pushed a change to trunk
# Manual run. `only` (optional) rebuilds just the named legs instead of every
# image: Gitea cannot re-run a single job and a full run takes ~1h.
# Syntax: comma list of cac:php85, cac-fpm:php84, cac-litespeed:php83,
# cac-lsphp:php82, shared-httpd, shared-ols; a bare family name (cac) = all its
# PHP versions. Empty = everything (same as a push). Unknown values fail the
# plan job. See .gitea/scripts/plan-legs.sh.
on:
push:
branches:
- trunk
workflow_dispatch:
inputs:
only:
description: 'Only build these legs (comma list, e.g. cac:php85 or cac-fpm:php84,shared-httpd). Empty = build everything.'
required: false
default: ''
type: string
jobs:
# Shell gate. Runs FIRST and costs seconds; the images below do not depend on
@@ -67,7 +79,8 @@ jobs:
scripts/tests/image-label-amd64.test.sh \
scripts/tests/index-platforms.test.sh \
scripts/tests/lsphp-abi-gate.test.sh \
.gitea/scripts/resolve-base-image.sh
.gitea/scripts/resolve-base-image.sh \
.gitea/scripts/plan-legs.sh
- name: lsphp repo-version lookup test
run: ./scripts/tests/lsphp-repo-version.test.sh
@@ -96,7 +109,34 @@ jobs:
fi
./scripts/tests/lsphp-abi-gate.test.sh
# Decides which image legs run. A job-level `if:` cannot see the `matrix`
# context, so instead of skipping matrix cells this job emits one filtered
# matrix per image family (and a true/false per single-leg image); each image
# job below takes its matrix from here and is skipped when its list is empty.
# Push events and an empty `only` input plan the full set, as before.
plan:
runs-on: ubuntu-latest
timeout-minutes: 5
outputs:
cac: ${{ steps.plan.outputs.cac }}
cac_fpm: ${{ steps.plan.outputs.cac_fpm }}
cac_litespeed: ${{ steps.plan.outputs.cac_litespeed }}
cac_lsphp: ${{ steps.plan.outputs.cac_lsphp }}
shared_httpd: ${{ steps.plan.outputs.shared_httpd }}
shared_ols: ${{ steps.plan.outputs.shared_ols }}
steps:
- name: Checkout
uses: actions/checkout@v4
- name: Plan legs
id: plan
env:
ONLY: ${{ github.event.inputs.only }}
run: .gitea/scripts/plan-legs.sh
Build-and-Push:
needs: plan
if: needs.plan.outputs.cac != '[]'
runs-on: ubuntu-latest
timeout-minutes: 75
strategy:
@@ -104,7 +144,7 @@ jobs:
# security builds of the others.
fail-fast: false
matrix:
phpver: [82, 83, 84, 85]
phpver: ${{ fromJSON(needs.plan.outputs.cac) }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -262,6 +302,8 @@ jobs:
"${tags[@]}"
Build-FPM-Images:
needs: plan
if: needs.plan.outputs.cac_fpm != '[]'
runs-on: ubuntu-latest
timeout-minutes: 75
strategy:
@@ -269,7 +311,7 @@ jobs:
# security builds of the others.
fail-fast: false
matrix:
phpver: [82, 83, 84, 85]
phpver: ${{ fromJSON(needs.plan.outputs.cac_fpm) }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -429,6 +471,8 @@ jobs:
"${tags[@]}"
Build-LiteSpeed-Images:
needs: plan
if: needs.plan.outputs.cac_litespeed != '[]'
runs-on: ubuntu-latest
# One place for the OLS base tag: digest lookup and build-args both read it.
# OLS_VERSION pinned to 1.8.4 (see the build step); Dockerfile default differs.
@@ -443,7 +487,7 @@ jobs:
# images stop at older OLS releases for those PHP versions, and the
# cac-litespeed tier is a paid premium offering: 8.1+ is the
# modernization story we're selling.
phpver: [82, 83, 84, 85]
phpver: ${{ fromJSON(needs.plan.outputs.cac_litespeed) }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -585,6 +629,8 @@ jobs:
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-litespeed:latest' || '' }}
Build-LSPHP-Images:
needs: plan
if: needs.plan.outputs.cac_lsphp != '[]'
runs-on: ubuntu-latest
# One place for the OLS base tag: digest lookup and build-args both read it.
# OLS_VERSION pinned to 1.8.4 (see the build step); Dockerfile default differs.
@@ -599,7 +645,7 @@ jobs:
# backend for the shared-ols tier and shares the litespeed prebuilt
# base, which only ships lsphp for 8.1+. Keep this matrix in lockstep
# with Build-LiteSpeed-Images.
phpver: [82, 83, 84, 85]
phpver: ${{ fromJSON(needs.plan.outputs.cac_lsphp) }}
steps:
- name: Checkout
uses: actions/checkout@v4
@@ -739,6 +785,8 @@ jobs:
${{ matrix.phpver == '85' && 'repo.anhonesthost.net/cloud-hosting-platform/cac-lsphp:latest' || '' }}
Build-Shared-httpd:
needs: plan
if: needs.plan.outputs.shared_httpd == 'true'
runs-on: ubuntu-latest
timeout-minutes: 50
steps:
@@ -861,6 +909,8 @@ jobs:
--tag latest
Build-Shared-OLS:
needs: plan
if: needs.plan.outputs.shared_ols == 'true'
runs-on: ubuntu-latest
# One place for the OLS base tag: digest lookup and build-args both read it.
env: