tenseleyflow/shithub / 1baf580

Browse files

actions: document arbitrary repo smoke

Authored by mfwolffe <wolffemf@dukes.jmu.edu>
SHA
1baf5803a9a155cdfaaf91022b3c5fc9f1910519
Parents
b9f1094
Tree
21a4e6d

5 changed files

StatusFile+-
M docs/internal/runbooks/actions-runner.md 92 1
M docs/internal/runbooks/runner-deploy.md 7 0
M docs/public/user/actions.md 25 0
M internal/actions/workflow/parse_test.go 36 0
A tests/fixtures/workflows/arbitrary-repo-smoke.yml 13 0
docs/internal/runbooks/actions-runner.mdmodified
@@ -10,7 +10,7 @@ For host provisioning and the systemd/Ansible path, see
10
 
10
 
11
 Prereqs:
11
 Prereqs:
12
 
12
 
13
-- Database migrations are current through `0057_workflow_job_secret_masks.sql`.
13
+- Database migrations are current through `0067_runner_pool_ops.sql`.
14
 - `SHITHUB_TOTP_KEY` or `auth.totp_key_b64` is set on the web process.
14
 - `SHITHUB_TOTP_KEY` or `auth.totp_key_b64` is set on the web process.
15
 - Object storage is configured if testing artifact upload.
15
 - Object storage is configured if testing artifact upload.
16
 - Docker or Podman is installed on the runner host.
16
 - Docker or Podman is installed on the runner host.
@@ -175,6 +175,97 @@ Environment variables use the `SHITHUB_RUNNER_` prefix, for example
175
 Use `--expires-in` only for tokens that your automation rotates before expiry;
175
 Use `--expires-in` only for tokens that your automation rotates before expiry;
176
 the runner presents its registration token on every heartbeat.
176
 the runner presents its registration token on every heartbeat.
177
 
177
 
178
+## Arbitrary Repository Smoke
179
+
180
+Use this checklist after provisioning a shared runner pool or changing runner
181
+labels. The purpose is to prove that ordinary repositories can use the pool
182
+without repo-specific labels.
183
+
184
+Pick at least two repositories:
185
+
186
+- `mfwolffe/scratch`, the historical dogfood repo;
187
+- one additional public repository;
188
+- one private repository if one is available for the operator account.
189
+
190
+In each repository, commit this file as `.shithub/workflows/smoke.yml` on
191
+`trunk`:
192
+
193
+```yaml
194
+name: Smoke
195
+on:
196
+  push:
197
+    branches: [trunk]
198
+jobs:
199
+  green:
200
+    runs-on: ubuntu-latest
201
+    steps:
202
+      - uses: actions/checkout@v4
203
+      - name: Verify checkout
204
+        run: test -f README.md || test -f readme.md || pwd
205
+      - name: Smoke
206
+        run: printf 'shithub actions smoke passed\n'
207
+```
208
+
209
+Expected results for each repo:
210
+
211
+- the repo heading shows a green check after the push;
212
+- the Actions run page shows `Triggered via push` on `refs/heads/trunk`;
213
+- the `green` job is completed with conclusion `success`;
214
+- the checkout step logs the scoped repository URL for that repo;
215
+- the smoke step log contains `shithub actions smoke passed`;
216
+- the check run attached to the commit agrees with the workflow run state;
217
+- the downloaded or raw archived step log matches the in-page step log.
218
+
219
+Confirm the pool is shared:
220
+
221
+```sh
222
+shithubd admin runner list --output json
223
+shithubd admin runner queue --output json
224
+```
225
+
226
+The same online runner labels should satisfy both repositories. The smoke
227
+workflow must use `runs-on: ubuntu-latest`; do not add per-repo labels for this
228
+test.
229
+
230
+Unsupported-label negative test:
231
+
232
+```yaml
233
+name: Unsupported runner label
234
+on:
235
+  workflow_dispatch:
236
+jobs:
237
+  nope:
238
+    runs-on: windows-latest
239
+    steps:
240
+      - run: echo should-not-run
241
+```
242
+
243
+Trigger it manually. The run should stay queued and the run page should say
244
+`Waiting for runner with labels: windows-latest`. `shithubd admin runner queue
245
+--output json` should show one queued `windows-latest` job with zero matching
246
+runners. Cancel the run after confirming the diagnostic.
247
+
248
+Untrusted-PR secret negative test:
249
+
250
+1. Add a repo secret named `S41J_SECRET_SMOKE` with any non-production value.
251
+2. Open an untrusted pull request whose workflow prints whether that secret is
252
+   present.
253
+3. Before approval, confirm the claimed job contains no injected secrets and
254
+   logs do not contain the secret value.
255
+4. Only after explicit approval should the run be allowed through the trusted
256
+   secret path.
257
+
258
+Runner-outage negative test:
259
+
260
+1. Drain every shared runner with `shithubd admin runner drain --id <id>`.
261
+2. Push the smoke workflow to a test repo.
262
+3. Confirm the run stays queued with the requested `ubuntu-latest` label visible.
263
+4. Undrain one runner and confirm the same queued job is claimed and completes.
264
+
265
+This smoke is considered passing only when scratch and the second repository
266
+both complete from the same shared label set and the negative cases produce
267
+clear queued/secret-denied behavior.
268
+
178
 The Ansible runner role creates the `shithub-actions` bridge, runs the
269
 The Ansible runner role creates the `shithub-actions` bridge, runs the
179
 allowlist resolver at `172.30.0.1`, and installs firewall rules that
