e2e/runnerattach_test.go
129 lines · 5272 bytes
1package e2e
2
3import (
4 "fmt"
5 "os"
6 "os/exec"
7 "path/filepath"
8 "strings"
9 "testing"
10)
11
12// The whole flow a user goes through: init on their machine, attach the
13// printed key to their repository, start the runner from the config init
14// wrote. The runner builds their push and leaves a fork's merge request
15// head alone until started with -untrusted.
16func TestAttachedRunnerBuildsOwnRepo(t *testing.T) {
17 t.Parallel()
18 inst := startInstance(t)
19 inst.runner = buildRunner(t)
20 aliceKey := inst.newKey(t, "alice")
21 inst.admin(t, "admin", "user", "create", "alice", "--key", aliceKey+".pub")
22 bobKey := inst.newKey(t, "bob")
23 inst.admin(t, "admin", "user", "create", "bob", "--key", bobKey+".pub")
24 if _, errOut, code := inst.ssh(t, aliceKey, "", "repo", "create", "alice/app"); code != 0 {
25 t.Fatalf("repo create: %s", errOut)
26 }
27
28 // init on "alice's laptop".
29 xdg := t.TempDir()
30 initCmd := exec.Command(inst.runner, "init", "-remote", "git@127.0.0.1")
31 initCmd.Env = append(os.Environ(), "XDG_CONFIG_HOME="+xdg)
32 initOut, err := initCmd.CombinedOutput()
33 if err != nil {
34 t.Fatalf("init: %v\n%s", err, initOut)
35 }
36 cdir := filepath.Join(xdg, "gitbay-runner")
37 pub, err := os.ReadFile(filepath.Join(cdir, "id_ed25519.pub"))
38 if err != nil {
39 t.Fatal(err)
40 }
41 if !strings.Contains(string(initOut), strings.TrimSpace(string(pub))) {
42 t.Fatalf("init did not print the key:\n%s", initOut)
43 }
44
45 // The unattached key claims nothing, even with a build queued.
46 work := t.TempDir()
47 env := inst.gitEnv(aliceKey)
48 mustGit(t, work, env, "clone", inst.sshURL("alice/app"), "w")
49 dir := filepath.Join(work, "w")
50 os.MkdirAll(filepath.Join(dir, ".gitbay"), 0o755)
51 os.WriteFile(filepath.Join(dir, ".gitbay", "ci.yml"), []byte("jobs:\n unit:\n steps:\n - echo built\n"), 0o644)
52 mustGit(t, dir, env, "checkout", "-q", "-b", "main")
53 mustGit(t, dir, env, "add", ".")
54 mustGit(t, dir, env, "commit", "-q", "-m", "ci")
55 mustGit(t, dir, env, "push", "-q", "origin", "main")
56
57 run := func(extra ...string) string {
58 t.Helper()
59 // No -i in ssh-opts: the identity from the config is what
60 // authenticates, which is the point.
61 opts := fmt.Sprintf("-p %d -o StrictHostKeyChecking=no -o UserKnownHostsFile=%s -o BatchMode=yes",
62 inst.port, filepath.Join(inst.sshDir, "known_hosts"))
63 args := append([]string{"-config", filepath.Join(cdir, "config.toml"), "-once",
64 "-ssh-opts", opts,
65 "-clone-base", fmt.Sprintf("ssh://git@127.0.0.1:%d", inst.port),
66 "-workdir", t.TempDir()}, extra...)
67 cmd := exec.Command(inst.runner, args...)
68 cmd.Env = append(os.Environ(), "XDG_CONFIG_HOME="+xdg, "GIT_CONFIG_NOSYSTEM=1", "GIT_CONFIG_GLOBAL=/dev/null")
69 out, err := cmd.CombinedOutput()
70 if err != nil {
71 t.Fatalf("runner: %v\n%s", err, out)
72 }
73 return string(out)
74 }
75 if out, _, _ := inst.ssh(t, aliceKey, "", "build", "list", "alice/app"); !strings.Contains(out, "unit\tpending") {
76 t.Fatalf("build not pending before attach: %s", out)
77 }
78 run()
79 if out, _, _ := inst.ssh(t, aliceKey, "", "build", "list", "alice/app"); !strings.Contains(out, "unit\tpending") {
80 t.Fatalf("unattached runner claimed the build: %s", out)
81 }
82
83 // Attach with the printed key.
84 if _, errOut, code := inst.ssh(t, aliceKey, string(pub), "repo", "runner", "add", "alice/app"); code != 0 {
85 t.Fatalf("repo runner add: %s", errOut)
86 }
87 out, _, _ := inst.ssh(t, aliceKey, "", "repo", "runner", "list", "alice/app", "--json")
88 if !strings.Contains(out, `"username":"alice"`) || strings.Contains(out, `"last_seen":"20`) {
89 t.Fatalf("list after attach: %s", out)
90 }
91
92 // The runner builds it.
93 run()
94 if out, _, _ = inst.ssh(t, aliceKey, "", "build", "list", "alice/app"); !strings.Contains(out, "unit\tsuccess") {
95 t.Fatalf("build not built by the attached runner: %s", out)
96 }
97 if out, _, _ = inst.ssh(t, aliceKey, "", "repo", "runner", "list", "alice/app", "--json"); !strings.Contains(out, `"last_seen":"20`) {
98 t.Fatalf("no heartbeat after a poll: %s", out)
99 }
100
101 // bob forks and opens a merge request: an untrusted build in the target.
102 if _, errOut, code := inst.ssh(t, bobKey, "", "repo", "fork", "alice/app"); code != 0 {
103 t.Fatalf("fork: %s", errOut)
104 }
105 bwork := t.TempDir()
106 benv := inst.gitEnv(bobKey)
107 mustGit(t, bwork, benv, "clone", inst.sshURL("bob/app"), "w")
108 bdir := filepath.Join(bwork, "w")
109 mustGit(t, bdir, benv, "checkout", "-q", "-b", "feat")
110 os.WriteFile(filepath.Join(bdir, "f.txt"), []byte("y\n"), 0o644)
111 mustGit(t, bdir, benv, "add", ".")
112 mustGit(t, bdir, benv, "commit", "-q", "-m", "change")
113 mustGit(t, bdir, benv, "push", "-q", "origin", "feat")
114 if _, _, code := inst.ssh(t, bobKey, "", "build", "cancel", "bob/app", "1"); code != 0 {
115 t.Fatal("cancel bob's own build")
116 }
117 if _, errOut, code := inst.ssh(t, bobKey, "", "mr", "create", "alice/app",
118 "--source", "bob/app:feat", "--target", "main", "--title", "change"); code != 0 {
119 t.Fatalf("mr create: %s", errOut)
120 }
121 run()
122 if out, _, _ = inst.ssh(t, aliceKey, "", "build", "list", "alice/app"); strings.Count(out, "pending") != 1 {
123 t.Fatalf("fork head was claimed without -untrusted:\n%s", out)
124 }
125 run("-untrusted")
126 if out, _, _ = inst.ssh(t, aliceKey, "", "build", "list", "alice/app"); strings.Contains(out, "pending") {
127 t.Fatalf("fork head not built with -untrusted:\n%s", out)
128 }
129}