Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 20 additions & 9 deletions .github/workflows/build-and-publish.yml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Builds + publishes container images to ghcr.io with `:latest` and
# `:<sha>` tags. Cluster rollout is handled out-of-band: Keel polls
# Builds + publishes container images to ghcr.io with `:latest`,
# `:<sha>`, and release tags. Cluster rollout is handled out-of-band: Keel polls
# ghcr every 2 minutes and rolls the matching Deployment when the
# `:latest` digest changes, while Flux reconciles manifest changes
# from `main`. Neither is this workflow's responsibility — the name
Expand All @@ -9,6 +9,7 @@ name: Build & Publish
on:
push:
branches: [main]
tags: ['v*.*.*']
# Manual trigger for the cases where a push event never fires the
# workflow (rare GitHub oddity — observed once with the `#308`
# merge: workflow silently absent for that SHA) or where a merge
Expand Down Expand Up @@ -52,7 +53,7 @@ jobs:
- uses: dorny/paths-filter@v4.0.1
id: filter
with:
base: ${{ github.event.before }}
base: ${{ github.ref_type == 'tag' && github.sha || github.event.before }}
filters: |
workflow:
- '.github/workflows/build-and-publish.yml'
Expand Down Expand Up @@ -136,11 +137,8 @@ jobs:
#
# Multi-arch is gated on `main` only. The arm64 leg builds under
# QEMU emulation and roughly doubles the UI build time; we don't
# want to pay that on every reconcile / dispatch / future trigger
# that someone might add. Today the workflow's `on:` only fires
# for push to main, but this guard makes the intent explicit and
# forward-safe — if you add workflow_dispatch later, feature
# branches still build amd64-only.
# want to pay that on every reconcile, dispatch, or release-tag
# trigger; feature branches and tags still build amd64-only.
AMD64=linux/amd64
if [[ "${{ github.ref }}" == "refs/heads/main" && "${{ github.event_name }}" == "push" ]]; then
UI_PLATFORMS=linux/amd64,linux/arm64
Expand All @@ -150,8 +148,12 @@ jobs:
fi

# `force_all=true` on workflow_dispatch overrides every
# per-service filter so all images rebuild in one go.
# per-service filter so all images rebuild in one go. Release tags
# also rebuild every service so each image gets the version tag.
FORCE_ALL="${{ github.event.inputs.force_all || 'false' }}"
if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" == v*.*.* ]]; then
FORCE_ALL=true
fi

[[ "$(any "$FORCE_ALL" "$KOTLIN_SHARED" "$FILTER_AUTH_API")" == "true" ]] && append auth-api services/auth-api/Dockerfile "$AMD64"
[[ "$(any "$FORCE_ALL" "$KOTLIN_SHARED" "$FILTER_ASSISTANT_API")" == "true" ]] && append assistant-api services/assistant-api/Dockerfile "$AMD64"
Expand Down Expand Up @@ -197,6 +199,14 @@ jobs:
- uses: actions/checkout@v6.0.2
- name: Set image prefix (lowercase)
run: echo "IMAGE_PREFIX=$(echo '${{ github.repository }}' | tr '[:upper:]' '[:lower:]')" >> "$GITHUB_ENV"
- name: Compute release version
id: release-version
run: |
if [[ "$GITHUB_REF_TYPE" == "tag" && "$GITHUB_REF_NAME" == v*.*.* ]]; then
echo "version=${GITHUB_REF_NAME#v}" >> "$GITHUB_OUTPUT"
else
echo "version=" >> "$GITHUB_OUTPUT"
fi
# QEMU is only needed when a service builds for a non-runner arch
# (the UIs build linux/arm64 alongside amd64). No-op for the
# amd64-only JVM services.
Expand Down Expand Up @@ -225,5 +235,6 @@ jobs:
tags: |
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service.name }}:${{ github.sha }}
${{ env.REGISTRY }}/${{ env.IMAGE_PREFIX }}/${{ matrix.service.name }}:latest
${{ steps.release-version.outputs.version && format('{0}/{1}/{2}:{3}', env.REGISTRY, env.IMAGE_PREFIX, matrix.service.name, steps.release-version.outputs.version) || '' }}
cache-from: type=gha,scope=${{ matrix.service.name }}
cache-to: type=gha,mode=max,scope=${{ matrix.service.name }}
23 changes: 23 additions & 0 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
name: Release

on:
push:
branches: [main]

permissions:
contents: write
pull-requests: write

concurrency:
group: release-please
cancel-in-progress: false

jobs:
release:
name: Release Please
runs-on: ubuntu-latest
steps:
- uses: googleapis/release-please-action@v4
with:
config-file: release-please-config.json
manifest-file: .release-please-manifest.json
3 changes: 3 additions & 0 deletions .release-please-manifest.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
{
".": "0.1.0"
}
36 changes: 36 additions & 0 deletions docs/runbooks/versioned-deploy.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
# Versioned Deploy Model

Production deploys are driven by explicit version pins in Git, not by pushes to
`main` or mutable `:latest` tags.

## Target Model

- `main` is the integration branch. Merging to `main` does not by itself define
the production version.
- release-please creates release tags in the form `vX.Y.Z`.
- `build-and-publish` publishes in-house container images with version tags that
match the release tag.
- A release PR bumps explicit image tags under `platform/cluster/flux/**` from
the previous version to the new version.
- Flux deploys the commit that pins those image tags.
- Rollback is a Git revert of the release PR that bumped the image tags.

## Deploying a Version

To deploy a specific release, reconcile the commit that pins the desired
`vX.Y.Z` image tags in `platform/cluster/flux/**`. The deployed state is the
Flux-applied Git state, so the image tags in the cluster manifests are the
source of truth.

## Rollback

Rollback is performed by reverting the tag-bump commit or PR. Flux reconciles
the reverted manifests and returns the affected workloads to the previously
pinned image versions.

## Keel Removal Sequencing

Keel-based `:latest` auto-rolls are removed from the in-house app deploy path in
a separate, sequenced PR. That Flux/Keel change must land only after the first
versioned in-house images have been published, so every workload can be pinned
to an existing version tag before `:latest` automation is removed.
10 changes: 10 additions & 0 deletions release-please-config.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
{
"packages": {
".": {
"release-type": "simple",
"bump-minor-pre-major": true,
"changelog-path": "CHANGELOG.md",
"include-component-in-tag": false
}
}
}
22 changes: 22 additions & 0 deletions renovate.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"$schema": "https://docs.renovatebot.com/renovate-schema.json",
"extends": ["config:recommended", ":semanticCommits", ":dependencyDashboard"],
"rangeStrategy": "pin",
"labels": ["dependencies"],
"minimumReleaseAge": "3 days",
"packageRules": [
{
"description": "Group all ExtraToast shared artifacts so a coherent platform bump lands in one PR.",
"matchPackagePatterns": ["^dev\\.extratoast", "^@extratoast/"],
"matchDepNames": ["ExtraToast/github-workflows"],
"groupName": "extratoast shared artifacts",
"labels": ["dependencies", "extratoast-platform"]
},
{
"description": "Pin GitHub Actions to immutable refs.",
"matchManagers": ["github-actions"],
"pinDigests": true
}
],
"lockFileMaintenance": { "enabled": true }
}
Loading