TBD-V32 / openova-io/openova#2062.
The deploy job in every `.github/workflows/*build*.yaml` previously
ended with either a bare `git push` (catalyst-build, marketplace-api-
build, marketplace-build) or a single `git pull --rebase --autostash
origin main || true` followed by `git push origin HEAD:main` (the
controller family + sandbox + openova-flow). When two build workflows
committed to `main` within ~2 min of each other, the second push raced
the first and the remote rejected it with:
! [rejected] main -> main (fetch first)
The image was already pushed to GHCR, but the values.yaml / template
SHA-pin commit was lost. Concrete operational damage in the
2026-05-20T01:54-05:20Z window: PR #2050 (V16 admin-token wiring) shipped
the catalyst-api image to GHCR at 829474a but no
`deploy: update catalyst images to 829474a` commit ever landed on main.
Operators installing the current chart kept getting the previous
catalyst-build success (5ed4995), missing the admin-token wiring.
This PR introduces a shared composite action at
`.github/actions/deploy-bump` that concentrates the race-recovery logic
in a single file:
for i in 1..5; do
git push origin HEAD:main && break
git fetch origin main
git pull --rebase --autostash origin main || true
sleep $((i * 2)) # 2/4/6/8/10s — ~30s total backoff
done
Inputs: `paths` (whitespace/newline-separated files to stage),
`commit-message`, plus optional `max-attempts` (default 5), `user-name`,
`user-email`. Outputs: `pushed` (bool) and `commit-sha`. The `pushed`
output preserves the existing downstream gating pattern
(`if: steps.deploy_commit.outputs.pushed == 'true'` on the
blueprint-release dispatch step) used by 14 of the 21 modified
workflows.
20 of 21 build workflows now use the composite action:
- catalyst-build.yaml (Group A: bare git push — CRITICAL)
- marketplace-api-build.yaml (Group A: bare git push)
- admin-build.yaml (Group B: 3-retry inline, no fetch)
- console-build.yaml (Group B)
- marketplace-build.yaml (Group B)
- build-bp-guacamole.yaml (Group B)
- build-bp-newapi.yaml (Group B)
- build-k8s-ws-proxy.yaml (Group B)
- build-application-controller.yaml (Group C: single pull-rebase)
- build-blueprint-controller.yaml (Group C)
- build-continuum-controller.yaml (Group C)
- build-environment-controller.yaml (Group C)
- build-organization-controller.yaml (Group C)
- build-projector.yaml (Group C)
- build-openova-flow-server.yaml (Group C)
- build-openova-flow-adapter-flux.yaml (Group C)
- build-sandbox-controller.yaml (Group C)
- build-sandbox-mcp-server.yaml (Group C)
- build-sandbox-pty-server.yaml (Group C)
- useraccess-controller-build.yaml (Group C)
services-build.yaml is the documented exception: its retry loop
re-runs an inline `rewrite()` closure that bumps the chart semver
patch on every iteration, so a rebased push lands at `vN.M.P+2`
instead of replaying the SAME staged diff (which would lose to a
parallel run that already bumped that patch). The composite action
treats files as opaque and cannot do this rewrite — so this workflow
keeps its inline loop, but the max-attempts ceiling moves from 3 to 5
and a `sleep $((i * 2))` between attempts is added to match the
composite action's backoff shape. The reason is documented inline.
Verification: actionlint clean on every modified workflow
(`actionlint -shellcheck= .github/workflows/*.yaml` reports zero new
findings — the only remaining warning is the pre-existing
`cosmetic-guards.yaml:48 if: false`). YAML parse OK on all 21 files +
the composite action.
This is intentionally `Refs #2062`, not `Closes #2062`. Per the 2026-05-19
anti-theater discipline (`docs/TRUST.md`), the issue closes only after
an observed race-recovery in a real CI run — when two builds commit
within ~2 min of each other and BOTH deploy commits land on main.
Co-authored-by: hatiyildiz <hatice.yildiz@openova.io>
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
70 lines
1.9 KiB
YAML
70 lines
1.9 KiB
YAML
name: Build & Deploy Catalyst Console
|
|
|
|
on:
|
|
push:
|
|
paths: ['core/console/**']
|
|
branches: [main]
|
|
workflow_dispatch:
|
|
|
|
env:
|
|
REGISTRY: ghcr.io
|
|
IMAGE: ghcr.io/openova-io/openova/console
|
|
|
|
jobs:
|
|
build:
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: read
|
|
packages: write
|
|
outputs:
|
|
sha_short: ${{ steps.vars.outputs.sha_short }}
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Set short SHA
|
|
id: vars
|
|
run: echo "sha_short=$(echo $GITHUB_SHA | head -c 7)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Login to GHCR
|
|
uses: docker/login-action@v3
|
|
with:
|
|
registry: ${{ env.REGISTRY }}
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Build and push
|
|
uses: docker/build-push-action@v6
|
|
with:
|
|
context: core/console
|
|
file: core/console/Containerfile
|
|
push: true
|
|
tags: |
|
|
${{ env.IMAGE }}:${{ steps.vars.outputs.sha_short }}
|
|
${{ env.IMAGE }}:latest
|
|
|
|
deploy:
|
|
needs: build
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
contents: write
|
|
steps:
|
|
- name: Checkout
|
|
uses: actions/checkout@v4
|
|
|
|
- name: Update deployment manifest
|
|
run: |
|
|
SHA=$(echo $GITHUB_SHA | head -c 7)
|
|
FILE="products/catalyst/chart/templates/sme-services/console.yaml"
|
|
if [ -f "$FILE" ]; then
|
|
sed -i "s|image: ${IMAGE}:.*|image: ${IMAGE}:${SHA}|" "$FILE"
|
|
fi
|
|
|
|
# TBD-V32 / openova-io/openova#2062 — race-safe push via the shared
|
|
# composite action.
|
|
- name: Commit and push
|
|
uses: ./.github/actions/deploy-bump
|
|
with:
|
|
paths: products/catalyst/chart/templates/sme-services/console.yaml
|
|
commit-message: "deploy: update Catalyst console image to ${{ needs.build.outputs.sha_short }}"
|