Skip to content

Commit 3ecba49

Browse files
committed
ci: split release publishing from PR validation
1 parent 0fce4ab commit 3ecba49

11 files changed

Lines changed: 253 additions & 55 deletions

File tree

.github/compose/.env

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
# renovate: datasource=github-releases depName=coder/coder
2+
CODER_VERSION="v2.35.6"
3+
# renovate: datasource=docker depName=postgres
4+
POSTGRES_VERSION="16.4"
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
---
2+
services:
3+
coder:
4+
image: ghcr.io/coder/coder:${CODER_VERSION}
5+
ports:
6+
- "7080:7080"
7+
environment:
8+
CODER_HTTP_ADDRESS: 0.0.0.0:7080
9+
CODER_PG_CONNECTION_URL: postgresql://coder:coder@database/coder?sslmode=disable
10+
KUBECONFIG: /home/coder/.kube/config
11+
volumes:
12+
- ${CODER_KUBECONFIG}:/home/coder/.kube/config:ro
13+
networks:
14+
default:
15+
kind:
16+
depends_on:
17+
database:
18+
condition: service_healthy
19+
20+
database:
21+
image: postgres:${POSTGRES_VERSION}
22+
environment:
23+
POSTGRES_USER: coder
24+
POSTGRES_PASSWORD: coder
25+
POSTGRES_DB: coder
26+
healthcheck:
27+
test: ["CMD-SHELL", "pg_isready -U coder -d coder"]
28+
interval: 5s
29+
timeout: 5s
30+
retries: 5
31+
32+
networks:
33+
kind:
34+
external: true

.github/renovate.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
"customType": "regex",
66
"managerFilePatterns": [
77
"/(^|/).+\\.ya?ml$/",
8+
"/(^|/)\\.env$/",
89
"/(^|/)Dockerfile$/"
910
],
1011
"matchStrings": [

.github/workflows/publish.yaml

Lines changed: 9 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,28 +3,21 @@
33
name: publish
44

55
on:
6-
pull_request:
7-
paths:
8-
- '.github/workflows/publish.yaml'
9-
- 'images/homelab-workspace/**'
10-
- 'templates/kubernetes/homelab-workspace/**'
116
release:
127
types:
138
- published
14-
workflow_dispatch:
159

1610
concurrency:
17-
group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.event.release.tag_name || github.ref }}
11+
group: ${{ github.workflow }}-${{ github.event.release.tag_name }}
1812
cancel-in-progress: true
1913

2014
env:
2115
image_name: ${{ secrets.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_REGISTRY_PATH }}/coder-workspace
2216
image_cache_name: ${{ secrets.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_REGISTRY_CACHE_PATH }}/coder-workspace
2317
image_path: images/homelab-workspace
2418
template_path: templates/kubernetes/homelab-workspace
25-
publish_mode: ${{ github.event_name == 'release' && 'release' || 'test' }}
26-
source_ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.head_ref || github.ref_name }}
27-
source_sha: ${{ github.event_name == 'release' && github.event.release.tag_name || github.event.pull_request.head.sha || github.sha }}
19+
source_ref: ${{ github.event.release.tag_name }}
20+
source_sha: ${{ github.event.release.tag_name }}
2821

2922
jobs:
3023
publish-image:
@@ -39,8 +32,8 @@ jobs:
3932
platforms: linux/amd64,linux/arm64
4033
private_registry_repository: ${{ vars.CONTAINER_REGISTRY_PATH }}/coder-workspace
4134
private_registry_build_cache: ${{ vars.CONTAINER_REGISTRY_CACHE_PATH }}/coder-workspace
42-
git_ref: ${{ github.event_name == 'release' && github.event.release.tag_name || github.head_ref || github.ref_name }}
43-
ghcr_repository: ${{ github.event_name == 'release' && 'ppat/coder-workspace' || '' }}
35+
git_ref: ${{ github.event.release.tag_name }}
36+
ghcr_repository: ppat/coder-workspace
4437
timeout_minutes: 180
4538
secrets:
4639
private_registry_username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }}
@@ -115,21 +108,13 @@ jobs:
115108
TEMPLATE_DIR: ${{ env.template_path }}
116109
TEMPLATE_VERSION: ${{ env.source_sha }}
117110
WORKSPACE_IMAGE: "${{ secrets.CONTAINER_REGISTRY }}/${{ vars.CONTAINER_REGISTRY_PATH }}/coder-workspace:${{ needs.publish-image.outputs.image_tag }}"
118-
PUBLISH_MODE: ${{ env.publish_mode }}
119111
shell: bash
120112
# yamllint disable-line rule:indentation
121113
run: |
122-
if [[ "${PUBLISH_MODE}" == "release" ]]; then
123-
export TEMPLATE_NAME
124-
TEMPLATE_NAME="$(echo "${TEMPLATE_DIR}" | cut -d/ -f3)"
125-
export RELEASE_MSG="[Release Notes](https://github.com/${{ github.repository }}/releases/tag/${TEMPLATE_VERSION})"
126-
export TEST_MODE=false
127-
else
128-
export TEMPLATE_NAME
129-
TEMPLATE_NAME="$(echo "${TEMPLATE_DIR}" | cut -d/ -f3)-test"
130-
export RELEASE_MSG="[Changes](https://github.com/${{ github.repository }}/commit/${TEMPLATE_VERSION})"
131-
export TEST_MODE=true
132-
fi
114+
export TEMPLATE_NAME
115+
TEMPLATE_NAME="$(echo "${TEMPLATE_DIR}" | cut -d/ -f3)"
116+
export RELEASE_MSG="[Release Notes](https://github.com/${{ github.repository }}/releases/tag/${TEMPLATE_VERSION})"
117+
export TEST_MODE=false
133118
echo "Publishing template ${TEMPLATE_DIR} as ${TEMPLATE_NAME}..."
134119
set -x
135120
coder template push \

