#!/bin/sh # 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) # # NOTE: uses #!/bin/sh — do NOT use bash-only features (pipefail, [[ ]], etc.) # The Gitea Actions runner uses dash as /bin/sh. set -u 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. echo "ci-run: PATH=$PATH" >&2 echo "ci-run: which go=$(command -v go 2>/dev/null || echo 'not found')" >&2 if command -v go >/dev/null 2>&1; then export GOROOT="${GOROOT:-$(go env GOROOT 2>/dev/null || echo "")}" export GOPATH="${GOPATH:-$(go env GOPATH 2>/dev/null || echo "$HOME/go")}" export GOCACHE="${GOCACHE:-$(go env GOCACHE 2>/dev/null || echo "$HOME/.cache/go-build")}" export GOMODCACHE="${GOMODCACHE:-$(go env GOMODCACHE 2>/dev/null || echo "$HOME/go/pkg/mod")}" echo "ci-run: GOROOT=$GOROOT GOPATH=$GOPATH GOCACHE=$GOCACHE GOMODCACHE=$GOMODCACHE" >&2 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) info "building orca binary..." VERSION="${CI_COMMIT_BRANCH:-dev}" GIT_COMMIT="${CI_COMMIT_SHA:-unknown}" BUILD_TIME="$(date -u +%Y-%m-%dT%H:%M:%SZ)" info "VERSION=$VERSION GIT_COMMIT=$GIT_COMMIT BUILD_TIME=$BUILD_TIME" 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 info "running: go build -trimpath -ldflags=... -o bin/orca ./cmd/orca" go build -trimpath -ldflags="${LDFLAGS}" -o bin/orca ./cmd/orca 2>&1 || err "go build failed with exit $?" file bin/orca 2>/dev/null || echo "file command not available" ./bin/orca version 2>&1 || echo "orca version failed" ;; # ── 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}" 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 can fill the disk. # Remove coverage.out and Go build cache to free space. rm -f coverage.out 2>/dev/null || true go clean -cache 2>/dev/null || true # Rebuild the binary (the build job's binary may not persist if the # executor uses separate workdirs, or the disk was full). info "building release binary..." 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 BUILD_EXIT=$? if [ ${BUILD_EXIT} -ne 0 ]; then info "go build failed (exit ${BUILD_EXIT}) — trying with existing bin/orca from build job" if [ -x bin/orca ]; then info "found existing bin/orca from build job" else err "go build failed and no existing binary found" fi 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}" ;; esac