Adopt commit-based container tags (#232)
## Summary - Replace git-tag-triggered container builds with path-based triggers on main and workflow_dispatch - Image tags now encode upstream app version + commit SHA (`vX.Y.Z-<sha>`) for full traceability - Replace `container-tag-and-release` task with `container-build-and-release` (dispatches workflows via Forgejo API) - Update dagger `publish()` to accept `commit_sha` parameter - Update all docs and references to the new workflow ## Deployment and Testing - [ ] Merge to main - [ ] `mise run container-build-and-release <name>` for each container to populate new-format tags - [ ] Verify tags in registry via `mise run container-list` - [ ] Existing images untouched — old tags remain available Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/232
This commit is contained in:
parent
0e2c10176d
commit
ffa8727660
13 changed files with 363 additions and 258 deletions
|
|
@ -1,82 +1,138 @@
|
|||
# Nix container build workflow
|
||||
# Triggers on tags matching: <container>-v<version>
|
||||
# Builds from containers/<container>/default.nix if it exists, skips otherwise
|
||||
# Pushes to Zot registry via skopeo with -nix image tag suffix
|
||||
#
|
||||
# Examples:
|
||||
# nettest-v1.0.0 -> builds containers/nettest/default.nix, pushes :v1.0.0-nix
|
||||
# devpi-v2.1.0 -> skips (no default.nix)
|
||||
# Triggers on pushes to main that modify containers/*, or via manual dispatch.
|
||||
# Detects which containers changed, builds from default.nix, and pushes via
|
||||
# skopeo with commit-SHA-based tags: vX.Y.Z-<sha>-nix
|
||||
name: Build Container (Nix)
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*-v[0-9]*'
|
||||
branches: [main]
|
||||
paths: ['containers/**']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
container:
|
||||
description: 'Container name (directory under containers/)'
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
description: 'Commit SHA to build (defaults to current HEAD)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
detect:
|
||||
runs-on: nix-container-builder
|
||||
outputs:
|
||||
containers: ${{ steps.list.outputs.containers }}
|
||||
steps:
|
||||
- name: Parse tag
|
||||
id: parse
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect changed containers
|
||||
id: list
|
||||
run: |
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
echo "Tag: $TAG"
|
||||
|
||||
# Extract container name (everything before -v)
|
||||
# e.g., "nettest-v1.0.0" -> "nettest", "my-app-v2.0.0" -> "my-app"
|
||||
CONTAINER="${TAG%-v[0-9]*}"
|
||||
VERSION="${TAG#"${CONTAINER}"-}"
|
||||
|
||||
echo "container=$CONTAINER" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Container: $CONTAINER"
|
||||
echo "Version: $VERSION"
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
CONTAINERS='["${{ inputs.container }}"]'
|
||||
else
|
||||
CONTAINERS=$(git diff --name-only HEAD~1 HEAD -- containers/ \
|
||||
| cut -d/ -f2 | sort -u \
|
||||
| jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||
fi
|
||||
echo "containers=$CONTAINERS" >> "$GITHUB_OUTPUT"
|
||||
echo "Containers to build: $CONTAINERS"
|
||||
|
||||
build:
|
||||
needs: detect
|
||||
if: needs.detect.outputs.containers != '[]'
|
||||
runs-on: nix-container-builder
|
||||
strategy:
|
||||
matrix:
|
||||
container: ${{ fromJson(needs.detect.outputs.containers) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check if nix container exists
|
||||
- name: Check for default.nix
|
||||
id: check
|
||||
run: |
|
||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
||||
CONTEXT="containers/$CONTAINER"
|
||||
|
||||
if [ -f "$CONTEXT/default.nix" ]; then
|
||||
echo "Found $CONTEXT/default.nix"
|
||||
if [ -f "containers/${{ matrix.container }}/default.nix" ]; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No default.nix found at $CONTEXT/default.nix — skipping"
|
||||
echo "No default.nix for ${{ matrix.container }} — skipping"
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Extract version and SHA
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
id: meta
|
||||
run: |
|
||||
CONTAINER="${{ matrix.container }}"
|
||||
NIX_FILE="containers/$CONTAINER/default.nix"
|
||||
|
||||
# Try extracting version = "..." from the nix file (e.g. ntfy)
|
||||
VERSION=$(grep -m1 '^\s*version\s*=\s*"' "$NIX_FILE" \
|
||||
| sed 's/.*"\(.*\)".*/\1/' || true)
|
||||
|
||||
# Fall back to CONTAINER_APP_VERSION from Dockerfile (e.g. nettest)
|
||||
if [ -z "$VERSION" ] && [ -f "containers/$CONTAINER/Dockerfile" ]; then
|
||||
VERSION=$(grep -m1 '^ARG CONTAINER_APP_VERSION=' \
|
||||
"containers/$CONTAINER/Dockerfile" \
|
||||
| sed 's/^ARG CONTAINER_APP_VERSION=//')
|
||||
fi
|
||||
|
||||
# Last resort: dagger call nix-version for nixpkgs packages (e.g. authentik)
|
||||
if [ -z "$VERSION" ]; then
|
||||
VERSION=$(dagger call nix-version --package="$CONTAINER")
|
||||
fi
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Error: Could not determine version for $CONTAINER"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
REF="${{ inputs.ref }}"
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${GITHUB_SHA}"
|
||||
fi
|
||||
SHORT_SHA=$(echo "$REF" | head -c 7)
|
||||
|
||||
# Ensure version starts with 'v'
|
||||
case "$VERSION" in
|
||||
v*) ;;
|
||||
*) VERSION="v${VERSION}" ;;
|
||||
esac
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION, SHA: $SHORT_SHA"
|
||||
|
||||
- name: Resolve nixpkgs
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
id: nixpkgs
|
||||
run: |
|
||||
# Resolve nixpkgs from the flake registry for <nixpkgs> lookup
|
||||
NIXPKGS_PATH=$(nix flake metadata nixpkgs --json | jq -r '.path')
|
||||
echo "Resolved nixpkgs: $NIXPKGS_PATH"
|
||||
echo "path=$NIXPKGS_PATH" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Build with nix
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
id: build
|
||||
env:
|
||||
NIX_PATH: "nixpkgs=${{ steps.nixpkgs.outputs.path }}"
|
||||
run: |
|
||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
||||
echo "Building containers/$CONTAINER/default.nix"
|
||||
echo "Building containers/${{ matrix.container }}/default.nix"
|
||||
echo "NIX_PATH=$NIX_PATH"
|
||||
nix-build "containers/$CONTAINER/default.nix" -o result
|
||||
nix-build "containers/${{ matrix.container }}/default.nix" -o result
|
||||
echo "Build complete: $(readlink result)"
|
||||
|
||||
- name: Push to registry
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
run: |
|
||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
||||
VERSION="${{ steps.parse.outputs.version }}"
|
||||
IMAGE="registry.ops.eblu.me/blumeops/$CONTAINER:$VERSION-nix"
|
||||
CONTAINER="${{ matrix.container }}"
|
||||
VERSION="${{ steps.meta.outputs.version }}"
|
||||
SHORT_SHA="${{ steps.meta.outputs.sha }}"
|
||||
IMAGE="registry.ops.eblu.me/blumeops/$CONTAINER:${VERSION}-${SHORT_SHA}-nix"
|
||||
|
||||
echo "Pushing to $IMAGE"
|
||||
skopeo copy \
|
||||
|
|
|
|||
|
|
@ -1,77 +1,105 @@
|
|||
# Generic container build workflow
|
||||
# Triggers on tags matching: <container>-v<version>
|
||||
# Builds from containers/<container>/Dockerfile if it exists
|
||||
#
|
||||
# Uses Dagger to build and push images to the Zot registry.
|
||||
#
|
||||
# Examples:
|
||||
# nettest-v1.0.0 -> builds containers/nettest/
|
||||
# devpi-v2.1.0 -> builds containers/devpi/
|
||||
# foo-v1.0.0 -> skips if containers/foo/ doesn't exist
|
||||
# Dockerfile container build workflow
|
||||
# Triggers on pushes to main that modify containers/*, or via manual dispatch.
|
||||
# Detects which containers changed, extracts version from CONTAINER_APP_VERSION,
|
||||
# and publishes with commit-SHA-based tags: vX.Y.Z-<sha>
|
||||
name: Build Container
|
||||
|
||||
on:
|
||||
push:
|
||||
tags:
|
||||
- '*-v[0-9]*'
|
||||
branches: [main]
|
||||
paths: ['containers/**']
|
||||
workflow_dispatch:
|
||||
inputs:
|
||||
container:
|
||||
description: 'Container name (directory under containers/)'
|
||||
required: true
|
||||
type: string
|
||||
ref:
|
||||
description: 'Commit SHA to build (defaults to current HEAD)'
|
||||
required: false
|
||||
type: string
|
||||
|
||||
jobs:
|
||||
build:
|
||||
detect:
|
||||
runs-on: k8s
|
||||
outputs:
|
||||
containers: ${{ steps.list.outputs.containers }}
|
||||
steps:
|
||||
- name: Parse tag
|
||||
id: parse
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 2
|
||||
|
||||
- name: Detect changed containers
|
||||
id: list
|
||||
run: |
|
||||
TAG="${GITHUB_REF_NAME}"
|
||||
echo "Tag: $TAG"
|
||||
|
||||
# Extract container name (everything before -v)
|
||||
# e.g., "nettest-v1.0.0" -> "nettest", "my-app-v2.0.0" -> "my-app"
|
||||
CONTAINER="${TAG%-v[0-9]*}"
|
||||
VERSION="${TAG#"${CONTAINER}"-}"
|
||||
|
||||
echo "container=$CONTAINER" >> "$GITHUB_OUTPUT"
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "Container: $CONTAINER"
|
||||
echo "Version: $VERSION"
|
||||
if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then
|
||||
CONTAINERS='["${{ inputs.container }}"]'
|
||||
else
|
||||
# Diff against parent commit to find changed container dirs
|
||||
CONTAINERS=$(git diff --name-only HEAD~1 HEAD -- containers/ \
|
||||
| cut -d/ -f2 | sort -u \
|
||||
| jq -R -s -c 'split("\n") | map(select(length > 0))')
|
||||
fi
|
||||
echo "containers=$CONTAINERS" >> "$GITHUB_OUTPUT"
|
||||
echo "Containers to build: $CONTAINERS"
|
||||
|
||||
build:
|
||||
needs: detect
|
||||
if: needs.detect.outputs.containers != '[]'
|
||||
runs-on: k8s
|
||||
strategy:
|
||||
matrix:
|
||||
container: ${{ fromJson(needs.detect.outputs.containers) }}
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
|
||||
- name: Check if container exists
|
||||
- name: Check for Dockerfile
|
||||
id: check
|
||||
run: |
|
||||
CONTAINER="${{ steps.parse.outputs.container }}"
|
||||
CONTEXT="containers/$CONTAINER"
|
||||
|
||||
if [ -f "$CONTEXT/Dockerfile" ]; then
|
||||
echo "Found $CONTEXT/Dockerfile"
|
||||
if [ -f "containers/${{ matrix.container }}/Dockerfile" ]; then
|
||||
echo "exists=true" >> "$GITHUB_OUTPUT"
|
||||
else
|
||||
echo "No Dockerfile found at $CONTEXT/Dockerfile"
|
||||
echo "No Dockerfile for ${{ matrix.container }} — skipping"
|
||||
echo "exists=false" >> "$GITHUB_OUTPUT"
|
||||
fi
|
||||
|
||||
- name: Skip if container not found
|
||||
if: steps.check.outputs.exists != 'true'
|
||||
- name: Extract version and SHA
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
id: meta
|
||||
run: |
|
||||
echo "========================================"
|
||||
echo "Container not found: ${{ steps.parse.outputs.container }}"
|
||||
echo "========================================"
|
||||
echo ""
|
||||
echo "Tag '${{ github.ref_name }}' does not match any container in containers/"
|
||||
echo ""
|
||||
echo "Available containers:"
|
||||
find containers -maxdepth 1 -mindepth 1 -type d -exec basename {} \; 2>/dev/null | sort | while read -r name; do
|
||||
echo " - $name"
|
||||
done || echo " (none)"
|
||||
echo ""
|
||||
echo "Skipping build."
|
||||
VERSION=$(grep -m1 '^ARG CONTAINER_APP_VERSION=' \
|
||||
"containers/${{ matrix.container }}/Dockerfile" \
|
||||
| sed 's/^ARG CONTAINER_APP_VERSION=//')
|
||||
|
||||
if [ -z "$VERSION" ]; then
|
||||
echo "Error: No CONTAINER_APP_VERSION found in Dockerfile"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Use dispatch input ref if provided, otherwise current commit
|
||||
REF="${{ inputs.ref }}"
|
||||
if [ -z "$REF" ]; then
|
||||
REF="${GITHUB_SHA}"
|
||||
fi
|
||||
SHORT_SHA=$(echo "$REF" | head -c 7)
|
||||
|
||||
# Ensure version starts with 'v'
|
||||
case "$VERSION" in
|
||||
v*) ;; # already has v prefix
|
||||
*) VERSION="v${VERSION}" ;;
|
||||
esac
|
||||
|
||||
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
||||
echo "sha=$SHORT_SHA" >> "$GITHUB_OUTPUT"
|
||||
echo "Version: $VERSION, SHA: $SHORT_SHA"
|
||||
|
||||
- name: Publish
|
||||
if: steps.check.outputs.exists == 'true'
|
||||
run: |
|
||||
dagger call publish \
|
||||
--src=. \
|
||||
--container-name=${{ steps.parse.outputs.container }} \
|
||||
--version=${{ steps.parse.outputs.version }}
|
||||
--container-name=${{ matrix.container }} \
|
||||
--version=${{ steps.meta.outputs.version }} \
|
||||
--commit-sha=${{ steps.meta.outputs.sha }}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue