diff --git a/.github/workflows/config-ci.yml b/.github/workflows/config-ci.yml index 0f24405413..602c39e0cc 100644 --- a/.github/workflows/config-ci.yml +++ b/.github/workflows/config-ci.yml @@ -16,52 +16,50 @@ on: - release-4.* jobs: - config: - runs-on: ubuntu-latest - outputs: - go_versions: ${{ steps.config.outputs.go_versions }} - steps: - - id: config - run: | - echo 'go_versions=["1.20", "1.19"]' >> "$GITHUB_OUTPUT" - commit-check: name: Commit Check runs-on: ubuntu-latest steps: - - name: commit check + - name: Commit Check uses: gsactions/commit-message-checker@v2 with: pattern: | - ^(.*):\s*(.*)\n.*$ + ^[^:!]+: .+\n\n.*$ error: 'Commit must begin with : ' flags: 'gm' excludeTitle: true excludeDescription: true checkAllCommitMessages: true accessToken: ${{ secrets.GITHUB_TOKEN }} + tidy: name: Tidy - needs: ['config'] runs-on: ubuntu-latest - strategy: - matrix: - go: - - ${{ fromJSON(needs.config.outputs.go_versions)[0] }} steps: - - uses: actions/checkout@v4 - - uses: actions/setup-go@v5 - with: - go-version: ${{ matrix.go }} - - uses: ./.github/actions/go-tidy + - name: Checkout + id: checkout + if: ${{ !cancelled() }} + uses: actions/checkout@v4 + - name: Setup Go + id: setupgo + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' }} + uses: actions/setup-go@v5 with: - go: ${{ matrix.go }} - dir: ./config + cache: false + go-version-file: ./config/go.mod + - name: Go Tidy + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' && steps.setupgo.conclusion == 'success' }} + working-directory: ./config + run: | + trap 'echo "::error file=go.mod,title=Tidy Check::Commit would leave go.mod untidy"' ERR + go mod tidy + git diff --exit-code tests: - needs: ['config'] + name: Tests + if: ${{ !cancelled() }} uses: ./.github/workflows/tests.yml with: cd: config package_expr: ./... - go_versions: ${{ needs.config.outputs.go_versions }} + qemu: false diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b682388208..cd7acb8318 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -12,63 +12,55 @@ on: - release-4.* jobs: - config: + lints: + name: Lints runs-on: ubuntu-latest - outputs: - go_versions: ${{ steps.config.outputs.go_versions }} steps: - - id: config - run: | - echo 'go_versions=["1.20"]' >> "$GITHUB_OUTPUT" - - commit-check: - name: Commit Check - runs-on: ubuntu-latest - steps: - - name: commit check + - name: Commit Check uses: gsactions/commit-message-checker@v2 with: pattern: | - ^(.*):\s*(.*)\n.*$ + ^[^:!]+: .+\n\n.*$ error: 'Commit must begin with : ' flags: 'gm' excludeTitle: true excludeDescription: true checkAllCommitMessages: true accessToken: ${{ secrets.GITHUB_TOKEN }} - - api-reference-check: - name: API Reference Check - runs-on: ubuntu-latest - steps: - name: Checkout + id: checkout + if: ${{ !cancelled() }} uses: actions/checkout@v4 - - name: gen api reference + - name: Check Filenames + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' }} + run: | # Check for all the characters Windows hates. + git ls-files -- ':/:*[<>:"|?*]*' | while read -r file; do + printf '::error file=%s,title=Bad Filename::Disallowed character in file name\n' "$file" + done + exit $(git ls-files -- ':/:*[<>:"|?*]*' | wc -l) + - name: Check API Reference + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' }} run: | npx widdershins --search false --language_tabs 'python:Python' 'go:Golang' 'javascript:Javascript' --summary ./openapi.yaml -o ./Documentation/reference/api.md - - name: diff - run: | git diff --exit-code - - tidy: - name: Tidy - needs: ['config'] - runs-on: ubuntu-latest - strategy: - matrix: - go: - - ${{ fromJSON(needs.config.outputs.go_versions)[0] }} - steps: - - uses: actions/setup-go@v5 + - name: Setup Go + id: 'setupgo' + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' }} + uses: actions/setup-go@v5 with: - go-version: ${{ matrix.go }} - - uses: actions/checkout@v4 - - uses: ./.github/actions/go-tidy - with: - go: ${{ matrix.go }} + cache: false + go-version-file: ./go.mod + - name: Go Tidy + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' && steps.setupgo.conclusion == 'success' }} + run: | + # go mod tidy + trap 'echo "::error file=go.mod,title=Tidy Check::Commit would leave go.mod untidy"' ERR + go mod tidy + git diff --exit-code documentation: name: Documentation + needs: ['lints'] runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 @@ -77,8 +69,9 @@ jobs: publish: false tests: - needs: ['config'] + name: Tests + needs: ['lints'] uses: ./.github/workflows/tests.yml with: package_expr: ./... - go_versions: ${{ needs.config.outputs.go_versions }} + qemu: false diff --git a/.github/workflows/nightly-ci.yml b/.github/workflows/nightly-ci.yml index 95ac5a91a6..41471d4719 100644 --- a/.github/workflows/nightly-ci.yml +++ b/.github/workflows/nightly-ci.yml @@ -10,46 +10,31 @@ on: required: false type: string description: 'Package expression(s) passed to `go test`' - go_versions: - required: false - type: string - description: 'JSON array of go versions to use for tests' - platforms: - required: false - type: string - description: 'JSON array of platforms to test' jobs: defaults: + name: Check Input runs-on: ubuntu-latest outputs: package_expr: ${{ steps.config.outputs.package_expr }} - go_versions: ${{ steps.config.outputs.go_versions }} - platforms: ${{ steps.config.outputs.platforms }} steps: - - uses: actions/checkout@v4 - - id: config + - name: Checkout + id: checkout + uses: actions/checkout@v4 + - name: Make package expressions + id: config + if: ${{ !cancelled() && steps.checkout.conclusion == 'success' }} run: | if test -n "${{ inputs.package_expr }}"; then printf 'package_expr=%s\n' '${{ inputs.package_expr }}' >> "$GITHUB_OUTPUT" else printf 'package_expr=%s\n' "$(go list -m github.com/quay/clair{core,}/... | awk '{printf("%s/... ",$1)}')" >> "$GITHUB_OUTPUT" fi - if test -n "${{ inputs.go_versions }}"; then - printf 'go_versions=%s\n' '${{ inputs.go_versions }}' >> "$GITHUB_OUTPUT" - else - printf 'go_versions=["1.20"]\n' >> "$GITHUB_OUTPUT" - fi - if test -n "${{ inputs.platforms }}"; then - printf 'platforms=%s\n' '${{ inputs.platforms }}' >> "$GITHUB_OUTPUT" - else - printf 'platforms=["linux/arm64", "linux/ppc64le", "linux/s390x"]\n' >> "$GITHUB_OUTPUT" - fi tests: + name: Tests needs: ['defaults'] uses: ./.github/workflows/tests.yml with: package_expr: ${{ needs.defaults.outputs.package_expr }} - go_versions: ${{ needs.defaults.outputs.go_versions }} - platforms: ${{ needs.defaults.outputs.platforms }} + qemu: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index ac16696b72..bcf8e5980a 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -8,18 +8,11 @@ on: required: true type: string description: 'Package expression(s) passed to `go test`' - go_versions: + qemu: required: false - type: string - default: | - ["1.20"] - description: 'JSON array of go versions to use for tests' - platforms: - required: false - type: string - default: | - ["linux/amd64"] - description: 'JSON array of platforms to test' + type: boolean + default: false + description: 'Run tests for additional architectures under qemu' cd: required: false type: string @@ -27,17 +20,96 @@ on: description: 'Change to this directory before running tests' jobs: + setup: + name: Setup + runs-on: ubuntu-latest + strategy: + matrix: + go: ['oldstable', 'stable'] + outputs: + go-cache: ${{ steps.check.outputs.cache-hit == 'true' || steps.setup-go.outputs.cache-hit == 'true' || steps.warm.conclusion == 'success' }} + runner-arch: ${{ steps.arch.outputs.result }} + steps: + - name: Map Arch + id: arch + uses: actions/github-script@v7 + with: + result-encoding: string + script: | + switch (process.env.RUNNER_ARCH) { + case "X64": + return "amd64"; + case "ARM64": + return "arm64"; + default: + core.setFailed(`unknown/unsupported architecture: ${process.env.RUNNER_ARCH}`); + } + return "" + - name: Checkout + if: ${{ inputs.cd == '' }} + id: checkout + uses: actions/checkout@v4 + - name: Check Go Version + if: ${{ inputs.cd == '' && steps.checkout.conclusion == 'success' }} + id: checkversion + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + cache: false + - name: Get ImageOS + # There's no way around this, because "ImageOS" is only available to + # processes, but the setup-go action uses it in its key. + if: ${{ inputs.cd == '' && steps.checkout.conclusion == 'success' }} + id: imageos + uses: actions/github-script@v7 + with: + result-encoding: string + script: | + return process.env.ImageOS + - name: Check Cache + if: ${{ inputs.cd == '' && steps.checkout.conclusion == 'success' }} + id: check + uses: actions/cache/restore@v4 + with: + key: >- + setup-go-${{ runner.os }}-${{ steps.imageos.outputs.result }}-go-${{ steps.checkversion.outputs.go-version }}-${{ hashFiles('go.sum') }} + lookup-only: true + path: | + ~/go/pkg/mod + ~/.cache/go-build + - name: Setup Go + if: ${{ inputs.cd == '' && steps.check.outputs.cache-hit != 'true' }} + id: setup-go + uses: actions/setup-go@v5 + with: + go-version: ${{ matrix.go }} + - name: Warm Cache on Miss + id: warm + if: ${{ inputs.cd == '' && steps.check.outputs.cache-hit != 'true' && steps.setup-go.outputs.cache-hit != 'true' }} + run: | + # Warm module+build cache + for GOARCH in amd64 arm64 ppc64le s390x; do + export GOARCH + for mod in '' github.com/quay/clair/config github.com/quay/claircore github.com/quay/claircore/toolkit; do + echo Downloading modules for "${mod-main}/$GOARCH" + go mod download $mod + done + echo Building '"std"' for "$GOARCH" + go build std + done + tests: name: Tests runs-on: ubuntu-latest + needs: ['setup'] strategy: matrix: - go: ${{ fromJSON(inputs.go_versions) }} - platform: ${{ fromJSON(inputs.platforms) }} + go: ['oldstable', 'stable'] + platform: ${{ inputs.qemu && fromJSON('["amd64","arm64","ppc64le","s390x"]') || fromJSON('["amd64"]')}} fail-fast: false services: postgres: - image: docker.io/library/postgres:11 + image: docker.io/library/postgres:15 env: POSTGRES_DB: "clair" POSTGRES_INITDB_ARGS: "--no-sync" @@ -60,63 +132,79 @@ jobs: steps: - name: Configure RabbitMQ + env: + brokername: ${{ matrix.platform == needs.setup.outputs.runner-arch && 'localhost' || 'rabbitmq' }} run: | docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl await_startup docker exec ${{ job.services.rabbitmq.id }} rabbitmq-plugins enable rabbitmq_stomp docker exec ${{ job.services.rabbitmq.id }} rabbitmq-plugins disable rabbitmq_management_agent rabbitmq_prometheus rabbitmq_web_dispatch - docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl add_vhost 'rabbitmq' - docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl set_permissions -p 'rabbitmq' guest '.*' '.*' '.*' + docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl add_vhost "${brokername}" + docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl set_permissions -p "${brokername}" guest '.*' '.*' '.*' docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl add_user clair password docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl set_permissions -p '/' clair '.*' '.*' '.*' - docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl set_permissions -p 'rabbitmq' clair '.*' '.*' '.*' + docker exec ${{ job.services.rabbitmq.id }} rabbitmqctl set_permissions -p "${brokername}" clair '.*' '.*' '.*' + - name: Checkout uses: actions/checkout@v4 - - uses: actions/setup-go@v5 + - name: Setup Go + id: setup-go + uses: actions/setup-go@v5 with: - cache: false # Set up with a finer-grained cache key below. go-version: ${{ matrix.go }} - - name: Check host - id: host - run: - printf 'platform=%s\n' "$(go env GOOS GOARCH | paste -s -d /)" >> "$GITHUB_OUTPUT" - - name: Cache go artifacts - uses: actions/cache@v4 - id: cache + cache: ${{ needs.setup.outputs.go-cache }} + - name: Assets Cache + id: assets + uses: actions/cache/restore@v4 with: - path: | - ~/.cache/go-build - ~/go/pkg/mod - key: ${{ runner.os }}-${{ matrix.platform }}-${{ matrix.go }}-${{ hashFiles('**/go.sum') }} + key: integration-assets-${{ hashFiles('go.sum') }} restore-keys: | - ${{ runner.os }}-${{ matrix.platform }}-${{ matrix.go }}- - - name: Warm the cache on miss - # This is faster to run outside the container, but the module cache - # can't easily be shared between runs because GOOS and GOARCH influence - # the dependency graph. - if: ${{ ! steps.cache.outputs.cache-hit }} + integration-assets- + path: | + ~/.cache/clair-testing + + - name: Tests + if: ${{ matrix.platform == needs.setup.outputs.runner-arch }} + env: + POSTGRES_CONNECTION_STRING: "host=localhost port=${{ job.services.postgres.ports[5432] }} user=clair dbname=clair password=password sslmode=disable" + RABBITMQ_CONNECTION_STRING: "amqp://clair:password@localhost:${{ job.services.rabbitmq.ports[5672] }}/" + STOMP_CONNECTION_STRING: "stomp://clair:password@localhost:${{ job.services.rabbitmq.ports[61613] }}/" + working-directory: ./${{ inputs.cd }} run: | - go env -w $(echo ${{ matrix.platform}} | awk -F / '{print "GOOS="$1, "GOARCH="$2}') - go mod download - go build -v std + # Go Tests + for expr in ${{ inputs.package_expr }}; do + printf '::group::go test %s\n' "$expr" + go test -tags integration "$expr" + printf '::endgroup::\n' + done + - name: Set up QEMU uses: docker/setup-qemu-action@v3 - if: ${{ steps.host.outputs.platform != matrix.platform }} + if: ${{ matrix.platform != needs.setup.outputs.runner-arch }} with: - platforms: ${{ matrix.platform }} - - name: Tests - run: >- - docker run - --rm - --network ${{ job.container.network }} - --platform ${{ matrix.platform }} - --mount "type=bind,src=$(go env GOMODCACHE),dst=/go/pkg/mod" - --mount "type=bind,src=$(go env GOCACHE),dst=/root/.cache/go-build" - --mount "type=bind,src=$(pwd),dst=/build" - --env "POSTGRES_CONNECTION_STRING=host=postgres port=5432 user=clair dbname=clair password=password sslmode=disable" - --env "RABBITMQ_CONNECTION_STRING=amqp://clair:password@rabbitmq:5672/" - --env "STOMP_CONNECTION_STRING=stomp://clair:password@rabbitmq:61613/" - -w "/build/${{ inputs.cd }}" - "quay.io/projectquay/golang:${{ matrix.go }}" - go test - -tags integration - ${{ inputs.package_expr }} + platforms: linux/${{ matrix.platform }} + - name: Qemu Tests + if: ${{ matrix.platform != needs.setup.outputs.runner-arch }} + env: + gover: ${{ steps.setup-go.outputs.go-version }} + run: | + # Go Tests + mkdir -p ~/.cache/clair-testing + docker run \ + --rm \ + --network ${{ job.container.network }} \ + --platform linux/${{ matrix.platform }} \ + --mount "type=bind,src=$(go env GOMODCACHE),dst=/go/pkg/mod" \ + --mount "type=bind,src=$(go env GOCACHE),dst=/root/.cache/go-build" \ + --mount "type=bind,src=${HOME}/.cache/clair-testing,dst=/root/.cache/clair-testing" \ + --mount "type=bind,src=$(pwd),dst=/build" \ + --env "POSTGRES_CONNECTION_STRING=host=postgres port=5432 user=clair dbname=clair password=password sslmode=disable" \ + --env "RABBITMQ_CONNECTION_STRING=amqp://clair:password@rabbitmq:5672/" \ + --env "STOMP_CONNECTION_STRING=stomp://clair:password@rabbitmq:61613/" \ + -w "/build/${{ inputs.cd }}" \ + "quay.io/projectquay/golang:${gover%.*}" \ + sh -c 'for expr in ${{ inputs.package_expr }}; do + printf '\''::group::go test %s\n'\'' "$expr" + go test -tags integration "$expr" + printf '\''::endgroup::\n'\'' + done;' + done