Skip to content

ci: fix Build ISO skipping draft PRs + unreadable skipped-job name #12

ci: fix Build ISO skipping draft PRs + unreadable skipped-job name

ci: fix Build ISO skipping draft PRs + unreadable skipped-job name #12

Workflow file for this run

# Build ISO — builds the installer AND appliance ISOs for every supported
# architecture.
#
# Each arch builds on a native runner; the build itself runs inside the
# official `nixos/nix` container via `docker run`. We deliberately do NOT put
# the whole job in `container:` — the nixos/nix image lacks a standard glibc
# loader, so GitHub's bundled Node can't run there and every JS action
# (checkout, upload-artifact) fails with `exec .../node: no such file or
# directory`. So checkout/upload run on the host runner and only `make` runs in
# the container.
#
# One job per arch builds BOTH kinds in the SAME container (installer first,
# appliance second). `make <kind>/iso` resolves to the runner's native
# `builtins.currentSystem` and produces
# out/<kind>-iso/iso/coder-box-<kind>-<arch>-linux.iso (+ a .sha256 sidecar);
# the container /nix/store is ephemeral, so we dereference the ISOs into a host
# /dist volume and upload from there.
#
# Triggers / what gets built per kind:
# * push to main, workflow_dispatch → always build both full ISOs.
# * pull_request → realise a kind's full ISO only when the
# PR is ready-for-review (non-draft) AND its label (test-installer-iso /
# test-appliance-iso) is applied; otherwise (draft, or no label) that kind
# is just instantiated (.drv, cheap validation, no image). The `labeled`
# trigger means adding the label kicks off the full build.
# The job always runs (drafts just do derivations) so its name renders rather
# than showing a skipped job's raw name expression; the job name shows the
# per-kind plan (installer/appliance → ISO or drv). Verification artifacts are
# short-lived (1 day).
name: Build ISO
on:
push:
branches: [main]
pull_request:
# `opened`/`reopened` cover a PR created/reopened already non-draft,
# `ready_for_review` a draft promoted to ready, `labeled` so applying a
# test-*-iso label starts the full build, and `synchronize` so pushing new
# commits re-runs the build — re-evaluating the labels so a labelled kind
# is re-built (not just its derivation) on every commit.
types: [opened, reopened, ready_for_review, labeled, synchronize]
workflow_dispatch:
inputs:
ref:
description: "Git ref/commit to build (defaults to the selected branch)"
required: false
type: string
# Cancel superseded runs on the same ref; a full ISO build is expensive so
# don't waste runners on stale commits.
concurrency:
group: build-${{ github.ref }}-${{ github.event.inputs.ref }}
cancel-in-progress: true
jobs:
iso:
# Title shows, at a glance, what each kind is doing: "ISO" (full image) or
# "drv" (derivation only), e.g.
# Build installer ISO + appliance drv (x86_64-linux)
# The literal words "installer"/"appliance" live OUTSIDE the expressions, so
# both kinds stay legible even in the raw form GitHub shows for a skipped job
# (the per-kind suffix just defaults to "drv"). NOTE: the `env` context is
# not available in a job name, so the label/draft checks are inlined here —
# they mirror the INSTALLER_FULL/APPLIANCE_FULL job env below.
name: >-
Build installer ${{ (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'test-installer-iso'))) && 'ISO' || 'drv' }}
+ appliance ${{ (github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'test-appliance-iso'))) && 'ISO' || 'drv' }}
(${{ matrix.system }})
# NOTE: no job-level draft skip. Skipping the whole job on draft PRs (a) left
# the job name unrendered/garbage and (b) meant a draft built nothing here.
# Instead the per-kind FULL flags below treat drafts as "no full build", so
# the job always runs and a draft just instantiates derivations (cheap).
runs-on: ${{ matrix.runner }}
env:
# Per kind: realise the full ISO, or (drv-only) just instantiate. Non-PR
# events (push, manual dispatch) build both full; a PR builds a kind's
# full ISO only when it is ready-for-review (non-draft) AND its label is
# applied. Draft PRs → drv-only for both. Steps below branch on these.
INSTALLER_FULL: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'test-installer-iso')) }}
APPLIANCE_FULL: ${{ github.event_name != 'pull_request' || (github.event.pull_request.draft == false && contains(github.event.pull_request.labels.*.name, 'test-appliance-iso')) }}
strategy:
fail-fast: false
matrix:
include:
- system: x86_64-linux
runner: ubuntu-24.04
- system: aarch64-linux
runner: ubuntu-24.04-arm
steps:
- name: Checkout
uses: actions/checkout@v5
with:
# Empty for push/PR (checks out the event ref); honored for manual
# dispatch to build an arbitrary commit.
ref: ${{ github.event.inputs.ref }}
# Per-kind plan (full ISO vs drv only) is in the job name; the run summary
# below also records it. INSTALLER_FULL / APPLIANCE_FULL come from the
# job-level env above.
- name: Build ISOs
id: build
run: |
# Record the per-kind plan in the run summary for quick scanning.
plan() { [ "$1" = "true" ] && echo "full ISO" || echo "derivation only"; }
{
echo "### Build plan (${{ matrix.system }})"
echo "- installer: $(plan "$INSTALLER_FULL")"
echo "- appliance: $(plan "$APPLIANCE_FULL")"
} >>"$GITHUB_STEP_SUMMARY"
# Host /dist volume, bind-mounted into the container, where full builds
# drop the finished ISO + checksum (the container's /nix/store is
# ephemeral, so the in-tree out/ symlink would dangle on the host).
dist="$(mktemp -d)"
echo "dist=$dist" >>"$GITHUB_OUTPUT"
# Both kinds run in ONE container; installer first, appliance later.
docker run --rm \
-v "$PWD":/work -w /work \
-v "$dist":/dist \
-e INSTALLER_FULL -e APPLIANCE_FULL \
nixos/nix:latest \
sh -euc '
# The Makefile builds via "nix build --impure" and needs flakes +
# nix-command, which the nixos/nix image does not enable by default.
export NIX_CONFIG="experimental-features = nix-command flakes"
# Repo is bind-mounted and owned by a different uid; allow git to
# read it so the Makefile can stamp the build rev.
git config --global --add safe.directory /work
# full build → realise the ISO and copy it (+ its .sha256 sidecar,
# written next to out/) into /dist; otherwise just instantiate the
# derivation. Bare target → native currentSystem (matrix pins the
# runner arch). gnumake provides "make"; git stamps the build rev.
build_kind() {
kind="$1"; full="$2"
if [ "$full" = "true" ]; then
nix-shell -p gnumake git --run "make $kind/iso"
cp -L "out/$kind-iso/iso"/*.iso /dist/
cp "out/coder-box-$kind"-*.iso.sha256 /dist/
else
nix-shell -p gnumake git --run "make $kind/drv"
fi
}
build_kind installer "$INSTALLER_FULL"
build_kind appliance "$APPLIANCE_FULL"
'
ls -lh "$dist"
- name: Upload installer ISO artifact
if: env.INSTALLER_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-installer-${{ matrix.system }}
path: ${{ steps.build.outputs.dist }}/coder-box-installer-*.iso
# Verification build; keep storage cost minimal.
retention-days: 1
if-no-files-found: error
- name: Upload installer checksum artifact
if: env.INSTALLER_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-installer-${{ matrix.system }}-sha256
path: ${{ steps.build.outputs.dist }}/coder-box-installer-*.iso.sha256
retention-days: 1
if-no-files-found: error
- name: Upload appliance ISO artifact
if: env.APPLIANCE_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-appliance-${{ matrix.system }}
path: ${{ steps.build.outputs.dist }}/coder-box-appliance-*.iso
retention-days: 1
if-no-files-found: error
- name: Upload appliance checksum artifact
if: env.APPLIANCE_FULL == 'true'
uses: actions/upload-artifact@v5
with:
name: coder-box-appliance-${{ matrix.system }}-sha256
path: ${{ steps.build.outputs.dist }}/coder-box-appliance-*.iso.sha256
retention-days: 1
if-no-files-found: error