Skip to content
Draft
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
48 changes: 40 additions & 8 deletions .github/ISSUE_TEMPLATE/build-request.yml
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,16 @@ body:
version-pinned tag to Docker Hub. **`:latest` is NOT updated** — only the
pinned tag is published.

Hard cap: **3 builds per requester per 24h**. The 4th request in that
window will be labelled `quota-exceeded` and not built.
Rate-limiting is by **serial execution**: one on-demand build runs at a
time; new requests queue. There is no per-requester quota.

Build wall-clock: ~5 min (gha-tools) up to ~30 min (playwright). The
workflow comments here when the build starts and again when it finishes.
Each (OS, OS version) tuple is its own matrix cell — picking
`ubuntu_version=22.04,24.04` builds both as separate cells in parallel.
Tags include the OS version (e.g. `jclaveau/ubuntu-24.04-gha-tools:...`).

Build wall-clock: ~5 min (gha-tools, one cell) up to ~30 min (playwright,
full matrix). The workflow comments here when the build starts and again
when it finishes.
- type: dropdown
id: os
attributes:
Expand All @@ -25,12 +30,39 @@ body:
- All
- ubuntu
- alpine
- type: dropdown
id: ubuntu_version
attributes:
label: Ubuntu version(s)
description: 'Pick one or more. Empty = the Ubuntu default (highest LTS in the list). Each pick is its own matrix cell. List is refreshed weekly by `refresh-os-version-options.yml`.'
multiple: true
options:
- '24.04'
- '22.04'
- '20.04'
- type: input
id: ubuntu_version_extra
attributes:
label: Ubuntu extra version(s)
description: 'Free-text CSV for Ubuntu versions NOT in the dropdown above (e.g. point releases like `22.04.1` or an unreleased rc). Not validated — typos build broken images.'
placeholder: '22.04.1'
- type: dropdown
id: alpine_version
attributes:
label: Alpine version(s)
description: 'Pick one or more. Empty = the Alpine default (highest in the list). Each pick is its own matrix cell. List is refreshed weekly by `refresh-os-version-options.yml`.'
multiple: true
options:
- '3.21'
- '3.20'
- '3.19'
- '3.18'
- type: input
id: os_version
id: alpine_version_extra
attributes:
label: OS version
description: 'e.g. `22.04` for Ubuntu, `3.20` for Alpine. Leave empty to use the default of each picked OS (24.04 / 3.21).'
placeholder: '22.04'
label: Alpine extra version(s)
description: 'Free-text CSV for Alpine versions NOT in the dropdown above. Not validated.'
placeholder: '3.21.3'
- type: dropdown
id: image
attributes:
Expand Down
57 changes: 40 additions & 17 deletions .github/workflows/on-demand-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -73,13 +73,14 @@ jobs:
# All three axes accept CSV / empty. The downstream test-and-publish workflow
# parses them into matrix arrays; an empty value fans out to every default
# entry on that axis (full chain rebuild at the requested version pins).
os: ${{ steps.parse.outputs.os }}
os_version: ${{ steps.parse.outputs.os_version }}
image: ${{ steps.parse.outputs.image }}
flavor: ${{ steps.parse.outputs.flavor }}
node_version: ${{ steps.parse.outputs.node_version }}
pnpm_version: ${{ steps.parse.outputs.pnpm_version }}
pw_version: ${{ steps.parse.outputs.pw_version }}
os: ${{ steps.parse.outputs.os }}
ubuntu_version: ${{ steps.parse.outputs.ubuntu_version }}
alpine_version: ${{ steps.parse.outputs.alpine_version }}
image: ${{ steps.parse.outputs.image }}
flavor: ${{ steps.parse.outputs.flavor }}
node_version: ${{ steps.parse.outputs.node_version }}
pnpm_version: ${{ steps.parse.outputs.pnpm_version }}
pw_version: ${{ steps.parse.outputs.pw_version }}
steps:
- id: ensure-labels
# `gh issue edit --add-label` errors if the label doesn't exist in the
Expand Down Expand Up @@ -132,23 +133,44 @@ jobs:
# on a single line — exactly the CSV format test-and-publish expects, so
# parse just passes the value through verbatim. Friendly flavor names
# (hardened/sudoer) map to matrix values inside the matrices step.
OS=$(opt "OS family")
OS_VERSION=$(opt "OS version")
IMAGE=$(opt "Image variant(s)")
FLAVOR=$(opt "Flavor")
NODE=$(opt "Node version (optional)")
PNPM=$(opt "pnpm version (optional)")
PW=$(opt "Playwright version (optional)")
OS=$(opt "OS family")
UBUNTU=$(opt "Ubuntu version(s)")
UBUNTU_EXTRA=$(opt "Ubuntu extra version(s)")
ALPINE=$(opt "Alpine version(s)")
ALPINE_EXTRA=$(opt "Alpine extra version(s)")
IMAGE=$(opt "Image variant(s)")
FLAVOR=$(opt "Flavor")
NODE=$(opt "Node version (optional)")
PNPM=$(opt "pnpm version (optional)")
PW=$(opt "Playwright version (optional)")
# Merge per-OS dropdown CSV with the free-text extra CSV: concat with
# commas, split, trim, drop empties, dedupe (first-seen order). The
# extras let users pin point releases or rcs without us hardcoding
# every patch version in the dropdown.
merge_csv() {
python3 - "$1" "$2" <<'PY'
import sys
seen = []
for v in (sys.argv[1] + ',' + sys.argv[2]).split(','):
v = v.strip()
if v and v not in seen:
seen.append(v)
print(','.join(seen))
PY
}
UBUNTU_VERSION=$(merge_csv "$UBUNTU" "$UBUNTU_EXTRA")
ALPINE_VERSION=$(merge_csv "$ALPINE" "$ALPINE_EXTRA")
{
echo "os=$OS"
echo "os_version=$OS_VERSION"
echo "ubuntu_version=$UBUNTU_VERSION"
echo "alpine_version=$ALPINE_VERSION"
echo "image=$IMAGE"
echo "flavor=$FLAVOR"
echo "node_version=$NODE"
echo "pnpm_version=$PNPM"
echo "pw_version=$PW"
} >> "$GITHUB_OUTPUT"
echo "Parsed: os='$OS' os_version='$OS_VERSION' image='$IMAGE' flavor='$FLAVOR' node='$NODE' pnpm='$PNPM' pw='$PW'"
echo "Parsed: os='$OS' ubuntu='$UBUNTU_VERSION' alpine='$ALPINE_VERSION' image='$IMAGE' flavor='$FLAVOR' node='$NODE' pnpm='$PNPM' pw='$PW'"

