Skip to content
Merged
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
63 changes: 63 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,8 @@ jobs:
with:
version: ${{ matrix.version }}
realm: e2e
# sind v0.10.0 and older ignore wait with a warning.
wait: 10m
clusters: |
- .github/e2e/cluster.yml
- kind: Cluster
Expand All @@ -58,6 +60,67 @@ jobs:
[[ "$CLUSTERS" == "file,inline" ]]
[[ "$VERSION" == v* ]]

# Runs install.sh again with a curl that flips one byte of the
# downloaded binary: the checksum check must reject it, even with
# verify: false, and with checksums.txt rewritten to match, the
# attestation check must. sind v0.8.0 has neither to check.
- name: Check that a modified binary is rejected
if: matrix.version == 'latest'
env:
GH_TOKEN: ${{ github.token }}
SIND_VERSION: ${{ steps.sind.outputs.version }}
run: |
gh --version
stub=$(mktemp -d)
REAL_CURL=$(command -v curl)
export REAL_CURL
cat > "$stub/curl" <<'EOF'
#!/usr/bin/env bash
set -euo pipefail
"$REAL_CURL" "$@"
out=
while (($#)); do
[[ "$1" == --output ]] && out=$2
shift
done
case "$out" in
*/sind-linux-*)
byte=$(od -An -tu1 -j4096 -N1 "$out")
printf "\\$(printf '%03o' $((byte ^ 1)))" |
dd of="$out" bs=1 seek=4096 count=1 conv=notrunc status=none
;;
*/checksums.txt)
if [[ "${MATCH_CHECKSUM:-}" == true ]]; then
(cd "${out%/*}" && sha256sum sind-linux-*) > "$out"
fi
;;
esac
EOF
chmod +x "$stub/curl"

installed=$(sha256sum ~/.local/bin/sind)
expect_rejected() { # expected error, env for install.sh
local want=$1
shift
if env PATH="$stub:$PATH" GITHUB_OUTPUT=/dev/null GITHUB_PATH=/dev/null "$@" \
bash scripts/install.sh > "$stub/log" 2>&1; then
cat "$stub/log"
echo "::error::install.sh installed a modified binary"
exit 1
fi
# Show the expected errors without turning them into annotations.
echo "::stop-commands::expected-failure"
cat "$stub/log"
echo "::expected-failure::"
if ! grep -q "$want" "$stub/log"; then
echo "::error::install.sh failed, but not with: ${want}"
exit 1
fi
}
expect_rejected "Checksum mismatch" SIND_VERIFY=false
expect_rejected "Failed to verify the build provenance attestation" MATCH_CHECKSUM=true
[[ "$(sha256sum ~/.local/bin/sind)" == "$installed" ]]

- name: Run Slurm jobs
run: |
sind exec file -- sinfo
Expand Down
14 changes: 9 additions & 5 deletions CLAUDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,16 +11,20 @@ jobs. Personal, uncommitted instructions belong in `CLAUDE.local.md` (gitignored
input, and create clusters (`scripts/create.sh`). Outputs: `clusters`, `version`.
- `scripts/install.sh`: resolves `latest` from the redirect of the latest release page
(not the rate-limited API), enforces `MIN_VERSION`, downloads `sind-linux-amd64` (or
`sind-linux-arm64` on ARM64 runners, sind releases after v0.9.0) into `~/.local/bin`,
and checks `sind version --json`.
`sind-linux-arm64` on ARM64 runners, sind releases after v0.9.0) into a temp dir,
verifies it (sind v0.10.0 and later: `checksums.txt`, and with the `verify` input
`gh attestation verify` using `GH_TOKEN` from the `token` input), and only then
installs it into `~/.local/bin` and checks `sind version --json`.
- `scripts/create.sh`: each `clusters` entry is a config file path (scalar) or an inline
config (map). It creates each cluster, prints its status, and writes the `clusters`
output.
config (map). It creates each cluster (with `--wait` from the `wait` input on sind
releases after v0.10.0, a warning on older ones), prints its status, and writes the
`clusters` output.
- `cleanup/action.yml`: `sind delete cluster --all` (in `SIND_REALM`, if set); a failure
is a warning, so cleanup never fails the job.
- `.github/workflows/ci.yml`: `lint` (shellcheck, actionlint) and `e2e`, which runs the
action from the checkout against the oldest supported sind release and `latest`, and
against `latest` on an ARM64 runner.
against `latest` on an ARM64 runner. On `latest` it also checks that `install.sh`
rejects a binary with one byte changed.

