openova/.github/workflows/console-build.yaml
e3mrah 257291e8d1
ci: wrap build-workflow deploy push in pull-rebase retry loop (Refs #2062) (#2063)
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>
2026-05-20 10:04:21 +04:00

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 }}"