chore(ci): consolidate Gitea CI #47
@@ -1,25 +1,22 @@
|
|||||||
# Cross-platform Crucible binaries (D7 trigger standard):
|
# Cross-platform Crucible release binaries: a v* tag builds all targets,
|
||||||
# PR / push main -> cross-build ALL targets to prove they compile (no publish)
|
# then uploads them, SHA256SUMS, and the canonical wrappers to its Gitea release.
|
||||||
# tag v* -> build all targets + SHA256SUMS, upload to the Gitea release
|
|
||||||
# Crucible is pure Go (CGO_ENABLED=0), so cross-compiling is a fast loop.
|
# Crucible is pure Go (CGO_ENABLED=0), so cross-compiling is a fast loop.
|
||||||
name: build-binaries
|
name: build-binaries
|
||||||
|
|
||||||
on:
|
on:
|
||||||
pull_request:
|
|
||||||
push:
|
push:
|
||||||
branches: [main]
|
|
||||||
tags: ['v*']
|
tags: ['v*']
|
||||||
paths-ignore:
|
|
||||||
- "docs/**"
|
permissions:
|
||||||
- "README.md"
|
code: read
|
||||||
- "AGENTS.md"
|
releases: write
|
||||||
- "LICENSE"
|
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build-binaries:
|
build-binaries:
|
||||||
runs-on: nix-docker
|
runs-on: nix-docker
|
||||||
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
with: { fetch-depth: 0 }
|
with: { fetch-depth: 0 }
|
||||||
|
|
||||||
- name: Cross-build all targets
|
- name: Cross-build all targets
|
||||||
@@ -45,9 +42,8 @@ jobs:
|
|||||||
'
|
'
|
||||||
|
|
||||||
- name: Upload to Gitea release
|
- name: Upload to Gitea release
|
||||||
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
||||||
env:
|
env:
|
||||||
TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
TAG: ${{ github.ref_name }}
|
TAG: ${{ github.ref_name }}
|
||||||
REPO: ${{ github.repository }}
|
REPO: ${{ github.repository }}
|
||||||
SERVER: ${{ github.server_url }}
|
SERVER: ${{ github.server_url }}
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
# Pull-request validation for Crucible. Releases and wrapper synchronization
|
||||||
|
# have their own narrow workflows because they need tag/main events.
|
||||||
|
name: ci
|
||||||
|
|
||||||
|
on:
|
||||||
|
pull_request:
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
ci:
|
||||||
|
runs-on: nix-docker
|
||||||
|
timeout-minutes: 60
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
with:
|
||||||
|
fetch-depth: 0
|
||||||
|
|
||||||
|
- name: vet + test + lint
|
||||||
|
run: |
|
||||||
|
nix develop --command bash -c '
|
||||||
|
set -euo pipefail
|
||||||
|
go vet ./...
|
||||||
|
go test ./...
|
||||||
|
shellcheck scripts/*.sh
|
||||||
|
yamllint .gitea
|
||||||
|
'
|
||||||
|
|
||||||
|
- name: binary smoke (fail-closed contract)
|
||||||
|
run: nix develop --command make smoke
|
||||||
|
|
||||||
|
- name: Cross-build all targets
|
||||||
|
run: |
|
||||||
|
nix develop --command bash -c '
|
||||||
|
set -euo pipefail
|
||||||
|
sha="$(git rev-parse --short=12 HEAD)"
|
||||||
|
ldflags="-s -w -X git.westgate.pw/ShadowsOverWestgate/sow-tools/internal/buildinfo.Version=${sha}"
|
||||||
|
rm -rf dist && mkdir -p dist
|
||||||
|
export CGO_ENABLED=0
|
||||||
|
for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do
|
||||||
|
os="${target%/*}"; arch="${target#*/}"
|
||||||
|
ext=""; [ "$os" = windows ] && ext=".exe"
|
||||||
|
echo "building crucible-${os}-${arch}${ext}"
|
||||||
|
GOOS="$os" GOARCH="$arch" go build -trimpath -ldflags "$ldflags" \
|
||||||
|
-o "dist/crucible-${os}-${arch}${ext}" ./cmd/crucible
|
||||||
|
done
|
||||||
|
'
|
||||||
@@ -14,11 +14,14 @@ on:
|
|||||||
- 'wrappers/crucible.sh'
|
- 'wrappers/crucible.sh'
|
||||||
- 'wrappers/crucible.ps1'
|
- 'wrappers/crucible.ps1'
|
||||||
|
|
||||||
|
permissions: read-all
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
sync:
|
sync:
|
||||||
runs-on: nix-docker
|
runs-on: nix-docker
|
||||||
|
timeout-minutes: 30
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
|
||||||
|
|
||||||
- name: Open sync PRs to consumers
|
- name: Open sync PRs to consumers
|
||||||
env:
|
env:
|
||||||
|
|||||||
@@ -1,30 +0,0 @@
|
|||||||
# Lint + unit tests for the Crucible suite. PR-first: PRs + push to main (D7).
|
|
||||||
name: test
|
|
||||||
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches: [main]
|
|
||||||
paths-ignore:
|
|
||||||
- "docs/**"
|
|
||||||
- "README.md"
|
|
||||||
- "AGENTS.md"
|
|
||||||
- "LICENSE"
|
|
||||||
pull_request:
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
test:
|
|
||||||
runs-on: nix-docker
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
with: { fetch-depth: 0 }
|
|
||||||
- name: vet + test + lint
|
|
||||||
run: |
|
|
||||||
nix develop --command bash -c '
|
|
||||||
set -euo pipefail
|
|
||||||
go vet ./...
|
|
||||||
go test ./...
|
|
||||||
shellcheck scripts/*.sh
|
|
||||||
yamllint .gitea
|
|
||||||
'
|
|
||||||
- name: binary smoke (fail-closed contract)
|
|
||||||
run: nix develop --command make smoke
|
|
||||||
@@ -6,6 +6,7 @@ SHELL := bash
|
|||||||
check: vet test
|
check: vet test
|
||||||
shellcheck scripts/*.sh
|
shellcheck scripts/*.sh
|
||||||
yamllint .gitea
|
yamllint .gitea
|
||||||
|
bash tests/workflow-contract.sh
|
||||||
|
|
||||||
vet:
|
vet:
|
||||||
go vet ./...
|
go vet ./...
|
||||||
|
|||||||
@@ -78,13 +78,14 @@ This retires the old habit of checking in `nwn-tool` / `sow-toolkit`.
|
|||||||
|
|
||||||
## CI
|
## CI
|
||||||
|
|
||||||
PR-first (D7): checks run on pull requests and on push to `main`; the only
|
PR-first (D7): checks run once on pull requests; the only publish event is a
|
||||||
publish event is a `v*` tag (see `runbooks/ci-trigger-standard.md` in sow-docs,
|
`v*` tag (see `runbooks/ci-trigger-standard.md` in sow-docs,
|
||||||
https://git.westgate.pw/ShadowsOverWestgate/sow-docs).
|
https://git.westgate.pw/ShadowsOverWestgate/sow-docs).
|
||||||
|
|
||||||
- `test.yml` — vet, test, shellcheck, yamllint, binary smoke (PR + main).
|
- `ci.yml` — vet, test, shellcheck, yamllint, binary smoke, and cross-build all
|
||||||
- `build-binaries.yml` — cross-build all targets (PR + main); on a `v*` tag, upload
|
targets once per pull request.
|
||||||
the binaries, `SHA256SUMS`, and the wrappers to the Gitea release.
|
- `build-binaries.yml` — on a `v*` tag, cross-build and upload the binaries,
|
||||||
|
`SHA256SUMS`, and the wrappers to the Gitea release.
|
||||||
- `sync-wrappers.yml` — on a `main` push that touches `wrappers/`, auto-PR the
|
- `sync-wrappers.yml` — on a `main` push that touches `wrappers/`, auto-PR the
|
||||||
canonical wrappers to the consumer repos in `wrappers/consumers.txt`.
|
canonical wrappers to the consumer repos in `wrappers/consumers.txt`.
|
||||||
Consumer drift checks run after those PRs merge to `main`, not on the PRs
|
Consumer drift checks run after those PRs merge to `main`, not on the PRs
|
||||||
|
|||||||
@@ -0,0 +1,84 @@
|
|||||||
|
#!/usr/bin/env bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
contract_script="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)/$(basename "${BASH_SOURCE[0]}")"
|
||||||
|
default_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||||
|
repo_root="${WORKFLOW_ROOT:-$default_root}"
|
||||||
|
cd "$repo_root"
|
||||||
|
|
||||||
|
ci=.gitea/workflows/ci.yml
|
||||||
|
release=.gitea/workflows/build-binaries.yml
|
||||||
|
sync=.gitea/workflows/sync-wrappers.yml
|
||||||
|
checkout='actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683'
|
||||||
|
|
||||||
|
assert_exact_events() {
|
||||||
|
local workflow="$1" expected="$2" actual
|
||||||
|
actual="$(awk '
|
||||||
|
/^on:$/ { in_on = 1 }
|
||||||
|
in_on && /^$/ { next }
|
||||||
|
in_on && $0 != "on:" && $0 !~ /^[[:space:]]/ { exit }
|
||||||
|
in_on { print }
|
||||||
|
' "$workflow")"
|
||||||
|
[[ "$actual" == "$expected" ]] || {
|
||||||
|
echo "workflow-contract: unexpected event scope in $workflow" >&2
|
||||||
|
return 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
[[ -f "$ci" ]] || { echo "missing consolidated CI workflow" >&2; exit 1; }
|
||||||
|
[[ ! -e .gitea/workflows/test.yml ]] || { echo "legacy test workflow still present" >&2; exit 1; }
|
||||||
|
|
||||||
|
assert_exact_events "$ci" $'on:\n pull_request:'
|
||||||
|
grep -Fqx 'permissions: read-all' "$ci"
|
||||||
|
grep -Fqx ' timeout-minutes: 60' "$ci"
|
||||||
|
[[ "$(grep -Fc "$checkout" "$ci")" -eq 1 ]]
|
||||||
|
grep -Fqx ' fetch-depth: 0' "$ci"
|
||||||
|
grep -Fq 'go vet ./...' "$ci"
|
||||||
|
grep -Fq 'go test ./...' "$ci"
|
||||||
|
grep -Fq 'shellcheck scripts/*.sh' "$ci"
|
||||||
|
grep -Fq 'yamllint .gitea' "$ci"
|
||||||
|
grep -Fq 'make smoke' "$ci"
|
||||||
|
grep -Fq 'for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do' "$ci"
|
||||||
|
|
||||||
|
assert_exact_events "$release" $'on:\n push:\n tags: [\'v*\']'
|
||||||
|
grep -Fqx ' code: read' "$release"
|
||||||
|
grep -Fqx ' releases: write' "$release"
|
||||||
|
grep -Fqx ' timeout-minutes: 30' "$release"
|
||||||
|
[[ "$(grep -Fc "$checkout" "$release")" -eq 1 ]]
|
||||||
|
grep -Fq 'secrets.GITEA_TOKEN' "$release"
|
||||||
|
if grep -Fq 'secrets.GITHUB_TOKEN' "$release"; then
|
||||||
|
echo "workflow-contract: release uses the GitHub token alias" >&2
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
grep -Fqx 'permissions: read-all' "$sync"
|
||||||
|
grep -Fqx ' timeout-minutes: 30' "$sync"
|
||||||
|
[[ "$(grep -Fc "$checkout" "$sync")" -eq 1 ]]
|
||||||
|
assert_exact_events "$sync" $'on:\n push:\n branches: [main]\n paths:\n - \'wrappers/crucible.sh\'\n - \'wrappers/crucible.ps1\''
|
||||||
|
|
||||||
|
expect_mutation_rejected() {
|
||||||
|
local name="$1" workflow="$2" expression="$3" tmp
|
||||||
|
tmp="$(mktemp -d)"
|
||||||
|
mkdir -p "$tmp/.gitea"
|
||||||
|
cp -R "$default_root/.gitea/workflows" "$tmp/.gitea/workflows"
|
||||||
|
sed -i "$expression" "$tmp/$workflow"
|
||||||
|
if WORKFLOW_ROOT="$tmp" WORKFLOW_CONTRACT_MUTATION=1 bash "$contract_script" >/dev/null 2>&1; then
|
||||||
|
echo "workflow-contract: accepted forbidden mutation: $name" >&2
|
||||||
|
rm -rf "$tmp"
|
||||||
|
return 1
|
||||||
|
fi
|
||||||
|
rm -rf "$tmp"
|
||||||
|
}
|
||||||
|
|
||||||
|
if [[ "${WORKFLOW_CONTRACT_MUTATION:-0}" != 1 ]]; then
|
||||||
|
mutations_ok=0
|
||||||
|
expect_mutation_rejected 'CI workflow_dispatch event' "$ci" '/^ pull_request:$/a\ workflow_dispatch:' || mutations_ok=1
|
||||||
|
expect_mutation_rejected 'release develop branch' "$release" "/^ tags:/a\\ branches: [develop]" || mutations_ok=1
|
||||||
|
expect_mutation_rejected 'release schedule event' "$release" "/^ tags:/a\\ schedule:\n - cron: '0 0 * * *'" || mutations_ok=1
|
||||||
|
expect_mutation_rejected 'release extra tag pattern' "$release" "s/tags: \['v\*'\]/tags: ['v*', 'release-*']/" || mutations_ok=1
|
||||||
|
expect_mutation_rejected 'wrapper sync pull_request event' "$sync" '/^ push:$/i\ pull_request:' || mutations_ok=1
|
||||||
|
expect_mutation_rejected 'wrapper sync broad path' "$sync" "/^ - 'wrappers\/crucible.ps1'$/a\\ - 'wrappers/**'" || mutations_ok=1
|
||||||
|
(( mutations_ok == 0 )) || exit 1
|
||||||
|
fi
|
||||||
|
|
||||||
|
echo "workflow-contract: CI is PR-only; release and wrapper sync retain their narrow triggers"
|
||||||
Reference in New Issue
Block a user