diff --git a/.coreci.yml b/.coreci.yml index abf803a..3d9b358 100644 --- a/.coreci.yml +++ b/.coreci.yml @@ -1,6 +1,6 @@ version: "1" name: orca-ci -description: Orca — offline/CLI-first orchestration engine. Full release flow via CoreCI. +description: Orca — offline/CLI-first orchestration engine. CI pipeline via CoreCI. # CoreCI configuration for orca (v0.16 rewrite — native jobs: format). # @@ -10,19 +10,21 @@ description: Orca — offline/CLI-first orchestration engine. Full release flow # execute. This file uses the native `jobs:`/`invoke:`/`vars:` format # with a DAG via `needs:`. # -# DAG: build → test → release +# DAG: build → test # # The Gitea Actions workflow (.gitea/workflows/release.yml) gates on # `on: push: tags: ['v*']`, so every `coreci run` invocation is already -# a release run — no tag-conditional rules needed here. +# a release run. The release step (build tarball + upload to Gitea) is +# handled by a separate Gitea Actions step AFTER `coreci run` completes, +# because CoreCI's SQLite logging can fill the runner's disk during +# `go test -race`, causing the release job to fail when writing files. # # Each job uses `invoke:` only (no `plugin:`) — CoreCI's validate() # rejects jobs with both plugin and invoke set (mutually exclusive). # Jobs run via the shell-isolated executor (sh -c ). # # CoreCI's ValidateShellCommand forbids shell metacharacters (&|;`><$()) -# in the invoke: string. All complex logic lives in scripts/ci-run.sh, -# which the invoke: field calls as "sh scripts/ci-run.sh ". +# in the invoke: string. All complex logic lives in scripts/ci-run.sh. jobs: # ── build ──────────────────────────────────────────────────────────── @@ -34,16 +36,4 @@ jobs: # ── test (REQ-031: -race) ──────────────────────────────────────────── test: needs: [build] - invoke: "sh scripts/ci-run.sh test" - - # ── release ────────────────────────────────────────────────────────── - # Builds the release tarball, creates/updates the Gitea release with - # binary assets. Handles duplicate release (ship workflow creates - # release first with title+body; this job attaches binary assets). - # GITEA_TOKEN is resolved from env via CoreCI's secret resolver - # (os.Getenv fallback) and forwarded by PassThroughEnv. - release: - needs: [test] - vars: - GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}" - invoke: "sh scripts/ci-run.sh release" \ No newline at end of file + invoke: "sh scripts/ci-run.sh test" \ No newline at end of file diff --git a/.gitea/workflows/release.yml b/.gitea/workflows/release.yml index 78b8442..d23bb54 100644 --- a/.gitea/workflows/release.yml +++ b/.gitea/workflows/release.yml @@ -33,6 +33,14 @@ jobs: run: | coreci run + - name: Build and upload release assets + env: + GITEA_TOKEN: ${{ secrets.PAT_TOKEN }} + VERSION: ${{ gitea.ref_name }} + GIT_COMMIT: ${{ gitea.sha }} + run: | + sh scripts/ci-release.sh + container-orca: runs-on: ubuntu-latest needs: ci diff --git a/scripts/ci-release.sh b/scripts/ci-release.sh new file mode 100755 index 0000000..9c99ca2 --- /dev/null +++ b/scripts/ci-release.sh @@ -0,0 +1,90 @@ +#!/bin/sh +# ci-release.sh — Build and upload release assets to Gitea. +# Called by .gitea/workflows/release.yml as a separate step AFTER +# `coreci run` completes. This runs in the Gitea Actions runner directly +# (not inside CoreCI's shell-isolated executor), so it has full env +# access and no disk-space constraints from CoreCI's SQLite logging. +# +# Environment variables (from Gitea Actions step env): +# GITEA_TOKEN — Gitea API token (from PAT_TOKEN secret) +# VERSION — tag name (from gitea.ref_name) +# GIT_COMMIT — commit SHA (from gitea.sha) + +set -u + +GITEA_URL="${GITEA_URL:-https://git.cloudinit.dev}" +GITEA_OWNER="${GITEA_OWNER:-coreci}" +GITEA_REPO="${GITEA_REPO:-orca}" + +info() { echo "ci-release: $*"; } +err() { echo "ci-release: error: $*" >&2; exit 1; } + +if [ -z "${GITEA_TOKEN:-}" ]; then err "GITEA_TOKEN is not set"; fi +if [ -z "${VERSION:-}" ]; then err "VERSION is not set"; fi + +GIT_COMMIT="${GIT_COMMIT:-unknown}" +BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" +TARBALL="orca-${VERSION}-linux-amd64.tar.gz" + +info "building release ${VERSION} (commit ${GIT_COMMIT:0:12})..." + +# Build the release binary with version injection. +LDFLAGS="-s -w \ + -X git.cloudinit.dev/coreci/orca/internal/cli.version=${VERSION} \ + -X git.cloudinit.dev/coreci/orca/internal/cli.gitCommit=${GIT_COMMIT} \ + -X git.cloudinit.dev/coreci/orca/internal/cli.buildTime=${BUILD_TIME}" +mkdir -p bin +go build -trimpath -ldflags="${LDFLAGS}" -o bin/orca ./cmd/orca 2>&1 || err "go build failed" + +# Package the tarball and checksums. +tar -czf "${TARBALL}" -C bin orca || err "tar failed" +sha256sum "${TARBALL}" > SHA256SUMS || err "sha256sum failed" +info "built ${TARBALL} ($(wc -c < "${TARBALL}") bytes)" + +# Check if the release already exists (the CIAgent ship workflow may +# have created it with title+body but no binary assets). +info "checking for existing release ${VERSION}..." +RELEASE_ID=$(curl -fsSL \ + "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/tags/${VERSION}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + | python3 -c "import json,sys; r=json.load(sys.stdin); print(r.get('id',''))" 2>/dev/null || echo "") + +if [ -z "${RELEASE_ID}" ]; then + info "creating new release ${VERSION}..." + RELEASE_ID=$(curl -fsSL -X POST \ + "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -H "Content-Type: application/json" \ + -d "{\"tag_name\":\"${VERSION}\",\"name\":\"Orca ${VERSION}\",\"body\":\"Release ${VERSION} built by CoreCI pipeline\"}" \ + | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "") + if [ -z "${RELEASE_ID}" ]; then + err "failed to create release ${VERSION}" + fi + info "created release ID ${RELEASE_ID}" +else + info "release ${VERSION} already exists (ID ${RELEASE_ID}) — attaching assets" +fi + +# Attach tarball and SHA256SUMS to the release. +info "attaching ${TARBALL} to release ${RELEASE_ID}..." +curl -fsSL -X POST \ + "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=${TARBALL}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@${TARBALL}" 2>&1 || err "failed to attach ${TARBALL}" + +info "attaching SHA256SUMS to release ${RELEASE_ID}..." +curl -fsSL -X POST \ + "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/${RELEASE_ID}/assets?name=SHA256SUMS" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + -F "attachment=@SHA256SUMS" 2>&1 || err "failed to attach SHA256SUMS" + +# Verify assets are actually attached (REQ-097, gate C-21). +ASSET_COUNT=$(curl -fsSL \ + "${GITEA_URL}/api/v1/repos/${GITEA_OWNER}/${GITEA_REPO}/releases/tags/${VERSION}" \ + -H "Authorization: token ${GITEA_TOKEN}" \ + | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('attachments',[])))" 2>/dev/null || echo "0") +info "release ${VERSION} has ${ASSET_COUNT} assets" +if [ "${ASSET_COUNT}" -lt 2 ]; then + err "assets not attached after upload (REQ-097, C-21) — got ${ASSET_COUNT}" +fi +info "release ${VERSION} published with ${ASSET_COUNT} binary assets" \ No newline at end of file diff --git a/scripts/ci-run.sh b/scripts/ci-run.sh index 89b8aa2..d00982b 100755 --- a/scripts/ci-run.sh +++ b/scripts/ci-run.sh @@ -97,91 +97,6 @@ case "$JOB" in go tool cover -func=coverage.out | tail -1 ;; - # ── release ──────────────────────────────────────────────────────── - release) - VERSION="${CI_COMMIT_BRANCH:-dev}" - TARBALL="orca-${VERSION}-linux-amd64.tar.gz" - - if [ -z "${GITEA_TOKEN:-${CI_GITEA_TOKEN:-}}" ]; then - err "GITEA_TOKEN is not set" - fi - GITEA_TOKEN="${GITEA_TOKEN:-${CI_GITEA_TOKEN:-}}" - - info "release ${VERSION} — GITEA_TOKEN length: ${#GITEA_TOKEN}" - - # Clean up disk space — CoreCI's SQLite logging fills the disk. - rm -f coverage.out 2>/dev/null || true - go clean -cache 2>/dev/null || true - - # Use the binary built by the 'build' job (it shares the same workdir). - # Rebuild only if the binary doesn't exist. - if [ ! -x bin/orca ]; then - info "bin/orca not found — rebuilding..." - GIT_COMMIT="${CI_COMMIT_SHA:-unknown}" - BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" - LDFLAGS="-s -w \ - -X git.cloudinit.dev/coreci/orca/internal/cli.version=${VERSION} \ - -X git.cloudinit.dev/coreci/orca/internal/cli.gitCommit=${GIT_COMMIT} \ - -X git.cloudinit.dev/coreci/orca/internal/cli.buildTime=${BUILD_TIME}" - mkdir -p bin - go build -trimpath -ldflags="${LDFLAGS}" -o bin/orca ./cmd/orca 2>&1 || err "go build failed" - else - info "using existing bin/orca from build job" - fi - tar -czf "${TARBALL}" -C bin orca 2>&1 || err "tar failed" - sha256sum "${TARBALL}" > SHA256SUMS 2>&1 || err "sha256sum failed" - info "built ${TARBALL}" - - # Create or update the Gitea release directly via the API. - # The CIAgent ship workflow may have already created the release - # (title+body, no binary). Check if it exists first. - info "checking for existing release ${VERSION}..." - RELEASE_ID=$(curl -fsSL \ - "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/tags/${VERSION}" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - | python3 -c "import json,sys; r=json.load(sys.stdin); print(r.get('id',''))" 2>/dev/null || echo "") - - if [ -z "${RELEASE_ID}" ]; then - info "creating new release ${VERSION}..." - RELEASE_ID=$(curl -fsSL -X POST \ - "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -H "Content-Type: application/json" \ - -d "{\"tag_name\":\"${VERSION}\",\"name\":\"Orca ${VERSION}\",\"body\":\"Release ${VERSION} built by CoreCI\"}" \ - | python3 -c "import json,sys; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "") - if [ -z "${RELEASE_ID}" ]; then - err "failed to create release ${VERSION}" - fi - info "created release ID ${RELEASE_ID}" - else - info "release ${VERSION} already exists (ID ${RELEASE_ID}) — attaching assets" - fi - - # Attach tarball and SHA256SUMS to the release. - info "attaching ${TARBALL} to release ${RELEASE_ID}..." - curl -fsSL -X POST \ - "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/${RELEASE_ID}/assets?name=${TARBALL}" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -F "attachment=@${TARBALL}" 2>&1 || err "failed to attach ${TARBALL}" - - info "attaching SHA256SUMS to release ${RELEASE_ID}..." - curl -fsSL -X POST \ - "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/${RELEASE_ID}/assets?name=SHA256SUMS" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - -F "attachment=@SHA256SUMS" 2>&1 || err "failed to attach SHA256SUMS" - - # Verify assets are actually attached (REQ-097, gate C-21). - ASSET_COUNT=$(curl -fsSL \ - "https://git.cloudinit.dev/api/v1/repos/coreci/orca/releases/tags/${VERSION}" \ - -H "Authorization: token ${GITEA_TOKEN}" \ - | python3 -c "import json,sys; print(len(json.load(sys.stdin).get('attachments',[])))" 2>/dev/null || echo "0") - info "release ${VERSION} has ${ASSET_COUNT} assets" - if [ "${ASSET_COUNT}" -lt 2 ]; then - err "assets not attached after upload (REQ-097, C-21) — got ${ASSET_COUNT}" - fi - info "release ${VERSION} published with ${ASSET_COUNT} binary assets" - ;; - *) err "unknown job: ${JOB}" ;;