.github/workflows/test-image.yaml

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
---
2+
# yamllint disable rule:line-length
3+
name: test image
4+
5+
on:
6+
pull_request:
7+
paths:
8+
- .github/workflows/test-image.yaml
9+
- images/homelab-workspace/**
10+
11+
concurrency:
12+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
13+
cancel-in-progress: true
14+
15+
permissions:
16+
contents: read
17+
18+
jobs:
19+
build:
20+
permissions:
21+
contents: read
22+
packages: write
23+
uses: ppat/github-workflows/.github/workflows/build-docker-image.yaml@5a96ced8ceefd58062f6b91ee9d6f3a31cd06e1c # v6.0.0
24+
with:
25+
image_context_path: images/homelab-workspace
26+
label_title: "Homelab Workspace"
27+
label_description: "Homelab workspace image"
28+
platforms: linux/amd64,linux/arm64
29+
private_registry_repository: ${{ vars.CONTAINER_REGISTRY_PATH }}/coder-workspace
30+
private_registry_build_cache: ${{ vars.CONTAINER_REGISTRY_CACHE_PATH }}/coder-workspace
31+
git_ref: ${{ github.head_ref }}
32+
timeout_minutes: 180
33+
secrets:
34+
private_registry_username: ${{ secrets.CONTAINER_REGISTRY_USERNAME }}
35+
private_registry_token: ${{ secrets.CONTAINER_REGISTRY_PASSWORD }}
36+
private_registry: ${{ secrets.CONTAINER_REGISTRY }}
37+
tailscale_oauth_client_id: ${{ secrets.TAILSCALE_OAUTH_CLIENT_ID }}
38+
tailscale_oauth_secret: ${{ secrets.TAILSCALE_OAUTH_SECRET }}
39+
build_secrets: |
40+
FETCH_GH_TOKEN=${{ secrets.GITHUB_TOKEN }}
Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
---
2+
# yamllint disable rule:line-length
3+
name: test template
4+
5+
on:
6+
pull_request:
7+
paths:
8+
- .github/compose/**
9+
- .github/workflows/test-template.yaml
10+
- templates/kubernetes/homelab-workspace/**
11+
12+
concurrency:
13+
group: ${{ github.workflow }}-${{ github.event.pull_request.number }}
14+
cancel-in-progress: true
15+
16+
permissions:
17+
contents: read
18+
19+
env:
20+
CODER_URL: http://localhost:7080
21+
KIND_CLUSTER: coder-template-test
22+
TEMPLATE_NAME: homelab-workspace-test
23+
WORKSPACE_NAME: template-test
24+
25+
jobs:
26+
integration:
27+
runs-on: ubuntu-latest
28+
timeout-minutes: 30
29+
steps:
30+
- name: Checkout
31+
uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
32+
with:
33+
fetch-depth: 1
34+
persist-credentials: false
35+
36+
- name: Create Kubernetes test cluster
37+
run: kind create cluster --name "${KIND_CLUSTER}"
38+
39+
- name: Start local Coder
40+
env:
41+
COMPOSE_FILE: .github/compose/coder-template-test.yaml
42+
CODER_KUBECONFIG: ${{ runner.temp }}/kubeconfig-coder
43+
RUNNER_KUBECONFIG: ${{ runner.temp }}/kubeconfig-runner
44+
shell: bash
45+
run: |
46+
kind export kubeconfig --name "${KIND_CLUSTER}" --kubeconfig "${RUNNER_KUBECONFIG}"
47+
cp "${RUNNER_KUBECONFIG}" "${CODER_KUBECONFIG}"
48+
sed -Ei "s#https://127\\.0\\.0\\.1:[0-9]+#https://${KIND_CLUSTER}-control-plane:6443#" "${CODER_KUBECONFIG}"
49+
export CODER_KUBECONFIG
50+
docker compose -f "${COMPOSE_FILE}" up --detach
51+
52+
- name: Wait for local Coder
53+
shell: bash
54+
run: |
55+
timeout 5m bash -c "until curl --fail --silent --show-error \"${CODER_URL}/api/v2/buildinfo\" > /dev/null; do sleep 2; done"
56+
57+
- name: Create local Coder user
58+
env:
59+
CODER_KUBECONFIG: ${{ runner.temp }}/kubeconfig-coder
60+
run: |
61+
docker compose -f .github/compose/coder-template-test.yaml exec --tty=false coder \
62+
coder server create-admin-user --username ci --email ci@example.invalid --password ci-password
63+
64+
- name: Install and log in to Coder CLI
65+
shell: bash
66+
run: |
67+
source .github/compose/.env
68+
curl --fail --silent --show-error --location \
69+
"https://github.com/coder/coder/releases/download/${CODER_VERSION}/coder_${CODER_VERSION#v}_linux_amd64.tar.gz" \
70+
| tar --extract --gzip --to-stdout ./coder > "${RUNNER_TEMP}/coder"
71+
chmod +x "${RUNNER_TEMP}/coder"
72+
echo "${RUNNER_TEMP}" >> "${GITHUB_PATH}"
73+
export PATH="${RUNNER_TEMP}:${PATH}"
74+
export CODER_SESSION_TOKEN
75+
CODER_SESSION_TOKEN="$(curl --fail --silent --show-error -X POST "${CODER_URL}/api/v2/users/login" \
76+
-H 'Content-Type: application/json' \
77+
--data '{"email":"ci@example.invalid","password":"ci-password"}' | jq --raw-output .session_token)"
78+
test -n "${CODER_SESSION_TOKEN}" && test "${CODER_SESSION_TOKEN}" != null
79+
coder login --use-token-as-session "${CODER_URL}"
80+
81+
- name: Publish template
82+
env:
83+
GH_TOKEN: ${{ github.token }}
84+
shell: bash
85+
run: |
86+
RELEASE_TAG="$(gh release view --repo "${{ github.repository }}" --json tagName --jq .tagName)"
87+
WORKSPACE_IMAGE="ghcr.io/ppat/coder-workspace:${RELEASE_TAG}"
88+
coder template push --directory templates/kubernetes/homelab-workspace \
89+
--var "workspace_image=${WORKSPACE_IMAGE}" --var test_mode=true \
90+
--name "${{ github.sha }}" --yes "${TEMPLATE_NAME}"
91+
92+
- name: Start workspace
93+
shell: bash
94+
run: |
95+
coder create "${WORKSPACE_NAME}" --template "${TEMPLATE_NAME}" --no-wait --yes \
96+
--parameter memory=4 --parameter preferred_nodes='[]' --parameter memory_watchdog_mode=enforce
97+
98+
- name: Ping workspace agent
99+
shell: bash
100+
run: coder ping --num 3 --timeout 30s "${WORKSPACE_NAME}"
101+
102+
- name: Run command over workspace SSH
103+
shell: bash
104+
run: |
105+
coder ssh "${WORKSPACE_NAME}" -- env
106+
107+
- name: Show Coder logs on failure
108+
if: failure()
109+
env:
110+
CODER_KUBECONFIG: ${{ runner.temp }}/kubeconfig-coder
111+
run: docker compose -f .github/compose/coder-template-test.yaml logs
Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
---
22
# yamllint disable rule:line-length
3-
name: test
3+
name: test watchdog
44

55
# Repo-local rather than a reusable workflow because ppat/github-workflows has
66
# nothing for "execute a test script" - the shellcheck workflow only lints,
@@ -13,7 +13,7 @@ name: test
1313
on:
1414
pull_request:
1515
paths:
16-
- .github/workflows/test.yaml
16+
- .github/workflows/test-watchdog.yaml
1717
- templates/kubernetes/homelab-workspace/script-memory-watchdog*.sh
1818
workflow_dispatch:
1919

CLAUDE.md

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,7 @@ tflint --config=../../../.tflint.hcl
3232

3333
CI (`.github/workflows/lint.yaml`) runs the same checks per file-type via reusable workflows in `ppat/github-workflows`, scoped to changed files on PRs, or everything on `workflow_dispatch`/schedule.
3434

35-
A separate workflow, `.github/workflows/test.yaml`, runs the one thing here that's a test rather than a linter: its `watchdog` job runs `script-memory-watchdog-test.sh` and fails the build on a failed assertion. It's repo-local rather than a reusable workflow because `ppat/github-workflows` has nothing for "execute a test script", and the suite needs only bash and a writable `TMPDIR`:
35+
A separate workflow, `.github/workflows/test-watchdog.yaml`, runs the one thing here that's a test rather than a linter: its `watchdog` job runs `script-memory-watchdog-test.sh` and fails the build on a failed assertion. It's repo-local rather than a reusable workflow because `ppat/github-workflows` has nothing for "execute a test script", and the suite needs only bash and a writable `TMPDIR`:
3636

3737
```bash
3838
./templates/kubernetes/homelab-workspace/script-memory-watchdog-test.sh
@@ -55,7 +55,10 @@ Commitlint (`commitlint.config.js`) enforces Conventional Commits.
5555
2. The published GitHub release triggers `.github/workflows/publish.yaml`, which builds the workspace image for `linux/amd64,linux/arm64` and pushes it to the private registry.
5656
3. The same publish workflow pushes the Terraform template to the live Coder deployment, tagged with the released version.
5757

58-
PRs affecting the image, template, or publish workflow exercise only `.github/workflows/publish.yaml` in test mode — no release simulation is needed. See [TESTING.md](TESTING.md), which is required reading before touching `templates/**` or `images/**`.
58+
PR validation is deliberately narrower than publishing: `test-image.yaml` builds image changes against the private
59+
registry cache, while `test-template.yaml` publishes template changes to a local Coder/Postgres test control plane
60+
and verifies a workspace on the test cluster. See [TESTING.md](TESTING.md), which is required reading before touching
61+
`templates/**` or `images/**`.
5962

6063
## Where things live
6164

@@ -76,7 +79,7 @@ Quick orientation map — for what each piece is *for* and the decisions behind
7679
| `script-agent-startup.sh` / `script-prepare-workspace.sh` | Scripts run on agent/workspace startup |
7780
| `script-container-entrypoint.sh` | The workspace container's `command`. Wipes `/tmp` and `exec`s Coder's generated `/workspace-init.sh` — the wipe must precede the agent, see the gotcha below |
7881
| `script-memory-watchdog.sh` | Userspace memory watchdog — see [DESIGN.md](DESIGN.md#design-tensions-and-decisions). It bounds the **standing population of restartable helpers** against a fixed **2048 MiB envelope** for the VS Code tree (per-role shares of it, PSS, ten-minute dwell) and records every per-process sweep. It does **not** try to prevent an acute OOM. `memory_watchdog_mode` selects `observe` / `enforce` (the default; arms every role). `enforce-all` is retired and honoured as `enforce` |
79-
| `script-memory-watchdog-test.sh` | Fixture tests for the watchdog's envelope and shares, process selection, dwell, the oversize rule and the kill-rate report. Run by hand (`./script-memory-watchdog-test.sh`) and by the `watchdog` job in `.github/workflows/test.yaml`. `kill` is shadowed by a function throughout — the fixture pids are real pids in whatever container runs the suite |
82+
| `script-memory-watchdog-test.sh` | Fixture tests for the watchdog's envelope and shares, process selection, dwell, the oversize rule and the kill-rate report. Run by hand (`./script-memory-watchdog-test.sh`) and by the `watchdog` job in `.github/workflows/test-watchdog.yaml`. `kill` is shadowed by a function throughout — the fixture pids are real pids in whatever container runs the suite |
8083
| `script-vscode-server-gc.sh` | Weekly GC of `~/.vscode-server` (interrupted downloads, superseded server versions/extensions, orphaned CLI binaries — see the script's own header for the exact signal per class, and the `coder_script.vscode_server_gc` comment in `scripts.tf` for why it's template-owned rather than dotfiles-owned) |
8184

8285
**Image** (`images/homelab-workspace/Dockerfile`): three build stages — `base` (minimal bootstrap deps) → `system-base` (`unminimize` + full interactive toolset) → final stage (env vars into `/etc/environment`, fixed-UID/GID `coder` user, `USER coder`). All `apt`-touching `RUN` steps use BuildKit cache mounts — match that pattern when adding packages.

0 commit comments

Comments
 (0)