ci_socket_test.go
⎇
Raw
1package e2e
2
3import (
4 "strings"
5 "testing"
6)
7
8// engine_socket lets a step talk to the container engine that runs the
9// pipeline. The server must opt in with CI_ENGINE_SOCKET.
10
11const ciEngineSocketTOML = `
12image = "debian:latest"
13
14[on]
15manual = true
16
17[[steps]]
18name = "build-image"
19run_sh = "engine-build ."
20engine_socket = true
21
22[[steps]]
23name = "plain"
24run_sh = "echo plain"
25`
26
27// ciBinds returns the HostConfig.Binds of the last created container.
28func ciBinds(t *testing.T, m *mockDocker) []string {
29 t.Helper()
30 body := m.createBody()
31 if body == nil {
32 t.Fatal("no container was created")
33 }
34 host, _ := body["HostConfig"].(map[string]any)
35 raw, _ := host["Binds"].([]any)
36 out := make([]string, 0, len(raw))
37 for _, b := range raw {
38 s, _ := b.(string)
39 out = append(out, s)
40 }
41 return out
42}
43
44func TestCIEngineSocketDisabled(t *testing.T) {
45 e, m, admin := ciEnv(t)
46 sha := ciSeedToml(e, ciEngineSocketTOML)
47 m.reset()
48
49 runID := ciTrigger(e, admin, sha, nil)
50 if status := ciWaitForRun(e, runID); status != "failure" {
51 t.Errorf("run status = %q, want failure", status)
52 }
53
54 step := ciStep(e, runID, "build-image")
55 if step.Status != "failure" {
56 t.Errorf("build-image status = %q, want failure", step.Status)
57 }
58 if !strings.Contains(step.Log, "CI_ENGINE_SOCKET") {
59 t.Errorf("build-image log = %q", step.Log)
60 }
61 if got := ciStep(e, runID, "plain").Status; got != "skipped" {
62 t.Errorf("plain status = %q, want skipped", got)
63 }
64 if binds := ciBinds(t, m); contains(binds, "engine.sock") {
65 t.Errorf("binds = %v, want no engine socket", binds)
66 }
67 if strings.Contains(m.allCommandText(), "engine-build") {
68 t.Error("the disabled step still ran")
69 }
70}
71
72func TestCIEngineSocketEnabled(t *testing.T) {
73 e, m, admin := ciEnv(t, "CI_ENGINE_SOCKET", "1")
74 sha := ciSeedToml(e, ciEngineSocketTOML)
75 m.reset()
76 m.queueExec(execResp{output: "built\n"})
77 m.queueExec(execResp{output: "plain\n"})
78
79 runID := ciTrigger(e, admin, sha, nil)
80 if status := ciWaitForRun(e, runID); status != "success" {
81 t.Fatalf("run status = %q, want success", status)
82 }
83 if got := ciStep(e, runID, "build-image").Status; got != "success" {
84 t.Errorf("build-image status = %q", got)
85 }
86
87 want := m.sock + ":/run/hearthforge/engine.sock"
88 if binds := ciBinds(t, m); !contains(binds, want) {
89 t.Errorf("binds = %v, want %q", binds, want)
90 }
91
92 socketEnv := m.envForCommand("engine-build")
93 if socketEnv == nil {
94 t.Fatal("the engine_socket step did not run")
95 }
96 if !contains(socketEnv, "DOCKER_HOST=unix:///run/hearthforge/engine.sock") {
97 t.Errorf("engine_socket step env = %v", socketEnv)
98 }
99 if !contains(socketEnv, "CONTAINER_HOST=unix:///run/hearthforge/engine.sock") {
100 t.Errorf("CONTAINER_HOST missing from %v", socketEnv)
101 }
102
103 // CI_REGISTRY points at this server's registry path for the repo.
104 wantRegistry := "CI_REGISTRY=" + strings.TrimPrefix(e.Base, "http://") + "/ci-repo"
105 if !contains(socketEnv, wantRegistry) {
106 t.Errorf("env has no %q: %v", wantRegistry, socketEnv)
107 }
108
109 plainEnv := m.envForCommand("echo plain")
110 if plainEnv == nil {
111 t.Fatal("the plain step did not run")
112 }
113 if contains(plainEnv, "DOCKER_HOST") {
114 t.Errorf("plain step env = %v, want no DOCKER_HOST", plainEnv)
115 }
116}
117