Thanks to visit codestin.com
Credit goes to github.com

Skip to content

feat: gate the OAuth2 provider on CODER_OAUTH2_PROVIDER_ENABLE #103037

feat: gate the OAuth2 provider on CODER_OAUTH2_PROVIDER_ENABLE

feat: gate the OAuth2 provider on CODER_OAUTH2_PROVIDER_ENABLE #103037

Workflow file for this run

name: ci
on:
push:
branches:
- main
- release/*
# GitHub Actions does not reliably trigger push-based CI when a new
# branch is created at a commit that already has a workflow run (e.g.
# from main). The create event fires separately and ensures CI runs
# on newly cut release branches. Non-release branch creations are
# filtered out by the changes job condition.
create:
pull_request:
workflow_dispatch:
permissions:
contents: read
# Cancel in-progress runs for pull requests when developers push
# additional changes
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: ${{ github.ref != 'refs/heads/main' }}
jobs:
changes:
runs-on: ubuntu-latest
# For create events, only run on release branches to avoid
# triggering CI for every feature branch creation.
if: |
github.event_name != 'create' || (
github.event.ref_type == 'branch' &&
startsWith(github.event.ref, 'release/')
)
outputs:
docs-only: ${{ steps.filter.outputs.docs_count == steps.filter.outputs.all_count }}
docs: ${{ steps.filter.outputs.docs }}
go: ${{ steps.filter.outputs.go }}
site: ${{ steps.filter.outputs.site }}
k8s: ${{ steps.filter.outputs.k8s }}
ci: ${{ steps.filter.outputs.ci }}
db: ${{ steps.filter.outputs.db }}
gomod: ${{ steps.filter.outputs.gomod }}
offlinedocs-only: ${{ steps.filter.outputs.offlinedocs_count == steps.filter.outputs.all_count }}
offlinedocs: ${{ steps.filter.outputs.offlinedocs }}
tailnet-integration: ${{ steps.filter.outputs.tailnet-integration }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: check changed files
uses: dorny/paths-filter@ceb8a2b8f2d89434be7ff52d3de7ec3738c5cc9d # v4.0.3
id: filter
with:
filters: |
all:
- "**"
docs:
- "docs/**"
- ".claude/docs/**"
- "*.md"
- "examples/web-server/**"
- "examples/monitoring/**"
- "examples/lima/**"
db:
- "**.sql"
- "coderd/database/**"
go:
- "**.sql"
- "**.go"
- "**.golden"
- "go.mod"
- "go.sum"
# Other non-Go files that may affect Go code:
- "**.rego"
- "**.sh"
- "**.tpl"
- "**.gotmpl"
- "**.gotpl"
- "Makefile"
- "site/static/error.html"
# Icon and theme files tested by Go (scripts/gensite):
- "site/static/icon/**"
- "site/src/theme/**"
# Main repo directories for completeness in case other files are
# touched:
- "agent/**"
- "aibridge/**"
- "cli/**"
- "cmd/**"
- "coderd/**"
- "enterprise/**"
- "examples/**"
- "helm/**"
- "provisioner/**"
- "provisionerd/**"
- "provisionersdk/**"
- "pty/**"
- "scaletest/**"
- "tailnet/**"
- "testutil/**"
gomod:
- "go.mod"
- "go.sum"
site:
- "site/**"
k8s:
- "helm/**"
- "scripts/Dockerfile"
- "scripts/Dockerfile.base"
- "scripts/helm.sh"
ci:
- ".github/actions/**"
- ".github/workflows/**"
offlinedocs:
- "offlinedocs/**"
tailnet-integration:
- "tailnet/**"
- "go.mod"
- "go.sum"
- id: debug
run: |
echo "$FILTER_JSON"
env:
FILTER_JSON: ${{ toJSON(steps.filter.outputs) }}
lint-docs:
needs: changes
if: needs.changes.outputs.docs == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
runs-on: ubuntu-latest
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "node pnpm"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- name: Check docs
run: pnpm check-docs
# Vale prose linter, advisory only. Scoped to changed Markdown under
# docs/. Every Vale step is `continue-on-error` so this section can
# never block the required `lint-docs` job: a `vale sync` network
# blip or a baseline rule violation surfaces as an annotation, not a
# merge gate. Only markdownlint/table-formatter above stay blocking.
# `vale --no-exit` additionally keeps the baseline error count from
# un-overridden upstream Google rules from failing the step. Lives
# here rather than a standalone workflow so docs lint stays in the
# single required CI umbrella (see #25608). See DOCS-40.
- name: Detect changed Markdown
id: changed-md
continue-on-error: true
uses: tj-actions/changed-files@9426d40962ed5378910ee2e21d5f8c6fcbf2dd96 # v45.0.7
with:
files: |
**.md
separator: ","
# `**.md` (not `docs/**.md`) because the action's globber collapses a
# `**` adjacent to `.md` to a single path segment, so `docs/**.md`
# only matches top-level docs/*.md and misses nested pages such as
# docs/.style/style-guide/README.md. The prose step below re-filters to
# docs/ paths.
# Cache split into restore + conditional save to avoid letting PR
# runs populate a cache that other branches restore from (the
# zizmor `cache-poisoning` concern). Only pushes to the default
# branch may write the cache; PRs may only read it.
- name: Restore Vale styles
id: vale-cache
if: steps.changed-md.outputs.any_changed == 'true'
continue-on-error: true
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
# Negation excludes the hand-authored Coder rules from the cache;
# the key also hashes them so a rule change always invalidates it.
# mise manages the Vale binary, so only the synced styles cache.
path: |
docs/.style/styles/*
!docs/.style/styles/Coder
docs/.style/.vale-synced
key: vale-${{ hashFiles('.vale.ini', 'mise.toml', 'docs/.style/styles/Coder/**') }}
restore-keys: |
vale-
- name: Prepare Vale styles
if: steps.changed-md.outputs.any_changed == 'true'
continue-on-error: true
env:
# Non-interactive: let mise auto-install the pinned Vale on first use.
MISE_YES: "1"
run: make docs/.style/.vale-synced
- name: Vale prose lint
if: steps.changed-md.outputs.any_changed == 'true'
continue-on-error: true
env:
ALL_CHANGED_FILES: ${{ steps.changed-md.outputs.all_changed_files }}
# Non-interactive: let mise auto-install the pinned Vale on first use.
MISE_YES: "1"
run: |
# all_changed_files is ACMRD and so lists paths this PR deleted.
# Vale errors on a missing file (--no-exit only suppresses alert
# exits, not runtime errors), so keep only docs/ paths still on
# disk. See DOCS-40.
files=$(printf '%s\n' "$ALL_CHANGED_FILES" \
| tr ',' '\n' \
| grep -E '^docs/' \
| while IFS= read -r f; do [ -f "$f" ] && printf '%s\n' "$f"; done || true)
if [ -z "$files" ]; then
echo "No changed Markdown files under docs/ on disk; skipping Vale."
exit 0
fi
# Vale's --output=line strips per-finding severity, so the
# previous problem-matcher approach collapsed every finding to
# a single hard-coded severity. Use --output=JSON instead and
# emit GitHub workflow commands directly so error/warning/
# suggestion render with their actual Vale severities. URL-
# encode message bodies for `%`, `\r`, and `\n` per the
# GitHub Actions workflow command spec. See DOCS-426.
printf '%s\n' "$files" \
| xargs -d '\n' mise exec "aqua:errata-ai/vale" -- vale --no-exit --output=JSON \
| jq -r '
to_entries[]
| .key as $file
| .value[]
| (if .Severity == "suggestion" then "notice"
elif .Severity == "warning" then "warning"
else "error" end) as $level
| (.Message | gsub("%"; "%25") | gsub("\r"; "%0D") | gsub("\n"; "%0A")) as $msg
| "::\($level) file=\($file),line=\(.Line),col=\(.Span[0]),title=\(.Check)::\($msg)"
'
- name: Save Vale styles
# Only the default branch is trusted to write the cache, so PR
# runs cannot poison the cache that subsequent runs restore from.
# Skip when the cache already had an exact key hit (no new content).
if: github.ref == 'refs/heads/main' && steps.changed-md.outputs.any_changed == 'true' && steps.vale-cache.outputs.cache-hit != 'true'
continue-on-error: true
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
docs/.style/styles/*
!docs/.style/styles/Coder
docs/.style/.vale-synced
key: ${{ steps.vale-cache.outputs.cache-primary-key }}
# Disabled due to instability. See: https://github.com/coder/coder/issues/14553
# Re-enable once the flake hash calculation is stable.
# update-flake:
# needs: changes
# if: needs.changes.outputs.gomod == 'true'
# runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
# steps:
# - name: Checkout
# uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
# with:
# fetch-depth: 1
# # See: https://github.com/stefanzweifel/git-auto-commit-action?tab=readme-ov-file#commits-made-by-this-action-do-not-trigger-new-workflow-runs
# token: ${{ secrets.CDRCI_GITHUB_TOKEN }}
# - name: Set up mise tools
# uses: ./.github/actions/setup-mise
# with:
# install-args: "go"
# - name: Update Nix Flake SRI Hash
# run: ./scripts/update-flake.sh
# # auto update flake for dependabot
# - uses: stefanzweifel/git-auto-commit-action@8621497c8c39c72f3e2a999a26b4ca1b5058a842 # v5.0.1
# if: github.actor == 'dependabot[bot]'
# with:
# # Allows dependabot to still rebase!
# commit_message: "[dependabot skip] Update Nix Flake SRI Hash"
# commit_user_name: "dependabot[bot]"
# commit_user_email: "49699333+dependabot[bot]@users.noreply.github.com>"
# commit_author: "dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>"
# # require everyone else to update it themselves
# - name: Ensure No Changes
# if: github.actor != 'dependabot[bot]'
# run: git diff --exit-code
lint:
needs: changes
if: needs.changes.outputs.offlinedocs-only == 'false' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# Depth 2 makes the PR merge commit's first parent (the base
# branch tip) available so lint/emdash can diff against HEAD^
# without fetching the base branch at runtime.
fetch-depth: 2
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm helm aqua:crate-ci/typos"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:github.com/golangci/golangci-lint/v2/cmd/golangci-lint go:github.com/coder/paralleltestctx/cmd/paralleltestctx go:github.com/rhysd/actionlint/cmd/actionlint
- name: Get golangci-lint cache dir
run: |
dir=$(golangci-lint cache status | awk '/Dir/ { print $2 }')
echo "LINT_CACHE_DIR=$dir" >> "$GITHUB_ENV"
# Cache split into restore + conditional save to avoid letting PR
# runs populate a cache that other branches restore from (the
# zizmor `cache-poisoning` concern). Only pushes to the default
# branch may write the cache; PRs may only read it.
- name: Restore golangci-lint cache
id: golangci-lint-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
${{ env.LINT_CACHE_DIR }}
key: golangci-lint-${{ runner.os }}-${{ hashFiles('**/*.go') }}
restore-keys: |
golangci-lint-${{ runner.os }}-
# Check for any typos
- name: Check for typos
run: typos --config .github/workflows/typos.toml
- name: Fix the typos
if: ${{ failure() }}
run: |
echo "::notice:: you can automatically fix typos from your CLI:
mise exec aqua:crate-ci/typos -- typos -c .github/workflows/typos.toml -w"
- name: Verify helm version
run: helm version --short
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- name: make lint
# Bound concurrent lint targets to keep peak memory below the runner limit.
run: make --output-sync=line -j"$(nproc)" lint
- name: Save golangci-lint cache
# Only the default branch is trusted to write the cache, so PR
# runs cannot poison the cache that subsequent runs restore from.
# Skip when the cache already had an exact key hit (no new content).
if: github.ref == 'refs/heads/main' && steps.golangci-lint-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: |
${{ env.LINT_CACHE_DIR }}
key: ${{ steps.golangci-lint-cache.outputs.cache-primary-key }}
- name: Check workflow files
run: actionlint -color -shellcheck= -ignore "set-output"
shell: bash
- name: Check for unstaged files
run: ./scripts/check_unstaged.sh
shell: bash
lint-actions:
needs: changes
# Only run this job if changes to CI workflow files are detected. This job
# can flake as it reaches out to GitHub to check referenced actions.
if: needs.changes.outputs.ci == 'true'
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-24.04-8' || 'ubuntu-24.04' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go zizmor"
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:github.com/rhysd/actionlint/cmd/actionlint
- name: make lint/actions
run: make --output-sync=line -j lint/actions
env:
# Used by zizmor to lint third-party GitHub actions.
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
gen:
timeout-minutes: 20
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
if: ${{ !cancelled() }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm terraform protoc protoc-gen-go"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:storj.io/drpc/cmd/protoc-gen-go-drpc go:github.com/coder/sqlc/cmd/sqlc
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- name: Start PostgreSQL container
run: make test-postgres-docker
- name: make gen
timeout-minutes: 8
run: |
# Remove golden files to detect discrepancy in generated files.
make clean/golden-files
# Notifications require DB, we could start a DB instance here but
# let's just restore for now.
git checkout -- coderd/notifications/testdata/rendered-templates
make -j --output-sync -B gen
- name: Check for unstaged files
run: ./scripts/check_unstaged.sh
- name: Collect PostgreSQL logs
if: always()
run: make test-postgres-docker-logs > postgres.log 2>&1
- name: Upload PostgreSQL logs
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: gen-postgres-logs
path: postgres.log
fmt:
needs: changes
if: needs.changes.outputs.offlinedocs-only == 'false' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
timeout-minutes: 20
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Check Go version
run: IGNORE_NIX=true ./scripts/check_go_versions.sh
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm terraform"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:mvdan.cc/sh/v3/cmd/shfmt
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- name: make fmt
timeout-minutes: 7
run: make --output-sync -j -B fmt
- name: Check for unstaged files
run: ./scripts/check_unstaged.sh
test-go-pg:
# make sure to adjust NUM_PARALLEL_PACKAGES and NUM_PARALLEL_TESTS below
# when changing runner sizes
runs-on: ${{ matrix.os == 'ubuntu-latest' && github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || matrix.os && matrix.os == 'macos-latest' && github.repository_owner == 'coder' && 'depot-macos-latest' || matrix.os == 'windows-2022' && github.repository_owner == 'coder' && 'depot-windows-2022-16' || matrix.os }}
needs: changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
# This timeout must be greater than the timeout set by `go test` in
# `make test` to ensure we receive a trace of running goroutines.
# Setting this to the timeout +5m should work quite well even if
# some of the preceding steps are slow.
timeout-minutes: 30
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-2022
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
# macOS indexes all new files in the background. Our Postgres tests
# create and destroy thousands of databases on disk, and Spotlight
# tries to index all of them, seriously slowing down the tests.
- name: Disable Spotlight Indexing
if: runner.os == 'macOS'
run: |
enabled=$(sudo mdutil -a -s | { grep -Fc "Indexing enabled" || true; })
if [ "$enabled" -eq 0 ]; then
echo "Spotlight indexing is already disabled"
exit 0
fi
sudo mdutil -a -i off
sudo mdutil -X /
sudo launchctl bootout system /System/Library/LaunchDaemons/com.apple.metadata.mds.plist
# Set up RAM disks to speed up the rest of the job. This action is in
# a separate repository to allow its use before actions/checkout.
- name: Setup RAM Disks
if: runner.os == 'Windows'
uses: coder/setup-ramdisk-action@e1100847ab2d7bcd9d14bcda8f2d1b0f07b36f1b # v0.1.0
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Setup Go Paths
uses: ./.github/actions/setup-go-paths
- name: Setup GNU tools (macOS)
uses: ./.github/actions/setup-gnu-tools
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go terraform"
- name: Install Go mise tools
shell: bash
run: ./.github/scripts/retry.sh -- mise install --locked go:gotest.tools/gotestsum go:github.com/slsyy/mtimehash/cmd/mtimehash
- name: Download Test Cache
id: download-cache
uses: ./.github/actions/test-cache/download
with:
key-prefix: test-go-pg-${{ runner.os }}-${{ runner.arch }}
- name: Setup Embedded Postgres Cache Paths
id: embedded-pg-cache
uses: ./.github/actions/setup-embedded-pg-cache-paths
- name: Download Embedded Postgres Cache
id: download-embedded-pg-cache
uses: ./.github/actions/embedded-pg-cache/download
with:
key-prefix: embedded-pg-${{ runner.os }}-${{ runner.arch }}
cache-path: ${{ steps.embedded-pg-cache.outputs.cached-dirs }}
- name: Normalize File and Directory Timestamps
shell: bash
# Normalize file modification timestamps so that go test can use the
# cache from the previous CI run. See https://github.com/golang/go/issues/58571
# for more details.
#
# mtimehash only optimizes cache hits, so a missing binary must not
# fail the job before any tests run (coder/internal#1630).
run: |
if command -v mtimehash >/dev/null 2>&1; then
find . -type f ! -path ./.git/\*\* | mtimehash
else
echo "::warning::mtimehash not found; skipping file timestamp normalization (Go test cache may be cold)"
fi
find . -type d ! -path ./.git/\*\* -exec touch -t 200601010000 {} +
- name: Normalize Terraform Path for Caching
shell: bash
# Terraform gets installed in a random directory, so we need to normalize
# the path or many cached tests will be invalidated.
run: |
mkdir -p "$RUNNER_TEMP/sym"
source scripts/normalize_path.sh
normalize_path_with_symlinks "$RUNNER_TEMP/sym" "$(dirname "$(which terraform)")"
- name: Setup RAM disk for Embedded Postgres (Windows)
if: runner.os == 'Windows'
shell: bash
# The default C: drive is extremely slow:
# https://github.com/actions/runner-images/issues/8755
run: mkdir -p "R:/temp/embedded-pg"
- name: Setup RAM disk for Embedded Postgres (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
# Postgres runs faster on a ramdisk on macOS.
mkdir -p /tmp/tmpfs
sudo mount_tmpfs -o noowners -s 8g /tmp/tmpfs
# macOS will output "The default interactive shell is now zsh" intermittently in CI.
touch ~/.bash_profile && echo "export BASH_SILENCE_DEPRECATION_WARNING=1" >> ~/.bash_profile
- name: Increase PTY limit (macOS)
if: runner.os == 'macOS'
shell: bash
run: |
# Increase PTY limit to avoid exhaustion during tests.
# Default is 511; 999 is the maximum value on CI runner.
sudo sysctl -w kern.tty.ptmx_max=999
- name: Test with PostgreSQL Database (Linux)
if: runner.os == 'Linux'
uses: ./.github/actions/test-go-pg
with:
postgres-version: "13"
# Our Linux runners have 8 cores.
test-parallelism-packages: "8"
test-parallelism-tests: "8"
# By default, run tests with cache for improved speed (possibly at the expense of correctness).
# On main, run tests without cache for the inverse.
test-count: ${{ github.ref == 'refs/heads/main' && '1' || '' }}
gotestsum-json-file: default
- name: Test with PostgreSQL Database (macOS)
if: runner.os == 'macOS'
uses: ./.github/actions/test-go-pg
with:
postgres-version: "13"
# Our macOS runners have 8 cores. test-parallelism-tests matches
# Linux's rather than the previous 16, to rule out scheduler
# contention as a contributor to the macOS test-go-pg timeouts
# tracked in coder/internal#1365.
test-parallelism-packages: "8"
test-parallelism-tests: "8"
# By default, run tests with cache for improved speed (possibly at the expense of correctness).
# On main, run tests without cache for the inverse.
test-count: ${{ github.ref == 'refs/heads/main' && '1' || '' }}
# Only the CLI and Agent are officially supported on macOS; the rest are too flaky.
test-packages: "./cli/... ./enterprise/cli/... ./agent/..."
embedded-pg-path: "/tmp/tmpfs/embedded-pg"
embedded-pg-cache: ${{ steps.embedded-pg-cache.outputs.embedded-pg-cache }}
- name: Test with PostgreSQL Database (Windows)
if: runner.os == 'Windows'
uses: ./.github/actions/test-go-pg
with:
postgres-version: "13"
# Our Windows runners have 16 cores.
# On Windows Postgres chokes up when we have 16x16=256 tests
# running in parallel, and dbtestutil.NewDB starts to take more than
# 10s to complete sometimes causing test timeouts. With 16x8=128 tests
# Postgres tends not to choke.
test-parallelism-packages: "8"
test-parallelism-tests: "16"
# By default, run tests with cache for improved speed (possibly at the expense of correctness).
# On main, run tests without cache for the inverse.
test-count: ${{ github.ref == 'refs/heads/main' && '1' || '' }}
# Only the CLI and Agent are officially supported on Windows; the rest are too flaky.
test-packages: "./cli/... ./enterprise/cli/... ./agent/..."
embedded-pg-path: "R:/temp/embedded-pg"
embedded-pg-cache: ${{ steps.embedded-pg-cache.outputs.embedded-pg-cache }}
- name: Publish Go test failure report
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
uses: ./.github/actions/go-test-failure-report
with:
artifact-name: go-test-failures-${{ github.job }}-${{ github.sha }}
- name: Upload failed test db dumps
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: failed-test-db-dump-${{matrix.os}}
path: "**/*.test.sql"
- name: Upload Test Cache
uses: ./.github/actions/test-cache/upload
with:
cache-key: ${{ steps.download-cache.outputs.cache-key }}
- name: Upload Embedded Postgres Cache
uses: ./.github/actions/embedded-pg-cache/upload
# We only use the embedded Postgres cache on macOS and Windows runners.
if: runner.OS == 'macOS' || runner.OS == 'Windows'
with:
cache-key: ${{ steps.download-embedded-pg-cache.outputs.cache-key }}
cache-path: "${{ steps.embedded-pg-cache.outputs.embedded-pg-cache }}"
- name: Upload test stats to Datadog
timeout-minutes: 1
continue-on-error: true
uses: ./.github/actions/upload-datadog
if: success() || failure()
with:
api-key: ${{ secrets.DATADOG_API_KEY }}
test-go-pg-17:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs:
- changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
# This timeout must be greater than the timeout set by `go test` in
# `make test` to ensure we receive a trace of running goroutines.
# Setting this to the timeout +5m should work quite well even if
# some of the preceding steps are slow.
timeout-minutes: 30
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go terraform"
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:gotest.tools/gotestsum
- name: Download Test Cache
id: download-cache
uses: ./.github/actions/test-cache/download
with:
key-prefix: test-go-pg-17-${{ runner.os }}-${{ runner.arch }}
- name: Normalize Terraform Path for Caching
shell: bash
# Terraform gets installed in a random directory, so we need to normalize
# the path or many cached tests will be invalidated.
run: |
mkdir -p "$RUNNER_TEMP/sym"
source scripts/normalize_path.sh
normalize_path_with_symlinks "$RUNNER_TEMP/sym" "$(dirname "$(which terraform)")"
- name: Test with PostgreSQL Database
uses: ./.github/actions/test-go-pg
with:
postgres-version: "17"
# Our Linux runners have 8 cores.
test-parallelism-packages: "8"
test-parallelism-tests: "8"
# By default, run tests with cache for improved speed (possibly at the expense of correctness).
# On main, run tests without cache for the inverse.
test-count: ${{ github.ref == 'refs/heads/main' && '1' || '' }}
gotestsum-json-file: default
- name: Publish Go test failure report
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
uses: ./.github/actions/go-test-failure-report
with:
artifact-name: go-test-failures-${{ github.job }}-${{ github.sha }}
- name: Upload Test Cache
uses: ./.github/actions/test-cache/upload
with:
cache-key: ${{ steps.download-cache.outputs.cache-key }}
- name: Upload test stats to Datadog
timeout-minutes: 1
continue-on-error: true
uses: ./.github/actions/upload-datadog
if: success() || failure()
with:
api-key: ${{ secrets.DATADOG_API_KEY }}
test-go-race-pg:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-16' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.go == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
# This timeout must be greater than the timeout set by `go test` in
# `make test-race` to ensure we receive a trace of running goroutines.
# Setting this to the timeout +5m should work quite well even if
# some of the preceding steps are slow.
timeout-minutes: 30
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go terraform"
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:gotest.tools/gotestsum
- name: Download Test Cache
id: download-cache
uses: ./.github/actions/test-cache/download
with:
key-prefix: test-go-race-pg-${{ runner.os }}-${{ runner.arch }}
- name: Normalize Terraform Path for Caching
shell: bash
# Terraform gets installed in a random directory, so we need to normalize
# the path or many cached tests will be invalidated.
run: |
mkdir -p "$RUNNER_TEMP/sym"
source scripts/normalize_path.sh
normalize_path_with_symlinks "$RUNNER_TEMP/sym" "$(dirname "$(which terraform)")"
# We run race tests with reduced parallelism because they use more CPU and we were finding
# instances where tests appear to hang for multiple seconds, resulting in flaky tests when
# short timeouts are used.
# c.f. discussion on https://github.com/coder/coder/pull/15106
# Our Linux runners have 16 cores, but we reduce parallelism since race detection adds a lot of overhead.
# We aim to have parallelism match CPU count (4*4=16) to avoid making flakes worse.
- name: Run Tests
uses: ./.github/actions/test-go-pg
with:
postgres-version: "17"
test-parallelism-packages: "4"
test-parallelism-tests: "4"
race-detection: "true"
gotestsum-json-file: default
- name: Publish Go test failure report
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && (github.event_name != 'pull_request' || !github.event.pull_request.head.repo.fork)
uses: ./.github/actions/go-test-failure-report
with:
artifact-name: go-test-failures-${{ github.job }}-${{ github.sha }}
- name: Upload Test Cache
uses: ./.github/actions/test-cache/upload
with:
cache-key: ${{ steps.download-cache.outputs.cache-key }}
- name: Upload test stats to Datadog
timeout-minutes: 1
continue-on-error: true
uses: ./.github/actions/upload-datadog
if: always()
with:
api-key: ${{ secrets.DATADOG_API_KEY }}
# Tailnet integration tests only run when the `tailnet` directory or `go.sum`
# and `go.mod` are changed. These tests are to ensure we don't add regressions
# to tailnet, either due to our code or due to updating dependencies.
#
# These tests are skipped in the main go test jobs because they require root
# and mess with networking.
test-go-tailnet-integration:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
# Unnecessary to run on main for now
if: needs.changes.outputs.tailnet-integration == 'true' || needs.changes.outputs.ci == 'true'
timeout-minutes: 20
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go"
# Used by some integration tests.
- name: Install Nginx
run: sudo apt-get update && sudo apt-get install -y nginx
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- name: Run Tests
run: make test-tailnet-integration
test-js:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.site == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
timeout-minutes: 20
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "node pnpm"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- run: pnpm test:ci --max-workers "$(nproc)"
working-directory: site
test-e2e:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-4' || 'ubuntu-latest' }}
needs: changes
strategy:
fail-fast: false
matrix:
variant:
- premium: false
name: test-e2e
#- premium: true
# name: test-e2e-premium
# Skip test-e2e on forks as they don't have access to CI secrets
if: (needs.changes.outputs.go == 'true' || needs.changes.outputs.site == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main') && !(github.event.pull_request.head.repo.fork)
timeout-minutes: 20
name: ${{ matrix.variant.name }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
# Assume that the checked-in versions are up-to-date
- run: make gen/mark-fresh
name: make gen
- run: pnpm build
env:
NODE_OPTIONS: ${{ github.repository_owner == 'coder' && '--max_old_space_size=8192' || '' }}
working-directory: site
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- run: make site/e2e/bin/coder
name: make coder
- run: pnpm exec playwright install chromium
working-directory: site
# Cache the Coder release binaries downloaded by the outdatedCLI /
# outdatedAgent e2e tests so most runs skip the flaky GitHub release
# download entirely. The cache key is keyed off the file that pins the
# downloaded versions, so it invalidates when those versions change.
- name: Restore e2e Coder release binary cache
id: coder-e2e-cache
uses: actions/cache/restore@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /tmp/coder-e2e-cache
key: coder-e2e-cache-${{ runner.os }}-${{ hashFiles('site/e2e/constants.ts') }}
restore-keys: |
coder-e2e-cache-${{ runner.os }}-
# Run tests that don't require a premium license without a premium license
- run: pnpm playwright:test --forbid-only --workers 1
if: ${{ !matrix.variant.premium }}
env:
DEBUG: pw:api
working-directory: site
# Run all of the tests with a premium license
- run: pnpm playwright:test --forbid-only --workers 1
if: ${{ matrix.variant.premium }}
env:
DEBUG: pw:api
CODER_E2E_LICENSE: ${{ secrets.CODER_E2E_LICENSE }}
CODER_E2E_REQUIRE_PREMIUM_TESTS: "1"
working-directory: site
- name: Save e2e Coder release binary cache
# Only the default branch is trusted to write the cache, so PR runs
# cannot poison the cache that subsequent runs restore from. Skip when
# the cache already had an exact key hit (no new content).
if: always() && github.ref == 'refs/heads/main' && steps.coder-e2e-cache.outputs.cache-hit != 'true'
uses: actions/cache/save@55cc8345863c7cc4c66a329aec7e433d2d1c52a9 # v6.1.0
with:
path: /tmp/coder-e2e-cache
key: ${{ steps.coder-e2e-cache.outputs.cache-primary-key }}
- name: Upload Playwright failure artifacts
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: playwright-artifacts-${{ matrix.variant.name }}-${{ github.sha }}
path: |
./site/test-results/**
./site/playwright-report/**
retention-days: 7
- name: Publish Playwright failure summary
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
env:
MATRIX_VARIANT: ${{ matrix.variant.name }}
GITHUB_SHA_SHORT: ${{ github.sha }}
run: bash scripts/playwright-failure-summary.sh site/test-results/results.json >> "$GITHUB_STEP_SUMMARY"
- name: Upload debug log
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coderd-debug-logs-${{ matrix.variant.name }}-${{ github.sha }}
path: ./site/e2e/test-results/debug.log
retention-days: 7
- name: Upload pprof dumps
if: failure() && github.actor != 'dependabot[bot]' && runner.os == 'Linux' && !github.event.pull_request.head.repo.fork
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: debug-pprof-dumps-${{ matrix.variant.name }}-${{ github.sha }}
path: ./site/test-results/**/debug-pprof-*.txt
retention-days: 7
storybook:
name: Storybook
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-24.04-16' || 'ubuntu-latest' }}
# Forks and Dependabot receive no secrets, so they only build Storybook;
# trusted runs snapshot it against Pixel instead.
env:
PIXEL_ENABLED: ${{ github.event_name != 'pull_request' || (!github.event.pull_request.head.repo.fork && github.actor != 'dependabot[bot]') }}
steps:
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
name: Checkout
with:
persist-credentials: false
- uses: pnpm/action-setup@0977fd99725f1db4007ccb2928dbb4e90d06cc86 # v6.0.10
name: Install dependencies
with:
run_install: true
cache: true
- run: pnpm exec playwright install chromium
working-directory: site/
name: Install Chromium
- run: pnpm storybook:build
working-directory: site/
name: Build
if: env.PIXEL_ENABLED != 'true'
- run: pnpm pixel-storybook:ci
working-directory: site/
name: Snapshot
if: env.PIXEL_ENABLED == 'true'
env:
PIXEL_KEY: ${{ secrets.PIXEL_KEY }}
PIXEL_AUTO_REVIEW: ${{ github.repository_owner == 'coder' && github.ref_name == 'main' }}
offlinedocs:
name: offlinedocs
needs: changes
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
if: needs.changes.outputs.offlinedocs == 'true' || needs.changes.outputs.ci == 'true' || needs.changes.outputs.docs == 'true'
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
# 0 is required here for version.sh to work.
fetch-depth: 0
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm protoc protoc-gen-go"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
with:
directory: offlinedocs
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:storj.io/drpc/cmd/protoc-gen-go-drpc go:github.com/coder/sqlc/cmd/sqlc
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- name: Format
run: |
cd offlinedocs
pnpm format:check
- name: Lint
run: |
cd offlinedocs
pnpm lint
- name: Build
# no `-j` flag as `make` fails with:
# coderd/rbac/object_gen.go:1:1: syntax error: package statement must be first
run: |
make build/coder_docs_"$(./scripts/version.sh)".tgz
- name: Check for unstaged files
run: ./scripts/check_unstaged.sh
required:
runs-on: ubuntu-latest
needs:
- changes
- fmt
- lint
- lint-docs
- lint-actions
- gen
- test-go-pg
- test-go-pg-17
- test-go-race-pg
- test-js
- test-e2e
- offlinedocs
- sqlc-vet
- check-build
# Allow this job to run even if the needed jobs fail, are skipped or
# cancelled.
if: always()
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Ensure required checks
run: | # zizmor: ignore[template-injection] We're just reading needs.x.result here, no risk of injection
echo "Checking required checks"
echo "- changes: ${{ needs.changes.result }}"
echo "- fmt: ${{ needs.fmt.result }}"
echo "- lint: ${{ needs.lint.result }}"
echo "- lint-docs: ${{ needs.lint-docs.result }}"
echo "- lint-actions: ${{ needs.lint-actions.result }}"
echo "- gen: ${{ needs.gen.result }}"
echo "- test-go-pg: ${{ needs.test-go-pg.result }}"
echo "- test-go-pg-17: ${{ needs.test-go-pg-17.result }}"
echo "- test-go-race-pg: ${{ needs.test-go-race-pg.result }}"
echo "- test-js: ${{ needs.test-js.result }}"
echo "- test-e2e: ${{ needs.test-e2e.result }}"
echo "- offlinedocs: ${{ needs.offlinedocs.result }}"
echo "- check-build: ${{ needs.check-build.result }}"
echo
# We allow skipped jobs to pass, but not failed or cancelled jobs.
if [[ "${{ contains(needs.*.result, 'failure') }}" == "true" || "${{ contains(needs.*.result, 'cancelled') }}" == "true" ]]; then
echo "One of the required checks has failed or has been cancelled"
exit 1
fi
echo "Required checks have passed"
check-build:
# This job runs make build to verify compilation on PRs.
# The build doesn't get signed, and is not suitable for usage, unlike the
# `build` job that runs on main.
needs: changes
if: needs.changes.outputs.go == 'true' && github.ref != 'refs/heads/main'
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:github.com/tc-hib/go-winres go:github.com/goreleaser/nfpm/v2/cmd/nfpm
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Build
run: |
set -euxo pipefail
./.github/scripts/retry.sh -- go mod download
make gen/mark-fresh
make build
build:
# This builds and publishes ghcr.io/coder/coder-preview:main for each commit
# to main branch.
needs:
- changes
if: (github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/')) && needs.changes.outputs.docs-only == 'false' && !github.event.pull_request.head.repo.fork
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-22.04' }}
permissions:
# Necessary to push docker images to ghcr.io.
packages: write
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
# Also necessary for keyless cosign (https://docs.sigstore.dev/cosign/signing/overview/)
# And for GitHub Actions attestation
id-token: write
# Required for GitHub Actions attestation
attestations: write
env:
DOCKER_CLI_EXPERIMENTAL: "enabled"
outputs:
IMAGE: ghcr.io/coder/coder-preview:${{ steps.build-docker.outputs.tag }}
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 0
persist-credentials: false
- name: GHCR Login
uses: docker/login-action@dbcb813823bdd20940b903addbd779551569679f # v4.6.0
with:
registry: ghcr.io
username: ${{ github.actor }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go node pnpm cosign syft"
- name: Install pnpm dependencies
uses: ./.github/actions/pnpm-install
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:github.com/tc-hib/go-winres go:github.com/goreleaser/nfpm/v2/cmd/nfpm
- name: Install rcodesign
run: |
set -euo pipefail
./.github/scripts/retry.sh -- wget -O /tmp/rcodesign.tar.gz https://github.com/indygreg/apple-platform-rs/releases/download/apple-codesign%2F0.22.0/apple-codesign-0.22.0-x86_64-unknown-linux-musl.tar.gz
sudo tar -xzf /tmp/rcodesign.tar.gz \
-C /usr/bin \
--strip-components=1 \
apple-codesign-0.22.0-x86_64-unknown-linux-musl/rcodesign
rm /tmp/rcodesign.tar.gz
- name: Setup Apple Developer certificate
run: |
set -euo pipefail
touch /tmp/{apple_cert.p12,apple_cert_password.txt}
chmod 600 /tmp/{apple_cert.p12,apple_cert_password.txt}
echo "$AC_CERTIFICATE_P12_BASE64" | base64 -d > /tmp/apple_cert.p12
echo "$AC_CERTIFICATE_PASSWORD" > /tmp/apple_cert_password.txt
env:
AC_CERTIFICATE_P12_BASE64: ${{ secrets.AC_CERTIFICATE_P12_BASE64 }}
AC_CERTIFICATE_PASSWORD: ${{ secrets.AC_CERTIFICATE_PASSWORD }}
# Necessary for signing Windows binaries.
- name: Setup Java
uses: actions/setup-java@dd06d9cba3e5552c54d9f8ea23572deb30010f7c # v6.0.0
with:
distribution: "zulu"
java-version: "11.0"
- name: Install zstd
run: sudo apt-get install -y zstd
- name: Setup Windows EV Signing Certificate
run: |
set -euo pipefail
touch /tmp/ev_cert.pem
chmod 600 /tmp/ev_cert.pem
echo "$EV_SIGNING_CERT" > /tmp/ev_cert.pem
./.github/scripts/retry.sh -- wget https://github.com/ebourg/jsign/releases/download/6.0/jsign-6.0.jar -O /tmp/jsign-6.0.jar
env:
EV_SIGNING_CERT: ${{ secrets.EV_SIGNING_CERT }}
# Setup GCloud for signing Windows binaries.
- name: Authenticate to Google Cloud
id: gcloud_auth
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
with:
workload_identity_provider: ${{ vars.GCP_CODE_SIGNING_WORKLOAD_ID_PROVIDER }}
service_account: ${{ vars.GCP_CODE_SIGNING_SERVICE_ACCOUNT }}
token_format: "access_token"
- name: Setup GCloud SDK
uses: google-github-actions/setup-gcloud@aa5489c8933f4cc7a4f7d45035b3b1440c9c10db # v3.0.1
- name: Build
run: |
set -euxo pipefail
./.github/scripts/retry.sh -- go mod download
version="$(./scripts/version.sh)"
tag="main-${version//+/-}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
make gen/mark-fresh
make -j \
build/coder_linux_{amd64,arm64,armv7} \
build/coder_"$version"_windows_amd64.zip \
build/coder_"$version"_linux_{amd64,arm64,armv7}.{tar.gz,deb}
env:
# The Windows and Darwin slim binaries must be signed for Coder
# Desktop to accept them.
CODER_SIGN_WINDOWS: "1"
CODER_WINDOWS_RESOURCES: "1"
CODER_SIGN_GPG: "1"
CODER_GPG_RELEASE_KEY_BASE64: ${{ secrets.GPG_RELEASE_KEY_BASE64 }}
CODER_SIGN_DARWIN: "1"
AC_CERTIFICATE_FILE: /tmp/apple_cert.p12
AC_CERTIFICATE_PASSWORD_FILE: /tmp/apple_cert_password.txt
EV_KEY: ${{ secrets.EV_KEY }}
EV_KEYSTORE: ${{ secrets.EV_KEYSTORE }}
EV_TSA_URL: ${{ secrets.EV_TSA_URL }}
EV_CERTIFICATE_PATH: /tmp/ev_cert.pem
GCLOUD_ACCESS_TOKEN: ${{ steps.gcloud_auth.outputs.access_token }}
JSIGN_PATH: /tmp/jsign-6.0.jar
# Enable React profiling build and discoverable source maps
# for the dogfood deployment (dogfood.cdr.dev). This also
# applies to release/* branch builds, but those still
# produce coder-preview images, not release images.
# Release images are built by release.yaml (no profiling).
CODER_REACT_PROFILING: "true"
# Free up disk space before building Docker images. The preceding
# Build step produces ~2 GB of binaries and packages, the Go build
# cache is ~1.3 GB, and node_modules is ~500 MB. Docker image
# builds, pushes, and SBOM generation need headroom that isn't
# available without reclaiming some of that space.
- name: Clean up build cache
run: |
set -euxo pipefail
# Go caches are no longer needed — binaries are already compiled.
go clean -cache -modcache
# Remove .apk and .rpm packages that are not uploaded as
# artifacts and were only built as make prerequisites.
rm -f ./build/*.apk ./build/*.rpm
- name: Build Linux Docker images
id: build-docker
env:
CODER_IMAGE_BASE: ghcr.io/coder/coder-preview
DOCKER_CLI_EXPERIMENTAL: "enabled"
# Skip building .deb/.rpm/.apk/.tar.gz as prerequisites for
# the Docker image targets — they were already built above.
DOCKER_IMAGE_NO_PREREQUISITES: "true"
run: |
set -euxo pipefail
# build Docker images for each architecture
version="$(./scripts/version.sh)"
tag="${version//+/-}"
echo "tag=$tag" >> "$GITHUB_OUTPUT"
# build images for each architecture
# note: omitting the -j argument to avoid race conditions when pushing
make build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# only push if we are on main branch or release branch
if [[ "${GITHUB_REF}" == "refs/heads/main" || "${GITHUB_REF}" == refs/heads/release/* ]]; then
# build and push multi-arch manifest, this depends on the other images
# being pushed so will automatically push them
# note: omitting the -j argument to avoid race conditions when pushing
make push/build/coder_"$version"_linux_{amd64,arm64,armv7}.tag
# Define specific tags
tags=("$tag")
if [ "${GITHUB_REF}" == "refs/heads/main" ]; then
tags+=("main" "latest")
elif [[ "${GITHUB_REF}" == refs/heads/release/* ]]; then
tags+=("release-${GITHUB_REF#refs/heads/release/}")
fi
# Create and push a multi-arch manifest for each tag
# we are adding `latest` tag and keeping `main` for backward
# compatibality
for t in "${tags[@]}"; do
echo "Pushing multi-arch manifest for tag: $t"
# shellcheck disable=SC2046
./scripts/build_docker_multiarch.sh \
--push \
--target "ghcr.io/coder/coder-preview:$t" \
--version "$version" \
$(cat build/coder_"$version"_linux_{amd64,arm64,armv7}.tag)
done
fi
- name: SBOM Generation and Attestation
if: github.ref == 'refs/heads/main'
continue-on-error: true
env:
COSIGN_EXPERIMENTAL: 1
BUILD_TAG: ${{ steps.build-docker.outputs.tag }}
run: |
set -euxo pipefail
# Define image base and tags
IMAGE_BASE="ghcr.io/coder/coder-preview"
TAGS=("${BUILD_TAG}" "main" "latest")
# Generate and attest SBOM for each tag
for tag in "${TAGS[@]}"; do
IMAGE="${IMAGE_BASE}:${tag}"
SBOM_FILE="coder_sbom_${tag//[:\/]/_}.spdx.json"
echo "Generating SBOM for image: ${IMAGE}"
syft "${IMAGE}" -o spdx-json > "${SBOM_FILE}"
echo "Attesting SBOM to image: ${IMAGE}"
cosign clean --force=true "${IMAGE}"
cosign attest --type spdxjson \
--predicate "${SBOM_FILE}" \
--yes \
"${IMAGE}"
done
- name: Resolve Docker image digests for attestation
id: docker_digests
if: github.ref == 'refs/heads/main'
continue-on-error: true
env:
IMAGE_BASE: ghcr.io/coder/coder-preview
BUILD_TAG: ${{ steps.build-docker.outputs.tag }}
run: |
set -euxo pipefail
main_digest=$(docker buildx imagetools inspect --raw "${IMAGE_BASE}:main" | sha256sum | awk '{print "sha256:"$1}')
echo "main_digest=${main_digest}" >> "$GITHUB_OUTPUT"
latest_digest=$(docker buildx imagetools inspect --raw "${IMAGE_BASE}:latest" | sha256sum | awk '{print "sha256:"$1}')
echo "latest_digest=${latest_digest}" >> "$GITHUB_OUTPUT"
version_digest=$(docker buildx imagetools inspect --raw "${IMAGE_BASE}:${BUILD_TAG}" | sha256sum | awk '{print "sha256:"$1}')
echo "version_digest=${version_digest}" >> "$GITHUB_OUTPUT"
- name: GitHub Attestation for Docker image
id: attest_main
if: github.ref == 'refs/heads/main' && steps.docker_digests.outputs.main_digest != ''
continue-on-error: true
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ghcr.io/coder/coder-preview
subject-digest: ${{ steps.docker_digests.outputs.main_digest }}
push-to-registry: true
- name: GitHub Attestation for Docker image (latest tag)
id: attest_latest
if: github.ref == 'refs/heads/main' && steps.docker_digests.outputs.latest_digest != ''
continue-on-error: true
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ghcr.io/coder/coder-preview
subject-digest: ${{ steps.docker_digests.outputs.latest_digest }}
push-to-registry: true
- name: GitHub Attestation for version-specific Docker image
id: attest_version
if: github.ref == 'refs/heads/main' && steps.docker_digests.outputs.version_digest != ''
continue-on-error: true
uses: actions/attest@1e69f48acb82d1966a394da916b4c1698aa569d6 # v4.2.2
with:
subject-name: ghcr.io/coder/coder-preview
subject-digest: ${{ steps.docker_digests.outputs.version_digest }}
push-to-registry: true
# Report attestation failures but don't fail the workflow
- name: Check attestation status
if: github.ref == 'refs/heads/main'
run: | # zizmor: ignore[template-injection] We're just reading steps.attest_x.outcome here, no risk of injection
if [[ "${{ steps.attest_main.outcome }}" == "failure" ]]; then
echo "::warning::GitHub attestation for main tag failed"
fi
if [[ "${{ steps.attest_latest.outcome }}" == "failure" ]]; then
echo "::warning::GitHub attestation for latest tag failed"
fi
if [[ "${{ steps.attest_version.outcome }}" == "failure" ]]; then
echo "::warning::GitHub attestation for version-specific tag failed"
fi
- name: Prune old images
if: github.ref == 'refs/heads/main'
uses: vlaurin/action-ghcr-prune@0cf7d39f88546edd31965acba78cdcb0be14d641 # v0.6.0
with:
token: ${{ secrets.GITHUB_TOKEN }}
organization: coder
container: coder-preview
keep-younger-than: 7 # days
keep-tags: latest
keep-tags-regexes: ^pr
prune-tags-regexes: |
^main-
^v
prune-untagged: true
- name: Upload build artifact (coder-linux-amd64.tar.gz)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-linux-amd64.tar.gz
path: ./build/*_linux_amd64.tar.gz
retention-days: 7
- name: Upload build artifact (coder-linux-amd64.deb)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-linux-amd64.deb
path: ./build/*_linux_amd64.deb
retention-days: 7
- name: Upload build artifact (coder-linux-arm64.tar.gz)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-linux-arm64.tar.gz
path: ./build/*_linux_arm64.tar.gz
retention-days: 7
- name: Upload build artifact (coder-linux-arm64.deb)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-linux-arm64.deb
path: ./build/*_linux_arm64.deb
retention-days: 7
- name: Upload build artifact (coder-linux-armv7.tar.gz)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-linux-armv7.tar.gz
path: ./build/*_linux_armv7.tar.gz
retention-days: 7
- name: Upload build artifact (coder-linux-armv7.deb)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-linux-armv7.deb
path: ./build/*_linux_armv7.deb
retention-days: 7
- name: Upload build artifact (coder-windows-amd64.zip)
if: github.ref == 'refs/heads/main'
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: coder-windows-amd64.zip
path: ./build/*_windows_amd64.zip
retention-days: 7
# Deploy is handled in deploy.yaml so we can apply concurrency limits.
deploy:
needs:
- changes
- build
if: |
(github.ref == 'refs/heads/main' || startsWith(github.ref, 'refs/heads/release/'))
&& needs.changes.outputs.docs-only == 'false'
&& !github.event.pull_request.head.repo.fork
uses: ./.github/workflows/deploy.yaml
with:
image: ${{ needs.build.outputs.IMAGE }}
permissions:
contents: read
id-token: write
packages: write # to retag image as dogfood
# sqlc-vet runs a postgres docker container, runs Coder migrations, and then
# runs sqlc-vet to ensure all queries are valid. This catches any mistakes
# in migrations or sqlc queries that makes a query unable to be prepared.
sqlc-vet:
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
needs: changes
if: needs.changes.outputs.db == 'true' || needs.changes.outputs.ci == 'true' || github.ref == 'refs/heads/main'
steps:
- name: Harden Runner
uses: step-security/harden-runner@05e31511f85b41b11d1cf0ef85d0992719546e2c # v2.21.0
with:
egress-policy: audit
- name: Checkout
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
with:
fetch-depth: 1
persist-credentials: false
- name: Set up mise tools
uses: ./.github/actions/setup-mise
with:
install-args: "go"
- name: Install Go mise tools
run: ./.github/scripts/retry.sh -- mise install --locked go:github.com/coder/sqlc/cmd/sqlc
- name: Download Go modules
run: ./.github/scripts/retry.sh -- go mod download
- name: Setup and run sqlc vet
run: |
make sqlc-vet
notify-slack-on-failure:
needs:
- required
runs-on: ubuntu-latest
if: failure() && github.ref == 'refs/heads/main'
steps:
- name: Send Slack notification
run: |
ESCAPED_PROMPT=$(printf "%s" "<@U09LQ75AHKR> $BLINK_CI_FAILURE_PROMPT" | jq -Rsa .)
curl -X POST -H 'Content-type: application/json' \
--data '{
"blocks": [
{
"type": "header",
"text": {
"type": "plain_text",
"text": "❌ CI Failure in main",
"emoji": true
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": "*View failure:* <'"${RUN_URL}"'|Click here>"
}
},
{
"type": "section",
"text": {
"type": "mrkdwn",
"text": '"$ESCAPED_PROMPT"'
}
}
]
}' "${SLACK_WEBHOOK}"
env:
SLACK_WEBHOOK: ${{ secrets.CI_FAILURE_SLACK_WEBHOOK }}
RUN_URL: "${{ github.server_url }}/${{ github.repository }}/actions/runs/${{ github.run_id }}"
BLINK_CI_FAILURE_PROMPT: ${{ vars.BLINK_CI_FAILURE_PROMPT }}