diff --git a/.forgejo/workflows/build-grid-rs.yaml b/.forgejo/workflows/build-grid-rs.yaml new file mode 100644 index 00000000..d23be379 --- /dev/null +++ b/.forgejo/workflows/build-grid-rs.yaml @@ -0,0 +1,133 @@ +name: Build prop-grid-rs + +# Path-filtered so this doesn't fire on every Elixir push — only when the +# Rust crate or the workflow itself changes. +on: + push: + branches: + - main + paths: + - 'rust/prop_grid_rs/**' + - '.forgejo/workflows/build-grid-rs.yaml' + +env: + REGISTRY: git.mcintire.me + IMAGE_NAME: graham/prop-grid-rs + +concurrency: + group: ${{ github.workflow }}-${{ github.ref }} + cancel-in-progress: true + +jobs: + build-and-push: + name: Test, build, push + runs-on: ubuntu-22.04 + + steps: + - name: Checkout code + uses: https://github.com/actions/checkout@v4 + + # Same static-binary dance as .forgejo/workflows/build.yaml — the + # runner image's apt repos have failing GPG signatures, so we bypass + # them entirely. buildx is needed for the BuildKit-only + # `--mount=type=cache` feature the Dockerfile uses. + - name: Install Docker CLI and buildx + run: | + curl -fsSL -o /tmp/docker.tgz \ + https://download.docker.com/linux/static/stable/x86_64/docker-27.5.1.tgz + tar xzf /tmp/docker.tgz -C /tmp + install -m 0755 /tmp/docker/docker /usr/local/bin/docker + rm -rf /tmp/docker /tmp/docker.tgz + + mkdir -p /usr/libexec/docker/cli-plugins + curl -fsSL -o /usr/libexec/docker/cli-plugins/docker-buildx \ + https://github.com/docker/buildx/releases/download/v0.19.3/buildx-v0.19.3.linux-amd64 + chmod +x /usr/libexec/docker/cli-plugins/docker-buildx + + docker --version + docker buildx version + + - name: Wait for Docker daemon + run: | + for i in $(seq 1 30); do + if docker info >/dev/null 2>&1; then + echo "Docker daemon reachable after ${i}s" + exit 0 + fi + sleep 1 + done + echo "Docker daemon never became reachable" >&2 + exit 1 + + # Gate the image build on a clean test run so a push that regresses + # the scorer or breaks clippy never produces an image. + - name: cargo clippy + test + run: | + docker run --rm \ + -v "$(pwd)/rust/prop_grid_rs":/src \ + -w /src \ + rust:1.94-trixie \ + sh -euc ' + cargo clippy --all-targets -- -D warnings + cargo test + ' + + - name: Generate image tag + id: tag + run: | + TIMESTAMP=$(date +%s) + SHORT_SHA=$(git rev-parse --short=7 HEAD | cut -c1-7) + TAG="main-${TIMESTAMP}-${SHORT_SHA}" + echo "tag=${TAG}" >> $GITHUB_OUTPUT + + - name: Log in to container registry + run: | + attempt=1 + max_attempts=3 + while : ; do + if echo "${{ secrets.REGISTRY_PASSWORD }}" | \ + docker login "${{ secrets.REGISTRY_URL }}" \ + -u "${{ secrets.REGISTRY_USER }}" \ + --password-stdin; then + exit 0 + fi + if [ "$attempt" -ge "$max_attempts" ]; then + echo "docker login failed after $attempt attempts" >&2 + exit 1 + fi + delay=$((2 ** attempt)) + echo "docker login attempt $attempt failed; retrying in ${delay}s" + sleep "$delay" + attempt=$((attempt + 1)) + done + + # Push three tags so (a) `:main` is a stable handle the k8s manifest + # can pin to without flux image-automation wired up, (b) `:latest` + # mirrors the Elixir workflow for consistency, (c) the timestamped + # tag is what flux image-automation picks up when we add an + # ImagePolicy later. + - name: Build and push Docker image + run: | + IMAGE="${{ env.REGISTRY }}/${{ env.IMAGE_NAME }}" + TAG="${{ steps.tag.outputs.tag }}" + + attempt=1 + max_attempts=3 + while : ; do + if docker buildx build \ + -t "${IMAGE}:${TAG}" \ + -t "${IMAGE}:latest" \ + -t "${IMAGE}:main" \ + --push \ + rust/prop_grid_rs; then + exit 0 + fi + if [ "$attempt" -ge "$max_attempts" ]; then + echo "docker buildx build failed after $attempt attempts" >&2 + exit 1 + fi + delay=$((2 ** attempt)) + echo "docker buildx build attempt $attempt failed; retrying in ${delay}s" + sleep "$delay" + attempt=$((attempt + 1)) + done