.github/scripts/dev_alias_live.sh92 of 92 statements covered (100.00%).
coveredmissednever traced by Bash (not counted)A line ending in … continues the statement above it and shares its fate.
| 1 | 18 | #!/usr/bin/env bash |
| 2 | # | |
| 3 | # dev_alias_live.sh — LIVE proof that scripts/setup-dev-alias.sh generates a | |
| 4 | # working `gco` shell function against a real container runtime, using the real | |
| 5 | # gco-dev image. | |
| 6 | # | |
| 7 | # tests/BATS/test_setup_dev_alias.bats mocks the runtimes and only inspects the | |
| 8 | # emitted text. This script closes that gap end to end: it runs | |
| 9 | # scripts/setup-dev-alias.sh to build the real gco-dev image and install the | |
| 10 | # generated function into a throwaway rc, sources it in a fresh shell, and | |
| 11 | # proves through that function: | |
| 12 | # * `gco --version` — the real CLI runs (and the arg reaches it) | |
| 13 | # * `gco dag validate <rel>/ci-dag.yaml` — an offline command that reads files | |
| 14 | # from the mounted workspace via a | |
| 15 | # *relative* path, proving arg-forwarding, | |
| 16 | # the $PWD -> /workspace bind mount, and | |
| 17 | # cwd=/workspace all at once. | |
| 18 | # | |
| 19 | # Dockerfile.dev installs the CLI editable (`pip install -e .`) at /workspace, | |
| 20 | # so the generated function must be run from the project directory: it mounts | |
| 21 | # $PWD at /workspace, and gco resolves its source there. We therefore run from | |
| 22 | # the repo root and drop the DAG fixture into a throwaway subdirectory of it. | |
| 23 | # | |
| 24 | # Modes: | |
| 25 | # dev_alias_live.sh <docker|finch|podman> [--skip-build] [--image NAME] | |
| 26 | # dev_alias_live.sh --no-runtime | |
| 27 | # | |
| 28 | # Privilege note: on Linux, finch talks to a root-owned daemon, so the finch CI | |
| 29 | # job invokes this via sudo. docker (docker group) and podman (rootless) run it | |
| 30 | # as the normal user. This script never calls sudo itself. | |
| 31 | 22 | set -euo pipefail |
| 32 | ||
| 33 | # The checkout to prove: normally the one this script lives in. The BATS suite | |
| 34 | # points GCO_DEV_ALIAS_LIVE_REPO_ROOT at a disposable fixture (carrying the | |
| 35 | # setup script and answering runtimes on PATH) so the tracked script runs in | |
| 36 | # place without writing its DAG fixture into a real checkout. | |
| 37 | 71 | REPO_ROOT="$(cd "${GCO_DEV_ALIAS_LIVE_REPO_ROOT:-$(dirname "${BASH_SOURCE[0]}")/../..}" && pwd)" |
| 38 | 22 | SETUP="$REPO_ROOT/scripts/setup-dev-alias.sh" |
| 39 | ||
| 40 | 22 | RUNTIME="" |
| 41 | 22 | MODE="runtime" |
| 42 | 22 | IMAGE="gco-dev" |
| 43 | 22 | SKIP_BUILD=0 |
| 44 | ||
| 45 | 32 | die() { printf 'FAIL: %s\n' "$*" >&2; exit 1; } |
| 46 | 47 | note() { printf '\n=== %s ===\n' "$*"; } |
| 47 | ||
| 48 | 48 | while [ "$#" -gt 0 ]; do |
| 49 | 31 | case "$1" in |
| 50 | 6 | --no-runtime) MODE="none"; shift ;; |
| 51 | 10 | --skip-build) SKIP_BUILD=1; shift ;; |
| 52 | 8 | --image) [ "$#" -ge 2 ] || die "--image needs a value"; IMAGE="$2"; shift 2 ;; |
| 53 | 2 | --image=*) IMAGE="${1#*=}"; shift ;; |
| 54 | 30 | docker|finch|podman) RUNTIME="$1"; shift ;; |
| 55 | 3 | -h|--help) grep '^#' "$0" | sed 's/^# \{0,1\}//'; exit 0 ;; |
| 56 | 1 | *) die "usage: dev_alias_live.sh <docker|finch|podman> [--skip-build] [--image NAME] | --no-runtime (got: $1)" ;; |
| 57 | esac | |
| 58 | done | |
| 59 | ||
| 60 | 20 | [ -x "$SETUP" ] || die "setup script not found or not executable: $SETUP" |
| 61 | ||
| 62 | 36 | WORK="$(mktemp -d)" |
| 63 | 18 | FIXTURE_DIR="" # a throwaway subdir of the repo; set + cleaned up below |
| 64 | cleanup() { | |
| 65 | 18 | rm -rf "$WORK" 2>/dev/null || true |
| 66 | 26 | [ -n "$FIXTURE_DIR" ] && rm -rf "$FIXTURE_DIR" 2>/dev/null |
| 67 | 18 | return 0 |
| 68 | } | |
| 69 | 18 | trap cleanup EXIT |
| 70 | ||
| 71 | # --------------------------------------------------------------------------- | |
| 72 | # Mode: no-runtime — prove graceful refusal. | |
| 73 | # --------------------------------------------------------------------------- | |
| 74 | 18 | if [ "$MODE" = "none" ]; then |
| 75 | 3 | note "no-runtime refusal" |
| 76 | # Mask any real runtimes with stubs that fail `<rt> info`, so detection sees | |
| 77 | # "installed but not answering" for all three — the same code path as "not | |
| 78 | # installed at all". The rest of PATH still resolves awk/mktemp/etc. | |
| 79 | 3 | stub="$WORK/stub-bin" |
| 80 | 3 | mkdir -p "$stub" |
| 81 | 9 | for rt in docker finch podman; do |
| 82 | 9 | printf '#!/bin/sh\nexit 1\n' >"$stub/$rt" |
| 83 | 9 | chmod +x "$stub/$rt" |
| 84 | done | |
| 85 | 3 | rc="$WORK/rc-none" |
| 86 | 3 | set +e |
| 87 | 15 | out="$(PATH="$stub:$PATH" GCO_CONTAINER_RUNTIME='' CDK_DOCKER='' "$SETUP" --rc "$rc" 2>&1)" |
| 88 | 3 | code=$? |
| 89 | 3 | set -e |
| 90 | 6 | printf '%s\n' "$out" | sed 's/^/ | /' |
| 91 | 3 | [ "$code" -ne 0 ] || die "expected a non-zero exit when no runtime answers (got 0)" |
| 92 | 6 | printf '%s\n' "$out" | grep -qi 'no container runtime' \ |
| 93 | 1 | || die "expected 'no container runtime' guidance in the output" |
| 94 | 3 | if [ -f "$rc" ] && grep -q '>>> gco >>>' "$rc"; then |
| 95 | 1 | die "rc must not contain a gco function block when no runtime is available" |
| 96 | fi | |
| 97 | 1 | printf 'PASS: script refused with guidance and wrote no gco function.\n' |
| 98 | 1 | exit 0 |
| 99 | fi | |
| 100 | ||
| 101 | # --------------------------------------------------------------------------- | |
| 102 | # Mode: runtime — preflight. | |
| 103 | # --------------------------------------------------------------------------- | |
| 104 | 16 | [ -n "$RUNTIME" ] || die "a runtime (docker|finch|podman) or --no-runtime is required" |
| 105 | ||
| 106 | 14 | note "preflight: $RUNTIME" |
| 107 | 15 | command -v "$RUNTIME" >/dev/null 2>&1 || die "$RUNTIME is not on PATH" |
| 108 | 14 | "$RUNTIME" info >/dev/null 2>&1 || die "$RUNTIME is installed but '$RUNTIME info' does not answer" |
| 109 | 12 | "$RUNTIME" --version || true |
| 110 | ||
| 111 | # --------------------------------------------------------------------------- | |
| 112 | # Setup GCO: setup-dev-alias.sh builds the dev image and installs the function. | |
| 113 | # --------------------------------------------------------------------------- | |
| 114 | # Building the image is the setup script's own job, so the normal path lets it | |
| 115 | # do exactly that: this is the end-to-end proof that `setup-dev-alias.sh` builds | |
| 116 | # gco-dev *and* wires up the alias in a single run. --skip-build instead reuses | |
| 117 | # an already-built image and tells the script to skip its build. | |
| 118 | 12 | rc="$WORK/rc" |
| 119 | 12 | if [ "$SKIP_BUILD" -eq 1 ]; then |
| 120 | 5 | note "using existing image: $IMAGE (--skip-build); setup-dev-alias.sh --no-build" |
| 121 | 5 | "$RUNTIME" image inspect "$IMAGE" >/dev/null 2>&1 \ |
| 122 | 1 | || die "--skip-build set but image '$IMAGE' was not found in $RUNTIME" |
| 123 | 4 | "$SETUP" --runtime "$RUNTIME" --image "$IMAGE" --no-build --rc "$rc" >/dev/null \ |
| 124 | 1 | || die "setup-dev-alias.sh failed to install the gco function for $RUNTIME" |
| 125 | else | |
| 126 | 7 | note "setup GCO: setup-dev-alias.sh builds $IMAGE from Dockerfile.dev with $RUNTIME and installs the function" |
| 127 | # The build pulls the base image from Docker Hub, whose registry endpoints | |
| 128 | # intermittently time out on GitHub runners. Retry the whole setup run | |
| 129 | # (build + rc write, both idempotent; completed layers stay cached) so a | |
| 130 | # transient registry blip does not fail the job — or, in the podman job, | |
| 131 | # masquerade as an OCI-runtime configuration failure and burn one of its | |
| 132 | # crun/runc attempts. | |
| 133 | 7 | build_ok=0 |
| 134 | 7 | retry_delay=15 |
| 135 | 10 | for attempt in 1 2 3; do |
| 136 | 10 | if "$SETUP" --runtime "$RUNTIME" --image "$IMAGE" --rc "$rc"; then |
| 137 | 6 | build_ok=1 |
| 138 | 6 | break |
| 139 | fi | |
| 140 | 4 | if [ "$attempt" -lt 3 ]; then |
| 141 | 3 | printf 'setup-dev-alias.sh failed (attempt %s/3); retrying in %ss (usually a transient registry/network error)\n' \ |
| 142 | "$attempt" "$retry_delay" >&2 | |
| 143 | 3 | sleep "$retry_delay" |
| 144 | 3 | retry_delay=$((retry_delay * 2)) |
| 145 | fi | |
| 146 | done | |
| 147 | 7 | [ "$build_ok" -eq 1 ] \ |
| 148 | 1 | || die "setup-dev-alias.sh failed to build $IMAGE / install the gco function for $RUNTIME (after 3 attempts)" |
| 149 | fi | |
| 150 | 10 | grep -q '>>> gco >>>' "$rc" || die "setup script did not write a gco function block" |
| 151 | ||
| 152 | # --------------------------------------------------------------------------- | |
| 153 | # Workspace fixture. The dev image's editable install needs the repo at | |
| 154 | # /workspace, so we run from the repo root (the function mounts $PWD) and drop | |
| 155 | # the DAG fixture into a throwaway subdir of it. The DAG references its manifest | |
| 156 | # by a path relative to cwd; DagDefinition.validate() checks both the DAG and | |
| 157 | # the manifest exist, so a successful validate proves the bind mount surfaced | |
| 158 | # the files and that cwd is /workspace. | |
| 159 | # --------------------------------------------------------------------------- | |
| 160 | 8 | FIXTURE_REL=".gco_dev_alias_live.$$" |
| 161 | 8 | FIXTURE_DIR="$REPO_ROOT/$FIXTURE_REL" |
| 162 | 8 | mkdir -p "$FIXTURE_DIR" |
| 163 | 8 | printf '# placeholder job manifest for the dev-alias live proof\n' >"$FIXTURE_DIR/probe-job.yaml" |
| 164 | 8 | cat >"$FIXTURE_DIR/ci-dag.yaml" <<YAML |
| 165 | name: dev-alias-live-probe | |
| 166 | steps: | |
| 167 | - name: probe | |
| 168 | manifest: $FIXTURE_REL/probe-job.yaml | |
| 169 | YAML | |
| 170 | ||
| 171 | # Run under the real $HOME so that rootless podman's image store | |
| 172 | # (~/.local/share/containers) is the same one the build above wrote to. | |
| 173 | # Overriding HOME here would point podman at an empty store and it would try | |
| 174 | # to *pull* the locally-built image (docker is daemon-wide and finch is | |
| 175 | # rootful, so neither is HOME-sensitive — but podman is). | |
| 176 | 8 | mkdir -p "$HOME/.aws" # the generated function bind-mounts ~/.aws read-only |
| 177 | ||
| 178 | # --------------------------------------------------------------------------- | |
| 179 | # Proof 1: the real CLI runs through the generated function (run from the repo | |
| 180 | # root so the editable install resolves cli/ + gco/ at /workspace). | |
| 181 | # --------------------------------------------------------------------------- | |
| 182 | 8 | note "prove the real gco CLI runs through the generated function" |
| 183 | 24 | if ! ver="$(cd "$REPO_ROOT" && bash --noprofile --norc -c ". '$rc'; gco --version" 2>&1)"; then |
| 184 | 2 | printf '%s\n' "$ver" | sed 's/^/ | /' |
| 185 | 1 | die "'gco --version' failed through the generated function" |
| 186 | fi | |
| 187 | 7 | printf ' gco --version -> %s\n' "$ver" |
| 188 | 8 | [ -n "$ver" ] || die "'gco --version' produced no output" |
| 189 | ||
| 190 | # --------------------------------------------------------------------------- | |
| 191 | # Proof 2: arg-forwarding + workspace bind mount + cwd, via an offline command. | |
| 192 | # The relative path resolves only if $PWD was mounted at /workspace and cwd is | |
| 193 | # /workspace; gco dag validate then reads both the DAG and its manifest. | |
| 194 | # --------------------------------------------------------------------------- | |
| 195 | 6 | note "prove arg-forwarding + workspace bind mount + cwd via 'gco dag validate'" |
| 196 | 18 | if ! out="$(cd "$REPO_ROOT" && bash --noprofile --norc -c ". '$rc'; gco dag validate '$FIXTURE_REL/ci-dag.yaml'" 2>&1)"; then |
| 197 | 2 | printf '%s\n' "$out" | sed 's/^/ | /' |
| 198 | 1 | die "'gco dag validate $FIXTURE_REL/ci-dag.yaml' failed through the generated function" |
| 199 | fi | |
| 200 | 10 | printf '%s\n' "$out" | sed 's/^/ | /' |
| 201 | 10 | printf '%s\n' "$out" | grep -qi 'is valid' \ |
| 202 | 1 | || die "expected 'is valid' (workspace bind mount or cwd not wired correctly)" |
| 203 | ||
| 204 | 4 | note "ALL CHECKS PASSED for $RUNTIME" |