d7896e5287
CoreCI's validate() rejects jobs with both plugin and invoke set (mutually exclusive — pipeline.go:119). The previous commit used both plugin: docker://golang:1.25.12 and invoke: on each job, causing: Error: load config: validate: job "gosec": plugin and invoke are mutually exclusive Fix: remove all plugin: fields. Jobs run via the shell-isolated executor which runs sh -c <invoke> directly. Go 1.25 is installed on the Gitea Actions runner via actions/setup-go, so all Go commands work. Tool installation via go install (gosec, govulncheck) and curl (gitleaks, tea) works in the shell-isolated executor. ---ci--- project: orca phase: 1 milestone: v0.16 status: execute ---/ci---
194 lines
9.4 KiB
YAML
194 lines
9.4 KiB
YAML
version: "1"
|
|
name: orca-ci
|
|
description: Orca — offline/CLI-first orchestration engine. Full release flow via CoreCI.
|
|
|
|
# CoreCI configuration for orca (v0.16 rewrite — native jobs: format).
|
|
#
|
|
# CoreCI's Pipeline struct only recognizes `jobs:`, `services:`, and `env:`
|
|
# top-level keys. Unknown keys (like the old `pipelines:`) are silently
|
|
# dropped by yaml.Unmarshal, producing an empty Jobs map → zero jobs
|
|
# execute. This file uses the native `jobs:`/`invoke:`/`vars:` format
|
|
# with a DAG via `needs:`.
|
|
#
|
|
# DAG: go-vet → fan-out (verify-reqs, gosec, govulncheck, gitleaks) → build → test → release
|
|
#
|
|
# 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.
|
|
#
|
|
# 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, which runs `sh -c <invoke>`
|
|
# directly. The Gitea Actions runner has Go 1.25 installed via
|
|
# actions/setup-go, so all Go commands work. Tool installation via
|
|
# `go install` (gosec, govulncheck) and `curl` (gitleaks, tea) works.
|
|
#
|
|
# Security scans (REQ-014, REQ-027, REQ-039):
|
|
# - gosec Static analysis for Go security smells
|
|
# - govulncheck Offline vuln scan of dependencies
|
|
# - gitleaks Pre-commit-style secret scan
|
|
# verify-reqs (REQ-060): ROADMAP COMPLETE ↔ REQUIREMENTS Complete
|
|
# test runs with -race (REQ-031).
|
|
|
|
jobs:
|
|
# ── validate ──────────────────────────────────────────────────────────
|
|
go-vet:
|
|
invoke: |
|
|
go version
|
|
gofmt -l .
|
|
go vet ./...
|
|
|
|
verify-reqs:
|
|
needs: [go-vet]
|
|
invoke: "make verify-reqs"
|
|
|
|
gosec:
|
|
needs: [go-vet]
|
|
invoke: |
|
|
go install github.com/securego/gosec/v2/cmd/gosec@v2.18.2
|
|
gosec -fmt text -quiet ./...
|
|
|
|
govulncheck:
|
|
needs: [go-vet]
|
|
vars:
|
|
GOFLAGS: "-mod=mod"
|
|
invoke: |
|
|
go install golang.org/x/vuln/cmd/govulncheck@v1.1.3
|
|
govulncheck -mode binary ./...
|
|
|
|
gitleaks:
|
|
needs: [go-vet]
|
|
invoke: |
|
|
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 || sudo mv /tmp/gitleaks /usr/local/bin/gitleaks 2>/dev/null || cp /tmp/gitleaks ./gitleaks
|
|
chmod +x ./gitleaks 2>/dev/null || true
|
|
./gitleaks detect --source . --config .gitleaks.toml --baseline-path .gitleaks-baseline.json --no-banner || gitleaks detect --source . --config .gitleaks.toml --baseline-path .gitleaks-baseline.json --no-banner
|
|
|
|
# ── build ────────────────────────────────────────────────────────────
|
|
# CI_COMMIT_BRANCH contains the tag name on tag pushes (CoreCI's github.go
|
|
# maps GITHUB_REF_NAME → CI_COMMIT_BRANCH). CI_COMMIT_SHA is the commit.
|
|
# BUILD_TIME is computed inline via `date`. Shell expansion works inside
|
|
# invoke: via sh -c at runtime.
|
|
build:
|
|
needs: [verify-reqs, gosec, govulncheck, gitleaks]
|
|
invoke: |
|
|
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}"
|
|
go build -trimpath -ldflags="${LDFLAGS}" -o bin/orca ./cmd/orca
|
|
file bin/orca
|
|
./bin/orca version
|
|
|
|
# ── test (REQ-031: -race) ────────────────────────────────────────────
|
|
test:
|
|
needs: [build]
|
|
invoke: |
|
|
go test -race -coverprofile=coverage.out ./...
|
|
go tool cover -func=coverage.out | tail -1
|
|
|
|
# ── release ──────────────────────────────────────────────────────────
|
|
# Builds the release tarball, creates/updates the Gitea release with
|
|
# binary assets. Handles the case where the release already exists
|
|
# (created by the CIAgent ship workflow with title+body but no binary)
|
|
# by falling back to Gitea API asset attachment.
|
|
# GITEA_TOKEN is resolved from env via CoreCI's secret resolver
|
|
# (os.Getenv fallback in run.go:146-153) and forwarded by PassThroughEnv.
|
|
release:
|
|
needs: [test]
|
|
vars:
|
|
GITEA_TOKEN: "${{ secrets.GITEA_TOKEN }}"
|
|
invoke: |
|
|
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"
|
|
|
|
# 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}"
|
|
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.
|
|
tea releases create "${VERSION}" \
|
|
--repo coreci/orca \
|
|
--title "Orca ${VERSION}" \
|
|
--note-file CHANGELOG.md \
|
|
--asset "${TARBALL}" \
|
|
--asset SHA256SUMS 2>/dev/null && echo "✓ release created via tea" || ATTACH_TO_EXISTING=1
|
|
|
|
if [ "${ATTACH_TO_EXISTING:-0}" = "1" ]; then
|
|
echo "Release ${VERSION} already exists — 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
|
|
echo "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"
|
|
echo "✓ assets attached via API"
|
|
else
|
|
echo "ERROR: Could not resolve release ID for ${VERSION}"
|
|
exit 1
|
|
fi
|
|
fi
|
|
|
|
# Verify assets are actually attached (REQ-097, gate C-21).
|
|
# tea releases create has been observed to exit 0 without attaching
|
|
# the asset in some versions. Verify via the API.
|
|
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")
|
|
echo "Release ${VERSION} has ${ASSET_COUNT} assets"
|
|
if [ "${ASSET_COUNT}" -lt 2 ]; then
|
|
echo "ERROR: Expected at least 2 assets (tarball + SHA256SUMS), got ${ASSET_COUNT}"
|
|
echo "Attempting manual asset 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")
|
|
echo "After retry: ${ASSET_COUNT} assets"
|
|
fi
|
|
if [ "${ASSET_COUNT}" -lt 2 ]; then
|
|
echo "FATAL: assets not attached after retry (REQ-097, C-21)"
|
|
exit 1
|
|
fi
|
|
fi
|
|
echo "✓ release ${VERSION} published with ${ASSET_COUNT} binary assets" |