diff --git a/.gitea/actions/bump-stacks/action.yml b/.gitea/actions/bump-stacks/action.yml index 88b1bd9..51cb79c 100644 --- a/.gitea/actions/bump-stacks/action.yml +++ b/.gitea/actions/bump-stacks/action.yml @@ -1,7 +1,8 @@ name: bump-stacks description: | Push the new short sha for a stack into libretech/gitops-sandbox/stacks.yml. - Idempotent: a no-op when the sha is already pinned. + Idempotent: a no-op when the sha is already pinned. Retries up to 3× on + non-fast-forward to absorb concurrent bumps from sibling stack repos. inputs: stack: @@ -32,6 +33,7 @@ runs: RUN: ${{ inputs.run_number }} BOT_TOKEN: ${{ inputs.bot_token }} SANDBOX_REPO: ${{ inputs.sandbox_repo }} + ACTION_PATH: ${{ github.action_path }} run: | set -euo pipefail WORK=$(mktemp -d) @@ -40,44 +42,15 @@ runs: git config --global user.email "gitops-bot@librete.ch" REPO_URL="https://oauth2:${BOT_TOKEN}@git.librete.ch/${SANDBOX_REPO}.git" - # Retry up to 3 times on non-fast-forward to absorb concurrent bumps. for attempt in 1 2 3; do rm -rf clone git clone --depth=2 "$REPO_URL" clone cd clone - bun --version >/dev/null # baked into runner-image - - # Bun-native edit: load yaml, mutate, write back. Preserves order - # because the `yaml` package keeps key positions on round-trip. - cat > /tmp/bump.js <<'JS' - import { readFileSync, writeFileSync } from "node:fs"; - import { parseDocument } from "yaml"; - - const [, , stack, sha, run] = process.argv; - const doc = parseDocument(readFileSync("stacks.yml", "utf8")); - const stacks = doc.get("stacks"); - if (!stacks?.has(stack)) { - console.error(`stack '${stack}' not in stacks.yml`); - process.exit(2); - } - const node = stacks.get(stack); - const oldSha = node.get("sha"); - if (String(oldSha) === String(sha)) { - console.log(`no-op: ${stack} already at sha=${sha}`); - process.exit(10); // sentinel for caller - } - node.set("sha", sha); - node.set("run", Number(run)); - writeFileSync("stacks.yml", doc.toString()); - console.log(`bumped ${stack}: ${oldSha} → ${sha}`); - JS - - # Install deps inside clone (cached after first run). [ -f package.json ] || echo '{"type":"module","dependencies":{"yaml":"^2.6.1"}}' > package.json - bun install --frozen-lockfile 2>/dev/null || bun install + bun install --silent set +e - bun /tmp/bump.js "$STACK" "$SHA" "$RUN" + bun "$ACTION_PATH/bump.js" "$STACK" "$SHA" "$RUN" rc=$? set -e if [ "$rc" = "10" ]; then diff --git a/.gitea/actions/bump-stacks/bump.js b/.gitea/actions/bump-stacks/bump.js new file mode 100644 index 0000000..38c47bb --- /dev/null +++ b/.gitea/actions/bump-stacks/bump.js @@ -0,0 +1,30 @@ +#!/usr/bin/env bun +// Edit stacks.yml in the gitops-sandbox checkout to pin a new sha for `stack`. +// Exit 10 = no-op (already at sha). Exit 0 = mutated, ready to commit. +import { readFileSync, writeFileSync } from "node:fs"; +import { parseDocument } from "yaml"; + +const [, , stack, sha, run] = process.argv; +if (!stack || !sha) { + console.error("usage: bump.js "); + process.exit(2); +} + +const doc = parseDocument(readFileSync("stacks.yml", "utf8")); +const stacks = doc.get("stacks"); +if (!stacks?.has(stack)) { + console.error(`stack '${stack}' not in stacks.yml`); + process.exit(2); +} + +const node = stacks.get(stack); +const oldSha = node.get("sha"); +if (String(oldSha) === String(sha)) { + console.log(`no-op: ${stack} already at sha=${sha}`); + process.exit(10); +} + +node.set("sha", sha); +node.set("run", Number(run ?? 0)); +writeFileSync("stacks.yml", doc.toString()); +console.log(`bumped ${stack}: ${oldSha} → ${sha}`);