Fix every false positive, false negative, warning and error in CI/CD #39
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: go | |
| on: | |
| push: | |
| branches: main | |
| paths: | |
| - 'go/**' | |
| - '.github/workflows/go.yml' | |
| pull_request: | |
| paths: | |
| - 'go/**' | |
| - '.github/workflows/go.yml' | |
| workflow_dispatch: | |
| inputs: | |
| verbose: | |
| description: 'Print extra diagnostics (never secret values)' | |
| type: boolean | |
| required: false | |
| default: false | |
| permissions: | |
| contents: read | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| CI_VERBOSE: ${{ inputs.verbose && 'true' || vars.CI_VERBOSE || 'false' }} | |
| defaults: | |
| run: | |
| working-directory: go | |
| jobs: | |
| findChangedGoFiles: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-detect-changes | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| outputs: | |
| isGoFilesChanged: ${{ steps.setIsGoFilesChangedOutput.outputs.isGoFilesChanged }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| fetch-depth: 0 | |
| - name: Get changed files using defaults | |
| id: changed-files | |
| uses: tj-actions/changed-files@v47 | |
| - name: Set output isGoFilesChanged | |
| id: setIsGoFilesChangedOutput | |
| env: | |
| ALL_CHANGED_FILES: ${{ steps.changed-files.outputs.all_changed_files }} | |
| run: | | |
| isGoFilesChanged='false' | |
| # workflow_dispatch has no diff to inspect, so treat it as "changed". | |
| if [ "${{ github.event_name }}" = "workflow_dispatch" ]; then | |
| isGoFilesChanged='true' | |
| fi | |
| echo "Changed files: $ALL_CHANGED_FILES" | |
| for changedFile in $ALL_CHANGED_FILES; do | |
| if [[ $changedFile == go/* ]] || [[ $changedFile == .github/workflows/go.yml ]]; then | |
| isGoFilesChanged='true' | |
| break | |
| fi | |
| done | |
| echo "isGoFilesChanged=${isGoFilesChanged}" >> "$GITHUB_OUTPUT" | |
| echo "isGoFilesChanged: ${isGoFilesChanged}" | |
| lint: | |
| needs: [findChangedGoFiles] | |
| if: ${{ needs.findChangedGoFiles.outputs.isGoFilesChanged == 'true' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 10 | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-lint | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| submodules: true | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.21' | |
| # This module has no dependencies: go/go.sum does not exist and | |
| # go.mod has no require block. Pointing the dependency cache at a | |
| # file that is not there made setup-go emit "Restore cache failed: | |
| # Some specified paths were not resolved" on every run, in both | |
| # jobs. There is nothing to cache, so the cache is off. | |
| cache: false | |
| - name: Check formatting | |
| run: | | |
| gofmt_output=$(gofmt -l .) | |
| if [ -n "$gofmt_output" ]; then | |
| echo "The following files are not formatted correctly:" | |
| echo "$gofmt_output" | |
| exit 1 | |
| fi | |
| - name: Run go vet | |
| run: go vet ./... | |
| test: | |
| needs: [findChangedGoFiles, lint] | |
| if: ${{ needs.findChangedGoFiles.outputs.isGoFilesChanged == 'true' }} | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 20 | |
| concurrency: | |
| group: ${{ github.workflow }}-${{ github.ref }}-test | |
| cancel-in-progress: ${{ github.ref != 'refs/heads/main' }} | |
| env: | |
| # A step `if:` cannot read the secrets context, so the presence of the | |
| # token is exposed as job-level env for the coverage step below. | |
| CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| submodules: true | |
| - name: Setup Go | |
| uses: actions/setup-go@v6 | |
| with: | |
| go-version: '1.21' | |
| # This module has no dependencies: go/go.sum does not exist and | |
| # go.mod has no require block. Pointing the dependency cache at a | |
| # file that is not there made setup-go emit "Restore cache failed: | |
| # Some specified paths were not resolved" on every run, in both | |
| # jobs. There is nothing to cache, so the cache is off. | |
| cache: false | |
| - name: Build | |
| run: go build -v ./... | |
| - name: Test | |
| run: go test -v -race -coverprofile=coverage.out ./... | |
| # Codecov requires a token, and this repository has no CODECOV_TOKEN: | |
| # every run answered `Upload queued for processing failed: {"message": | |
| # "Token required - not valid tokenless upload"}` and the step still | |
| # reported success, because fail_ci_if_error was false. The job has been | |
| # claiming to publish coverage while publishing none. Run the step only | |
| # when the secret exists, and let a real upload failure fail the job. | |
| # This is the shape the csharp and python pipeline templates already use: | |
| # gate on the secret, pass it explicitly, and fail loudly when an upload | |
| # that should work does not. | |
| - name: Upload coverage | |
| if: env.CODECOV_TOKEN != '' | |
| uses: codecov/codecov-action@v7 | |
| with: | |
| token: ${{ env.CODECOV_TOKEN }} | |
| files: go/coverage.out | |
| # `files` narrows what is uploaded but does not stop the CLI | |
| # searching the workspace: it also found and uploaded | |
| # experiments/test_coverage_data.json under the `go` flag. | |
| disable_search: true | |
| flags: go | |
| fail_ci_if_error: true | |
| - name: Note that coverage was not uploaded | |
| if: env.CODECOV_TOKEN == '' | |
| run: echo "::notice::CODECOV_TOKEN is not configured, so coverage was not uploaded" | |
| publishRelease: | |
| runs-on: ubuntu-latest | |
| timeout-minutes: 15 | |
| needs: [findChangedGoFiles, test] | |
| if: ${{ needs.findChangedGoFiles.outputs.isGoFilesChanged == 'true' && github.event_name == 'push' && github.ref == 'refs/heads/main' }} | |
| concurrency: | |
| group: ${{ github.workflow }}-publish-go | |
| cancel-in-progress: false | |
| permissions: | |
| contents: write | |
| steps: | |
| - uses: actions/checkout@v7 | |
| with: | |
| persist-credentials: false | |
| submodules: true | |
| - name: Read the module version | |
| id: version-check | |
| run: | | |
| set -euo pipefail | |
| PACKAGE_VERSION=$(tr -d '[:space:]' < VERSION) | |
| if [ -z "$PACKAGE_VERSION" ]; then | |
| echo "::error::go/VERSION is empty" | |
| exit 1 | |
| fi | |
| MODULE=$(sed -n 's/^module //p' go.mod | head -1) | |
| echo "version=$PACKAGE_VERSION" >> "$GITHUB_OUTPUT" | |
| echo "module=$MODULE" >> "$GITHUB_OUTPUT" | |
| echo "Module: $MODULE@v$PACKAGE_VERSION" | |
| # Go has no registry to push to: the module proxy serves whatever the | |
| # tag points at, so creating the tag *is* the publish. That is why this | |
| # job both releases and verifies, unlike the other languages. | |
| - name: Create Go module tag and GitHub release | |
| id: publish | |
| env: | |
| GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} | |
| PACKAGE_VERSION: ${{ steps.version-check.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| # Go modules living in the go/ subdirectory require go/vX.Y.Z tags. | |
| TAG="go/v${PACKAGE_VERSION}" | |
| if gh release view "$TAG" >/dev/null 2>&1; then | |
| echo "Release $TAG already exists" | |
| echo "published=skipped" >> "$GITHUB_OUTPUT" | |
| exit 0 | |
| fi | |
| gh release create "$TAG" \ | |
| --title "[Go] $PACKAGE_VERSION" \ | |
| --notes "Go module release. Install with: \`go get github.com/link-foundation/links-notation/go@v$PACKAGE_VERSION\`" | |
| echo "published=true" >> "$GITHUB_OUTPUT" | |
| - name: Verify the module is really on proxy.golang.org | |
| if: steps.publish.outputs.published == 'true' | |
| env: | |
| MODULE: ${{ steps.version-check.outputs.module }} | |
| PACKAGE_VERSION: ${{ steps.version-check.outputs.version }} | |
| run: | | |
| set -euo pipefail | |
| # The proxy only fetches a version once someone asks for it, so this | |
| # also warms it for the first consumer. | |
| for attempt in $(seq 1 20); do | |
| if curl -fsS "https://proxy.golang.org/${MODULE}/@v/v${PACKAGE_VERSION}.info" >/dev/null 2>&1; then | |
| echo "Verified ${MODULE}@v${PACKAGE_VERSION} on proxy.golang.org (attempt ${attempt})" | |
| exit 0 | |
| fi | |
| echo "Not on the proxy yet, retrying in 15s (attempt ${attempt}/20)" | |
| sleep 15 | |
| done | |
| echo "::error::${MODULE}@v${PACKAGE_VERSION} did not appear on proxy.golang.org within 5 minutes" | |
| exit 1 |