9482a0a2a7
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.
157 lines
6.7 KiB
YAML
157 lines
6.7 KiB
YAML
name: Provision Coder Templates
|
|
|
|
# Keeps Coder templates in sync with templates/*/ in this repo:
|
|
# - every push to main pushes a new version of each templates/<env>/ dir
|
|
# (coder templates push creates it if it doesn't exist yet, so adding a
|
|
# new templates/<env>/ directory is enough to provision a new one)
|
|
# - if a templates/<env>/ directory is removed on main, its template is
|
|
# deleted from Coder. `coder templates delete` refuses to delete a
|
|
# template that still has active workspaces, so this can't silently
|
|
# orphan running workspaces - it just fails loudly and needs a human.
|
|
#
|
|
# Requires two repo/org secrets (Settings > Actions > Secrets):
|
|
# CODER_URL e.g. https://code.octoturge.com
|
|
# CODER_SESSION_TOKEN a token from `coder tokens create`, ideally under a
|
|
# dedicated service account rather than a personal one
|
|
# GITEA_PACKAGE_TOKEN a Gitea access token (user Settings > Applications)
|
|
# with write:package scope, for pushing each
|
|
# Dockerfile-having template's image to this
|
|
# instance's container registry. Only the octoturge
|
|
# account's own token is used - docker login below
|
|
# hardcodes that username to match.
|
|
#
|
|
# Any templates/<env>/ that has its own Dockerfile gets its image built and
|
|
# pushed here (build-images, on the dedicated "docker-build" runner - see
|
|
# templates/web/main.tf for why: that runner is scoped to this repo only and
|
|
# has host Docker socket access that the shared runner-1 deliberately
|
|
# doesn't). provision then just pulls the tag build-images produced, instead
|
|
# of building it itself at `terraform apply` time - keeps a slow toolchain
|
|
# 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:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- "templates/**"
|
|
- ".gitea/workflows/coder-templates.yml"
|
|
workflow_dispatch: {}
|
|
|
|
jobs:
|
|
build-images:
|
|
# The docker-build runner's docker_host: "" setting already auto-injects
|
|
# /var/run/docker.sock into job containers - an explicit
|
|
# container.volumes mount for the same path here fails at container
|
|
# creation with "Duplicate mount point: /var/run/docker.sock".
|
|
runs-on: docker-build
|
|
outputs:
|
|
failed_templates: ${{ steps.build.outputs.failed_templates }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Build and push every template's image (skips a tag that's already in the registry)
|
|
id: build
|
|
run: |
|
|
set -e
|
|
echo "${{ secrets.GITEA_PACKAGE_TOKEN }}" | docker login git.octoturge.com -u octoturge --password-stdin
|
|
|
|
FAILED=""
|
|
for dockerfile in templates/*/Dockerfile; do
|
|
[ -e "$dockerfile" ] || continue
|
|
dir="$(dirname "$dockerfile")"
|
|
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:
|
|
# 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
|
|
env:
|
|
CODER_URL: ${{ secrets.CODER_URL }}
|
|
CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }}
|
|
FAILED_TEMPLATES: ${{ needs.build-images.outputs.failed_templates }}
|
|
steps:
|
|
- name: Checkout (full history, needed to detect removed templates)
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Install coder CLI
|
|
run: |
|
|
set -e
|
|
curl -fsSL https://coder.com/install.sh | sh
|
|
coder version
|
|
|
|
- name: Push (create or update) every template
|
|
run: |
|
|
set -e
|
|
for dir in templates/*/; do
|
|
name="$(basename "$dir")"
|
|
full="profiles-$name"
|
|
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}"
|
|
echo "::endgroup::"
|
|
done
|
|
|
|
- name: Delete templates whose directory was removed
|
|
if: github.event_name == 'push'
|
|
run: |
|
|
set -e
|
|
PREV_SHA="$(git rev-parse HEAD~1 2>/dev/null || true)"
|
|
if [ -z "$PREV_SHA" ]; then
|
|
echo "No previous commit on this branch (first push), nothing to diff. Skipping."
|
|
exit 0
|
|
fi
|
|
|
|
OLD_DIRS="$(git ls-tree -d --name-only "$PREV_SHA" -- 'templates/*' 2>/dev/null | xargs -n1 basename 2>/dev/null || true)"
|
|
if [ -z "$OLD_DIRS" ]; then
|
|
echo "No templates/ directory at $PREV_SHA, nothing to diff. Skipping."
|
|
exit 0
|
|
fi
|
|
|
|
for old in $OLD_DIRS; do
|
|
if [ ! -d "templates/$old" ]; then
|
|
name="profiles-$old"
|
|
echo "::group::Deleting $name (templates/$old was removed)"
|
|
coder templates delete "$name" --yes \
|
|
|| echo "::warning::Failed to delete $name - check for active workspaces still using it."
|
|
echo "::endgroup::"
|
|
fi
|
|
done
|