# Earlier revisions had a 3/24h quota step here and an existing-tag
# precheck before that. Both gone: rate-limiting is now serial-execution
Expand Down Expand Up @@ -197,7 +219,8 @@ jobs:
uses: ./.github/workflows/test-and-publish.yml
secrets: inherit
with:
os_version: ${{ needs.prepare.outputs.os_version }}
ubuntu_version: ${{ needs.prepare.outputs.ubuntu_version }}
alpine_version: ${{ needs.prepare.outputs.alpine_version }}
node_version: ${{ needs.prepare.outputs.node_version }}
pnpm_version: ${{ needs.prepare.outputs.pnpm_version }}
pw_version: ${{ needs.prepare.outputs.pw_version }}
Expand Down
124 changes: 124 additions & 0 deletions .github/workflows/refresh-os-version-options.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,124 @@
name: Refresh OS-version dropdown options

# Keep build-request.yml's Ubuntu/Alpine version dropdowns in sync with
# upstream supported releases.
#
# - Runs weekly + on manual dispatch.
# - Source of truth: https://endoflife.date — public, unauthenticated JSON.
# - Filter: non-EOL only. Ubuntu: additionally LTS only (the project doesn't
# build interim Ubuntu releases).
# - On drift, commits + pushes directly to main (no PR — keeps maintenance
# overhead at zero).
# - On fetch failure (e.g. endoflife.date down), exits clean without touching
# the form. A failing fetch must NOT empty the dropdown.
#
# What this workflow does NOT touch:
# - The Dockerfile ARG OS_VERSION default. That's the build's default-cell
# version; bumping it is a deliberate maintainer step, not a refresh.

