ci: build every template's image independently, skip only failed ones
Provision Coder Templates / provision (push) Successful in 2m5s
Provision Coder Templates / build-images (push) Failing after 16s

Fixes a regression where the whole provision job (all 6 templates) was
skipped whenever build-web-image failed, since Actions skips a job whose
needs: dependency failed by default. Generalizes the single web-image
build step into a loop over every templates/*/Dockerfile, building and
pushing each independently so one failure doesn't block the rest, and
reports failures via a job output. provision now runs unconditionally
(if: always()) and skips pushing only the specific template(s) whose
image build failed this run, leaving their previous working version in
place instead of pushing one with no matching registry tag.
This commit is contained in:
2026-08-27 01:18:25 +02:00
parent 278023e4c2
commit 9482a0a2a7
+65 -24
View File
@@ -14,18 +14,25 @@ name: Provision Coder Templates
# CODER_SESSION_TOKEN a token from `coder tokens create`, ideally under a # CODER_SESSION_TOKEN a token from `coder tokens create`, ideally under a
# dedicated service account rather than a personal one # dedicated service account rather than a personal one
# GITEA_PACKAGE_TOKEN a Gitea access token (user Settings > Applications) # GITEA_PACKAGE_TOKEN a Gitea access token (user Settings > Applications)
# with write:package scope, for pushing templates/web's # with write:package scope, for pushing each
# image to this instance's container registry. Only # Dockerfile-having template's image to this
# the octoturge account's own token is used - login() # instance's container registry. Only the octoturge
# account's own token is used - docker login below
# hardcodes that username to match. # hardcodes that username to match.
# #
# templates/web builds its Docker image here (build-web-image, on the # Any templates/<env>/ that has its own Dockerfile gets its image built and
# dedicated "docker-build" runner - see templates/web/main.tf for why: that # pushed here (build-images, on the dedicated "docker-build" runner - see
# runner is scoped to this repo only and has host Docker socket access that # templates/web/main.tf for why: that runner is scoped to this repo only and
# the shared runner-1 deliberately doesn't). provision then just pulls the # has host Docker socket access that the shared runner-1 deliberately
# tag build-web-image produced, instead of building it itself at # doesn't). provision then just pulls the tag build-images produced, instead
# `terraform apply` time - keeps the slow Rust toolchain compile off of # of building it itself at `terraform apply` time - keeps a slow toolchain
# "someone is waiting to create a workspace". # compile off of "someone is waiting to create a workspace".
#
# Each template's image is built independently - one Dockerfile failing to
# build doesn't stop the others from building, and provision skips pushing
# only the specific template(s) whose image build failed this run (leaving
# their previous, already-working Coder template version in place) rather
# than skipping every template or pushing one with no matching image.
on: on:
push: push:
@@ -36,35 +43,62 @@ on:
workflow_dispatch: {} workflow_dispatch: {}
jobs: jobs:
build-web-image: build-images:
# The docker-build runner's docker_host: "" setting already auto-injects # The docker-build runner's docker_host: "" setting already auto-injects
# /var/run/docker.sock into job containers - an explicit # /var/run/docker.sock into job containers - an explicit
# container.volumes mount for the same path here fails at container # container.volumes mount for the same path here fails at container
# creation with "Duplicate mount point: /var/run/docker.sock". # creation with "Duplicate mount point: /var/run/docker.sock".
runs-on: docker-build runs-on: docker-build
outputs:
failed_templates: ${{ steps.build.outputs.failed_templates }}
steps: steps:
- name: Checkout - name: Checkout
uses: actions/checkout@v4 uses: actions/checkout@v4
- name: Build and push templates/web's image (skips if the tag already exists) - name: Build and push every template's image (skips a tag that's already in the registry)
id: build
run: | run: |
set -e set -e
TAG="$(sha1sum templates/web/Dockerfile | cut -d' ' -f1)"
IMAGE="git.octoturge.com/octo-tech/profiles-web:${TAG}"
echo "${{ secrets.GITEA_PACKAGE_TOKEN }}" | docker login git.octoturge.com -u octoturge --password-stdin echo "${{ secrets.GITEA_PACKAGE_TOKEN }}" | docker login git.octoturge.com -u octoturge --password-stdin
if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then
echo "$IMAGE already in the registry (Dockerfile unchanged), skipping build." FAILED=""
exit 0 for dockerfile in templates/*/Dockerfile; do
fi [ -e "$dockerfile" ] || continue
docker build -t "$IMAGE" templates/web dir="$(dirname "$dockerfile")"
docker push "$IMAGE" name="$(basename "$dir")"
TAG="$(sha1sum "$dockerfile" | cut -d' ' -f1)"
IMAGE="git.octoturge.com/octo-tech/profiles-${name}:${TAG}"
echo "::group::${name}"
if docker manifest inspect "$IMAGE" >/dev/null 2>&1; then
echo "$IMAGE already in the registry (Dockerfile unchanged), skipping build."
elif docker build -t "$IMAGE" "$dir" && docker push "$IMAGE"; then
echo "Built and pushed $IMAGE"
else
echo "::warning::Failed to build/push $IMAGE - templates/$name will be skipped this run."
FAILED="$FAILED $name"
fi
echo "::endgroup::"
done
echo "failed_templates=${FAILED# }" >> "$GITHUB_OUTPUT"
provision: provision:
needs: build-web-image # needs: build-images orders this after the image builds (so a fresh
# template push never points at a tag that isn't in the registry yet)
# without making every template's reprovisioning depend on ALL builds
# succeeding - if:always() overrides the default "skip if a dependency
# failed" behavior, since build-images only fails outright on an
# infra-level problem (e.g. registry login); a single template's build
# failure is reported via failed_templates instead and only skips that
# one template below.
needs: build-images
if: always()
runs-on: ubuntu-latest runs-on: ubuntu-latest
env: env:
CODER_URL: ${{ secrets.CODER_URL }} CODER_URL: ${{ secrets.CODER_URL }}
CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }} CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }}
FAILED_TEMPLATES: ${{ needs.build-images.outputs.failed_templates }}
steps: steps:
- name: Checkout (full history, needed to detect removed templates) - name: Checkout (full history, needed to detect removed templates)
uses: actions/checkout@v4 uses: actions/checkout@v4
@@ -81,9 +115,16 @@ jobs:
run: | run: |
set -e set -e
for dir in templates/*/; do for dir in templates/*/; do
name="profiles-$(basename "$dir")" name="$(basename "$dir")"
echo "::group::Pushing $name from $dir" full="profiles-$name"
coder templates push "$name" -d "$dir" --yes \ case " $FAILED_TEMPLATES " in
*" $name "*)
echo "::warning::Skipping $full - its Docker image failed to build this run (see build-images), leaving the previous template version in place."
continue
;;
esac
echo "::group::Pushing $full from $dir"
coder templates push "$full" -d "$dir" --yes \
-m "auto-provisioned from ${GITHUB_SHA:0:12}" -m "auto-provisioned from ${GITHUB_SHA:0:12}"
echo "::endgroup::" echo "::endgroup::"
done done