# This is our main "CI tests" workflow. It runs everything that should run on # both PRs and merged commits, and for the latter reports failures to slack. name: CI env: # Our fuzz job, powered by OSS-Fuzz, fails periodically because we upgrade to # new Go versions very eagerly. OSS-Fuzz is a little more conservative, and # ends up being unable to compile our code. # # When this happens, we want to disable the fuzz target until OSS-Fuzz catches # up. However, we also don't want to forget to turn it back on when OSS-Fuzz # can once again build our code. # # This variable toggles the fuzz job between two modes: # - false: we expect fuzzing to be happy, and should report failure if it's not. # - true: we expect fuzzing is broken, and should report failure if it start working. TS_FUZZ_CURRENTLY_BROKEN: false # GOMODCACHE is the same definition on all OSes. Within the workspace, we use # toplevel directories "src" (for the checked out source code), and "gomodcache" # and other caches as siblings to follow. GOMODCACHE: ${{ github.workspace }}/gomodcache on: push: branches: - "main" - "release-branch/*" pull_request: # all PRs on all branches merge_group: branches: - "main" concurrency: # For PRs, later CI runs preempt previous ones. e.g. a force push on a PR # cancels running CI jobs and starts all new ones. # # For non-PR pushes, concurrency.group needs to be unique for every distinct # CI run we want to have happen. Use run_id, which in practice means all # non-PR CI runs will be allowed to run without preempting each other. group: ${{ github.workflow }}-$${{ github.pull_request.number || github.run_id }} cancel-in-progress: true jobs: gomod-cache: runs-on: ubuntu-24.04 outputs: cache-key: ${{ steps.hash.outputs.key }} steps: - name: Checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Compute cache key from go.{mod,sum} id: hash run: echo "key=gomod-cross3-${{ hashFiles('src/go.mod', 'src/go.sum') }}" >> $GITHUB_OUTPUT # See if the cache entry already exists to avoid downloading it # and doing the cache write again. - id: check-cache uses: actions/cache/restore@v4 with: path: gomodcache # relative to workspace; see env note at top of file key: ${{ steps.hash.outputs.key }} lookup-only: true enableCrossOsArchive: true - name: Download modules if: steps.check-cache.outputs.cache-hit != 'true' working-directory: src run: go mod download - name: Cache Go modules if: steps.check-cache.outputs.cache-hit != 'true' uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache # relative to workspace; see env note at top of file key: ${{ steps.hash.outputs.key }} enableCrossOsArchive: true race-root-integration: runs-on: ubuntu-24.04 needs: gomod-cache strategy: fail-fast: false # don't abort the entire matrix if one element fails matrix: include: - shard: '1/4' - shard: '2/4' - shard: '3/4' - shard: '4/4' steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: build test wrapper working-directory: src run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper - name: integration tests as root working-directory: src run: PATH=$PWD/tool:$PATH /tmp/testwrapper -exec "sudo -E" -race ./tstest/integration/ env: TS_TEST_SHARD: ${{ matrix.shard }} test: strategy: fail-fast: false # don't abort the entire matrix if one element fails matrix: include: - goarch: amd64 - goarch: amd64 buildflags: "-race" shard: '1/3' - goarch: amd64 buildflags: "-race" shard: '2/3' - goarch: amd64 buildflags: "-race" shard: '3/3' - goarch: "386" # thanks yaml runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # Note: unlike the other setups, this is only grabbing the mod download # cache, rather than the whole mod directory, as the download cache # contains zips that can be unpacked in parallel faster than they can be # fetched and extracted by tar path: | ~/.cache/go-build ~\AppData\Local\go-build # The -2- here should be incremented when the scheme of data to be # cached changes (e.g. path above changes). key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} restore-keys: | ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2-${{ hashFiles('**/go.sum') }} ${{ github.job }}-${{ runner.os }}-${{ matrix.goarch }}-${{ matrix.buildflags }}-go-2- - name: build all if: matrix.buildflags == '' # skip on race builder working-directory: src run: ./tool/go build ${{matrix.buildflags}} ./... env: GOARCH: ${{ matrix.goarch }} - name: build variant CLIs if: matrix.buildflags == '' # skip on race builder working-directory: src run: | ./build_dist.sh --extra-small ./cmd/tailscaled ./build_dist.sh --box ./cmd/tailscaled ./build_dist.sh --extra-small --box ./cmd/tailscaled rm -f tailscaled env: GOARCH: ${{ matrix.goarch }} - name: get qemu # for tstest/archtest if: matrix.goarch == 'amd64' && matrix.buildflags == '' run: | sudo apt-get -y update sudo apt-get -y install qemu-user - name: build test wrapper working-directory: src run: ./tool/go build -o /tmp/testwrapper ./cmd/testwrapper - name: test all working-directory: src run: NOBASHDEBUG=true PATH=$PWD/tool:$PATH /tmp/testwrapper ./... ${{matrix.buildflags}} env: GOARCH: ${{ matrix.goarch }} TS_TEST_SHARD: ${{ matrix.shard }} - name: bench all working-directory: src run: ./tool/go test ${{matrix.buildflags}} -bench=. -benchtime=1x -run=^$ $(for x in $(git grep -l "^func Benchmark" | xargs dirname | sort | uniq); do echo "./$x"; done) env: GOARCH: ${{ matrix.goarch }} - name: check that no tracked files changed working-directory: src run: git diff --no-ext-diff --name-only --exit-code || (echo "Build/test modified the files above."; exit 1) - name: check that no new files were added working-directory: src run: | # Note: The "error: pathspec..." you see below is normal! # In the success case in which there are no new untracked files, # git ls-files complains about the pathspec not matching anything. # That's OK. It's not worth the effort to suppress. Please ignore it. if git ls-files --others --exclude-standard --directory --no-empty-directory --error-unmatch -- ':/*' then echo "Build/test created untracked files in the repo (file names above)." exit 1 fi - name: Tidy cache working-directory: src shell: bash run: | find $(go env GOCACHE) -type f -mmin +90 -delete windows: # windows-8vpu is a 2022 GitHub-managed runner in our # org with 8 cores and 32 GB of RAM: # https://github.com/organizations/tailscale/settings/actions/github-hosted-runners/1 runs-on: windows-8vcpu needs: gomod-cache name: Windows (${{ matrix.name || matrix.shard}}) strategy: fail-fast: false # don't abort the entire matrix if one element fails matrix: include: - key: "win-bench" name: "benchmarks" - key: "win-tool-go" name: "./tool/go" - key: "win-shard-1-2" shard: "1/2" - key: "win-shard-2-2" shard: "2/2" steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Install Go if: matrix.key != 'win-tool-go' uses: actions/setup-go@d35c59abb061a4a6fb18e82ac0862c26744d6ab5 # v5.5.0 with: go-version-file: src/go.mod cache: false - name: Restore Go module cache if: matrix.key != 'win-tool-go' uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Restore Cache if: matrix.key != 'win-tool-go' uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: | ~/.cache/go-build ~\AppData\Local\go-build # The -2- here should be incremented when the scheme of data to be # cached changes (e.g. path above changes). key: ${{ github.job }}-${{ matrix.key }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} restore-keys: | ${{ github.job }}-${{ matrix.key }}-go-2-${{ hashFiles('**/go.sum') }} ${{ github.job }}-${{ matrix.key }}-go-2- - name: test-tool-go if: matrix.key == 'win-tool-go' working-directory: src run: ./tool/go version - name: test if: matrix.key != 'win-bench' && matrix.key != 'win-tool-go' # skip on bench builder working-directory: src run: go run ./cmd/testwrapper sharded:${{ matrix.shard }} - name: bench all if: matrix.key == 'win-bench' working-directory: src # Don't use -bench=. -benchtime=1x. # Somewhere in the layers (powershell?) # the equals signs cause great confusion. run: go test ./... -bench . -benchtime 1x -run "^$" - name: Tidy cache if: matrix.key != 'win-tool-go' working-directory: src shell: bash run: | find $(go env GOCACHE) -type f -mmin +90 -delete privileged: needs: gomod-cache runs-on: ubuntu-24.04 container: image: golang:latest options: --privileged steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: chown working-directory: src run: chown -R $(id -u):$(id -g) $PWD - name: privileged tests working-directory: src run: ./tool/go test ./util/linuxfw ./derp/xdp vm: needs: gomod-cache runs-on: ["self-hosted", "linux", "vm"] # VM tests run with some privileges, don't let them run on 3p PRs. if: github.repository == 'tailscale/tailscale' steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: Run VM tests working-directory: src run: ./tool/go test ./tstest/integration/vms -v -no-s3 -run-vm-tests -run=TestRunUbuntu2004 env: HOME: "/var/lib/ghrunner/home" TMPDIR: "/tmp" XDG_CACHE_HOME: "/var/lib/ghrunner/cache" cross: # cross-compile checks, build only. needs: gomod-cache strategy: fail-fast: false # don't abort the entire matrix if one element fails matrix: include: # Note: linux/amd64 is not in this matrix, because that goos/goarch is # tested more exhaustively in the 'test' job above. - goos: linux goarch: arm64 - goos: linux goarch: "386" # thanks yaml - goos: linux goarch: loong64 - goos: linux goarch: arm goarm: "5" - goos: linux goarch: arm goarm: "7" # macOS - goos: darwin goarch: amd64 - goos: darwin goarch: arm64 # Windows - goos: windows goarch: amd64 - goos: windows goarch: arm64 # BSDs - goos: freebsd goarch: amd64 - goos: openbsd goarch: amd64 runs-on: ubuntu-24.04 steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Cache uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # Note: unlike the other setups, this is only grabbing the mod download # cache, rather than the whole mod directory, as the download cache # contains zips that can be unpacked in parallel faster than they can be # fetched and extracted by tar path: | ~/.cache/go-build ~\AppData\Local\go-build # The -2- here should be incremented when the scheme of data to be # cached changes (e.g. path above changes). key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} restore-keys: | ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }} ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2- - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: build all working-directory: src run: ./tool/go build ./cmd/... env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} CGO_ENABLED: "0" - name: build tests working-directory: src run: ./tool/go test -exec=true ./... env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} CGO_ENABLED: "0" - name: Tidy cache working-directory: src shell: bash run: | find $(go env GOCACHE) -type f -mmin +90 -delete ios: # similar to cross above, but iOS can't build most of the repo. So, just # make it build a few smoke packages. runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: build some working-directory: src run: ./tool/go build ./ipn/... ./ssh/tailssh ./wgengine/ ./types/... ./control/controlclient env: GOOS: ios GOARCH: arm64 crossmin: # cross-compile for platforms where we only check cmd/tailscale{,d} needs: gomod-cache strategy: fail-fast: false # don't abort the entire matrix if one element fails matrix: include: # Plan9 - goos: plan9 goarch: amd64 # AIX - goos: aix goarch: ppc64 # Solaris - goos: solaris goarch: amd64 # illumos - goos: illumos goarch: amd64 runs-on: ubuntu-24.04 steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Cache uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # Note: unlike the other setups, this is only grabbing the mod download # cache, rather than the whole mod directory, as the download cache # contains zips that can be unpacked in parallel faster than they can be # fetched and extracted by tar path: | ~/.cache/go-build ~\AppData\Local\go-build # The -2- here should be incremented when the scheme of data to be # cached changes (e.g. path above changes). key: ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} restore-keys: | ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2-${{ hashFiles('**/go.sum') }} ${{ github.job }}-${{ runner.os }}-${{ matrix.goos }}-${{ matrix.goarch }}-go-2- - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: build core working-directory: src run: ./tool/go build ./cmd/tailscale ./cmd/tailscaled env: GOOS: ${{ matrix.goos }} GOARCH: ${{ matrix.goarch }} GOARM: ${{ matrix.goarm }} CGO_ENABLED: "0" - name: Tidy cache working-directory: src shell: bash run: | find $(go env GOCACHE) -type f -mmin +90 -delete android: # similar to cross above, but android fails to build a few pieces of the # repo. We should fix those pieces, they're small, but as a stepping stone, # only test the subset of android that our past smoke test checked. runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src # Super minimal Android build that doesn't even use CGO and doesn't build everything that's needed # and is only arm64. But it's a smoke build: it's not meant to catch everything. But it'll catch # some Android breakages early. # TODO(bradfitz): better; see https://github.com/tailscale/tailscale/issues/4482 - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: build some working-directory: src run: ./tool/go install ./net/netns ./ipn/ipnlocal ./wgengine/magicsock/ ./wgengine/ ./wgengine/router/ ./wgengine/netstack ./util/dnsname/ ./ipn/ ./net/netmon ./wgengine/router/ ./tailcfg/ ./types/logger/ ./net/dns ./hostinfo ./version ./ssh/tailssh env: GOOS: android GOARCH: arm64 wasm: # builds tsconnect, which is the only wasm build we support runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Cache uses: actions/cache@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: # Note: unlike the other setups, this is only grabbing the mod download # cache, rather than the whole mod directory, as the download cache # contains zips that can be unpacked in parallel faster than they can be # fetched and extracted by tar path: | ~/.cache/go-build ~\AppData\Local\go-build # The -2- here should be incremented when the scheme of data to be # cached changes (e.g. path above changes). key: ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }}-${{ github.run_id }} restore-keys: | ${{ github.job }}-${{ runner.os }}-go-2-${{ hashFiles('**/go.sum') }} ${{ github.job }}-${{ runner.os }}-go-2- - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: build tsconnect client working-directory: src run: ./tool/go build ./cmd/tsconnect/wasm ./cmd/tailscale/cli env: GOOS: js GOARCH: wasm - name: build tsconnect server working-directory: src # Note, no GOOS/GOARCH in env on this build step, we're running a build # tool that handles the build itself. run: | ./tool/go run ./cmd/tsconnect --fast-compression build ./tool/go run ./cmd/tsconnect --fast-compression build-pkg - name: Tidy cache working-directory: src shell: bash run: | find $(go env GOCACHE) -type f -mmin +90 -delete tailscale_go: # Subset of tests that depend on our custom Go toolchain. runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: test tailscale_go run: ./tool/go test -tags=tailscale_go,ts_enable_sockstats ./net/sockstats/... fuzz: # This target periodically breaks (see TS_FUZZ_CURRENTLY_BROKEN at the top # of the file), so it's more complex than usual: the 'build fuzzers' step # might fail, and depending on the value of 'TS_FUZZ_CURRENTLY_BROKEN', that # might or might not be fine. The steps after the build figure out whether # the success/failure is expected, and appropriately pass/fail the job # overall accordingly. # # Practically, this means that all steps after 'build fuzzers' must have an # explicit 'if' condition, because the default condition for steps is # 'success()', meaning "only run this if no previous steps failed". if: github.event_name == 'pull_request' runs-on: ubuntu-24.04 steps: - name: build fuzzers id: build uses: google/oss-fuzz/infra/cifuzz/actions/build_fuzzers@master # continue-on-error makes steps.build.conclusion be 'success' even if # steps.build.outcome is 'failure'. This means this step does not # contribute to the job's overall pass/fail evaluation. continue-on-error: true with: oss-fuzz-project-name: 'tailscale' dry-run: false language: go - name: report unexpectedly broken fuzz build if: steps.build.outcome == 'failure' && env.TS_FUZZ_CURRENTLY_BROKEN != 'true' run: | echo "fuzzer build failed, see above for why" echo "if the failure is due to OSS-Fuzz not being on the latest Go yet," echo "set TS_FUZZ_CURRENTLY_BROKEN=true in .github/workflows/test.yml" echo "to temporarily disable fuzzing until OSS-Fuzz works again." exit 1 - name: report unexpectedly working fuzz build if: steps.build.outcome == 'success' && env.TS_FUZZ_CURRENTLY_BROKEN == 'true' run: | echo "fuzzer build succeeded, but we expect it to be broken" echo "please set TS_FUZZ_CURRENTLY_BROKEN=false in .github/workflows/test.yml" echo "to reenable fuzz testing" exit 1 - name: run fuzzers id: run # Run the fuzzers whenever they're able to build, even if we're going to # report a failure because TS_FUZZ_CURRENTLY_BROKEN is set to the wrong # value. if: steps.build.outcome == 'success' uses: google/oss-fuzz/infra/cifuzz/actions/run_fuzzers@master with: oss-fuzz-project-name: 'tailscale' fuzz-seconds: 150 dry-run: false language: go - name: Set artifacts_path in env (workaround for actions/upload-artifact#176) if: steps.run.outcome != 'success' && steps.build.outcome == 'success' run: | echo "artifacts_path=$(realpath .)" >> $GITHUB_ENV - name: upload crash uses: actions/upload-artifact@ea165f8d65b6e75b540449e92b4886f43607fa02 # v4.6.2 if: steps.run.outcome != 'success' && steps.build.outcome == 'success' with: name: artifacts path: ${{ env.artifacts_path }}/out/artifacts depaware: runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Set GOMODCACHE env run: echo "GOMODCACHE=$HOME/.cache/go-mod" >> $GITHUB_ENV - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: check depaware working-directory: src run: make depaware go_generate: runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: check that 'go generate' is clean working-directory: src run: | pkgs=$(./tool/go list ./... | grep -Ev 'dnsfallback|k8s-operator|xdp') ./tool/go generate $pkgs echo echo git diff --name-only --exit-code || (echo "The files above need updating. Please run 'go generate'."; exit 1) go_mod_tidy: runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: check that 'go mod tidy' is clean working-directory: src run: | ./tool/go mod tidy echo echo git diff --name-only --exit-code || (echo "Please run 'go mod tidy'."; exit 1) licenses: runs-on: ubuntu-24.04 needs: gomod-cache steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: check licenses working-directory: src run: | grep -q TestLicenseHeaders *.go || (echo "Expected a test named TestLicenseHeaders"; exit 1) ./tool/go test -v -run=TestLicenseHeaders staticcheck: runs-on: ubuntu-24.04 needs: gomod-cache name: staticcheck (${{ matrix.name }}) strategy: fail-fast: false # don't abort the entire matrix if one element fails matrix: include: - name: "macOS" goos: "darwin" goarch: "arm64" flags: "--with-tags-all=darwin" - name: "Windows" goos: "windows" goarch: "amd64" flags: "--with-tags-all=windows" - name: "Linux" goos: "linux" goarch: "amd64" flags: "--with-tags-all=linux" - name: "Portable (1/4)" goos: "linux" goarch: "amd64" flags: "--without-tags-any=windows,darwin,linux --shard=1/4" - name: "Portable (2/4)" goos: "linux" goarch: "amd64" flags: "--without-tags-any=windows,darwin,linux --shard=2/4" - name: "Portable (3/4)" goos: "linux" goarch: "amd64" flags: "--without-tags-any=windows,darwin,linux --shard=3/4" - name: "Portable (4/4)" goos: "linux" goarch: "amd64" flags: "--without-tags-any=windows,darwin,linux --shard=4/4" steps: - name: checkout uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2 with: path: src - name: Restore Go module cache uses: actions/cache/restore@5a3ec84eff668545956fd18022155c47e93e2684 # v4.2.3 with: path: gomodcache key: ${{ needs.gomod-cache.outputs.cache-key }} enableCrossOsArchive: true - name: run staticcheck (${{ matrix.name }}) working-directory: src run: | export GOROOT=$(./tool/go env GOROOT) ./tool/go run -exec \ "env GOOS=${{ matrix.goos }} GOARCH=${{ matrix.goarch }}" \ honnef.co/go/tools/cmd/staticcheck -- \ $(./tool/go run ./tool/listpkgs --ignore-3p --goos=${{ matrix.goos }} --goarch=${{ matrix.goarch }} ${{ matrix.flags }} ./...) notify_slack: if: always() # Any of these jobs failing causes a slack notification. needs: - android - test - windows - vm - cross - ios - wasm - tailscale_go - fuzz - depaware - go_generate - go_mod_tidy - licenses - staticcheck runs-on: ubuntu-24.04 steps: - name: notify # Only notify slack for merged commits, not PR failures. # # It may be tempting to move this condition into the job's 'if' block, but # don't: Github only collapses the test list into "everything is OK" if # all jobs succeeded. A skipped job results in the list staying expanded. # By having the job always run, but skipping its only step as needed, we # let the CI output collapse nicely in PRs. if: failure() && github.event_name == 'push' uses: slackapi/slack-github-action@91efab103c0de0a537f72a35f6b8cda0ee76bf0a # v2.1.1 with: webhook: ${{ secrets.SLACK_WEBHOOK_URL }} webhook-type: incoming-webhook payload: | { "attachments": [{ "title": "Failure: ${{ github.workflow }}", "title_link": "https://github.com/${{ github.repository }}/commit/${{ github.sha }}/checks", "text": "${{ github.repository }}@${{ github.ref_name }}: ", "fields": [{ "value": ${{ toJson(github.event.head_commit.message) }}, "short": false }], "footer": "${{ github.event.head_commit.committer.name }} at ${{ github.event.head_commit.timestamp }}", "color": "danger" }] } merge_blocker: if: always() runs-on: ubuntu-24.04 needs: - android - test - windows - vm - cross - ios - wasm - tailscale_go - fuzz - depaware - go_generate - go_mod_tidy - licenses - staticcheck steps: - name: Decide if change is okay to merge if: github.event_name != 'push' uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} # This waits on all the jobs which must never fail. Branch protection rules # enforce these. No flaky tests are allowed in these jobs. (We don't want flaky # tests anywhere, really, but a flaky test here prevents merging.) check_mergeability_strict: if: always() runs-on: ubuntu-24.04 needs: - android - cross - crossmin - ios - tailscale_go - depaware - go_generate - go_mod_tidy - licenses - staticcheck steps: - name: Decide if change is okay to merge if: github.event_name != 'push' uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }} check_mergeability: if: always() runs-on: ubuntu-24.04 needs: - check_mergeability_strict - test - windows - vm - wasm - fuzz - race-root-integration - privileged steps: - name: Decide if change is okay to merge if: github.event_name != 'push' uses: re-actors/alls-green@05ac9388f0aebcb5727afa17fcccfecd6f8ec5fe # v1.2.2 with: jobs: ${{ toJSON(needs) }}