diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..48a579d --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,81 @@ +name: CI + +on: + push: + branches: [main] + pull_request: + +# Cancel superseded runs on the same ref to save minutes. +concurrency: + group: ci-${{ github.ref }} + cancel-in-progress: true + +permissions: + contents: read + +jobs: + test: + name: Test + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Check formatting (gofmt -s) + run: | + unformatted="$(gofmt -s -l .)" + if [ -n "$unformatted" ]; then + echo "These files are not gofmt -s formatted:" + echo "$unformatted" + exit 1 + fi + - name: Vet + run: go vet ./... + - name: Test (race + coverage) + run: go test -race -cover ./... + + lint: + name: Lint + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - uses: golangci/golangci-lint-action@v6 + with: + version: v1.62.2 + + vulncheck: + name: Govulncheck + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Run govulncheck + run: | + go install golang.org/x/vuln/cmd/govulncheck@latest + govulncheck ./... + + build: + name: Build (${{ matrix.goos }}/${{ matrix.goarch }}) + runs-on: ubuntu-latest + strategy: + fail-fast: false + matrix: + goos: [linux, darwin] + goarch: [amd64, arm64] + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version-file: go.mod + - name: Cross-compile + env: + GOOS: ${{ matrix.goos }} + GOARCH: ${{ matrix.goarch }} + CGO_ENABLED: "0" + run: go build -o /dev/null ./cmd/scanner diff --git a/.golangci.yml b/.golangci.yml new file mode 100644 index 0000000..cd63ea8 --- /dev/null +++ b/.golangci.yml @@ -0,0 +1,16 @@ +# golangci-lint configuration. Mirrors the linters required by CONVENTIONS.md. +run: + timeout: 5m + +linters: + enable: + - errcheck # unchecked errors + - govet # suspicious constructs + - staticcheck # static analysis + - revive # style / lint rules + - gosec # security issues + +issues: + # Don't cap the number of reported issues per linter/file. + max-issues-per-linter: 0 + max-same-issues: 0 diff --git a/ROADMAP.md b/ROADMAP.md index d4367cf..95c115b 100644 --- a/ROADMAP.md +++ b/ROADMAP.md @@ -27,7 +27,7 @@ Living document. Tick items off as they land; reorder when priorities change. ## Phase 4 — Project Hygiene -- [ ] **CI pipeline** (GitHub Actions): `go vet`, `go test -race -cover`, `golangci-lint`, `govulncheck`, build matrix for linux/darwin amd64+arm64. +- [x] **CI pipeline** (GitHub Actions): `go vet`, `go test -race -cover`, `golangci-lint`, `govulncheck`, build matrix for linux/darwin amd64+arm64. - [ ] **Release automation** with `goreleaser` → signed binaries + multi-arch container image. - [ ] **Integration tests** that scan a curated set of pinned images (`alpine:3.14`, `alpine:3.19`, a known-clean image, a known-vulnerable image) and assert findings. diff --git a/internal/extractor/extractor.go b/internal/extractor/extractor.go index 3c99690..bea238c 100644 --- a/internal/extractor/extractor.go +++ b/internal/extractor/extractor.go @@ -38,7 +38,7 @@ func ExtractImage(imageName string, verbose bool) (string, func(), error) { // Find the docker binary safely dockerBinary := getDockerPath() - + // 1.5. Pull the image from the registry to ensure it exists locally if verbose { fmt.Printf("Pulling image '%s' from registry...\n", imageName)