## Summary The runner pod's `TZ` env var (#159, #160) doesn't propagate to workflow job containers — jobs run inside Docker containers spawned by the DinD sidecar, not in the runner process itself. Set `TZ: America/Los_Angeles` at the job level so `uvx towncrier build` uses the correct timezone. This is the actual fix for the Feb 12 changelog dates. The runner pod TZ is still useful for runner daemon logs but doesn't affect job execution. Reviewed-on: https://forge.ops.eblu.me/eblume/blumeops/pulls/161
334 lines
12 KiB
YAML
334 lines
12 KiB
YAML
# BlumeOps Release Workflow
|
|
#
|
|
# Creates a versioned release of BlumeOps with all build artifacts.
|
|
# Currently includes:
|
|
# - Documentation site (Quartz static build)
|
|
# - Changelog (built from towncrier fragments)
|
|
#
|
|
# Usage:
|
|
# 1. Go to Actions > Build BlumeOps > Run workflow
|
|
# 2. Select version bump type (patch/minor/major) or choose specific version
|
|
# 3. The workflow creates a release with attached artifacts
|
|
#
|
|
# Documentation asset URL:
|
|
# https://forge.ops.eblu.me/eblume/blumeops/releases/download/<tag>/docs-<version>.tar.gz
|
|
|
|
name: Build BlumeOps
|
|
|
|
on:
|
|
workflow_dispatch:
|
|
inputs:
|
|
version_type:
|
|
description: 'Version bump type'
|
|
required: true
|
|
default: 'BUMP_PATCH'
|
|
type: choice
|
|
options:
|
|
- BUMP_PATCH
|
|
- BUMP_MINOR
|
|
- BUMP_MAJOR
|
|
- SPECIFIC_VERSION
|
|
specific_version:
|
|
description: 'Specific version (only used when version_type is SPECIFIC_VERSION, e.g., v1.2.0)'
|
|
required: false
|
|
default: ''
|
|
type: string
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: k8s
|
|
env:
|
|
TZ: America/Los_Angeles
|
|
steps:
|
|
- name: Resolve version
|
|
id: version
|
|
run: |
|
|
VERSION_TYPE="${{ inputs.version_type }}"
|
|
SPECIFIC_VERSION="${{ inputs.specific_version }}"
|
|
|
|
# Fetch latest release
|
|
echo "Fetching latest release..."
|
|
LATEST=$(curl -s "https://forge.ops.eblu.me/api/v1/repos/eblume/blumeops/releases/latest" | jq -r '.tag_name // empty' || true)
|
|
|
|
if [ -z "$LATEST" ]; then
|
|
LATEST="v0.0.0"
|
|
echo "No previous releases found, using base version: $LATEST"
|
|
else
|
|
echo "Latest release: $LATEST"
|
|
fi
|
|
|
|
# Parse current version components (strip 'v' prefix)
|
|
CURRENT="${LATEST#v}"
|
|
MAJOR=$(echo "$CURRENT" | cut -d. -f1)
|
|
MINOR=$(echo "$CURRENT" | cut -d. -f2)
|
|
PATCH=$(echo "$CURRENT" | cut -d. -f3)
|
|
|
|
case "$VERSION_TYPE" in
|
|
BUMP_MAJOR)
|
|
VERSION="v$((MAJOR + 1)).0.0"
|
|
echo "Bumping major: $LATEST -> $VERSION"
|
|
;;
|
|
BUMP_MINOR)
|
|
VERSION="v${MAJOR}.$((MINOR + 1)).0"
|
|
echo "Bumping minor: $LATEST -> $VERSION"
|
|
;;
|
|
BUMP_PATCH)
|
|
VERSION="v${MAJOR}.${MINOR}.$((PATCH + 1))"
|
|
echo "Bumping patch: $LATEST -> $VERSION"
|
|
;;
|
|
SPECIFIC_VERSION)
|
|
if [ -z "$SPECIFIC_VERSION" ]; then
|
|
echo "Error: specific_version is required when version_type is SPECIFIC_VERSION"
|
|
exit 1
|
|
fi
|
|
# Validate format
|
|
if [[ ! "$SPECIFIC_VERSION" =~ ^v[0-9]+\.[0-9]+\.[0-9]+$ ]]; then
|
|
echo "Error: Version must be in format vX.Y.Z (e.g., v1.0.0)"
|
|
exit 1
|
|
fi
|
|
VERSION="$SPECIFIC_VERSION"
|
|
echo "Using specific version: $VERSION"
|
|
;;
|
|
*)
|
|
echo "Error: Unknown version_type: $VERSION_TYPE"
|
|
exit 1
|
|
;;
|
|
esac
|
|
|
|
# Check if this version already exists
|
|
if curl -sf "https://forge.ops.eblu.me/api/v1/repos/eblume/blumeops/releases/tags/$VERSION" > /dev/null 2>&1; then
|
|
echo "Error: Release $VERSION already exists"
|
|
echo "See: https://forge.ops.eblu.me/eblume/blumeops/releases/tag/$VERSION"
|
|
exit 1
|
|
fi
|
|
|
|
echo "version=$VERSION" >> "$GITHUB_OUTPUT"
|
|
echo "Building BlumeOps release: $VERSION"
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
with:
|
|
fetch-depth: 0
|
|
|
|
- name: Ensure Dagger CLI
|
|
run: |
|
|
# Bootstrap: install dagger if not already in the runner image.
|
|
# Remove once all runners include dagger (Phase 3).
|
|
if ! command -v dagger &>/dev/null; then
|
|
echo "Dagger not found, installing..."
|
|
curl -fsSL https://dl.dagger.io/dagger/install.sh | DAGGER_VERSION=0.19.11 sh
|
|
mv ./bin/dagger /usr/local/bin/dagger && rmdir ./bin
|
|
fi
|
|
dagger version
|
|
|
|
- name: Build docs
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
TARBALL="docs-${VERSION}.tar.gz"
|
|
echo "Building docs via Dagger..."
|
|
# build-docs calls build_changelog internally (towncrier runs inside
|
|
# the Dagger container). The host working tree is not modified — only
|
|
# the tarball is exported. Towncrier runs a second time on the runner
|
|
# in the next step so that CHANGELOG.md and fragment deletion are
|
|
# captured in the git commit.
|
|
dagger call build-docs --src=. --version="$VERSION" \
|
|
export --path="./$TARBALL"
|
|
echo "Build complete!"
|
|
ls -lh "$TARBALL"
|
|
|
|
- name: Build changelog
|
|
id: changelog
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
|
|
# Run towncrier on the runner (not in Dagger) so that CHANGELOG.md
|
|
# updates and fragment deletions appear in the working tree for the
|
|
# git commit step. This is intentionally a second towncrier run —
|
|
# the first happened inside the Dagger build-docs container above.
|
|
# Check if there are any changelog fragments
|
|
FRAGMENTS=$(find docs/changelog.d -name "*.md" -not -name ".gitkeep" 2>/dev/null | wc -l)
|
|
|
|
if [ "$FRAGMENTS" -gt 0 ]; then
|
|
echo "Found $FRAGMENTS changelog fragments, building changelog..."
|
|
uvx towncrier build --version "$VERSION" --yes
|
|
echo "changelog_updated=true" >> "$GITHUB_OUTPUT"
|
|
|
|
# Extract the changelog section for this release to include in release body
|
|
RELEASE_NOTES=$(awk -v ver="$VERSION" '
|
|
/^## \[/ {
|
|
if (found) exit
|
|
if (index($0, "[" ver "]")) found=1
|
|
}
|
|
found {print}
|
|
' CHANGELOG.md | tail -n +2)
|
|
|
|
echo "$RELEASE_NOTES" > /tmp/release_notes.md
|
|
echo "Release notes extracted for $VERSION"
|
|
else
|
|
echo "No changelog fragments found, skipping towncrier"
|
|
echo "changelog_updated=false" >> "$GITHUB_OUTPUT"
|
|
echo "" > /tmp/release_notes.md
|
|
fi
|
|
|
|
- name: Create release
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
TARBALL="docs-${VERSION}.tar.gz"
|
|
CHANGELOG_UPDATED="${{ steps.changelog.outputs.changelog_updated }}"
|
|
|
|
echo "Creating release $VERSION..."
|
|
|
|
# Build release body with changelog if available
|
|
{
|
|
echo "BlumeOps release $VERSION"
|
|
echo ""
|
|
|
|
if [ "$CHANGELOG_UPDATED" = "true" ] && [ -s /tmp/release_notes.md ]; then
|
|
echo "## What's Changed"
|
|
echo ""
|
|
cat /tmp/release_notes.md
|
|
echo ""
|
|
fi
|
|
|
|
echo "## Documentation"
|
|
echo ""
|
|
echo "Download \`$TARBALL\` and configure the quartz container with:"
|
|
echo ""
|
|
echo "\`\`\`"
|
|
echo "DOCS_RELEASE_URL=https://forge.ops.eblu.me/eblume/blumeops/releases/download/$VERSION/$TARBALL"
|
|
echo "\`\`\`"
|
|
} > /tmp/release_body.txt
|
|
|
|
# Use jq to properly escape the body for JSON
|
|
RELEASE_DATA=$(jq -n \
|
|
--arg tag "$VERSION" \
|
|
--arg name "BlumeOps $VERSION" \
|
|
--rawfile body /tmp/release_body.txt \
|
|
'{tag_name: $tag, name: $name, body: $body, draft: false, prerelease: false}')
|
|
|
|
RELEASE_RESPONSE=$(curl -s \
|
|
-X POST \
|
|
-H "Content-Type: application/json" \
|
|
-H "Authorization: token $GITHUB_TOKEN" \
|
|
-d "$RELEASE_DATA" \
|
|
"https://forge.ops.eblu.me/api/v1/repos/eblume/blumeops/releases")
|
|
|
|
echo "API Response: $RELEASE_RESPONSE"
|
|
|
|
RELEASE_ID=$(echo "$RELEASE_RESPONSE" | jq -r '.id')
|
|
|
|
if [ -z "$RELEASE_ID" ] || [ "$RELEASE_ID" = "null" ]; then
|
|
echo "Error: Failed to create release"
|
|
exit 1
|
|
fi
|
|
|
|
echo "Created release ID: $RELEASE_ID"
|
|
|
|
# Upload the asset
|
|
echo "Uploading $TARBALL..."
|
|
UPLOAD_RESPONSE=$(curl -s \
|
|
-X POST \
|
|
-H "Content-Type: application/gzip" \
|
|
-H "Authorization: token $GITHUB_TOKEN" \
|
|
--data-binary "@$TARBALL" \
|
|
"https://forge.ops.eblu.me/api/v1/repos/eblume/blumeops/releases/$RELEASE_ID/assets?name=$TARBALL")
|
|
|
|
echo "Upload Response: $UPLOAD_RESPONSE"
|
|
echo ""
|
|
echo "Release created successfully!"
|
|
|
|
- name: Update docs deployment
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
TARBALL="docs-${VERSION}.tar.gz"
|
|
DEPLOYMENT_FILE="argocd/manifests/docs/deployment.yaml"
|
|
RELEASE_URL="https://forge.ops.eblu.me/eblume/blumeops/releases/download/${VERSION}/${TARBALL}"
|
|
|
|
echo "Updating $DEPLOYMENT_FILE with new release URL..."
|
|
sed -i "s|value: \"https://forge.ops.eblu.me/eblume/blumeops/releases/download/[^\"]*\"|value: \"${RELEASE_URL}\"|" "$DEPLOYMENT_FILE"
|
|
|
|
echo "Updated deployment:"
|
|
grep -A1 "DOCS_RELEASE_URL" "$DEPLOYMENT_FILE"
|
|
|
|
- name: Commit release changes
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
CHANGELOG_UPDATED="${{ steps.changelog.outputs.changelog_updated }}"
|
|
|
|
# Configure git
|
|
git config user.name "Forgejo Actions"
|
|
git config user.email "actions@forge.ops.eblu.me"
|
|
|
|
# Stage deployment changes
|
|
git add argocd/manifests/docs/deployment.yaml
|
|
|
|
# Stage changelog changes if updated
|
|
if [ "$CHANGELOG_UPDATED" = "true" ]; then
|
|
git add CHANGELOG.md docs/changelog.d/
|
|
fi
|
|
|
|
# Check if there are changes to commit
|
|
if git diff --cached --quiet; then
|
|
echo "No changes to commit"
|
|
else
|
|
git commit -m "Update docs release to $VERSION
|
|
|
|
$([ "$CHANGELOG_UPDATED" = "true" ] && echo "- Built changelog from towncrier fragments")
|
|
|
|
[skip ci]"
|
|
|
|
# Push to main
|
|
git push origin HEAD:main
|
|
echo "Changes committed and pushed"
|
|
fi
|
|
|
|
- name: Deploy docs
|
|
env:
|
|
ARGOCD_AUTH_TOKEN: ${{ secrets.ARGOCD_AUTH_TOKEN }}
|
|
run: |
|
|
echo "Syncing docs app via ArgoCD..."
|
|
|
|
# Sync docs app (uses ARGOCD_AUTH_TOKEN env var for auth)
|
|
argocd app sync docs \
|
|
--server argocd.ops.eblu.me \
|
|
--grpc-web \
|
|
--prune
|
|
|
|
# Wait for sync to complete
|
|
argocd app wait docs \
|
|
--server argocd.ops.eblu.me \
|
|
--grpc-web \
|
|
--timeout 120
|
|
|
|
echo "Docs app synced successfully!"
|
|
|
|
- name: Install flyctl
|
|
run: |
|
|
curl -L https://fly.io/install.sh | sh
|
|
echo "/root/.fly/bin" >> "$GITHUB_PATH"
|
|
|
|
- name: Purge Fly.io proxy cache
|
|
env:
|
|
FLY_API_TOKEN: ${{ secrets.FLY_DEPLOY_TOKEN }}
|
|
run: |
|
|
echo "Purging nginx cache on Fly.io proxy..."
|
|
fly ssh console -a blumeops-proxy -C "sh -c 'rm -rf /tmp/cache && nginx -s reload'"
|
|
echo "Cache purged"
|
|
|
|
- name: Summary
|
|
run: |
|
|
VERSION="${{ steps.version.outputs.version }}"
|
|
TARBALL="docs-${VERSION}.tar.gz"
|
|
echo "================================================"
|
|
echo "BlumeOps Release: $VERSION"
|
|
echo "================================================"
|
|
echo ""
|
|
echo "Release URL:"
|
|
echo " https://forge.ops.eblu.me/eblume/blumeops/releases/tag/$VERSION"
|
|
echo ""
|
|
echo "Asset URL (for DOCS_RELEASE_URL ConfigMap):"
|
|
echo " https://forge.ops.eblu.me/eblume/blumeops/releases/download/$VERSION/$TARBALL"
|