#!/bin/bash # ci-run.sh — CoreCI pipeline runner for orca. # Called by .coreci.yml jobs via: sh scripts/ci-run.sh # # CoreCI's ValidateShellCommand forbids shell metacharacters (&|;`><$()) # in the invoke: string. This script wraps the complex logic so the # invoke: field is just "sh scripts/ci-run.sh ". # # Environment variables (provided by CoreCI's CI context + PassThroughEnv): # CI_COMMIT_BRANCH — tag name on tag pushes (from GITHUB_REF_NAME) # CI_COMMIT_SHA — commit SHA # GITEA_TOKEN — Gitea API token (from Gitea Actions secret PAT_TOKEN) set -euo pipefail JOB="${1:-}" if [ -z "$JOB" ]; then echo "usage: sh scripts/ci-run.sh " >&2 exit 1 fi # CoreCI's shell-isolated executor (buildIsolatedEnv) does NOT forward # Go toolchain env vars (GOROOT, GOPATH, GOCACHE, GOMODCACHE are in the # systemVars deny-list). Re-derive them from the `go` binary on PATH so # Go commands work in the shell-isolated executor. if command -v go >/dev/null 2>&1; then export GOROOT="${GOROOT:-$(go env GOROOT)}" export GOPATH="${GOPATH:-$(go env GOPATH)}" export GOCACHE="${GOCACHE:-$(go env GOCACHE)}" export GOMODCACHE="${GOMODCACHE:-$(go env GOMODCACHE)}" fi info() { echo "ci-run: $*"; } err() { echo "ci-run: error: $*" >&2; exit 1; } case "$JOB" in # ── validate ────────────────────────────────────────────────────── go-vet) go version gofmt -l . go vet ./... ;; verify-reqs) make verify-reqs ;; gosec) go install github.com/securego/gosec/v2/cmd/gosec@v2.18.2 gosec -fmt text -quiet ./... ;; govulncheck) go install golang.org/x/vuln/cmd/govulncheck@v1.1.3 govulncheck -mode binary ./... ;; gitleaks) curl -fsSL https://github.com/gitleaks/gitleaks/releases/latest/download/gitleaks-linux-amd64.tar.gz -o /tmp/gitleaks.tar.gz tar -xzf /tmp/gitleaks.tar.gz -C /tmp gitleaks mv /tmp/gitleaks /usr/local/bin/gitleaks 2>/dev/null || cp /tmp/gitleaks ./gitleaks chmod +x ./gitleaks 2>/dev/null || true if [ -x ./gitleaks ]; then ./gitleaks detect --source . --config .gitleaks.toml --baseline-path .gitleaks-baseline.json --no-banner else gitleaks detect --source . --config .gitleaks.toml --baseline-path .gitleaks-baseline.json --no-banner fi ;; # ── build ────────────────────────────────────────────────────────── build) VERSION="${CI_COMMIT_BRANCH:-dev}" 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 file bin/orca ./bin/orca version ;; # ── test (REQ-031: -race) ───────────────────────────────────────── test) go test -race -coverprofile=coverage.out ./... go tool cover -func=coverage.out | tail -1 ;; # ── release ──────────────────────────────────────────────────────── release) VERSION="${CI_COMMIT_BRANCH:-dev}" GIT_COMMIT="${CI_COMMIT_SHA:-unknown}" BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" TARBALL="orca-${VERSION}-linux-amd64.tar.gz" if [ -z "${GITEA_TOKEN:-}" ]; then err "GITEA_TOKEN is not set" fi # 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 make changelog tar -czf "${TARBALL}" -C bin orca sha256sum "${TARBALL}" > SHA256SUMS ls -lh "${TARBALL}" SHA256SUMS cat SHA256SUMS # Install tea CLI for Gitea release creation. sh -c "$(curl -fsSL https://gitea.com/gitea/tea/releases/latest/download/install.sh)" 2>/dev/null || true # Create release with assets. If the release already exists (created # by the CIAgent ship workflow with title+body but no binary), fall # back to attaching assets via the Gitea API. info "creating gitea release ${VERSION}..." if tea releases create "${VERSION}" \ --repo coreci/orca \ --title "Orca ${VERSION}" \ --note-file CHANGELOG.md \ --asset "${TARBALL}" \ --asset SHA256SUMS 2>/dev/null; then info "release created via tea" else info "release may already exist — attaching assets via Gitea API..." 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; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "") if [ -n "${RELEASE_ID}" ]; then info "attaching assets to release ID ${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}" 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" info "assets attached via API" else err "could not resolve release ID for ${VERSION}" fi fi # 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 info "assets missing after tea — attempting manual attachment..." 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; print(json.load(sys.stdin).get('id',''))" 2>/dev/null || echo "") if [ -n "${RELEASE_ID}" ]; then 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}" 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" 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 "after retry: ${ASSET_COUNT} assets" fi if [ "${ASSET_COUNT}" -lt 2 ]; then err "assets not attached after retry (REQ-097, C-21)" fi fi info "release ${VERSION} published with ${ASSET_COUNT} binary assets" ;; *) err "unknown job: ${JOB}" ;; esac