From 5b28f20af8a4eeed13be49474d3cf160d97246ca Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 15 Aug 2026 14:39:45 +0100 Subject: [PATCH 1/2] ci: run GHA in container for glibc --- .github/workflows/_build-rs.yml | 34 ++++++++++----- .github/workflows/_builder-image.yml | 63 ++++++++++++++++++++++++++++ .github/workflows/_package-linux.yml | 10 +++-- .github/workflows/test.yml | 26 +++++++----- README.md | 8 ++++ ak-agent-desktop/Makefile | 2 +- ak-cli/Makefile | 9 ++-- ak-pam/Makefile | 6 --- ak-sysd/Makefile | 9 ++-- common.mk | 39 +---------------- containers/builder/Dockerfile | 45 ++++++++++++++++++++ containers/builder/Makefile | 16 +++++-- 12 files changed, 185 insertions(+), 82 deletions(-) create mode 100644 .github/workflows/_builder-image.yml diff --git a/.github/workflows/_build-rs.yml b/.github/workflows/_build-rs.yml index b9f56c05f..015d089a6 100644 --- a/.github/workflows/_build-rs.yml +++ b/.github/workflows/_build-rs.yml @@ -20,8 +20,12 @@ on: value: ${{ jobs.build.outputs.artifact-id }} jobs: + builder: + uses: ./.github/workflows/_builder-image.yml + build: name: Build + needs: builder strategy: fail-fast: false matrix: @@ -45,6 +49,10 @@ jobs: - target: ak-nss platform: windows-2025 runs-on: ${{ matrix.platform }} + # Linux builds run inside the prebuilt builder image. ak-agent-desktop is + # the exception: it needs webkit2gtk-4.1, which bullseye doesn't have, so it + # keeps building on the runner. An empty string means "no container". + container: ${{ startsWith(matrix.platform, 'ubuntu') && matrix.target != 'ak-agent-desktop' && needs.builder.outputs.image || '' }} env: CARGO_TERM_COLOR: always permissions: @@ -65,15 +73,16 @@ jobs: cache-dependency-path: "pnpm-lock.yaml" # ubuntu builds run inside the builder container image, which already # bakes in the pinned toolchain - only macos/windows need it installed - # (and cached) on the runner itself. - - if: ${{ !startsWith(matrix.platform, 'ubuntu') }} + # (and cached) on the runner itself. ak-agent-desktop is not containerised, + # so it needs the toolchain on the ubuntu runner too. + - if: ${{ !startsWith(matrix.platform, 'ubuntu') || matrix.target == 'ak-agent-desktop' }} uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v4 with: path: | ~/.rustup/toolchains ~/.rustup/update-hashes key: rustup-toolchain-${{ matrix.platform }}-${{ hashFiles('rust-toolchain.toml') }} - - if: ${{ !startsWith(matrix.platform, 'ubuntu') }} + - if: ${{ !startsWith(matrix.platform, 'ubuntu') || matrix.target == 'ak-agent-desktop' }} uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1 with: rustflags: "" @@ -90,26 +99,27 @@ jobs: run: | $linkExe = (Get-Command link.exe -All | Where-Object { $_.Source -notlike '*\Git\*' } | Select-Object -First 1).Source "CARGO_TARGET_X86_64_PC_WINDOWS_MSVC_LINKER=$linkExe" | Out-File -FilePath $env:GITHUB_ENV -Append -Encoding utf8 - - if: ${{ !startsWith(matrix.platform, 'ubuntu') }} + - if: ${{ !startsWith(matrix.platform, 'ubuntu') || matrix.target == 'ak-agent-desktop' }} uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v4 with: path: | ~/.rustup/toolchains ~/.rustup/update-hashes key: rustup-toolchain-${{ matrix.platform }}-${{ hashFiles('rust-toolchain.toml') }} + # /usr/local/cargo is CARGO_HOME inside the builder image; ~/.cargo is the + # runner's. Only one of the two exists per job, the other matches nothing. - uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v4 with: path: | ~/.cargo/registry ~/.cargo/git + /usr/local/cargo/registry + /usr/local/cargo/git cache/ - key: target-cache-${{ matrix.platform }}-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.target }} + key: target-cache-v2-${{ matrix.platform }}-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.target }} restore-keys: | - target-cache-${{ matrix.platform }}-${{ hashFiles('**/Cargo.lock') }}- - target-cache-${{ matrix.platform }}- - - name: Build builder image - if: startsWith(matrix.platform, 'ubuntu') - run: make containers/builder/build + target-cache-v2-${{ matrix.platform }}-${{ hashFiles('**/Cargo.lock') }}- + target-cache-v2-${{ matrix.platform }}- - run: | make ${{ matrix.target }}/build env: @@ -127,5 +137,7 @@ jobs: path: | ~/.cargo/registry ~/.cargo/git + /usr/local/cargo/registry + /usr/local/cargo/git cache/ - key: target-cache-${{ matrix.platform }}-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.target }} + key: target-cache-v2-${{ matrix.platform }}-${{ hashFiles('**/Cargo.lock') }}-${{ matrix.target }} diff --git a/.github/workflows/_builder-image.yml b/.github/workflows/_builder-image.yml new file mode 100644 index 000000000..a7d9535b5 --- /dev/null +++ b/.github/workflows/_builder-image.yml @@ -0,0 +1,63 @@ +name: _builder-image + +# Publishes the Linux build environment to GHCR and hands the tag back to the +# caller, which uses it as the `container:` runtime for its Linux jobs. +# +# The tag is a hash of the image's inputs (see containers/builder/Makefile), so +# this is a no-op on almost every run: it only builds when that exact tag is +# missing from the registry, which happens the first time a Dockerfile or +# rust-toolchain.toml change is pushed. + +on: + workflow_call: + outputs: + image: + description: "Fully qualified builder image reference" + value: ${{ jobs.builder.outputs.image }} + +jobs: + builder: + name: Builder image + runs-on: ubuntu-24.04 + permissions: + contents: read + packages: write + outputs: + image: ${{ steps.meta.outputs.tag }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + - uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0 + with: + registry: ghcr.io + username: ${{ github.repository_owner }} + password: ${{ secrets.GITHUB_TOKEN }} + - id: meta + run: | + cd containers/builder + echo "tag<> $GITHUB_OUTPUT + make ci-container-tag >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT + - id: check + env: + IMAGE: ${{ steps.meta.outputs.tag }} + run: | + if docker manifest inspect "${IMAGE}" > /dev/null 2>&1; then + echo "${IMAGE} already published, skipping build" + echo "exists=true" >> $GITHUB_OUTPUT + else + echo "${IMAGE} not found, building" + echo "exists=false" >> $GITHUB_OUTPUT + fi + - if: steps.check.outputs.exists != 'true' + uses: docker/setup-qemu-action@96fe6ef7f33517b61c61be40b68a1882f3264fb8 # v4.2.0 + - if: steps.check.outputs.exists != 'true' + uses: docker/setup-buildx-action@bb05f3f5519dd87d3ba754cc423b652a5edd6d2c # v4.2.0 + - if: steps.check.outputs.exists != 'true' + uses: docker/build-push-action@53b7df96c91f9c12dcc8a07bcb9ccacbed38856a # v7.3.0 + with: + # Repo root: the Dockerfile bind-mounts rust-toolchain.toml. + context: . + file: containers/builder/Dockerfile + push: true + tags: ${{ steps.meta.outputs.tag }} + platforms: linux/amd64,linux/arm64 diff --git a/.github/workflows/_package-linux.yml b/.github/workflows/_package-linux.yml index b87b939bc..d82c954c4 100644 --- a/.github/workflows/_package-linux.yml +++ b/.github/workflows/_package-linux.yml @@ -14,12 +14,18 @@ on: default: '["ubuntu-24.04","ubuntu-24.04-arm"]' jobs: + builder: + uses: ./.github/workflows/_builder-image.yml + package: + needs: builder strategy: fail-fast: false matrix: platform: ${{ fromJSON(inputs.platforms) }} runs-on: ${{ matrix.platform }} + # nfpm and dpkg-dev are baked into the builder image. + container: ${{ needs.builder.outputs.image }} permissions: contents: read steps: @@ -52,10 +58,6 @@ jobs: with: name: authentik_${{ matrix.platform }}_ak-pam${{ inputs.artifact_suffix }} path: bin/ - - run: | - echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' | sudo tee /etc/apt/sources.list.d/goreleaser.list - sudo apt update - sudo apt install nfpm -y - shell: bash run: make vpkg/linux/package env: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 31d99f3c0..e784a7c0f 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,10 +12,17 @@ env: permissions: contents: read id-token: write + # Workflow-level permissions cap what called workflows can request, and + # _builder-image.yml needs to push to GHCR. + packages: write jobs: + builder: + uses: ./.github/workflows/_builder-image.yml + test_rs: name: Test (rust) + needs: builder strategy: fail-fast: false matrix: @@ -45,22 +52,21 @@ jobs: - platform: macos-26 target: ak-nss runs-on: ${{ matrix.platform }} + # Linux tests run inside the builder image, which bakes in the pinned + # toolchain, cargo-llvm-cov/nextest and the osquery toolchain. + # ak-agent-desktop needs webkit2gtk-4.1, which bullseye doesn't have, so it + # stays on the runner. An empty string means "no container". + container: ${{ startsWith(matrix.platform, 'ubuntu') && matrix.target != 'ak-agent-desktop' && needs.builder.outputs.image || '' }} steps: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 - - uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1 + - if: ${{ !startsWith(matrix.platform, 'ubuntu') || matrix.target == 'ak-agent-desktop' }} + uses: actions-rust-lang/setup-rust-toolchain@166cdcfd11aee3cb47222f9ddb555ce30ddb9659 # v1 with: rustflags: "" - - uses: taiki-e/install-action@7f4eb899022d8fe70b20c4f3de697aa85c309026 # v2 + - if: ${{ !startsWith(matrix.platform, 'ubuntu') || matrix.target == 'ak-agent-desktop' }} + uses: taiki-e/install-action@7f4eb899022d8fe70b20c4f3de697aa85c309026 # v2 with: tool: cargo-llvm-cov nextest - - name: Install osquery-toolchain - if: ${{ (matrix.target == 'ak-platform-facts' || matrix.target == 'ak-sysd') && (matrix.platform == 'ubuntu-24.04' || matrix.platform == 'ubuntu-24.04-arm') }} - run: | - ARCH=$(uname -m) - curl -fsSL -o /tmp/osquery-toolchain.tar.xz \ - "https://github.com/osquery/osquery-toolchain/releases/download/1.3.0/osquery-toolchain-1.3.0-${ARCH}.tar.xz" - sudo tar xf /tmp/osquery-toolchain.tar.xz -C /usr/local - rm /tmp/osquery-toolchain.tar.xz - if: ${{ (matrix.target == 'ak-platform-facts' || matrix.target == 'ak-sysd') && matrix.platform == 'windows-2025' }} uses: ilammy/msvc-dev-cmd@0b201ec74fa43914dc39ae48a89fd1d8cb592756 # v1 - name: Force real MSVC link.exe diff --git a/README.md b/README.md index deb182099..c781e2d81 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,14 @@ Linux DEB and RPM packages, produced via `nfpm` (invoked as a Go tool). **Linux Requirements: Pre-built outputs from `ak-cli/build`, `sysd/build`, `ak-agent/build`, `ak-browser-support/build`, `nss/build`, and `pam/build`. Go (used to run `nfpm`). Packages produced: `authentik-cli`, `authentik-sysd`, `authentik-agent`, `libnss-authentik`, `libpam-authentik`. +#### `containers/builder/%` + +Linux build environment, published to `ghcr.io/goauthentik/platform-builder`. CI uses it as the runtime for the Linux build, package and test jobs; it pins the Rust toolchain, the osquery toolchain, `nfpm` and the coverage tooling, and its Debian bullseye base is what keeps the shipped binaries' glibc requirement at 2.31. + +The tag is a hash of `Dockerfile` + `rust-toolchain.toml` (`make containers/builder/ci-container-tag`), so CI only rebuilds and pushes when one of those changes. + +Requirements: Docker. + #### `containers/selenium/%` Selenium test Docker container. diff --git a/ak-agent-desktop/Makefile b/ak-agent-desktop/Makefile index a48830d50..30a422ae6 100644 --- a/ak-agent-desktop/Makefile +++ b/ak-agent-desktop/Makefile @@ -22,7 +22,7 @@ endif mkdir -p "${TOP}/cache/shared" pnpm install pnpm run build - $(call cargo_build_local,$(TARGET),--features tauri/custom-protocol --features tauri/devtools -p ak-agent-desktop) + $(call cargo_build,$(TARGET),--features tauri/custom-protocol --features tauri/devtools -p ak-agent-desktop) cp "${TOP}/cache/shared/release/${BIN}" "${TOP}/bin/${TARGET}/${BIND}" ifdef SENTRY_AUTH_TOKEN @$(call sentry_upload_symbols,"${TOP}/cache/shared/release") diff --git a/ak-cli/Makefile b/ak-cli/Makefile index ea73120a1..9c4aa3eb4 100644 --- a/ak-cli/Makefile +++ b/ak-cli/Makefile @@ -20,11 +20,10 @@ ifdef SENTRY_AUTH_TOKEN @$(call sentry_upload_symbols,"${TOP}/cache/shared/release") endif -build-local: - mkdir -p "${TOP}/bin/${TARGET}" - mkdir -p "${TOP}/cache/shared" - $(call cargo_build_local,$(TARGET)) - cp "${TOP}/cache/shared/release/${BIN}" "${TOP}/bin/${TARGET}/${BIND}" +# Kept as an alias: this used to be the escape hatch from the docker-wrapped +# build, which no longer exists. +.PHONY: build-local +build-local: build test-deploy: build $(TME) dpkg -i /workspaces/bin/${TARGET}/authentik-${TARGET}_${VERSION}+_${ARCH}.deb diff --git a/ak-pam/Makefile b/ak-pam/Makefile index c4d30868a..df336eacf 100644 --- a/ak-pam/Makefile +++ b/ak-pam/Makefile @@ -14,12 +14,6 @@ endif .PHONY: test test: -ifeq ($(PLATFORM),gnu/linux) -ifeq ($(CI),true) - sudo apt-get update - sudo apt-get install -y libpam0g-dev libudev-dev -endif -endif $(call cargo_test,ak-pam) test-deploy: build diff --git a/ak-sysd/Makefile b/ak-sysd/Makefile index e4dc8502a..1fc8d8b57 100644 --- a/ak-sysd/Makefile +++ b/ak-sysd/Makefile @@ -20,11 +20,10 @@ ifdef SENTRY_AUTH_TOKEN @$(call sentry_upload_symbols,"${TOP}/cache/shared/release") endif -build-local: - mkdir -p "${TOP}/bin/${TARGET}" - mkdir -p "${TOP}/cache/shared" - $(call cargo_build_local,$(TARGET)) - cp "${TOP}/cache/shared/release/${BIN}" "${TOP}/bin/${TARGET}/${BIND}" +# Kept as an alias: this used to be the escape hatch from the docker-wrapped +# build, which no longer exists. +.PHONY: build-local +build-local: build test-deploy: build $(TME) dpkg -i /workspaces/bin/${TARGET}/authentik-${TARGET}_${VERSION}+_${ARCH}.deb diff --git a/common.mk b/common.mk index 7315074c4..1550748ea 100644 --- a/common.mk +++ b/common.mk @@ -29,43 +29,9 @@ TOP = $(dir $(abspath $(lastword $(MAKEFILE_LIST)))) PROTO_DIR := "${TOP}/protobuf" RUST_BUILD_FLAGS ?= -DOCKER_BUILDER_IMAGE ?= authentik/ak-builder -CARGO_CRATE_DIR := $(subst $(TOP),,$(CURDIR)) -ifneq ($(LOCAL_WORKSPACE),) -CONTAINER_TOP := ${LOCAL_WORKSPACE} -else -CONTAINER_TOP := ${TOP} -endif - -define cargo_build_local -RUSTFLAGS="$(RUST_BUILD_FLAGS)" \ - AK_VERSION=${VERSION} \ - AK_BUILDHASH=${VERSION_HASH} \ - AK_TAG=${VERSION_TAG} \ - cargo build \ - --target-dir $(TOP)cache/shared \ - --verbose \ - --release $(2) -endef - -ifeq ($(PLATFORM),gnu/linux) -define cargo_build - docker run --rm \ - -i \ - --volume "$(CONTAINER_TOP):/workspace" \ - --workdir "/workspace/$(CARGO_CRATE_DIR)" \ - --env RUSTFLAGS="$(RUST_BUILD_FLAGS)" \ - --env AK_VERSION="${VERSION}" \ - --env AK_BUILDHASH="${VERSION_HASH}" \ - --env AK_TAG="${VERSION_TAG}" \ - $(DOCKER_BUILDER_IMAGE) \ - cargo build \ - --target-dir /workspace/cache/shared \ - --verbose \ - --release -endef -else +# Linux CI runs inside the builder image (see containers/builder), so builds are +# always a plain cargo invocation - no docker wrapper, on any platform. define cargo_build RUSTFLAGS="$(RUST_BUILD_FLAGS)" \ AK_VERSION=${VERSION} \ @@ -76,7 +42,6 @@ RUSTFLAGS="$(RUST_BUILD_FLAGS)" \ --verbose \ --release $(2) endef -endif define cargo_test mkdir -p "${TOP}/cache" diff --git a/containers/builder/Dockerfile b/containers/builder/Dockerfile index 9b38e213f..781f0f793 100644 --- a/containers/builder/Dockerfile +++ b/containers/builder/Dockerfile @@ -16,6 +16,16 @@ RUN apt-get update && \ libpam0g-dev libudev-dev zstd && \ rm -rf /var/lib/apt/lists/* +# dpkg-dev provides dpkg-architecture (common.mk, vpkg/linux/Makefile); nfpm +# builds the deb/rpm packages. The goreleaser repo is a flat, distro-independent +# apt repo, so it needs ca-certificates from the layer above to be reachable. +RUN echo 'deb [trusted=yes] https://repo.goreleaser.com/apt/ /' > /etc/apt/sources.list.d/goreleaser.list && \ + apt-get update && \ + apt-get install -y --no-install-recommends \ + dpkg-dev \ + nfpm && \ + rm -rf /var/lib/apt/lists/* + RUN ARCH=$(uname -m) && \ curl -fsSL -o /tmp/osquery-toolchain.tar.xz \ "https://github.com/osquery/osquery-toolchain/releases/download/1.3.0/osquery-toolchain-1.3.0-${ARCH}.tar.xz" && \ @@ -28,3 +38,38 @@ RUN --mount=type=bind,target=rust-toolchain.toml,src=rust-toolchain.toml \ rustup default "$(sed -n 's/channel = "\(.*\)"/\1/p' rust-toolchain.toml)" && \ rustc --version && \ cargo --version + +# Test tooling, so CI jobs don't have to install it per-job. Prebuilt archives +# rather than `cargo install`, which would be painfully slow under QEMU on the +# arm64 leg of the image build. +RUN curl -fsSL "https://github.com/taiki-e/cargo-llvm-cov/releases/latest/download/cargo-llvm-cov-$(uname -m)-unknown-linux-gnu.tar.gz" \ + | tar xzf - -C "${CARGO_HOME}/bin" && \ + case "$(uname -m)" in \ + x86_64) NEXTEST_URL=https://get.nexte.st/latest/linux ;; \ + aarch64) NEXTEST_URL=https://get.nexte.st/latest/linux-arm ;; \ + *) echo "unsupported arch $(uname -m)" >&2; exit 1 ;; \ + esac && \ + curl -fsSL "${NEXTEST_URL}" | tar xzf - -C "${CARGO_HOME}/bin" && \ + cargo llvm-cov --version && \ + cargo nextest --version + +# node, purely so `npx @sentry/cli` works for the debug-symbol upload in +# common.mk. Node-based *actions* get node mounted in by the runner, but a +# `run:` step only sees the image's PATH. +ARG NODE_VERSION=24.19.0 +RUN case "$(uname -m)" in \ + x86_64) NODE_ARCH=x64 ;; \ + aarch64) NODE_ARCH=arm64 ;; \ + *) echo "unsupported arch $(uname -m)" >&2; exit 1 ;; \ + esac && \ + curl -fsSL "https://nodejs.org/dist/v${NODE_VERSION}/node-v${NODE_VERSION}-linux-${NODE_ARCH}.tar.xz" \ + | tar xJf - -C /usr/local --strip-components=1 \ + --exclude=CHANGELOG.md --exclude=LICENSE --exclude=README.md && \ + node --version && \ + npx --version + +# common.mk shells out to git for the version stamp, which trips git's +# dubious-ownership check when the checkout uid differs from the build uid. +# --system, not --global: container jobs run with HOME=/github/home, so a +# gitconfig written to root's home would never be read. +RUN git config --system --add safe.directory '*' diff --git a/containers/builder/Makefile b/containers/builder/Makefile index 59f9b2aa0..8a68b93e0 100644 --- a/containers/builder/Makefile +++ b/containers/builder/Makefile @@ -1,8 +1,18 @@ include ../../common.mk -.PHONY: build -build: +# The image only depends on its own definition and the pinned toolchain, so tag +# it by content: CI can then skip the build entirely whenever the tag already +# exists in the registry, and a toolchain bump republishes within the same PR. +# `git hash-object` avoids branching on shasum (macOS) vs sha256sum (Linux). +container_ver := $(shell cat "${PWD}/Dockerfile" "${TOP}/rust-toolchain.toml" | git hash-object --stdin | head -c 12) + +.PHONY: ci-container-tag +ci-container-tag: + @echo ghcr.io/goauthentik/platform-builder:${container_ver} + +.PHONY: local-build +local-build: docker build \ - -t ${DOCKER_BUILDER_IMAGE} \ + -t ghcr.io/goauthentik/platform-builder:${container_ver} \ -f "${PWD}/Dockerfile" \ "${TOP}" From 1daddcbedf52f72d908132717c0c6bfeac12e9c1 Mon Sep 17 00:00:00 2001 From: Jens Langhammer Date: Sat, 15 Aug 2026 14:52:59 +0100 Subject: [PATCH 2/2] idk --- .github/workflows/_build-rs.yml | 5 +++++ .github/workflows/_builder-image.yml | 5 +++++ .github/workflows/_package-linux.yml | 17 ++++++++++++++++- .github/workflows/build.yml | 4 ++++ .github/workflows/release-publish.yml | 4 ++++ .github/workflows/test.yml | 24 ++++++++++++++++++++---- containers/builder/Dockerfile | 17 +++++++++-------- 7 files changed, 63 insertions(+), 13 deletions(-) diff --git a/.github/workflows/_build-rs.yml b/.github/workflows/_build-rs.yml index 015d089a6..e0eb76b82 100644 --- a/.github/workflows/_build-rs.yml +++ b/.github/workflows/_build-rs.yml @@ -21,6 +21,11 @@ on: jobs: builder: + # Reusable workflows can't request more than their caller was granted, so + # packages: write has to be declared at every level of the chain. + permissions: + contents: read + packages: write uses: ./.github/workflows/_builder-image.yml build: diff --git a/.github/workflows/_builder-image.yml b/.github/workflows/_builder-image.yml index a7d9535b5..35196ec51 100644 --- a/.github/workflows/_builder-image.yml +++ b/.github/workflows/_builder-image.yml @@ -3,6 +3,11 @@ name: _builder-image # Publishes the Linux build environment to GHCR and hands the tag back to the # caller, which uses it as the `container:` runtime for its Linux jobs. # +# This is the ONLY place the builder image is pushed, and it is called from +# _build-rs.yml only. Everything else that needs the image (_package-linux.yml, +# test.yml) just re-derives the same tag with `make ci-container-tag` and pulls +# it - the package is public, so no credentials or packages: read are needed. +# # The tag is a hash of the image's inputs (see containers/builder/Makefile), so # this is a no-op on almost every run: it only builds when that exact tag is # missing from the registry, which happens the first time a Dockerfile or diff --git a/.github/workflows/_package-linux.yml b/.github/workflows/_package-linux.yml index d82c954c4..01321950d 100644 --- a/.github/workflows/_package-linux.yml +++ b/.github/workflows/_package-linux.yml @@ -14,8 +14,23 @@ on: default: '["ubuntu-24.04","ubuntu-24.04-arm"]' jobs: + # Packaging only ever pulls the builder image - it runs after _build-rs.yml, + # which is what publishes it - so this just derives the tag from the repo + # contents rather than calling _builder-image.yml and needing packages: write. builder: - uses: ./.github/workflows/_builder-image.yml + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + image: ${{ steps.meta.outputs.tag }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + - id: meta + run: | + cd containers/builder + echo "tag<> $GITHUB_OUTPUT + make ci-container-tag >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT package: needs: builder diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index f7e53ae07..a70844f08 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -11,6 +11,10 @@ jobs: uses: ./.github/workflows/_build-js.yml secrets: inherit call-build-rs: + # packages: write for the builder image it publishes to GHCR. + permissions: + contents: read + packages: write uses: ./.github/workflows/_build-rs.yml secrets: inherit call-build-ee-wcp: diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index a2c4a7296..44bc01b7c 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -11,6 +11,10 @@ jobs: with: is_release: true call-build-rs: + # packages: write for the builder image it publishes to GHCR. + permissions: + contents: read + packages: write uses: ./.github/workflows/_build-rs.yml secrets: inherit with: diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index e784a7c0f..c2e805bf2 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -12,13 +12,25 @@ env: permissions: contents: read id-token: write - # Workflow-level permissions cap what called workflows can request, and - # _builder-image.yml needs to push to GHCR. - packages: write jobs: + # Read-only: _build-rs.yml is the only thing that publishes the builder image + # (via build_rs_e2e below, and via the build workflow). This just derives the + # same tag from the repo contents so the test jobs can pull it. builder: - uses: ./.github/workflows/_builder-image.yml + runs-on: ubuntu-24.04 + permissions: + contents: read + outputs: + image: ${{ steps.meta.outputs.tag }} + steps: + - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 + - id: meta + run: | + cd containers/builder + echo "tag<> $GITHUB_OUTPUT + make ci-container-tag >> $GITHUB_OUTPUT + echo "EOF" >> $GITHUB_OUTPUT test_rs: name: Test (rust) @@ -178,6 +190,10 @@ jobs: - uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v5 - run: make ${{ matrix.target }}/lint build_rs_e2e: + # packages: write for the builder image it publishes to GHCR. + permissions: + contents: read + packages: write uses: ./.github/workflows/_build-rs.yml with: rust_build_flags: "-C instrument-coverage -C codegen-units=1" diff --git a/containers/builder/Dockerfile b/containers/builder/Dockerfile index 781f0f793..ff888303b 100644 --- a/containers/builder/Dockerfile +++ b/containers/builder/Dockerfile @@ -39,16 +39,17 @@ RUN --mount=type=bind,target=rust-toolchain.toml,src=rust-toolchain.toml \ rustc --version && \ cargo --version -# Test tooling, so CI jobs don't have to install it per-job. Prebuilt archives -# rather than `cargo install`, which would be painfully slow under QEMU on the -# arm64 leg of the image build. -RUN curl -fsSL "https://github.com/taiki-e/cargo-llvm-cov/releases/latest/download/cargo-llvm-cov-$(uname -m)-unknown-linux-gnu.tar.gz" \ - | tar xzf - -C "${CARGO_HOME}/bin" && \ - case "$(uname -m)" in \ - x86_64) NEXTEST_URL=https://get.nexte.st/latest/linux ;; \ - aarch64) NEXTEST_URL=https://get.nexte.st/latest/linux-arm ;; \ +# Test tooling, so CI jobs don't have to install it per-job. Both are the +# statically linked musl builds on purpose: the gnu ones are built against +# glibc 2.34 and will not run on bullseye. (This is also why +# taiki-e/install-action can't just be used inside this image.) +RUN case "$(uname -m)" in \ + x86_64) NEXTEST_URL=https://get.nexte.st/latest/linux-musl ;; \ + aarch64) NEXTEST_URL=https://get.nexte.st/latest/linux-arm-musl ;; \ *) echo "unsupported arch $(uname -m)" >&2; exit 1 ;; \ esac && \ + curl -fsSL "https://github.com/taiki-e/cargo-llvm-cov/releases/latest/download/cargo-llvm-cov-$(uname -m)-unknown-linux-musl.tar.gz" \ + | tar xzf - -C "${CARGO_HOME}/bin" && \ curl -fsSL "${NEXTEST_URL}" | tar xzf - -C "${CARGO_HOME}/bin" && \ cargo llvm-cov --version && \ cargo nextest --version