Skip to content
Draft
Show file tree
Hide file tree
Changes from 1 commit
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
12 changes: 9 additions & 3 deletions controller/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
# syntax=docker/dockerfile:1.18
FROM golang:1.26.5-bookworm AS build
FROM golang:1.26.5-bookworm@sha256:53eeac89074db483fdf0ab3be1df32bf6e47562263d2d0d6baa7f26acb4957dd AS build
ARG SOURCE_DATE_EPOCH=1786752000
WORKDIR /src
COPY go.mod *.go ./
RUN go mod tidy && go mod verify
Expand All @@ -8,11 +9,16 @@ ARG CI_FLEET_VERSION=dev
ARG CI_FLEET_COMMIT=unknown
RUN CGO_ENABLED=0 go build -trimpath -ldflags="-s -w -X main.version=${CI_FLEET_VERSION} -X main.commitSHA=${CI_FLEET_COMMIT}" -o /out/ci-fleet-controller .

FROM debian:13.6-slim
FROM debian:13.6-slim@sha256:3a39a0592364683e6bab97937b72cad5a8fa6dcbbee90edb3bb48c7f8e94f258
ARG SOURCE_DATE_EPOCH=1786752000
ARG DEBIAN_SNAPSHOT=20260815T000000Z
ARG CI_FLEET_COMMIT=unknown
LABEL org.opencontainers.image.revision="${CI_FLEET_COMMIT}" \
io.randomdevelopment.ci-fleet.managed="true"
RUN apt-get update && apt-get install -y --no-install-recommends ca-certificates \
RUN rm -f /etc/apt/sources.list.d/debian.sources \
&& printf 'deb http://snapshot.debian.org/archive/debian/%s trixie main\n' "$DEBIAN_SNAPSHOT" >/etc/apt/sources.list \
&& apt-get -o Acquire::Check-Valid-Until=false update \
&& apt-get install -y --no-install-recommends ca-certificates \
&& install -d -o 65532 -g 65532 /run/ci-fleet \
&& rm -rf /var/lib/apt/lists/*
COPY --from=build /out/ci-fleet-controller /usr/local/bin/ci-fleet-controller
Expand Down
52 changes: 52 additions & 0 deletions docs/MANAGED-IMAGES.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
# Reproducible managed images

Managed controller and runner images have two independent pins:

1. the engine checkout fixes all build instructions and checked module hashes;
Comment thread
Nickfost marked this conversation as resolved.
2. the reviewed private desired state maps that full engine commit to exact local Docker image IDs (`sha256:...`).

Convergence rejects an image whose revision label is correct but whose image ID differs. A missing image is rebuilt only from the pinned engine release and accepted only when its rebuilt ID equals the reviewed value. Rollback applies the same check and deterministic rebuild before it starts the restored controller.

## Reviewed upstream inputs

Reviewed on 2026-08-16 from official upstream endpoints:

| Input | Immutable selection | Evidence |
| --- | --- | --- |
| Debian 13.6 slim, both managed images | `sha256:3a39a0592364683e6bab97937b72cad5a8fa6dcbbee90edb3bb48c7f8e94f258` | Docker Hub registry manifest for `library/debian:13.6-slim` |
| Go 1.26.5 Bookworm build image | `sha256:53eeac89074db483fdf0ab3be1df32bf6e47562263d2d0d6baa7f26acb4957dd` | Docker Hub registry manifest for `library/golang:1.26.5-bookworm` |
| Debian packages | signed snapshot `20260815T000000Z` | `snapshot.debian.org/archive/debian/20260815T000000Z/dists/trixie/InRelease` |
| Build timestamps | `SOURCE_DATE_EPOCH=1786752000` | BuildKit reproducible-build timestamp input declared in every stage |
Comment thread
Nickfost marked this conversation as resolved.
| Docker CLI 29.7.2 amd64 / arm64 | exact package version and SHA-256 `3d1a00d1549f7539606252b1e2b88ae2a0c855e02e5fa92ad962c8a118d7f6ad` / `6bb694f01789b9869e99e34df3492104c0150d12df93192034db5f88cf0f7c8c` | official Docker Debian `trixie` package index |
| Compose plugin 5.4.0 amd64 / arm64 | exact package version and SHA-256 `75391b38459edf75a975504903673fdb87a5a4016d689d9b198e2db1ae2796e0` / `81261f286d48c1c9e586065bc52065bbbe3b023705ebdb72377ec465c216dcaf` | official Docker Debian `trixie` package index |
| Actions Runner 2.335.1 | architecture-specific archive hashes in `runner/Dockerfile` | official GitHub Actions Runner release |
Comment thread
Nickfost marked this conversation as resolved.
| Controller modules | versions plus checksums in `controller/go.mod` and `controller/go.sum` | Go checksum-verified module inputs |

The Dockerfiles fail closed if an upstream artifact no longer matches. Updating any pin requires a reviewed source change and new managed image IDs.

## Record image IDs for an engine commit

From a reviewed engine checkout on an authorized isolated build host, build both images twice from clean BuildKit state and compare IDs:

```bash
CI_FLEET_COMMIT=$(git rev-parse 'HEAD^{commit}')
CI_FLEET_VERSION=$(git rev-parse --short=12 HEAD)
export CI_FLEET_COMMIT CI_FLEET_VERSION
docker compose -f deploy/compose.yaml build --no-cache runner-image controller
Comment thread
Nickfost marked this conversation as resolved.
Outdated
docker image inspect --format '{{.Id}}' \
"ci-fleet-controller:${CI_FLEET_COMMIT:0:12}" \
"ci-fleet-runner:${CI_FLEET_COMMIT:0:12}"
```

After the repeated IDs match, add one private desired-state entry:

```json
"managed_images": {
"FULL_ENGINE_COMMIT": {
"controller": "sha256:REVIEWED_CONTROLLER_IMAGE_ID",
"runner": "sha256:REVIEWED_RUNNER_IMAGE_ID"
}
}
```

The checked-in template uses conspicuous `111...` / `222...` non-production fixture IDs only so validation and mocked tests are runnable; replace them during initialization. The repository-only marathon does not run a Docker daemon or invent production IDs, so live private configuration remains an external evidence gate.
4 changes: 3 additions & 1 deletion docs/QUICKSTART.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,7 +98,9 @@ jobs through the Docker socket.
--runner-group YOUR-CREATED-RUNNER-GROUP \
--capacity-budget 1 \
--max-runners 1 \
--engine-ref PINNED_ENGINE_COMMIT
--engine-ref PINNED_ENGINE_COMMIT \
--controller-image-digest sha256:REVIEWED_CONTROLLER_IMAGE_ID \
--runner-image-digest sha256:REVIEWED_RUNNER_IMAGE_ID
# Validate before committing so a rejected file or accidental value
# never enters branch history, then commit the initialized result:
./scripts/validate.sh --strict
Expand Down
2 changes: 1 addition & 1 deletion docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ New operator? Use [Operator workflows](OPERATOR-WORKFLOWS.md) to select a suppor
| Convert an existing GitHub Actions workflow | [Migrating existing CI](MIGRATING-EXISTING-CI.md) |
| Make a project compliant | [Project CI standard](PROJECT-STANDARD.md) and [compliance checklist](COMPLIANCE-CHECKLIST.md) |
| Split tests across parallel workers | [Project CI standard](PROJECT-STANDARD.md) and the [parallel workflow example](../examples/workflows/parallel-ci.yml.example) |
| Configure automatic updates and cleanup | [Host maintenance](HOST-MAINTENANCE.md) |
| Configure automatic updates and cleanup | [Host maintenance](HOST-MAINTENANCE.md) and [reproducible managed images](MANAGED-IMAGES.md) |
| Monitor hosts and detect missed reports | [Fleet health monitoring](HEALTH-MONITORING.md) and [authenticated status reporting](STATUS-REPORTING.md) |
| Handle GitHub App, workflow, or deployment secrets | [Secrets model](SECRETS.md) and [security policy](../SECURITY.md) |
| Review accepted implementation scope | [Design decisions](DESIGN-DECISIONS.md) |
Expand Down
32 changes: 23 additions & 9 deletions runner/Dockerfile
Original file line number Diff line number Diff line change
@@ -1,23 +1,37 @@
# syntax=docker/dockerfile:1.18
FROM debian:13.6-slim
FROM debian:13.6-slim@sha256:3a39a0592364683e6bab97937b72cad5a8fa6dcbbee90edb3bb48c7f8e94f258

ARG TARGETARCH
ARG SOURCE_DATE_EPOCH=1786752000
ARG DEBIAN_SNAPSHOT=20260815T000000Z
ARG CI_FLEET_COMMIT=unknown
ARG RUNNER_VERSION=2.335.1
ARG RUNNER_SHA256_AMD64=4ef2f25285f0ae4477f1fe1e346db76d2f3ebf03824e2ddd1973a2819bf6c8cf
ARG RUNNER_SHA256_ARM64=6d1e85bfd1a506a8b17c1f1b9b57dba458ffed90898799aaa9f599520b0d9207
ARG DOCKER_CLI_VERSION=29.7.2-1~debian.13~trixie
ARG DOCKER_CLI_SHA256_AMD64=3d1a00d1549f7539606252b1e2b88ae2a0c855e02e5fa92ad962c8a118d7f6ad
ARG DOCKER_CLI_SHA256_ARM64=6bb694f01789b9869e99e34df3492104c0150d12df93192034db5f88cf0f7c8c
ARG COMPOSE_VERSION=5.4.0-1~debian.13~trixie
ARG COMPOSE_SHA256_AMD64=75391b38459edf75a975504903673fdb87a5a4016d689d9b198e2db1ae2796e0
ARG COMPOSE_SHA256_ARM64=81261f286d48c1c9e586065bc52065bbbe3b023705ebdb72377ec465c216dcaf
LABEL org.opencontainers.image.revision="${CI_FLEET_COMMIT}" \
io.randomdevelopment.ci-fleet.managed="true"

SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN apt-get update \
&& apt-get install -y --no-install-recommends ca-certificates curl git gnupg jq sudo \
&& install -m 0755 -d /etc/apt/keyrings \
&& curl -fsSL https://download.docker.com/linux/debian/gpg -o /etc/apt/keyrings/docker.asc \
&& chmod a+r /etc/apt/keyrings/docker.asc \
&& echo "deb [arch=$(dpkg --print-architecture) signed-by=/etc/apt/keyrings/docker.asc] https://download.docker.com/linux/debian trixie stable" > /etc/apt/sources.list.d/docker.list \
&& apt-get update \
&& apt-get install -y --no-install-recommends docker-ce-cli docker-compose-plugin \
RUN rm -f /etc/apt/sources.list.d/debian.sources \
&& printf 'deb http://snapshot.debian.org/archive/debian/%s trixie main\n' "$DEBIAN_SNAPSHOT" >/etc/apt/sources.list \
&& apt-get -o Acquire::Check-Valid-Until=false update \
Comment thread
Nickfost marked this conversation as resolved.
Outdated
&& apt-get install -y --no-install-recommends ca-certificates curl git jq sudo \
&& case "$TARGETARCH" in \
amd64) docker_sha="$DOCKER_CLI_SHA256_AMD64"; compose_sha="$COMPOSE_SHA256_AMD64" ;; \
arm64) docker_sha="$DOCKER_CLI_SHA256_ARM64"; compose_sha="$COMPOSE_SHA256_ARM64" ;; \
*) echo "unsupported TARGETARCH: $TARGETARCH" >&2; exit 1 ;; \
esac \
&& curl -fsSLo docker-cli.deb "https://download.docker.com/linux/debian/dists/trixie/pool/stable/${TARGETARCH}/docker-ce-cli_${DOCKER_CLI_VERSION#*:}_${TARGETARCH}.deb" \
&& curl -fsSLo compose.deb "https://download.docker.com/linux/debian/dists/trixie/pool/stable/${TARGETARCH}/docker-compose-plugin_${COMPOSE_VERSION#*:}_${TARGETARCH}.deb" \
&& printf '%s docker-cli.deb\n%s compose.deb\n' "$docker_sha" "$compose_sha" | sha256sum -c - \
&& apt-get install -y --no-install-recommends ./docker-cli.deb ./compose.deb \
&& rm docker-cli.deb compose.deb \
&& rm -rf /var/lib/apt/lists/*

RUN useradd --create-home --uid 1001 --shell /bin/bash runner \
Expand Down
3 changes: 3 additions & 0 deletions scripts/desired_state.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,13 +170,15 @@ def build_rendered_env(
configured_max = controller["max_runners"]
effective_max = configured_max if state == "active" else 0
short_commit = engine_commit[:12]
images = config["managed_images"][engine_commit]
rendered = {
"CI_FLEET_CAPACITY_BUDGET": str(pool["capacity_budget"]),
"CI_FLEET_COMMIT": engine_commit,
"CI_FLEET_CONFIGURED_MAX_RUNNERS": str(configured_max),
"CI_FLEET_CONFIG_REF": config_ref,
"CI_FLEET_CONFIG_REPOSITORY": config_repository,
"CI_FLEET_CONTROLLER_IMAGE": f"ci-fleet-controller:{short_commit}",
"CI_FLEET_CONTROLLER_IMAGE_DIGEST": images["controller"],
"CI_FLEET_CONTROLLER_STATE": state,
"CI_FLEET_DESIRED_STATE_SCHEMA": "3",
"CI_FLEET_DOCKER_GID": str(docker_gid),
Expand All @@ -189,6 +191,7 @@ def build_rendered_env(
"CI_FLEET_RUNNER_CPUS": str(controller["runner_resources"]["cpu_cores"]),
"CI_FLEET_RUNNER_GROUP": pool["runner_group"],
"CI_FLEET_RUNNER_IMAGE": f"ci-fleet-runner:{short_commit}",
"CI_FLEET_RUNNER_IMAGE_DIGEST": images["runner"],
"CI_FLEET_RUNNER_MEMORY_MIB": str(controller["runner_resources"]["memory_mib"]),
"CI_FLEET_SCALE_SET_NAME": controller["scale_set_name"],
"CI_FLEET_VERSION": short_commit,
Expand Down
28 changes: 24 additions & 4 deletions scripts/install-worker-controller.sh
Original file line number Diff line number Diff line change
Expand Up @@ -527,13 +527,24 @@ release_matches() {
}

managed_images_match() {
local image provenance
local environment=${1:-$candidate_env} digest expected_engine image provenance
local -a expected_images=()
mapfile -t expected_images < <(awk -F= '$1 == "CI_FLEET_CONTROLLER_IMAGE" || $1 == "CI_FLEET_RUNNER_IMAGE" {print substr($0, index($0, "=") + 1)}' "$candidate_env")
expected_engine=$(awk -F= '$1 == "CI_FLEET_ENGINE_REF" {print $2}' "$environment")
[[ "$expected_engine" =~ ^[0-9a-f]{40}$ ]] || return 1
mapfile -t expected_images < <(awk -F= '
$1 == "CI_FLEET_CONTROLLER_IMAGE" {controller=$2}
$1 == "CI_FLEET_CONTROLLER_IMAGE_DIGEST" {controller_digest=$2}
$1 == "CI_FLEET_RUNNER_IMAGE" {runner=$2}
$1 == "CI_FLEET_RUNNER_IMAGE_DIGEST" {runner_digest=$2}
END {if (controller && controller_digest && runner && runner_digest) {print controller " " controller_digest; print runner " " runner_digest}}
' "$environment")
[[ ${#expected_images[@]} == 2 ]] || return 1
for image in "${expected_images[@]}"; do
for expected in "${expected_images[@]}"; do
read -r image digest <<<"$expected"
[[ "$digest" =~ ^sha256:[0-9a-f]{64}$ ]] || return 1
provenance=$(docker image inspect --format '{{ index .Config.Labels "org.opencontainers.image.revision" }}' "$image" 2>/dev/null) || return 1
[[ "$provenance" == "$engine_ref" ]] || return 1
[[ "$provenance" == "$expected_engine" ]] || return 1
[[ $(docker image inspect --format '{{.Id}}' "$image" 2>/dev/null) == "$digest" ]] || return 1
done
}

Expand Down Expand Up @@ -672,6 +683,7 @@ build_candidate() {
run_candidate_preflight
compose "$release_dir" "$candidate_env" config --quiet
compose "$release_dir" "$candidate_env" build runner-image controller
managed_images_match "$candidate_env" || die 'managed image digest does not match reviewed desired state'
}

make_checkpoint() {
Expand Down Expand Up @@ -983,6 +995,14 @@ restore_checkpoint() {
if [[ -n "$release_dir" && -f "$rendered_env" ]]; then
restored_state=$(awk -F= '$1 == "CI_FLEET_CONTROLLER_STATE" {print $2}' "$rendered_env") || failed=1
[[ "$restored_state" == active || "$restored_state" == drained || "$restored_state" == disabled ]] || failed=1
if ((failed == 0)) && grep -Eq '^CI_FLEET_(CONTROLLER|RUNNER)_IMAGE_DIGEST=sha256:[0-9a-f]{64}$' "$rendered_env"; then
if ! managed_images_match "$rendered_env"; then
compose "$release_dir" "$rendered_env" build runner-image controller || failed=1
if ((failed == 0)); then managed_images_match "$rendered_env" || failed=1; fi
fi
else
note 'ROLLBACK_IMAGE_DIGEST_UNAVAILABLE legacy_checkpoint=true'
Comment thread
Nickfost marked this conversation as resolved.
fi
if [[ "$restored_state" == active && "$failed" == 0 ]]; then
if [[ $(managed_runner_count) != 0 ]]; then
failed=1
Expand Down
24 changes: 16 additions & 8 deletions scripts/test-install-worker-controller.sh
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ case "${1:-}" in
exit 1
fi
[[ -f "$image_state" ]] || exit 1
if [[ "$*" == *'{{.Id}}'* ]]; then printf 'sha256:%s\n' "$(<"$image_state")"; else cat "$image_state"; fi
if [[ "$*" == *'org.opencontainers.image.revision'* ]]; then printf '%s\n' "${FAKE_ENGINE_REF:?}"; elif [[ "$*" == *'{{.Id}}'* ]]; then printf 'sha256:%s\n' "$(<"$image_state")"; else cat "$image_state"; fi
;;
rm)
(($# >= 2)) || exit 1
Expand Down Expand Up @@ -100,7 +100,10 @@ case "${1:-}" in
fi
: >"$state"
[[ -z "${FAKE_CONTROLLER_PROVENANCE_FILE:-}" ]] || printf '%s\n' "${FAKE_ENGINE_REF:?}" >"$FAKE_CONTROLLER_PROVENANCE_FILE"
[[ -z "${FAKE_CONTROLLER_IMAGE_ID_FILE:-}" ]] || printf 'sha256:%s\n' "${FAKE_ENGINE_REF:?}" >"$FAKE_CONTROLLER_IMAGE_ID_FILE"
if [[ -n "${FAKE_CONTROLLER_IMAGE_ID_FILE:-}" ]]; then
controller_digest=$(awk -F= '$1 == "CI_FLEET_CONTROLLER_IMAGE_DIGEST" {print $2}' "$env_file")
printf '%s\n' "$controller_digest" >"$FAKE_CONTROLLER_IMAGE_ID_FILE"
fi
[[ -z "${FAKE_CONTROLLER_ENV_FILE:-}" ]] || cp "$env_file" "$FAKE_CONTROLLER_ENV_FILE"
if [[ -n "${FAKE_RESTART_AFTER_UP:-}" && -f "$FAKE_RESTART_AFTER_UP" ]]; then
rm -f "$FAKE_RESTART_AFTER_UP"
Expand All @@ -127,8 +130,10 @@ case "${1:-}" in
[[ -z "$paused_state" ]] || rm -f "$paused_state"
;;
build)
[[ -z "${FAKE_RUNNER_IMAGE_STATE:-}" ]] || printf '%s\n' "${FAKE_ENGINE_REF:?}" >"$FAKE_RUNNER_IMAGE_STATE"
[[ -z "${FAKE_CONTROLLER_IMAGE_STATE:-}" ]] || printf '%s\n' "${FAKE_ENGINE_REF:?}" >"$FAKE_CONTROLLER_IMAGE_STATE"
runner_digest=$(awk -F= '$1 == "CI_FLEET_RUNNER_IMAGE_DIGEST" {print $2}' "$env_file")
controller_digest=$(awk -F= '$1 == "CI_FLEET_CONTROLLER_IMAGE_DIGEST" {print $2}' "$env_file")
[[ -z "${FAKE_RUNNER_IMAGE_STATE:-}" ]] || printf '%s\n' "${runner_digest#sha256:}" >"$FAKE_RUNNER_IMAGE_STATE"
[[ -z "${FAKE_CONTROLLER_IMAGE_STATE:-}" ]] || printf '%s\n' "${controller_digest#sha256:}" >"$FAKE_CONTROLLER_IMAGE_STATE"
;;
config|logs) ;;
*) exit 1 ;;
Expand Down Expand Up @@ -218,6 +223,8 @@ expect_command_failure() {
}

engine_ref=$(git -C "$repo_root" rev-parse 'HEAD^{commit}')
controller_image_digest=$(printf '1%.0s' {1..64})
runner_image_digest=$(printf '2%.0s' {1..64})
export FAKE_ENGINE_REF=$engine_ref
runner_image="ci-fleet-runner:${engine_ref:0:12}"
export FAKE_RUNNER_IMAGE=$runner_image
Expand Down Expand Up @@ -249,6 +256,7 @@ value = json.load(open(source, encoding="utf-8"))
value["organization"]["slug"] = "fixture-org"
value["runner_pools"]["trusted-ci"]["allowed_repositories"] = ["fixture-org/example-app"]
value["projects"]["example-app"]["repository"] = "fixture-org/example-app"
value["managed_images"] = {engine_ref: {"controller": "sha256:" + "1" * 64, "runner": "sha256:" + "2" * 64}}
controller = value["controllers"]["example-ci-01"]
controller["engine_ref"] = engine_ref
controller["state"] = state
Expand Down Expand Up @@ -408,10 +416,10 @@ printf '%040d\n' 0 >"$FAKE_CONTROLLER_PROVENANCE_FILE"
expect_failure 'DRIFT controller_runtime' "$installer" --check "${base_args[@]}" --ref "$ref_one"
expect_success "$installer" --install "${base_args[@]}" --ref "$ref_one" >/dev/null
[[ $(<"$FAKE_CONTROLLER_PROVENANCE_FILE") == "$engine_ref" ]] || fail 'controller convergence did not restore running image provenance'
printf 'sha256:%040d\n' 0 >"$FAKE_CONTROLLER_IMAGE_ID_FILE"
printf 'sha256:%064d\n' 3 >"$FAKE_CONTROLLER_IMAGE_ID_FILE"
expect_failure 'DRIFT controller_runtime' "$installer" --check "${base_args[@]}" --ref "$ref_one"
expect_success "$installer" --install "${base_args[@]}" --ref "$ref_one" >/dev/null
[[ $(<"$FAKE_CONTROLLER_IMAGE_ID_FILE") == "sha256:$engine_ref" ]] || fail 'controller convergence did not restore live image identity'
[[ $(<"$FAKE_CONTROLLER_IMAGE_ID_FILE") == "sha256:$controller_image_digest" ]] || fail 'controller convergence did not restore live image identity'
python3 -c 'from pathlib import Path; import sys; path = Path(sys.argv[1]); path.write_text(path.read_text().replace("CI_FLEET_MAX_RUNNERS=1", "CI_FLEET_MAX_RUNNERS=9"))' "$FAKE_CONTROLLER_ENV_FILE"
grep -Fxq 'CI_FLEET_MAX_RUNNERS=9' "$FAKE_CONTROLLER_ENV_FILE" || fail 'live-environment fixture did not mutate'
expect_failure 'DRIFT controller_runtime' "$installer" --check "${base_args[@]}" --ref "$ref_one"
Expand All @@ -427,10 +435,10 @@ rm -f "$FAKE_CONTROLLER_IMAGE_STATE"
expect_failure 'DRIFT managed_images' "$installer" --check "${base_args[@]}" --ref "$ref_one"
expect_success "$installer" --install "${base_args[@]}" --ref "$ref_one" >/dev/null
[[ -f "$FAKE_RUNNER_IMAGE_STATE" && -f "$FAKE_CONTROLLER_IMAGE_STATE" ]] || fail 'candidate build did not restore the controller image'
printf '%040d\n' 0 >"$FAKE_RUNNER_IMAGE_STATE"
printf '%064d\n' 3 >"$FAKE_RUNNER_IMAGE_STATE"
expect_failure 'DRIFT managed_images' "$installer" --check "${base_args[@]}" --ref "$ref_one"
expect_success "$installer" --install "${base_args[@]}" --ref "$ref_one" >/dev/null
[[ $(<"$FAKE_RUNNER_IMAGE_STATE") == "$engine_ref" && $(<"$FAKE_CONTROLLER_IMAGE_STATE") == "$engine_ref" ]] || fail 'candidate build did not restore managed image provenance'
[[ $(<"$FAKE_RUNNER_IMAGE_STATE") == "$runner_image_digest" && $(<"$FAKE_CONTROLLER_IMAGE_STATE") == "$controller_image_digest" ]] || fail 'candidate build did not restore reviewed managed image digests'
if docker image inspect unrelated:image >/dev/null 2>&1; then fail 'unrelated image fixture unexpectedly exists'; fi
: >"$FAKE_IMAGE_INSPECT_LOG"
expect_success "$installer" --check "${base_args[@]}" --ref "$ref_one" >/dev/null
Expand Down
Loading
Loading