270
 allowlist resolver at `172.30.0.1`, and installs firewall rules that
180
 reject direct-IP egress from step containers. If you run the binary
271
 reject direct-IP egress from step containers. If you run the binary
docs/internal/runbooks/runner-deploy.mdmodified
@@ -215,6 +215,13 @@ Expected state:
215
 Repeat with `exit 1`; the check should complete with conclusion
215
 Repeat with `exit 1`; the check should complete with conclusion
216
 `failure`.
216
 `failure`.
217
 
217
 
218
+Before declaring a shared pool available to arbitrary repositories, run the
219
+[arbitrary repository smoke](./actions-runner.md#arbitrary-repository-smoke)
220
+checklist against scratch and at least one additional repository. The single
221
+repo check above proves the runner process works; the arbitrary-repo smoke
222
+proves label routing, checkout scoping, archived logs, check runs, and negative
223
+queue/secret cases across normal repositories.
224
+
218
 Sandbox smoke checks:
225
 Sandbox smoke checks:
219
 
226
 
220
 ```yaml
227
 ```yaml
docs/public/user/actions.mdmodified
@@ -24,6 +24,31 @@ Commit that file as `.shithub/workflows/smoke.yml` and push to the repository.
24
 The run appears under the repository's Actions tab and its job also appears as
24
 The run appears under the repository's Actions tab and its job also appears as
25
 a check run on matching pull requests.
25
 a check run on matching pull requests.
26
 
26
 
27
+## Copy-paste smoke workflow
28
+
29
+Use this workflow to confirm a normal repository can use the shared Linux pool.
30
+It runs on every push to `trunk` while Actions are enabled for the repository
31
+and a runner advertising `ubuntu-latest` is online.
32
+
33
+```yaml
34
+name: Smoke
35
+on:
36
+  push:
37
+    branches: [trunk]
38
+jobs:
39
+  green:
40
+    runs-on: ubuntu-latest
41
+    steps:
42
+      - uses: actions/checkout@v4
43
+      - name: Verify checkout
44
+        run: test -f README.md || test -f readme.md || pwd
45
+      - name: Smoke
46
+        run: printf 'shithub actions smoke passed\n'
47
+```
48
+
49
+The same file should work in any repository that is allowed by the site, org,
50
+and repo Actions policies. It should not need a repo-specific runner label.
51
+
27
 ## What works today
52
 ## What works today
28
 
53
 
29
 - `push`, `pull_request`, `schedule`, and `workflow_dispatch` triggers
54
 - `push`, `pull_request`, `schedule`, and `workflow_dispatch` triggers
internal/actions/workflow/parse_test.gomodified
@@ -71,6 +71,42 @@ func TestParse_CheckoutOnly(t *testing.T) {
71
 	}
71
 	}
72
 }
72
 }
73
 
73
 
74
+func TestParse_ArbitraryRepoSmoke(t *testing.T) {
75
+	t.Parallel()
76
+	w, diags, err := workflow.Parse(readFixture(t, "arbitrary-repo-smoke"))
77
+	if err != nil {
78
+		t.Fatalf("Parse: %v", err)
79
+	}
80
+	if len(diags) != 0 {
81
+		t.Fatalf("unexpected diagnostics: %v", diags)
82
+	}
83
+	if w.Name != "Smoke" {
84
+		t.Fatalf("Name = %q, want Smoke", w.Name)
85
+	}
86
+	if w.On.Push == nil || len(w.On.Push.Branches) != 1 || w.On.Push.Branches[0] != "trunk" {
87
+		t.Fatalf("push branches = %+v", w.On.Push)
88
+	}
89
+	if len(w.Jobs) != 1 {
90
+		t.Fatalf("len(Jobs) = %d", len(w.Jobs))
91
+	}
92
+	job := w.Jobs[0]
93
+	if job.Key != "green" || job.RunsOn != "ubuntu-latest" {
94
+		t.Fatalf("job = %+v", job)
95
+	}
96
+	if len(job.Steps) != 3 {
97
+		t.Fatalf("steps = %+v", job.Steps)
98
+	}
99
+	if job.Steps[0].Uses != "actions/checkout@v4" {
100
+		t.Fatalf("checkout step = %+v", job.Steps[0])
101
+	}
102
+	if job.Steps[1].Name != "Verify checkout" || !strings.Contains(job.Steps[1].Run, "README.md") {
103
+		t.Fatalf("verify step = %+v", job.Steps[1])
104
+	}
105
+	if job.Steps[2].Name != "Smoke" || !strings.Contains(job.Steps[2].Run, "shithub actions smoke passed") {
106
+		t.Fatalf("smoke step = %+v", job.Steps[2])
107
+	}
108
+}
109
+
74
 func TestParse_MultiJob(t *testing.T) {
110
 func TestParse_MultiJob(t *testing.T) {
75
 	t.Parallel()
111
 	t.Parallel()
76
 	w, diags, err := workflow.Parse(readFixture(t, "multi-job"))
112
 	w, diags, err := workflow.Parse(readFixture(t, "multi-job"))
tests/fixtures/workflows/arbitrary-repo-smoke.ymladded
@@ -0,0 +1,13 @@
1
+name: Smoke
2
+on:
3
+  push:
4
+    branches: [trunk]
5
+jobs:
6
+  green:
7
+    runs-on: ubuntu-latest
8
+    steps:
9
+      - uses: actions/checkout@v4
10
+      - name: Verify checkout
11
+        run: test -f README.md || test -f readme.md || pwd
12
+      - name: Smoke
13
+        run: printf 'shithub actions smoke passed\n'