Day 151 — GitHub Actions: build, test, scan¶
Month 6 · Week 2 · ⬅ Day 150 · Day 152 ➡ · Journal index
🎯 Learning Objective¶
Build a CI pipeline that, on every push/PR, builds the module, runs vetting and the race-enabled test suite with coverage, and scans for known vulnerabilities with govulncheck.
📚 Topics¶
- GitHub Actions: workflow, jobs, steps, triggers, matrix
- Go CI essentials:
go vet,go test -race -cover, module/build caching - Supply-chain:
govulncheck,go vet, dependency review
📖 Reading / Sources¶
📝 Notes¶
- A workflow (
.github/workflows/ci.yml) holds jobs that run on triggers (on: [push, pull_request]). Each job runs on a fresh runner; steps within a job share a filesystem → [[github-actions]]. go test -raceruns the data-race detector — it instruments memory access and catches unsynchronized concurrent access at runtime. CI is the right place since it slows tests ~2–10x. Pair with-cover/-coverprofile.- Run
go vet ./...in CI: it flags suspicious-but-compiling code (badPrintfverbs, lostcontext.Cancel, copying locks). Cheap, high-signal → [[go-vet]]. govulncheck ./...checks your imports and call graph against the Go vuln DB. It's low-noise: it reports a CVE only if your code actually reaches the vulnerable symbol, not merely depends on the module → [[govulncheck]].- Cache the module + build cache (
actions/setup-godoes this whencache: true) keyed ongo.sum, so CI doesn't re-download/recompile everything each run. - A matrix runs the same job across Go versions / OSes in parallel.
fail-fast: falselets every cell finish so you see all failures. - Pin actions and use least-privilege
permissions:(defaultcontents: read); request only what a job needs. Don't expose secrets to PRs from forks.
💻 Code Examples¶
# .github/workflows/ci.yml
name: ci
on:
push: { branches: [main] }
pull_request:
permissions:
contents: read # least privilege
jobs:
test:
runs-on: ubuntu-latest
strategy:
fail-fast: false
matrix: { go: ["1.22", "1.23"] }
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version: ${{ matrix.go }}
cache: true # caches module + build cache by go.sum
- run: go vet ./...
- run: go test -race -coverprofile=cover.out ./...
- run: go tool cover -func=cover.out | tail -1
vulncheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with: { go-version: "1.22" }
- run: go install golang.org/x/vuln/cmd/govulncheck@latest
- run: govulncheck ./...
Locally, the same gate is just three commands:
// run before every push (a Makefile target or git pre-push hook):
// go vet ./...
// go test -race -cover ./...
// govulncheck ./...
🏋️ Exercises / Practice¶
| Exercise | Status | Link |
|---|---|---|
Make every week-2 package pass go test -race |
✅ | exercises/month-06/week-2/ |
🐛 Mistakes Made¶
- Ran tests without
-racelocally; CI's-racejob caught a real data race in a shared counter. The detector is only as good as the code paths exercised, so coverage matters. - Gave the workflow broad
permissions: write-allout of habit. Tightened tocontents: read— CI doesn't need write.
❓ Open Questions¶
- Should
govulncheckfail the build or just warn? (Fail on reachable HIGH/CRITICAL; otherwise the gate is theater.)
🧠 Active Recall (answer without looking)¶
- Q: How does
govulncheckavoid the false-positive noise of plain dependency scanners?A
It performs call-graph (reachability) analysis: it reports a vulnerability only if your code actually calls the affected symbol, not merely because a vulnerable module is in go.sum.
2. Q: Why run go test -race in CI rather than relying on local runs? A
The race detector adds significant CPU/memory overhead, so devs often skip it locally. CI runs it consistently across the full matrix and catches races that only surface under different scheduling/load.
🪶 Feynman Reflection¶
CI is a robot that, every time you push, rebuilds your code on a clean machine and runs the checks you'd otherwise forget: vet for sloppiness, -race for concurrency bugs, govulncheck for known-bad dependencies you actually use. Green means "safe to merge"; it's an automated second pair of eyes.
🕳️ Knowledge Gaps¶
- Reusable workflows / composite actions to share CI across repos — revisit during capstone.
✅ Summary¶
I can write a GitHub Actions pipeline that vets, race-tests with coverage, caches dependencies, and gates merges on govulncheck, all with least-privilege permissions.
⏭️ Next Steps / Prep for Tomorrow¶
- Day 152: feed secrets and config into all this without leaking them.
| Time spent | Difficulty | Confidence |
|---|---|---|
| 95 min | 🟦🟦⬜⬜⬜ | 🟦🟦🟦⬜⬜ |
Suggested commit: docs(journal): github actions build/test/scan (day 151)