name: Pull Request Validation on: pull_request: types: - opened - synchronize - reopened jobs: validate: runs-on: ubuntu-latest container: docker.io/catthehacker/ubuntu:act-latest defaults: run: shell: bash env: ARTEFACT_BUCKET_NAME: ${{ vars.ARTEFACT_BUCKET_NAME }} ARTEFACT_BUCKET_ENDPONT: ${{ vars.ARTEFACT_BUCKET_ENDPONT }} ARTEFACT_BUCKET_REGION: ${{ vars.ARTEFACT_BUCKET_REGION }} AWS_ACCESS_KEY_ID: ${{ secrets.ARTEFACT_BUCKET_WRITE_ACCESS_KEY }} AWS_SECRET_ACCESS_KEY: ${{ secrets.ARTEFACT_BUCKET_WRITE_ACCESS_SECRET }} AWS_DEFAULT_REGION: ${{ vars.ARTEFACT_BUCKET_REGION }} AWS_EC2_METADATA_DISABLED: true SUMMARY_FILE: ${{ runner.temp }}/summary.md steps: - name: Checkout uses: actions/checkout@v4 - name: Setup Go uses: actions/setup-go@v5 with: go-version: '1.26.1' check-latest: true cache: true cache-dependency-path: go.sum - name: Cache Go modules and build cache uses: actions/cache@v4 with: path: | ~/go/pkg/mod ~/.cache/go-build ~/go/bin key: ${{ runner.os }}-go-cache-${{ hashFiles('**/go.mod', '**/go.sum') }} restore-keys: | ${{ runner.os }}-go-cache- - name: Verify module hygiene run: | set -euo pipefail go mod tidy git diff --exit-code go.mod go.sum go mod verify - name: Install security tools run: | set -euo pipefail go install github.com/securego/gosec/v2/cmd/gosec@v2.22.3 go install golang.org/x/vuln/cmd/govulncheck@v1.1.4 - name: Install AWS CLI v2 uses: ankurk91/install-aws-cli-action@v1 - name: Ensure tooling is available run: | set -euo pipefail aws --version if ! command -v jq >/dev/null 2>&1; then apt-get update apt-get install -y jq fi - name: Prepare test runtime run: | set -euo pipefail apt-get update apt-get install -y ruby git config --global user.name "gitea-actions[bot]" git config --global user.email "gitea-actions[bot]@users.noreply.local" - name: Run full unit test suite with coverage id: coverage run: | set -euo pipefail go test -covermode=atomic -coverprofile=coverage.out ./... | tee go-test-coverage.log go tool cover -html=coverage.out -o coverage.html total="$(go tool cover -func=coverage.out | awk '/^total:/ {sub(/%/, "", $3); print $3}')" printf '{\n "total": "%s"\n}\n' "$total" > coverage-summary.json printf 'total=%s\n' "$total" >> "$GITHUB_OUTPUT" set +e awk ' /^ok[[:space:]]/ && /coverage: [0-9.]+% of statements/ { pkg = $2 cov = $0 sub(/^.*coverage: /, "", cov) sub(/% of statements.*$/, "", cov) status = "target" if (cov + 0 < 50) { status = "fail" fail = 1 } else if (cov + 0 < 65) { status = "high-risk" } else if (cov + 0 < 80) { status = "warning" } printf "%s %.1f %s\n", pkg, cov + 0, status } END { if (fail) { exit 2 } } ' go-test-coverage.log > coverage-packages.raw package_gate_status=$? set -e { echo '| Package | Coverage | Status |' echo '| --- | ---: | --- |' } > coverage-packages.md while read -r pkg cov status; do case "$status" in fail) pretty='FAIL (<50%)' ;; high-risk) pretty='High risk (50%-64.99%)' ;; warning) pretty='Warning (65%-79.99%)' ;; *) pretty='Target (>=80%)' ;; esac printf '| `%s` | %.1f%% | %s |\n' "$pkg" "$cov" "$pretty" >> coverage-packages.md done < coverage-packages.raw if [[ "$package_gate_status" -ne 0 ]]; then echo "Per-package coverage gate failed: one or more packages are below 50%." >&2 exit 1 fi - name: Check code formatting run: | set -euo pipefail fmt_output=$(go fmt ./...) if [[ -n "$fmt_output" ]]; then echo "Code formatting check failed. The following files need formatting:" >&2 echo "$fmt_output" >&2 exit 1 fi - name: Run Gosec Security Scanner uses: secureCodeBox/gosec-action@v1 with: args: './...' - name: Run Go Vulnerability Check uses: golang/govulncheck-action@v1 - name: Generate coverage badge env: COVERAGE_TOTAL: ${{ steps.coverage.outputs.total }} run: | set -euo pipefail color="$(awk -v total="$COVERAGE_TOTAL" 'BEGIN { if (total >= 80) print "brightgreen"; else if (total >= 70) print "green"; else if (total >= 60) print "yellowgreen"; else if (total >= 50) print "yellow"; else print "red"; }')" cat > coverage-badge.svg < coverage coverage ${COVERAGE_TOTAL}% ${COVERAGE_TOTAL}% EOF - name: Upload PR coverage artefacts id: upload run: | set -euo pipefail aws configure set default.s3.addressing_style path repo_name="${GITHUB_REPOSITORY##*/}" prefix="${repo_name}/pull-requests/${{ github.event.pull_request.number }}" report_url="${ARTEFACT_BUCKET_ENDPONT%/}/${ARTEFACT_BUCKET_NAME}/${prefix}/coverage.html" badge_url="${ARTEFACT_BUCKET_ENDPONT%/}/${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-badge.svg" aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage.html "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage.html" --content-type text/html aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage-badge.svg "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-badge.svg" --content-type image/svg+xml aws --endpoint-url "${ARTEFACT_BUCKET_ENDPONT}" s3 cp coverage-summary.json "s3://${ARTEFACT_BUCKET_NAME}/${prefix}/coverage-summary.json" --content-type application/json printf 'report_url=%s\n' "$report_url" >> "$GITHUB_OUTPUT" printf 'badge_url=%s\n' "$badge_url" >> "$GITHUB_OUTPUT" - name: Comment coverage report on pull request env: COVERAGE_BADGE_URL: ${{ steps.upload.outputs.badge_url }} COVERAGE_REPORT_URL: ${{ steps.upload.outputs.report_url }} COVERAGE_TOTAL: ${{ steps.coverage.outputs.total }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} run: | set -euo pipefail marker='' api_base="${GITHUB_SERVER_URL}/api/v1/repos/${GITHUB_REPOSITORY}" payload="$(jq -n \ --arg marker "$marker" \ --arg total "$COVERAGE_TOTAL" \ --arg report "$COVERAGE_REPORT_URL" \ --arg badge "$COVERAGE_BADGE_URL" \ '{body: ($marker + "\n## Coverage Report\n\nCoverage total: **" + $total + "%**\n\n[HTML report](" + $report + ")\n\n![Coverage badge](" + $badge + ")")}')" comments="$(curl -sS -H "Authorization: token ${GITHUB_TOKEN}" "${api_base}/issues/${{ github.event.pull_request.number }}/comments")" comment_id="$(printf '%s' "$comments" | jq -r '.[] | select(.body | contains("")) | .id' | tail -n 1)" if [[ -n "$comment_id" ]]; then curl -sS -X PATCH \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H 'Content-Type: application/json' \ -d "$payload" \ "${api_base}/issues/comments/${comment_id}" >/dev/null else curl -sS -X POST \ -H "Authorization: token ${GITHUB_TOKEN}" \ -H 'Content-Type: application/json' \ -d "$payload" \ "${api_base}/issues/${{ github.event.pull_request.number }}/comments" >/dev/null fi - name: Add coverage summary run: | { echo '## Coverage' echo echo '- Total: `${{ steps.coverage.outputs.total }}%`' echo '- Report: ${{ steps.upload.outputs.report_url }}' echo '- Badge: ${{ steps.upload.outputs.badge_url }}' echo echo '### Package Coverage' cat coverage-packages.md } >> "$SUMMARY_FILE" - name: Run behavior suite run: ./script/run-behavior-suite-docker.sh - name: Summary if: ${{ always() }} run: | if [[ -f "$SUMMARY_FILE" ]]; then cat "$SUMMARY_FILE" >> "$GITHUB_STEP_SUMMARY" fi