1c60bdc30d
Revert the hardcoded 16-job experiment: the combination spec belongs
in scripts/sandbox/generate-e2e-matrix.mjs (restored), not copy-pasted
YAML blocks. What survives from the experiment:
* leg names carry everything - 'os: install -> update (tag -> HEAD)' -
generated per entry, since slash-joined names are all the graph
renders;
* pick-releases annotates each tag ({ref, desktop}) and the generator
threads tag_has_desktop onto windows entries, so the windows run
workflow still gates pre-desktop tags without a probe job;
* the per-OS run workflows are untouched: single job, static 'e2e'
name, native skip gates own all capability knowledge.
install-e2e.yml is one generate job + three per-OS matrix fanouts.
Generator shape (4/16/12 legs for 2 tags), annotation threading, and
all six error paths verified; all four workflows pass actionlint;
driver parses clean pure-ASCII.
150 lines
6.3 KiB
YAML
150 lines
6.3 KiB
YAML
name: Install & Update E2E (reusable)
|
|
|
|
# Runs ONE {install-method, update-method} combination against ONE starting
|
|
# commit, in the dev sandbox, with a real install (uv, a managed Python,
|
|
# Node, the venv) behind it.
|
|
#
|
|
# Reusable so callers can fan out over the combinations that matter --
|
|
# update from the tip vs. from an older release, `hermes update` vs.
|
|
# re-running the installer -- without duplicating the runner setup. Each leg
|
|
# is independent: its own sandbox, its own install, nothing rewound or
|
|
# shared.
|
|
#
|
|
# Method ids come from scripts/sandbox/generate-e2e-matrix.mjs. Supported
|
|
# today: install via curl-bash, update via hermes-update or curl-bash.
|
|
# Anything else NATIVELY SKIPS (grey check, no runner): capability
|
|
# knowledge lives here, next to the driver, so the caller can dispatch
|
|
# every declared combination without knowing which ones work.
|
|
#
|
|
# Call it:
|
|
#
|
|
# jobs:
|
|
# tip:
|
|
# uses: ./.github/workflows/install-e2e-run.yml
|
|
# with:
|
|
# install-method: curl-bash
|
|
# update-method: hermes-update
|
|
# install-ref: refs/heads/main
|
|
|
|
on:
|
|
workflow_call:
|
|
inputs:
|
|
install-method:
|
|
description: 'How the starting version gets installed. Supported: curl-bash (the real curl | install.sh one-liner).'
|
|
required: true
|
|
type: string
|
|
update-method:
|
|
description: 'How the install updates to HEAD. Supported: hermes-update (the updater) or curl-bash (re-run the one-liner). Declared-but-TODO methods skip.'
|
|
required: true
|
|
type: string
|
|
install-ref:
|
|
description: 'What to install before updating: a branch, a tag (v2026.7.7), or a SHA reachable from main.'
|
|
required: false
|
|
type: string
|
|
default: refs/heads/main
|
|
runner:
|
|
description: 'Runner label.'
|
|
required: false
|
|
type: string
|
|
default: ubuntu-latest
|
|
timeout-minutes:
|
|
description: 'Job timeout. A cold run installs real toolchains twice.'
|
|
required: false
|
|
type: number
|
|
default: 45
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
e2e:
|
|
# Static name on purpose: the caller's job name already carries the
|
|
# method pair, and GitHub renders name expressions UNEXPANDED (literal
|
|
# "${{ inputs... }}") on natively skipped jobs. Short because it is
|
|
# only a rendered tail ("<tag> -> HEAD / e2e").
|
|
name: e2e
|
|
# The pairs the sandbox driver can run today; anything else is a
|
|
# declared TODO and natively skips.
|
|
if: inputs.install-method == 'curl-bash' && contains(fromJSON('["hermes-update", "curl-bash"]'), inputs.update-method)
|
|
runs-on: ${{ inputs.runner }}
|
|
timeout-minutes: ${{ inputs.timeout-minutes }}
|
|
|
|
steps:
|
|
# Full history: the sandbox fetches the starting commit and the test
|
|
# compares against this commit, so a shallow clone is not enough.
|
|
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
# bubblewrap + slirp4netns are what the sandbox is built on; util-linux
|
|
# supplies the `unshare` that builds the multi-uid userns for the
|
|
# user-level (non-root) install.
|
|
- name: Install sandbox dependencies
|
|
run: |
|
|
set -euo pipefail
|
|
sudo apt-get update -qq
|
|
sudo apt-get install -y -qq bubblewrap slirp4netns uidmap util-linux
|
|
|
|
# Ubuntu 24.04 restricts unprivileged user namespaces through AppArmor,
|
|
# which is exactly what bwrap needs. Report the state before touching it
|
|
# so a future runner-image change is visible in the log rather than
|
|
# silently altering what this job proves.
|
|
- name: Permit unprivileged user namespaces
|
|
run: |
|
|
set -euo pipefail
|
|
echo "--- kernel userns settings (before)"
|
|
sysctl kernel.unprivileged_userns_clone 2>/dev/null || echo " (sysctl absent)"
|
|
sysctl kernel.apparmor_restrict_unprivileged_userns 2>/dev/null || echo " (sysctl absent)"
|
|
if sysctl -n kernel.apparmor_restrict_unprivileged_userns >/dev/null 2>&1; then
|
|
sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0
|
|
fi
|
|
echo "--- subuid/subgid for $(id -un)"
|
|
grep "^$(id -un):" /etc/subuid /etc/subgid || echo " (none — sandbox will say so)"
|
|
|
|
- name: Run install + update E2E
|
|
run: |
|
|
set -euo pipefail
|
|
# Method id -> the driver script's --route vocabulary. The one
|
|
# place that knows both names.
|
|
case '${{ inputs.update-method }}' in
|
|
hermes-update) route=update ;;
|
|
curl-bash) route=installer ;;
|
|
*) echo "unreachable: update-method passed the job-level gate but has no route mapping" >&2; exit 1 ;;
|
|
esac
|
|
tests/install/install-update-e2e.sh \
|
|
--route "$route" \
|
|
--install-ref '${{ inputs.install-ref }}'
|
|
env:
|
|
# Outside the workspace on purpose: the script creates this directory
|
|
# up front, and an untracked dir inside the repo makes the worktree
|
|
# dirty -- which dev-sandbox reacts to by snapshotting the working
|
|
# copy into a fresh fake-main commit on every invocation, moving the
|
|
# update target mid-run.
|
|
HERMES_E2E_LOG_DIR: ${{ runner.temp }}/e2e-logs
|
|
|
|
# Artifact names cannot contain '/', and install-ref may be a full ref
|
|
# like refs/heads/main. GitHub Actions expressions have no string-replace
|
|
# function, so build the safe name here. Runs even on failure -- that is
|
|
# exactly when the logs are wanted.
|
|
- name: Build artifact name
|
|
if: always()
|
|
id: artifact
|
|
run: |
|
|
set -euo pipefail
|
|
safe_ref='${{ inputs.install-ref }}'
|
|
safe_ref="${safe_ref//\//-}"
|
|
echo "name=install-e2e-${{ inputs.update-method }}-${safe_ref}" >> "$GITHUB_OUTPUT"
|
|
|
|
# The installer's own transcripts say far more than the assertion that
|
|
# tripped when a real install breaks.
|
|
- name: Upload installer logs
|
|
if: always()
|
|
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
with:
|
|
# Unique per leg: a matrix over releases runs this workflow several
|
|
# times per route, and same-named artifacts collide.
|
|
name: ${{ steps.artifact.outputs.name }}-${{ github.sha }}
|
|
path: ${{ runner.temp }}/e2e-logs
|
|
retention-days: 14
|
|
if-no-files-found: ignore
|