## Checks

Expand Down
92 changes: 86 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -32,9 +32,12 @@ jobs:
The action installs the sind binary for the runner's architecture. ARM64
runners need sind v0.10.0 or later, the first release with linux/arm64
binaries and node images.
- Docker and cgroup v2, which `sind doctor` checks before any cluster is
created.
- A rootful Docker daemon (not rootless or userns-remap) on unified cgroup v2,
which `sind doctor` checks before any cluster is created.
- `bash`, `curl`, `jq` and [mikefarah `yq`](https://github.com/mikefarah/yq) v4.
- The [GitHub CLI](https://cli.github.com/) `gh` 2.93.0 or later, which
verifies the sind binary's build provenance attestation (see
[Binary verification](#binary-verification)).

GitHub's Ubuntu runners meet all of these.

Expand All @@ -43,10 +46,33 @@ GitHub's Ubuntu runners meet all of these.
| Input | Description | Default |
|-------|-------------|---------|
| `version` | sind version to install (e.g. `v0.9.0`; minimum `v0.8.0`) | `latest` |
| `verify` | Verify the sind binary's build provenance attestation with `gh` (see below) | `true` |
| `token` | GitHub token for `gh` to read the attestations of the public sind repository | `github.token` |
| `clusters` | YAML list of cluster definitions (see below) | — |
| `pull` | Pull container images before creating | `true` |
| `wait` | How long `sind create cluster` waits for the nodes and Slurm, e.g. `10m`, or `0` for no limit (see below) | sind's default |
| `realm` | sind realm for resource isolation (a DNS label, see below) | — |

### Binary verification

The action downloads the sind binary into a temporary directory and installs
it only once it passes these checks, for sind v0.10.0 and later:

- Its sha256 checksum must match the release's `checksums.txt`, which catches a
truncated or corrupted download.
- With `verify: true`, the default, its build provenance attestation must show
that sind's release workflow (`.github/workflows/release.yml`) built it from
the release tag on a GitHub-hosted runner. The action checks this with
`gh attestation verify` and `token`; the default `github.token` needs no extra
permissions to read the public sind repository's attestations. gh contacts
`api.github.com` and Sigstore's trust roots (`tuf-repo-cdn.sigstore.dev`,
`tuf-repo.github.com`), so runners with an egress allowlist must allow them.
On GitHub Enterprise Server, pass a github.com token as `token`.

sind v0.9.0 and older publish neither, so the action installs them unverified
and says so in a notice. On runners without `gh`, such as some self-hosted ones,
set `verify: false` to keep only the checksum check.

### Cluster definitions

Each entry in `clusters` is either a **filepath** to a sind cluster config or
Expand All @@ -64,6 +90,26 @@ clusters: |
- worker: 3
```

### Wait limit

`wait` limits how long `sind create cluster` waits for each cluster to become
ready: for the node checks and the Slurm daemons and, for a cluster with
accounting, for its registration with slurmdbd. The limit counts from when the
node containers have started, so image pulls don't count. When it expires, sind
removes the partly created cluster again and the step fails. Leave `wait` empty
for sind's default, or set `0` for no limit.

```yaml
- uses: GSI-HPC/sind-action@v2
with:
wait: 10m
clusters: |
- test/cluster.yml
```

`sind create cluster --wait` needs a sind release after v0.10.0. With older
ones, the action ignores `wait` with a warning.

## Outputs

| Output | Description |
Expand Down Expand Up @@ -114,10 +160,26 @@ jobs:

## Slurm Versions

Clusters use sind's default node image, `ghcr.io/gsi-hpc/sind-node:latest`, which
carries the newest supported Slurm release line. sind also publishes an image per
supported release line, tagged `<YY>.<MM>` (e.g. `25.11`). To pin a release line,
or test against several, set the image in the cluster config:
Clusters whose config sets no `defaults.image` use sind's default node image:

- sind releases after v0.10.0 default to the image published for that release,
`ghcr.io/gsi-hpc/sind-node:vX.Y.Z`, which carries the newest Slurm release
line at the time of that release. Pinning `version` therefore also pins the
Slurm version, and `version: latest` follows the newest sind release and its
image.
- sind v0.10.0 and older default to `ghcr.io/gsi-hpc/sind-node:latest`.

sind also publishes an image per supported Slurm release line:

- `<YY>.<MM>` (e.g. `25.11`) moves: built from sind's `main`, it follows the
newest Slurm release of that line.
- `vX.Y.Z-<YY>.<MM>` (e.g. `vX.Y.Z-25.11`) is fixed per sind release (after
v0.10.0): its Slurm version stays the same.

With `pull: true`, the default, every run fetches the current image behind a
moving tag such as `latest` or `<YY>.<MM>`.

To test against several release lines, set the image in the cluster config:

```yaml
jobs:
Expand Down Expand Up @@ -146,6 +208,24 @@ jobs:
if: always()
```

The `<YY>.<MM>` tags move, so a later run may test a newer Slurm release of the
same line. For reproducible runs with a sind release after v0.10.0, pin
`version` and use that release's fixed tags:

```yaml
- uses: GSI-HPC/sind-action@v2
with:
version: vX.Y.Z
clusters: |
- kind: Cluster
name: dev
defaults:
image: ghcr.io/gsi-hpc/sind-node:vX.Y.Z-${{ matrix.slurm }}
nodes:
- controller
- worker: 2
```

See [Official images](https://gsi-hpc.github.io/sind/container-images/building-images/#official-images)
for the available tags.

Expand Down
23 changes: 23 additions & 0 deletions action.yml
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,17 @@ inputs:
description: 'sind version to install (e.g., v0.9.0; minimum v0.8.0), or "latest"'
required: false
default: 'latest'
verify:
description: |
Verify the build provenance attestation of the sind binary (sind v0.10.0
and later) with the GitHub CLI (gh). Set to "false" on runners without
gh; the binary's checksum is checked either way.
required: false
default: 'true'
token:
description: 'GitHub token for gh to read the attestations of the public sind repository'
required: false
default: ${{ github.token }}
clusters:
description: |
YAML list of cluster definitions. Each entry is either a path to a sind
Expand All @@ -27,6 +38,13 @@ inputs:
description: 'Pull container images before creating clusters'
required: false
default: 'true'
wait:
description: |
How long `sind create cluster` waits for the nodes and Slurm, e.g. "10m",
or "0" for no limit; image pulls don't count. Empty uses sind's default.
Needs a sind release after v0.10.0; older ones ignore it with a warning.
required: false
default: ''
realm:
description: 'sind realm for resource isolation (useful for parallel jobs); a DNS label: lowercase letters, digits and "-"'
required: false
Expand All @@ -49,6 +67,10 @@ runs:
run: bash "$GITHUB_ACTION_PATH/scripts/install.sh"
env:
SIND_VERSION: ${{ inputs.version }}
SIND_VERIFY: ${{ inputs.verify }}
GH_TOKEN: ${{ inputs.token }}
# The attestations live on github.com, even on GitHub Enterprise Server.
GH_HOST: github.com

- name: Set verbosity
shell: bash
Expand Down Expand Up @@ -83,3 +105,4 @@ runs:
env:
SIND_CLUSTERS: ${{ inputs.clusters }}
SIND_PULL: ${{ inputs.pull }}
SIND_WAIT: ${{ inputs.wait }}
12 changes: 11 additions & 1 deletion scripts/create.sh
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,16 @@ else
status_cmd=(status)
fi

# `sind create cluster --wait` came after sind v0.10.0; sind validates the value.
wait_flags=()
if [[ -n "${SIND_WAIT:-}" ]]; then
if [[ "$(printf '%s\n' 0.10.0 "$sind_version" | sort -V | tail -n1)" != "0.10.0" ]]; then
wait_flags=(--wait "$SIND_WAIT")
else
echo "::warning::The wait input needs a sind release after v0.10.0 and is ignored with sind ${sind_version}"
fi
fi

# An empty input creates no clusters; anything else must be a list.
input_kind=$(yq 'kind' "$input_file")
if [[ "$input_kind" != "seq" && "$(yq 'tag' "$input_file")" != "!!null" ]]; then
Expand Down Expand Up @@ -54,7 +64,7 @@ for ((i = 0; i < count; i++)); do
fi

# Build command flags
flags=(--config "$config")
flags=(--config "$config" "${wait_flags[@]}")
[[ "${SIND_PULL:-false}" == "true" ]] && flags+=(--pull)

echo "::group::Creating cluster from $label"
Expand Down
Loading
Loading