package e2e import ( "strings" "testing" ) // engine_socket lets a step talk to the container engine that runs the // pipeline. The server must opt in with CI_ENGINE_SOCKET. const ciEngineSocketTOML = ` image = "debian:latest" [on] manual = true [[steps]] name = "build-image" run_sh = "engine-build ." engine_socket = true [[steps]] name = "plain" run_sh = "echo plain" ` // ciBinds returns the HostConfig.Binds of the last created container. func ciBinds(t *testing.T, m *mockDocker) []string { t.Helper() body := m.createBody() if body == nil { t.Fatal("no container was created") } host, _ := body["HostConfig"].(map[string]any) raw, _ := host["Binds"].([]any) out := make([]string, 0, len(raw)) for _, b := range raw { s, _ := b.(string) out = append(out, s) } return out } func TestCIEngineSocketDisabled(t *testing.T) { e, m, admin := ciEnv(t) sha := ciSeedToml(e, ciEngineSocketTOML) m.reset() runID := ciTrigger(e, admin, sha, nil) if status := ciWaitForRun(e, runID); status != "failure" { t.Errorf("run status = %q, want failure", status) } step := ciStep(e, runID, "build-image") if step.Status != "failure" { t.Errorf("build-image status = %q, want failure", step.Status) } if !strings.Contains(step.Log, "CI_ENGINE_SOCKET") { t.Errorf("build-image log = %q", step.Log) } if got := ciStep(e, runID, "plain").Status; got != "skipped" { t.Errorf("plain status = %q, want skipped", got) } if binds := ciBinds(t, m); contains(binds, "engine.sock") { t.Errorf("binds = %v, want no engine socket", binds) } if strings.Contains(m.allCommandText(), "engine-build") { t.Error("the disabled step still ran") } } func TestCIEngineSocketEnabled(t *testing.T) { e, m, admin := ciEnv(t, "CI_ENGINE_SOCKET", "1") sha := ciSeedToml(e, ciEngineSocketTOML) m.reset() m.queueExec(execResp{output: "built\n"}) m.queueExec(execResp{output: "plain\n"}) runID := ciTrigger(e, admin, sha, nil) if status := ciWaitForRun(e, runID); status != "success" { t.Fatalf("run status = %q, want success", status) } if got := ciStep(e, runID, "build-image").Status; got != "success" { t.Errorf("build-image status = %q", got) } want := m.sock + ":/run/hearthforge/engine.sock" if binds := ciBinds(t, m); !contains(binds, want) { t.Errorf("binds = %v, want %q", binds, want) } socketEnv := m.envForCommand("engine-build") if socketEnv == nil { t.Fatal("the engine_socket step did not run") } if !contains(socketEnv, "DOCKER_HOST=unix:///run/hearthforge/engine.sock") { t.Errorf("engine_socket step env = %v", socketEnv) } if !contains(socketEnv, "CONTAINER_HOST=unix:///run/hearthforge/engine.sock") { t.Errorf("CONTAINER_HOST missing from %v", socketEnv) } // CI_REGISTRY points at this server's registry path for the repo. wantRegistry := "CI_REGISTRY=" + strings.TrimPrefix(e.Base, "http://") + "/ci-repo" if !contains(socketEnv, wantRegistry) { t.Errorf("env has no %q: %v", wantRegistry, socketEnv) } plainEnv := m.envForCommand("echo plain") if plainEnv == nil { t.Fatal("the plain step did not run") } if contains(plainEnv, "DOCKER_HOST") { t.Errorf("plain step env = %v, want no DOCKER_HOST", plainEnv) } }