β CI β PR #8 #41
Workflow file for this run
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| name: "β CI" | |
| run-name: >- | |
| ${{ | |
| github.event_name == 'schedule' && 'β CI β Weekly schedule' || | |
| github.event_name == 'pull_request' && format('β CI β PR #{0}', github.event.pull_request.number) || | |
| format('β CI β {0}', github.ref_name) | |
| }} | |
| on: | |
| push: | |
| branches: [main, "dev/**"] | |
| pull_request: | |
| branches: [main, "dev/**"] | |
| schedule: | |
| - cron: '30 6 * * 1' # Weekly on Monday at 06:30 UTC | |
| concurrency: | |
| group: ci-${{ github.workflow }}-${{ github.ref }} | |
| cancel-in-progress: true | |
| permissions: | |
| contents: read | |
| jobs: | |
| build: | |
| name: "ποΈ Build & Test" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Verify dependencies | |
| run: go mod verify | |
| - name: Format check | |
| run: | | |
| FILES=$(gofmt -l .) | |
| if [ -n "$FILES" ]; then | |
| echo "::error::gofmt differences found β run 'gofmt -w .' and commit:" | |
| echo "$FILES" | |
| exit 1 | |
| fi | |
| - name: Vet | |
| run: go vet ./... | |
| - name: Build | |
| run: CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o lookout ./cmd/lookout | |
| - name: Test | |
| run: go test -race -coverprofile=coverage.out ./... | |
| lint: | |
| name: "π§Ή Lint" | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: "1.26" | |
| - name: golangci-lint | |
| uses: golangci/golangci-lint-action@82606bf257cbaff209d206a39f5134f0cfbfd2ee # v9.2.1 | |
| vuln: | |
| name: "π Govulncheck" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Install govulncheck | |
| run: go install golang.org/x/vuln/cmd/govulncheck@v1.2.0 | |
| - name: Run govulncheck | |
| run: govulncheck ./... | |
| zizmor: | |
| name: "π Workflow Security" | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| actions: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Run zizmor | |
| uses: zizmorcore/zizmor-action@5f14fd08f7cf1cb1609c1e344975f152c7ee938d # v0.5.6 | |
| with: | |
| token: ${{ github.token }} | |
| online-audits: false | |
| advanced-security: false | |
| commit-message: | |
| name: "π¬ Commit Message" | |
| if: github.event_name == 'pull_request' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| continue-on-error: true # advisory β does not block merge | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 0 | |
| - name: Validate commit messages | |
| run: | | |
| set -euo pipefail | |
| failed=0 | |
| while IFS= read -r msg; do | |
| first_line="$(echo "${msg}" | head -n1)" | |
| if ! echo "${first_line}" | grep -qP '^(\p{So}|\p{Emoji_Presentation})+\s+\w+(\(\w[\w-]*\))?!?:\s.+$'; then | |
| echo "::warning::Non-conventional commit: ${first_line}" | |
| failed=1 | |
| fi | |
| done < <(git log --format=%s origin/main..HEAD) | |
| if [ "${failed}" -eq 1 ]; then | |
| echo "::warning::Some commits don't follow emoji conventional format. Expected: <emoji> <type>(scope): description" | |
| fi | |
| goreleaser-check: | |
| name: "π¦ GoReleaser Config" | |
| if: github.event_name != 'schedule' | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Run GoReleaser check | |
| uses: goreleaser/goreleaser-action@5daf1e915a5f0af01ddbcd89a43b8061ff4f1a89 # v7.2.2 | |
| with: | |
| distribution: goreleaser | |
| # Pin to the v2 major line (matches release.yml) so the config check | |
| # runs the same GoReleaser major that ships releases, and to clear the | |
| # action's "using 'latest' as default version" advisory. | |
| version: "~> v2" | |
| args: check | |
| codeql: | |
| name: "π CodeQL Analysis" | |
| # CodeQL SARIF upload requires GitHub Advanced Security (GHAS), which is not | |
| # available on free private repos. Skip unless the repo is public. | |
| if: github.event.repository.visibility == 'public' && (github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'push' && github.ref == 'refs/heads/main')) | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 60 | |
| permissions: | |
| actions: read | |
| contents: read | |
| security-events: write | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Initialize CodeQL | |
| uses: github/codeql-action/init@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 | |
| with: | |
| languages: actions, go | |
| queries: security-and-quality | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Build Go | |
| run: go build ./... | |
| - name: Perform CodeQL Analysis | |
| uses: github/codeql-action/analyze@8aad20d150bbac5944a9f9d289da16a4b0d87c1e # v4.36.2 | |
| dependency-review: | |
| name: "π¦ Dependency Review" | |
| # Dependency Review requires GitHub Advanced Security on private repos. | |
| # Gated on public visibility so it auto-activates when the repo goes public. | |
| if: ${{ false && github.event.repository.visibility == 'public' && github.event_name == 'pull_request' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| permissions: | |
| contents: read | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Dependency Review | |
| uses: actions/dependency-review-action@a1d282b36b6f3519aa1f3fc636f609c47dddb294 # v5.0.0 | |
| go-fuzz: | |
| # Tier 1 β per-PR smoke-level fuzzing. Each fuzzer gets a fixed 60 s | |
| # coverage budget in its own matrix job. Deep-coverage tier lives in | |
| # quality-fuzz-nightly.yml where each fuzzer gets 5 minutes. | |
| name: "π Go Fuzz (${{ matrix.fuzzer.name }})" | |
| if: github.event_name == 'pull_request' || github.event_name == 'schedule' || (github.event_name == 'push' && github.ref == 'refs/heads/main') | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| strategy: | |
| fail-fast: false | |
| matrix: | |
| fuzzer: | |
| - { name: FuzzParsePHC, pkg: ./internal/server/ } | |
| - { name: FuzzParseTrustedProxies, pkg: ./internal/server/ } | |
| - { name: FuzzParseImageRef, pkg: ./internal/adapter/ } | |
| - { name: FuzzParseLabels, pkg: ./internal/adapter/drydock/ } | |
| - { name: FuzzMCPHandler, pkg: ./internal/mcp/ } | |
| steps: | |
| - name: Harden Runner | |
| uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 | |
| with: | |
| egress-policy: audit | |
| - name: Checkout | |
| uses: actions/checkout@df4cb1c069e1874edd31b4311f1884172cec0e10 # v6.0.3 | |
| with: | |
| persist-credentials: false | |
| - name: Setup Go | |
| uses: actions/setup-go@4a3601121dd01d1626a1e23e37211e3254c1c06c # v6.4.0 | |
| with: | |
| go-version: "1.26" | |
| - name: Fuzz ${{ matrix.fuzzer.name }} | |
| env: | |
| FUZZER: ${{ matrix.fuzzer.name }} | |
| PKG: ${{ matrix.fuzzer.pkg }} | |
| FUZZTIME: 60s | |
| TEST_TIMEOUT: 5m | |
| run: | | |
| LOG="${RUNNER_TEMP}/fuzz-${FUZZER}.log" | |
| # Primary fix: cap workers below the core count so the fuzzing | |
| # coordinator always keeps a core. This PREVENTS the starvation that | |
| # surfaces as a spurious "context deadline exceeded" (the hook does | |
| # the same); the retry below is only a backstop. max(1, min(4, cores-1)). | |
| cores=$(getconf _NPROCESSORS_ONLN 2>/dev/null || echo 4) | |
| workers=$(( cores > 1 ? cores - 1 : 1 )) | |
| [ "${workers}" -gt 4 ] && workers=4 | |
| run_fuzz() { | |
| go test -run='^$' \ | |
| -fuzz="^${FUZZER}\$" \ | |
| -fuzztime="${FUZZTIME}" \ | |
| -timeout="${TEST_TIMEOUT}" \ | |
| -parallel="${workers}" \ | |
| "${PKG}" 2>&1 | tee "${LOG}" | |
| return "${PIPESTATUS[0]}" | |
| } | |
| # Backstop: retry once on the residual known boundary race (a worker | |
| # still mid-input when -fuzztime expires). This trusts Go's internal | |
| # "context deadline exceeded" text as the flake signal β a genuinely | |
| # slow input that only *sometimes* exceeds -timeout would also match, | |
| # but handlers are verified sub-10ms and a persistently slow input | |
| # still fails BOTH attempts. A real crash ("Failing input written to | |
| # testdata") is never retried; any other failure exits immediately. | |
| # If Go rewords this message the gate fails closed (hard failure), | |
| # which is the safe direction. Mirrors quality-fuzz-nightly.yml. | |
| for attempt in 1 2; do | |
| rc=0 | |
| run_fuzz || rc=$? | |
| [ "${rc}" -eq 0 ] && exit 0 | |
| if grep -q "Failing input written to testdata" "${LOG}"; then | |
| echo "::error::${FUZZER} found a crashing input β commit it to the seed corpus and fix the regression." | |
| exit "${rc}" | |
| fi | |
| if ! grep -q "context deadline exceeded" "${LOG}"; then | |
| echo "::error::${FUZZER} failed for a non-flake reason (exit ${rc})." | |
| exit "${rc}" | |
| fi | |
| echo "::warning::${FUZZER}: known -fuzztime boundary flake on attempt ${attempt}/2." | |
| done | |
| echo "::error::${FUZZER} hit the boundary flake on both attempts." | |
| exit 1 | |
| - name: Upload fuzz corpus on failure | |
| if: failure() | |
| uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1 | |
| with: | |
| name: fuzz-corpus-${{ matrix.fuzzer.name }}-${{ github.run_id }} | |
| path: "**/testdata/fuzz/${{ matrix.fuzzer.name }}/" | |
| retention-days: 30 | |
| if-no-files-found: ignore |