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
10 changes: 7 additions & 3 deletions .github/workflows/audit.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,18 +14,22 @@ name: Policy repository self-audit

on:
pull_request:
push:
branches: [main]
merge_group:
schedule:
- cron: "23 3 * * *"
workflow_dispatch:

permissions:
contents: read

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name != 'merge_group' }}

jobs:
audit:
runs-on: [self-hosted]
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v4
- name: Checker tests
Expand Down
105 changes: 105 additions & 0 deletions .github/workflows/ci-auditor.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
# This repository's own checks for the CI guardrails (WI-3087), and the
# ci-auditor image on tags.
#
# Public repository: the light jobs run on GitHub-hosted ubuntu-24.04
# (operator, 2026-11-02). The image push uses only GITHUB_TOKEN
# (packages: write) and runs on tags only (rule CI-06).

name: CI guardrails

on:
pull_request:
merge_group:
push:
tags: ["v*"]
workflow_dispatch:

concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.event_name != 'merge_group' }}

permissions:
contents: read

jobs:
auditor:
name: auditor
runs-on: ubuntu-24.04
timeout-minutes: 10
defaults:
run:
working-directory: auditor
steps:
- uses: actions/checkout@v5
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
with:
enable-cache: true
- run: uv sync --frozen
- run: uv run ruff check .
- run: uv run ruff format --check .
- run: uv run mypy
- run: uv run pytest -q
- name: Standard and exemptions are valid
run: uv run ci-auditor validate --fail-expired

policy:
name: policy
runs-on: ubuntu-24.04
timeout-minutes: 10
steps:
- uses: actions/checkout@v5
with:
fetch-depth: 0
- uses: astral-sh/setup-uv@1c37ad07a6a961277cf70c0d37d6f313000f5884 # v10.3.0
- name: actionlint
run: |
set -euo pipefail
v=1.7.7; sum=023070a287cd8cccd71515fedc843f1985bf96c436b7effaecce67290e7e0757
curl -fsSL -o "$RUNNER_TEMP/al.tgz" "https://github.com/rhysd/actionlint/releases/download/v$v/actionlint_${v}_linux_amd64.tar.gz"
echo "$sum $RUNNER_TEMP/al.tgz" | sha256sum -c -
tar -xzf "$RUNNER_TEMP/al.tgz" -C "$RUNNER_TEMP" actionlint
"$RUNNER_TEMP/actionlint" -config-file ci-standard/actionlint.yaml
- name: Lint this repository against the standard (dogfood, report only)
run: uv run --project auditor ci-auditor lint . --repo github-policy --no-private --format github

image:
# Built in the merge queue too (push off), so a tag is never the image's
# first build (rule CI-21); pushed on tags only (rule CI-06).
name: image
if: github.event_name != 'pull_request'
needs: [auditor, policy]
runs-on: ubuntu-24.04
timeout-minutes: 20
permissions:
contents: read
packages: write
steps:
- uses: actions/checkout@v5
- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
- uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}
if: startsWith(github.ref, 'refs/tags/v')
- uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: .
file: auditor/Dockerfile
push: ${{ startsWith(github.ref, 'refs/tags/v') }}
tags: ghcr.io/thedancingdeveloper-org/ci-auditor:${{ startsWith(github.ref, 'refs/tags/v') && github.ref_name || 'ci' }}
provenance: false

ci:
name: ci
if: always()
needs: [auditor, policy, image]
runs-on: ubuntu-24.04
timeout-minutes: 5
steps:
- name: Gate
env:
RESULTS: ${{ toJSON(needs.*.result) }}
run: |
echo "$RESULTS"
if echo "$RESULTS" | grep -qE '"(failure|cancelled)"'; then exit 1; fi
119 changes: 119 additions & 0 deletions .github/workflows/image.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
# Reusable container image build (WI-3087), BuildKit with a registry cache.
#
# image:
# uses: TheDancingDeveloper-org/github-policy/.github/workflows/image.yml@v1.0.0
# with: {image: ghcr.io/thedancingdeveloper-org/egressy, push: "${{ github.event_name == 'push' }}"}
# permissions: {contents: read, packages: write}
#
# - Layers come from `<image>-cache:<target>-main`, written only on the
# default branch, so merge-queue and PR builds reuse it and never rebuild
# from scratch (CI review §3.5). Those builds never write the cache.
# - `push: false` (PR / merge_group) builds and loads only. `push: true` is
# for push to main, tags or dispatch, on a `publish` runner (rule CI-06).
# - Private dependencies go in as BuildKit secrets (`secret-env`), never
# build args (rule CI-23).
# - `remote-buildkit` points buildx at the shared in-cluster buildkitd when
# it exists (phase 2); empty uses the runner's docker driver.

name: Image (reusable)

on:
workflow_call:
inputs:
image:
description: Image name without tag, e.g. ghcr.io/thedancingdeveloper-org/egressy.
type: string
required: true
context:
type: string
default: .
file:
type: string
default: Dockerfile
target:
description: Dockerfile target; also the cache key.
type: string
default: ""
push:
description: Push by digest (main, tags, dispatch only).
type: boolean
default: false
tags:
description: Newline-separated tags to apply when pushing.
type: string
default: ""
runs-on:
description: JSON runs-on. docker for builds, publish when push is true.
type: string
default: '["self-hosted", "docker"]'
remote-buildkit:
description: tcp:// address of a shared buildkitd (optional).
type: string
default: ""
timeout-minutes:
type: number
default: 45
secrets:
build-secrets:
description: Newline-separated id=value pairs passed as BuildKit secrets.
required: false
outputs:
digest:
value: ${{ jobs.image.outputs.digest }}

permissions:
contents: read

jobs:
image:
name: image
runs-on: ${{ fromJSON(inputs.runs-on) }}
timeout-minutes: ${{ inputs.timeout-minutes }}
outputs:
digest: ${{ steps.build.outputs.digest }}
steps:
- uses: actions/checkout@v5

- uses: docker/setup-buildx-action@f87e5991a6d7451dcb8d9637bfbc97413f497069 # v4.4.1
with:
driver: ${{ inputs.remote-buildkit != '' && 'remote' || 'docker-container' }}
endpoint: ${{ inputs.remote-buildkit }}

- name: Log in to GHCR
if: ${{ startsWith(inputs.image, 'ghcr.io/') }}
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ github.token }}

- name: Cache refs
id: cache
shell: bash
env:
IMAGE: ${{ inputs.image }}
TARGET: ${{ inputs.target }}
IS_MAIN: ${{ github.ref == format('refs/heads/{0}', github.event.repository.default_branch) }}
run: |
ref="${IMAGE}-cache:${TARGET:-default}-main"
echo "from=type=registry,ref=${ref}" >> "$GITHUB_OUTPUT"
if [ "$IS_MAIN" = "true" ]; then
echo "to=type=registry,ref=${ref},mode=max" >> "$GITHUB_OUTPUT"
else
echo "to=" >> "$GITHUB_OUTPUT"
fi

- name: Build
id: build
uses: docker/build-push-action@c3c9e263c25d99ce0380d002d59b67737d91b0dc # v7.4.0
with:
context: ${{ inputs.context }}
file: ${{ inputs.file }}
target: ${{ inputs.target }}
push: ${{ inputs.push }}
load: ${{ !inputs.push }}
tags: ${{ inputs.push && inputs.tags || format('{0}:ci-{1}', inputs.image, github.run_id) }}
cache-from: ${{ steps.cache.outputs.from }}
cache-to: ${{ steps.cache.outputs.to }}
secrets: ${{ secrets.build-secrets }}
provenance: false
Loading
Loading