ci: run trivy directly; document CI/CD setup in RUNBOOK
trivy-action@v0.29.0 internally uses setup-trivy@v0.2.2, a tag removed upstream (earliest published is now v0.2.6), so it cannot resolve on any runner. Run trivy from a digest-pinned image instead, as gitleaks already is. RUNBOOK gains a 'Setting up CI/CD from scratch' section with the traps that actually cost time: GITEA_TOKEN is 401 at the package registry, the runner's cache fails soft, service containers resolve by name not localhost. Not pushed: pushing triggers a run, and the runner is being restarted by the ansible change that enables its cache.
This commit is contained in:
+83
@@ -12,6 +12,89 @@ alias sfsql='psql "$SVCFORGE_PG_DSN_SESSION"'
|
||||
|
||||
---
|
||||
|
||||
## Setting up CI/CD from scratch
|
||||
|
||||
What it takes to get this repo building in Gitea Actions, including every trap that cost
|
||||
real time. Do it in this order; each step fails loudly if the one before it was skipped.
|
||||
|
||||
### 1. The repo and its secrets
|
||||
|
||||
```bash
|
||||
GITEA=https://gitea.oci-oci.duckdns.org
|
||||
USER=gitea_admin # the registry namespaces packages by OWNER, so this is IMAGE_NS too
|
||||
|
||||
curl -u "$USER:$PASS" -X POST "$GITEA/api/v1/user/repos" \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"name":"svcforge","default_branch":"master","private":false}'
|
||||
|
||||
# A PAT with exactly three scopes. Not admin.
|
||||
curl -u "$USER:$PASS" -X POST "$GITEA/api/v1/users/$USER/tokens" \
|
||||
-H 'content-type: application/json' \
|
||||
-d '{"name":"svcforge-ci","scopes":["write:package","write:repository","read:user"]}'
|
||||
```
|
||||
|
||||
Then set three repo Actions secrets (`Settings → Actions → Secrets`, or the API):
|
||||
|
||||
| Secret | Value | Why it exists |
|
||||
|---|---|---|
|
||||
| `REGISTRY_USER` | `gitea_admin` | |
|
||||
| `REGISTRY_TOKEN` | the PAT | **The auto-injected `GITEA_TOKEN` is rejected by the package registry with a 401.** This is the single most common reason a first pipeline fails at `docker push`. |
|
||||
| `CI_BOT_TOKEN` | the PAT | Used only by the `bump` job to push the digest commit. Note what it is *not*: a kubeconfig. CI's maximum blast radius is a bad commit. |
|
||||
|
||||
### 2. The runner needs a cache server, and it fails SOFT without one
|
||||
|
||||
`cache: enabled: false` in the act_runner config is the default, and it does not fail the
|
||||
build. It prints:
|
||||
|
||||
```
|
||||
Warning: Failed to restore: getCacheEntry failed: Cache Service Url not found, unable to restore cache.
|
||||
```
|
||||
|
||||
…and carries on, re-downloading every wheel on every run, forever. **A cache that is off
|
||||
looks exactly like a cache that is always cold.** Enable it in
|
||||
`oci-k8s/k8s/roles/addons/tasks/main.yml` (Ansible owns this — never `kubectl edit` it):
|
||||
|
||||
```yaml
|
||||
cache:
|
||||
enabled: true
|
||||
dir: /data/cache # the runner's PVC, so it survives a restart
|
||||
host: "" # auto-detect the pod IP; 127.0.0.1 would resolve to the JOB container
|
||||
port: 8088
|
||||
```
|
||||
|
||||
```bash
|
||||
cd oci-k8s/k8s && ansible-playbook 03_install_addons.yml --tags gitea
|
||||
```
|
||||
|
||||
Without this, `astral-sh/setup-uv`'s `enable-cache: true` and buildx's `--cache-to
|
||||
type=gha` are both no-ops.
|
||||
|
||||
### 3. Traps that are specific to Gitea, not GitHub
|
||||
|
||||
| Symptom | Cause | Fix |
|
||||
|---|---|---|
|
||||
| `Unable to resolve v0.2.2: reference not found` | A third-party action pinned by SHA still resolves **its own** dependencies by mutable tag. `trivy-action@v0.29.0` does `uses: setup-trivy@v0.2.2`, and that tag was removed upstream. | Run the tool directly from an image pinned by digest. Pinning the outer action bought nothing. |
|
||||
| `docker push` → 401 | Used `GITEA_TOKEN`. | Use a PAT with `write:package`. |
|
||||
| The pipeline retriggers itself forever | The `bump` job commits to the repo it is triggered by. | `[skip ci]` in the commit message (Gitea honours it), **and** a `git diff --quiet` guard so an unchanged digest commits nothing. |
|
||||
| Service container unreachable at `localhost` | Jobs run *inside* a container, so a service is reached by its **service name**, not localhost. | `postgres:5432`, not `localhost:5432`. |
|
||||
|
||||
### 4. Verify the whole loop, not just the green checkmarks
|
||||
|
||||
```bash
|
||||
# the digest CI pushed
|
||||
docker buildx imagetools inspect gitea.oci-oci.duckdns.org/gitea_admin/svcforge-api:<sha> \
|
||||
--format '{{.Manifest.Digest}}'
|
||||
# the digest the chart deploys — these must be equal
|
||||
grep -A2 'api:' deploy/chart/values.yaml
|
||||
# what ArgoCD actually synced
|
||||
kubectl -n argocd get application svcforge -o jsonpath='{.status.sync.revision}'
|
||||
```
|
||||
|
||||
If those three disagree, the deploy is not what CI tested, and every other guarantee in
|
||||
this document is void.
|
||||
|
||||
---
|
||||
|
||||
## Measured numbers
|
||||
|
||||
From `scripts/load.py` + in-process workers on a `FakeProvisioner` (`delay=0.05s`), 200 tasks
|
||||
|
||||
Reference in New Issue
Block a user