Files
orca/.coreci.yml
T
Jon Chery 3be86e6daf
Release / ci (push) Failing after 5m13s
Release / container-orca (push) Has been skipped
Release / container-traefik (push) Has been skipped
fix(P1): Gitea Actions clone auth + .coreci.yml native format rewrite (REQ-183,184)
REQ-183: Fix .gitea/workflows/release.yml — the git clone of the private
coreci repo failed because the clone command had no credentials. The
actions/checkout@v4 step only injects auth for the orca repo. Fix: pass
GITEA_TOKEN env to the Install CoreCI step and embed it in the clone
URL (https://cloudinit-bot:${GITEA_TOKEN}@git.cloudinit.dev/...).

REQ-184: Rewrite .coreci.yml from the invalid pipelines:/steps:/image:/
commands: format to CoreCI's native jobs:/plugin:/invoke:/vars: format
with a proper DAG (needs:). CoreCI's Pipeline struct only recognizes
jobs:/services:/env: top-level keys — unknown fields are silently dropped
by yaml.Unmarshal, producing an empty Jobs map → zero jobs execute.
The rewrite:
- 8 jobs: go-vet → fan-out (verify-reqs, gosec, govulncheck, gitleaks)
  → build → test → release
- plugin: docker://golang:1.25.12 + invoke: on each job (container path
  with shell-isolated fallback — Go is installed on the runner)
- GITEA_TOKEN via vars: with ${{ secrets.GITEA_TOKEN }} (resolved from
  env via CoreCI's secret resolver os.Getenv fallback)
- CI_COMMIT_BRANCH (tag name on tag push) and CI_COMMIT_SHA for version
  injection — no ${VAR} interpolation in YAML fields (shell expansion
  only works inside invoke: via sh -c)
- No apk add (runner is ubuntu, not alpine — uses curl for tool downloads)
- Release job handles duplicate release (ship workflow creates release
  first with title+body; coreci run attaches binary assets later via API
  fallback if tea releases create fails)
- Release job verifies asset count ≥ 2 (REQ-097 gate C-21) with retry

Root cause: all 87 releases in repo history had zero binary assets
because coreci run never executed any jobs (empty Jobs map from the
invalid format) and the Gitea Actions workflow failed before reaching
coreci run (private repo clone had no credentials).

---ci---
project: orca
phase: 1
milestone: v0.16
status: execute
requirements:
  covered: [183, 184]
  partial: []
---/ci---
2026-08-12 21:05:21 +00:00

201 lines
9.6 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:`/`plugin:`/`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 declares `plugin: docker://golang:1.25.12` for the container
# path and `invoke:` for the shell command. If no container runtime is
# available (podman/docker), CoreCI's shell-isolated executor runs the
# `invoke:` command directly — the Gitea Actions runner has Go 1.25
# installed via actions/setup-go, so Go commands work in both paths.
#
# 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:
plugin: docker://golang:1.25.12
invoke: |
go version
gofmt -l .
go vet ./...
verify-reqs:
needs: [go-vet]
plugin: docker://golang:1.25.12
invoke: "make verify-reqs"
gosec:
needs: [go-vet]
plugin: docker://golang:1.25.12
invoke: |
go install github.com/securego/gosec/v2/cmd/gosec@v2.18.2
gosec -fmt text -quiet ./...
govulncheck:
needs: [go-vet]
plugin: docker://golang:1.25.12
vars:
GOFLAGS: "-mod=mod"
invoke: |
go install golang.org/x/vuln/cmd/govulncheck@v1.1.3
govulncheck -mode binary ./...
gitleaks:
needs: [go-vet]
plugin: docker://golang:1.25.12
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]
plugin: docker://golang:1.25.12
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]
plugin: docker://golang:1.25.12
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]
plugin: docker://golang:1.25.12
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"