Commit 5bce95a7da
Verified · cmc
e2e/runnerweb_test.go added +48
| @@ -0,0 +1,48 @@ | ||
| 1 | package e2e | |
| 2 | ||
| 3 | import ( | |
| 4 | "net/url" | |
| 5 | "os" | |
| 6 | "strings" | |
| 7 | "testing" | |
| 8 | ) | |
| 9 | ||
| 10 | // The settings page attaches and detaches runners through the same | |
| 11 | // commands the CLI uses, and lists what is attached. | |
| 12 | func TestRunnerSettingsWeb(t *testing.T) { | |
| 13 | inst := startInstanceWith(t, "[web]\nmode = \"accounts\"\n") | |
| 14 | aliceKey := inst.newKey(t, "alice") | |
| 15 | inst.admin(t, "admin", "user", "create", "alice", | |
| 16 | "--key", aliceKey+".pub", "--email", "alice@example.test", "--verified") | |
| 17 | if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 { | |
| 18 | t.Fatalf("repo create: %s", errOut) | |
| 19 | } | |
| 20 | runnerKey := inst.newKey(t, "laptop") | |
| 21 | pub, _ := os.ReadFile(runnerKey + ".pub") | |
| 22 | ||
| 23 | alice := inst.login(t, aliceKey) | |
| 24 | settings := inst.base() + "/alice/app/settings" | |
| 25 | _, body := browserGet(t, alice, settings) | |
| 26 | if !strings.Contains(body, "No runners attached") { | |
| 27 | t.Fatalf("empty state missing:\n%s", body) | |
| 28 | } | |
| 29 | if status, _ := browserPost(t, alice, settings, url.Values{"field": {"runner-add"}, "key": {string(pub)}}); status != 200 { | |
| 30 | t.Fatalf("runner-add post: %d", status) | |
| 31 | } | |
| 32 | out, _, _ := inst.ssh(t, aliceKey, "", "repo", "runner", "list", "alice/app", "--json") | |
| 33 | if !strings.Contains(out, `"fingerprint":"SHA256:`) { | |
| 34 | t.Fatalf("not attached after the form: %s", out) | |
| 35 | } | |
| 36 | fp := out[strings.Index(out, "SHA256:"):] | |
| 37 | fp = fp[:strings.Index(fp, `"`)] | |
| 38 | _, body = browserGet(t, alice, settings) | |
| 39 | if !strings.Contains(body, fp) || !strings.Contains(body, `value="runner-remove"`) { | |
| 40 | t.Fatalf("attached runner not listed:\n%s", body) | |
| 41 | } | |
| 42 | if status, _ := browserPost(t, alice, settings, url.Values{"field": {"runner-remove"}, "fingerprint": {fp}}); status != 200 { | |
| 43 | t.Fatalf("runner-remove post: %d", status) | |
| 44 | } | |
| 45 | if out, _, _ = inst.ssh(t, aliceKey, "", "repo", "runner", "list", "alice/app", "--json"); strings.Contains(out, fp) { | |
| 46 | t.Fatalf("still attached after remove: %s", out) | |
| 47 | } | |
| 48 | } | |
internal/httpd/settings.go +19 −1
| @@ -21,6 +21,7 @@ type settingsPage struct { | ||
| 21 | 21 | Branches []gitutil.Ref |
| 22 | 22 | DepsEnabled bool |
| 23 | 23 | Deps control.DepsOut |
| 24 | Runners []store.RepoRunner | |
| 24 | 25 | Notice string |
| 25 | 26 | } |
| 26 | 27 | |
| @@ -41,10 +42,13 @@ func (s *Server) settingsForm(w http.ResponseWriter, r *http.Request, u store.Us | ||
| 41 | 42 | // prints (#164). |
| 42 | 43 | var deps control.DepsOut |
| 43 | 44 | s.runControlInto(u, []string{"repo", "deps", "status", repo.Path()}, &deps) |
| 45 | var runners []store.RepoRunner | |
| 46 | s.runControlInto(u, []string{"repo", "runner", "list", repo.Path()}, &runners) | |
| 44 | 47 | s.render(w, "settings.html", settingsPage{ |
| 45 | 48 | repoPage: p, Topics: topics, Branches: branches, |
| 46 | 49 | DepsEnabled: deps.Enabled, Deps: deps, |
| 47 | Notice: s.takeFlash(w, r), | |
| 50 | Runners: runners, | |
| 51 | Notice: s.takeFlash(w, r), | |
| 48 | 52 | }) |
| 49 | 53 | } |
| 50 | 54 | |
| @@ -113,6 +117,20 @@ func (s *Server) settingsSubmit(w http.ResponseWriter, r *http.Request, u store. | ||
| 113 | 117 | s.settingsRedirect(w, r, "name at least one topic") |
| 114 | 118 | return |
| 115 | 119 | } |
| 120 | case "runner-add": | |
| 121 | body := v("key") | |
| 122 | if body == "" { | |
| 123 | s.settingsRedirect(w, r, "paste the runner's public key") | |
| 124 | return | |
| 125 | } | |
| 126 | msg, ok := s.runControlStdin(u, []string{"repo", "runner", "add", repo}, body+"\n") | |
| 127 | if ok { | |
| 128 | msg = "" | |
| 129 | } | |
| 130 | s.settingsRedirect(w, r, msg) | |
| 131 | return | |
| 132 | case "runner-remove": | |
| 133 | argv = []string{"repo", "runner", "remove", repo, v("fingerprint")} | |
| 116 | 134 | default: |
| 117 | 135 | s.settingsRedirect(w, r, "unknown setting") |
| 118 | 136 | return |
internal/web/templates/settings.html +20
| @@ -157,6 +157,26 @@ depends on.</p> | ||
| 157 | 157 | {{else}}<p class="none">Nothing behind{{if not .Deps.LastCheck}} — the first check has not run yet{{end}}.</p>{{end}} |
| 158 | 158 | {{end}} |
| 159 | 159 | |
| 160 | <h2>Runners</h2> | |
| 161 | {{if .Runners}} | |
| 162 | <ul class="protlist"> | |
| 163 | {{range .Runners}}<li><code>{{.Fingerprint}}</code> <span class="meta">{{.Username}}{{if .LastSeen}}, last poll {{.LastSeen}}{{else}}, never polled{{end}}{{if .BuildNumber}}, running {{.BuildRepo}} #{{.BuildNumber}} {{.BuildJob}}{{end}}</span> | |
| 164 | <form method="post" action="{{$base}}" class="inline"> | |
| 165 | <input type="hidden" name="field" value="runner-remove"> | |
| 166 | <input type="hidden" name="fingerprint" value="{{.Fingerprint}}"> | |
| 167 | <button type="submit" class="linklike">Detach</button> | |
| 168 | </form></li> | |
| 169 | {{end}} | |
| 170 | </ul> | |
| 171 | {{else}}<p class="meta">No runners attached. Builds for this repository run on the runners attached here; a repository with none queues builds nothing claims.</p>{{end}} | |
| 172 | <form method="post" action="{{$base}}" class="setform"> | |
| 173 | <input type="hidden" name="field" value="runner-add"> | |
| 174 | <label for="runner-key">Attach a runner</label> | |
| 175 | <textarea id="runner-key" name="key" rows="3" placeholder="ssh-ed25519 AAAA… (from gitbay-runner init)"></textarea> | |
| 176 | <button type="submit">Attach</button> | |
| 177 | </form> | |
| 178 | <p class="meta">Install <code>gitbay-runner</code>, run <code>gitbay-runner init</code>, and paste the key it prints. The runner builds your commits with the repository's secrets; merge requests from forks wait unless it runs with <code>-untrusted</code>.</p> | |
| 179 | ||
| 160 | 180 | <h2>Lifecycle</h2> |
| 161 | 181 | <form method="post" action="{{$base}}" class="setform"> |
| 162 | 182 | <input type="hidden" name="field" value="archive"> |