mirror of
https://github.com/coder/coder.git
synced 2026-09-24 15:04:27 +08:00
## Problem The `lint/emdash` check fails on Graphite-stacked PRs. See [this failed run](https://github.com/coder/coder/actions/runs/28225390084/job/83616080375?pr=26650): ``` Base ref origin/graphite-base/26650 not found locally, fetching graphite-base/26650... ERROR: could not fetch base ref origin/graphite-base/26650. ERROR: could not determine base ref. make: *** [Makefile:768: lint/emdash] Error 1 ``` `scripts/check_emdash.sh` resolved its diff base by fetching `origin/$GITHUB_BASE_REF` and computing a merge-base. Graphite sets `GITHUB_BASE_REF` to a `graphite-base/<n>` ref that is ephemeral (it is not reliably present on origin), so the fetch fails and the check errors out instead of running. ## Fix `actions/checkout` checks out the PR **merge commit** (`refs/pull/<n>/merge`), whose **first parent (`HEAD^1`) is the exact base commit GitHub merged against**. Diffing `HEAD^1` against the checkout yields every change the PR makes against its base branch, for normal and Graphite-stacked PRs alike. No base-branch fetch, no merge-base computation, no `gh`-based deepen dance. - `scripts/check_emdash.sh`: use `HEAD^1` (the PR base commit) as the diff base in CI. Drops `resolve_merge_base` and `fetch_base_ref`. Emits a clear error if `HEAD^1` is missing (checkout too shallow). - `.github/workflows/ci.yaml`: bump the `lint` job checkout to `fetch-depth: 2` so `HEAD^1` is present with no runtime fetch. Local dev behavior (merge-base against `origin/main`) is unchanged. ## Verification - `make lint/emdash`, `make lint/shellcheck`, `make lint/actions/actionlint` pass. - Simulated the CI path with `GITHUB_BASE_REF` set: the check resolves to `HEAD^1` without fetching and still flags an added line containing an emdash. <details> <summary>Why the merge commit's first parent</summary> For a `pull_request` checkout of `refs/pull/<n>/merge`: - `HEAD` = GitHub's synthetic PR merge commit - `HEAD^1` = the exact base commit used for the merge - `HEAD^2` = the PR head commit `git diff HEAD^1 HEAD` is the full-tree diff from the base snapshot to the merged result, i.e. all of the PR's changes against its base. This is immutable and always local (given depth >= 2), unlike base branch refs which are mutable and, for Graphite stacks, ephemeral. </details> --- This PR was generated by Coder Agents on behalf of @dannykopping.
327 lines
13 KiB
YAML
327 lines
13 KiB
YAML
name: dogfood
|
|
|
|
on:
|
|
# Self-reference on `.github/workflows/dogfood.yaml` is intentional.
|
|
# The runtime cost is bounded and the matrix runs validate the
|
|
# workflow itself end to end. See DOCS-129 for the broader
|
|
# workflow-self-reference audit.
|
|
#
|
|
# Effects vary by event:
|
|
#
|
|
# PRs: `build_image` builds the base and runs `mise oci build`,
|
|
# loads the result into the local Docker daemon, and runs
|
|
# `make gen`, `fmt`, `lint`, and a Linux build inside the image
|
|
# to validate the baked-in tooling. Only the base image is pushed
|
|
# (to ghcr.io so the mise oci step can pull --from a real
|
|
# registry); the Docker Hub push is gated on
|
|
# `github.ref == 'refs/heads/main'`. Fork PRs skip the entire
|
|
# base+mise-oci pipeline since GITHUB_TOKEN is read-only for
|
|
# packages.
|
|
# `deploy_template` runs `terraform init` + `validate` only; the
|
|
# apply step and SHA/title gathering are gated on main.
|
|
#
|
|
# Pushes to main: `build_image` retags rolling tags on
|
|
# `codercom/oss-dogfood` (`:latest`, `:22.04`, `:26.04`) and
|
|
# `codercom/oss-dogfood-vscode-coder` (`:latest`), plus a
|
|
# per-branch tag on each. The image-tooling validation runs as
|
|
# above before any push, so a broken image never reaches Docker
|
|
# Hub.
|
|
# `deploy_template` runs `terraform apply` and creates new
|
|
# `coderd_template` versions on dev.coder.com whose `name` is the
|
|
# commit short SHA. Content is unchanged when `dogfood/**` is
|
|
# unchanged, so the new versions are cosmetic.
|
|
push:
|
|
branches:
|
|
- main
|
|
paths:
|
|
- "dogfood/**"
|
|
- ".github/workflows/dogfood.yaml"
|
|
- "mise.toml"
|
|
- "mise.lock"
|
|
- "scripts/dogfood/**"
|
|
- "scripts/dogfood_test_image.sh"
|
|
pull_request:
|
|
paths:
|
|
- "dogfood/**"
|
|
- ".github/workflows/dogfood.yaml"
|
|
- "mise.toml"
|
|
- "mise.lock"
|
|
- "scripts/dogfood/**"
|
|
- "scripts/dogfood_test_image.sh"
|
|
workflow_dispatch:
|
|
|
|
permissions:
|
|
contents: read
|
|
|
|
jobs:
|
|
build_image:
|
|
strategy:
|
|
fail-fast: false
|
|
matrix:
|
|
image-version: ["22.04", "26.04"]
|
|
|
|
if: github.actor != 'dependabot[bot]' # Skip Dependabot PRs
|
|
runs-on: ${{ github.repository_owner == 'coder' && 'depot-ubuntu-22.04-8' || 'ubuntu-latest' }}
|
|
permissions:
|
|
contents: read
|
|
packages: write # push the dogfood base image to ghcr.io/coder/oss-dogfood-base
|
|
env:
|
|
# MISE_EXPERIMENTAL opts into the experimental `oci` subcommand.
|
|
MISE_EXPERIMENTAL: "1"
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
# Depth 2 makes the PR merge commit's first parent (the base
|
|
# branch tip) available so lint/emdash, run inside the image via
|
|
# scripts/dogfood_test_image.sh, can diff against HEAD^1 without
|
|
# fetching the base branch at runtime.
|
|
fetch-depth: 2
|
|
persist-credentials: false
|
|
|
|
- name: Get branch name
|
|
id: branch-name
|
|
uses: tj-actions/branch-names@5250492686b253f06fa55861556d1027b067aeb5 # v9.0.2
|
|
|
|
- name: "Branch name to Docker tag name"
|
|
id: docker-tag-name
|
|
run: |
|
|
# Replace / with --, e.g. user/feature => user--feature.
|
|
tag=${BRANCH_NAME//\//--}
|
|
echo "tag=${tag}" >> "$GITHUB_OUTPUT"
|
|
env:
|
|
BRANCH_NAME: ${{ steps.branch-name.outputs.current_branch }}
|
|
|
|
- name: Set up Depot CLI
|
|
uses: depot/setup-action@15c09a5f77a0840ad4bce955686522a257853461 # v1.7.1
|
|
|
|
- name: Set up Docker Buildx
|
|
uses: docker/setup-buildx-action@4d04d5d9486b7bd6fa91e7baf45bbb4f8b9deedd # v4.0.0
|
|
|
|
- name: Set up mise tools
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
uses: ./.github/actions/setup-mise
|
|
|
|
- name: Compute image SHAs
|
|
# Match the fork guard on the downstream consumers of these
|
|
# outputs: nothing reads `steps.shas.outputs.*` outside the
|
|
# base-push + mise-oci pipeline, which is gated below.
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
id: shas
|
|
env:
|
|
IMAGE_VERSION: ${{ matrix.image-version }}
|
|
run: |
|
|
base_sha="$(./scripts/dogfood/compute-base-sha.sh "$IMAGE_VERSION")"
|
|
final_sha="$(./scripts/dogfood/compute-final-sha.sh "$IMAGE_VERSION")"
|
|
echo "base_sha=${base_sha}" >> "$GITHUB_OUTPUT"
|
|
echo "final_sha=${final_sha}" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Login to GHCR
|
|
# Fork PRs get a read-only GITHUB_TOKEN that cannot push to
|
|
# ghcr.io. Skip the entire GHCR-dependent pipeline (base push +
|
|
# mise oci build) for fork PRs.
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
with:
|
|
registry: ghcr.io
|
|
username: ${{ github.actor }}
|
|
password: ${{ secrets.GITHUB_TOKEN }}
|
|
|
|
- name: Login to DockerHub
|
|
if: github.ref == 'refs/heads/main'
|
|
uses: docker/login-action@4907a6ddec9925e35a0a9e82d7399ccc52663121 # v4.1.0
|
|
with:
|
|
username: ${{ secrets.DOCKERHUB_USERNAME }}
|
|
password: ${{ secrets.DOCKERHUB_PASSWORD }}
|
|
|
|
- name: Build base image
|
|
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
with:
|
|
project: b4q6ltmpzh
|
|
token: ${{ secrets.DEPOT_TOKEN }}
|
|
buildx-fallback: true
|
|
# Context is the repo root so Dockerfile.base can COPY the
|
|
# distro-specific files/ tree and configure-chrome-flags.sh.
|
|
context: "{{defaultContext}}"
|
|
file: dogfood/coder/ubuntu-${{ matrix.image-version }}/Dockerfile.base
|
|
pull: true
|
|
# Push to ghcr.io on every non-fork CI run so the downstream
|
|
# mise oci build can --from a real registry. The base-sha tag
|
|
# is a cache key (see scripts/dogfood/compute-base-sha.sh) so
|
|
# commits that don't change base inputs reuse the previous
|
|
# build.
|
|
push: true
|
|
tags: |
|
|
ghcr.io/coder/oss-dogfood-base:${{ matrix.image-version }}-${{ steps.shas.outputs.base_sha }}
|
|
ghcr.io/coder/oss-dogfood-base:${{ matrix.image-version }}-${{ steps.docker-tag-name.outputs.tag }}
|
|
|
|
- name: Build mise oci layer
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
env:
|
|
IMAGE_VERSION: ${{ matrix.image-version }}
|
|
BASE_SHA: ${{ steps.shas.outputs.base_sha }}
|
|
FINAL_SHA: ${{ steps.shas.outputs.final_sha }}
|
|
# --output makes the OCI layout location explicit so the later
|
|
# `mise oci push --image-dir` steps point at the right path even
|
|
# if mise oci's default ever changes (it's experimental).
|
|
run: |
|
|
mise oci build \
|
|
--from "ghcr.io/coder/oss-dogfood-base:${IMAGE_VERSION}-${BASE_SHA}" \
|
|
--tag "codercom/oss-dogfood:${FINAL_SHA}-${IMAGE_VERSION}" \
|
|
--output ./mise-oci
|
|
|
|
# Load the OCI layout into the local Docker daemon so the next
|
|
# step can `docker run` it. crane lacks a direct OCI-layout-to-
|
|
# daemon command, but its built-in registry server gives us a
|
|
# simple two-hop path with no extra dependencies.
|
|
- name: Load mise oci image into Docker daemon
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
env:
|
|
IMAGE_VERSION: ${{ matrix.image-version }}
|
|
run: |
|
|
set -euo pipefail
|
|
crane registry serve --address localhost:5000 &
|
|
reg_pid=$!
|
|
trap 'kill $reg_pid 2>/dev/null || true' EXIT
|
|
for _ in 1 2 3 4 5; do
|
|
curl -sf http://localhost:5000/v2/ >/dev/null && break
|
|
sleep 1
|
|
done
|
|
crane push ./mise-oci "localhost:5000/dogfood-test:${IMAGE_VERSION}"
|
|
docker pull "localhost:5000/dogfood-test:${IMAGE_VERSION}"
|
|
docker tag "localhost:5000/dogfood-test:${IMAGE_VERSION}" "dogfood-test:${IMAGE_VERSION}"
|
|
|
|
# Validate the dogfood image's tooling by running make gen, fmt,
|
|
# lint, and a fat build inside it. Failures here block the
|
|
# Docker Hub push below so broken images never reach workspaces.
|
|
- name: Test image tooling
|
|
if: ${{ !github.event.pull_request.head.repo.fork }}
|
|
env:
|
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
|
run: ./scripts/dogfood_test_image.sh "dogfood-test:${{ matrix.image-version }}"
|
|
|
|
- name: Push final Ubuntu 22.04 image
|
|
if: matrix.image-version == '22.04' && github.ref == 'refs/heads/main'
|
|
env:
|
|
FINAL_SHA: ${{ steps.shas.outputs.final_sha }}
|
|
DOCKER_TAG: ${{ steps.docker-tag-name.outputs.tag }}
|
|
# --image-dir points at the OCI layout written by the previous
|
|
# `mise oci build` step. Without it, `mise oci push` rebuilds
|
|
# from mise.toml and forgets the --from base. --tool crane
|
|
# forces the registry client mise oci shells out to, so we
|
|
# don't drift between the apt-shipped skopeo on whatever runner
|
|
# image we land on.
|
|
# TODO: move the `latest` tag to 26.04 soon. we don't want to
|
|
# transition it immediately because that would make workspaces
|
|
# switch to it automatically without any grace period.
|
|
run: |
|
|
set -euo pipefail
|
|
for tag in "${FINAL_SHA}-22.04" "$DOCKER_TAG" 22.04 latest; do
|
|
mise oci push --tool crane --image-dir ./mise-oci "codercom/oss-dogfood:$tag"
|
|
done
|
|
|
|
- name: Push final Ubuntu 26.04 image
|
|
if: matrix.image-version == '26.04' && github.ref == 'refs/heads/main'
|
|
env:
|
|
FINAL_SHA: ${{ steps.shas.outputs.final_sha }}
|
|
DOCKER_TAG: ${{ steps.docker-tag-name.outputs.tag }}
|
|
run: |
|
|
set -euo pipefail
|
|
for tag in "${FINAL_SHA}-26.04" "$DOCKER_TAG" 26.04; do
|
|
mise oci push --tool crane --image-dir ./mise-oci "codercom/oss-dogfood:$tag"
|
|
done
|
|
|
|
- name: Build and push vscode-coder image
|
|
uses: depot/build-push-action@5f3b3c2e5a00f0093de47f657aeaefcedff27d18 # v1.17.0
|
|
with:
|
|
project: b4q6ltmpzh
|
|
token: ${{ secrets.DEPOT_TOKEN }}
|
|
buildx-fallback: true
|
|
context: "{{defaultContext}}:dogfood/vscode-coder"
|
|
pull: true
|
|
save: true
|
|
push: ${{ github.ref == 'refs/heads/main' }}
|
|
tags: "codercom/oss-dogfood-vscode-coder:${{ steps.docker-tag-name.outputs.tag }},codercom/oss-dogfood-vscode-coder:latest"
|
|
if: matrix.image-version == '22.04'
|
|
|
|
deploy_template:
|
|
needs: build_image
|
|
runs-on: ubuntu-latest
|
|
permissions:
|
|
# Necessary for GCP authentication (https://github.com/google-github-actions/setup-gcloud#usage)
|
|
id-token: write
|
|
steps:
|
|
- name: Harden Runner
|
|
uses: step-security/harden-runner@f808768d1510423e83855289c910610ca9b43176 # v2.17.0
|
|
with:
|
|
egress-policy: audit
|
|
|
|
- name: Checkout
|
|
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
|
|
with:
|
|
persist-credentials: false
|
|
|
|
- name: Set up mise tools
|
|
uses: ./.github/actions/setup-mise
|
|
with:
|
|
install-args: "terraform"
|
|
|
|
- name: Authenticate to Google Cloud
|
|
uses: google-github-actions/auth@7c6bc770dae815cd3e89ee6cdf493a5fab2cc093 # v3.0.0
|
|
with:
|
|
workload_identity_provider: ${{ vars.GCP_WORKLOAD_ID_PROVIDER }}
|
|
service_account: ${{ vars.GCP_SERVICE_ACCOUNT }}
|
|
|
|
- name: Terraform init and validate
|
|
run: |
|
|
pushd dogfood/
|
|
terraform init
|
|
terraform validate
|
|
popd
|
|
pushd dogfood/coder
|
|
terraform init
|
|
terraform validate
|
|
popd
|
|
pushd dogfood/coder-envbuilder
|
|
terraform init
|
|
terraform validate
|
|
popd
|
|
pushd dogfood/vscode-coder
|
|
terraform init
|
|
terraform validate
|
|
popd
|
|
|
|
- name: Get short commit SHA
|
|
if: github.ref == 'refs/heads/main'
|
|
id: vars
|
|
run: echo "sha_short=$(git rev-parse --short HEAD)" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: Get latest commit title
|
|
if: github.ref == 'refs/heads/main'
|
|
id: message
|
|
run: echo "pr_title=$(git log --format=%s -n 1 ${{ github.sha }})" >> "$GITHUB_OUTPUT"
|
|
|
|
- name: "Push template"
|
|
if: github.ref == 'refs/heads/main'
|
|
run: |
|
|
cd dogfood
|
|
terraform apply -auto-approve
|
|
env:
|
|
# Consumed by coderd provider
|
|
CODER_URL: https://dev.coder.com
|
|
CODER_SESSION_TOKEN: ${{ secrets.CODER_SESSION_TOKEN }}
|
|
# Template source & details
|
|
TF_VAR_CODER_DOGFOOD_ANTHROPIC_API_KEY: ${{ secrets.CODER_DOGFOOD_ANTHROPIC_API_KEY }}
|
|
TF_VAR_CODER_DOGFOOD_OPENAI_API_KEY: ${{ secrets.CODER_DOGFOOD_OPENAI_API_KEY }}
|
|
TF_VAR_CODER_TEMPLATE_NAME: ${{ secrets.CODER_TEMPLATE_NAME }}
|
|
TF_VAR_CODER_TEMPLATE_VERSION: ${{ steps.vars.outputs.sha_short }}
|
|
TF_VAR_CODER_TEMPLATE_DIR: ./coder
|
|
TF_VAR_CODER_TEMPLATE_MESSAGE: ${{ steps.message.outputs.pr_title }}
|
|
TF_LOG: info
|