on:
schedule:
- cron: '0 7 * * 1' # Mondays 07:00 UTC
workflow_dispatch:

permissions:
contents: write

concurrency:
group: refresh-os-version-options
cancel-in-progress: false

jobs:
refresh:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4

- id: fetch
name: Fetch supported versions from endoflife.date
run: |
set -euo pipefail
today=$(date -u +%Y-%m-%d)
# curl: --fail flips non-2xx into a non-zero exit; --max-time caps
# the call so a slow endoflife.date doesn't stall the workflow.
fetch() { curl -sS --max-time 15 --fail "$1"; }
if ! ubuntu_json=$(fetch https://endoflife.date/api/ubuntu.json); then
echo "::warning::endoflife.date Ubuntu fetch failed — skipping refresh"
echo "skip=1" >> "$GITHUB_OUTPUT"
exit 0
fi
if ! alpine_json=$(fetch https://endoflife.date/api/alpine.json); then
echo "::warning::endoflife.date Alpine fetch failed — skipping refresh"
echo "skip=1" >> "$GITHUB_OUTPUT"
exit 0
fi
# Ubuntu: non-EOL AND LTS. `eol` is either false (still supported,
# no date set) or an ISO date; `lts` is either false (interim) or
# an ISO date / true (LTS).
ubuntu_versions=$(jq -c --arg today "$today" '
[ .[]
| select( ((.eol | type) != "string") or (.eol > $today) )
| select( ((.lts | type) == "string") or (.lts == true) )
| .cycle ]
| sort | reverse
' <<< "$ubuntu_json")
# Alpine: non-EOL.
alpine_versions=$(jq -c --arg today "$today" '
[ .[]
| select( ((.eol | type) != "string") or (.eol > $today) )
| .cycle ]
| sort | reverse
' <<< "$alpine_json")
if [ "$ubuntu_versions" = "[]" ] || [ "$alpine_versions" = "[]" ]; then
echo "::warning::endoflife.date returned an empty list for one OS — skipping refresh (would empty the dropdown)"
echo "skip=1" >> "$GITHUB_OUTPUT"
exit 0
fi
echo "ubuntu=$ubuntu_versions" >> "$GITHUB_OUTPUT"
echo "alpine=$alpine_versions" >> "$GITHUB_OUTPUT"
echo "skip=0" >> "$GITHUB_OUTPUT"
echo "Ubuntu LTS (non-EOL): $ubuntu_versions"
echo "Alpine (non-EOL): $alpine_versions"

- name: Patch build-request.yml dropdown options
if: steps.fetch.outputs.skip != '1'
env:
UBUNTU_JSON: ${{ steps.fetch.outputs.ubuntu }}
ALPINE_JSON: ${{ steps.fetch.outputs.alpine }}
FORM: .github/ISSUE_TEMPLATE/build-request.yml
run: |
set -euo pipefail
# mikefarah/yq is preinstalled on ubuntu-latest. `with(...)` scopes
# the assignment so the matched body entry gets its options replaced
# while the rest of the file (other dropdowns, descriptions, formatting)
# stays byte-identical.
yq -i '
with(.body[];
select(.id == "ubuntu_version") |
.attributes.options = (strenv(UBUNTU_JSON) | from_json)) |
with(.body[];
select(.id == "alpine_version") |
.attributes.options = (strenv(ALPINE_JSON) | from_json))
' "$FORM"

- name: Commit + push on drift
if: steps.fetch.outputs.skip != '1'
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
set -euo pipefail
if git diff --quiet -- .github/ISSUE_TEMPLATE/build-request.yml; then
echo "No drift — dropdown options already match upstream."
exit 0
fi
git config user.name 'github-actions[bot]'
git config user.email 'github-actions[bot]@users.noreply.github.com'
git add .github/ISSUE_TEMPLATE/build-request.yml
git commit -m "$(cat <<'EOF'
chore(form): refresh OS-version dropdown options

Source: endoflife.date (Ubuntu LTS + Alpine, non-EOL).
Auto-run by .github/workflows/refresh-os-version-options.yml.
EOF
)"
git push origin HEAD
Loading