From 3297425c9f4952f05d0b15294047b8219c39dac9 Mon Sep 17 00:00:00 2001 From: Safee Imran Date: Mon, 28 Sep 2026 10:29:59 +0500 Subject: [PATCH] fix(connectors): resolve Docker host from active context Resolve the active Docker CLI context when DOCKER_HOST is unset, allowing the existing Docker client to connect to Docker Desktop without migrating to docker/go-sdk. Add connector tests covering context resolution, existing DOCKER_HOST preservation, command failures, and an actual Docker client ping. Refs: #539 Signed-off-by: Safee Imran --- pkg/connectors/container_client.go | 29 ++++++ pkg/connectors/container_client_test.go | 112 ++++++++++++++++++++++++ 2 files changed, 141 insertions(+) create mode 100644 pkg/connectors/container_client_test.go diff --git a/pkg/connectors/container_client.go b/pkg/connectors/container_client.go index e231649f..101b9273 100644 --- a/pkg/connectors/container_client.go +++ b/pkg/connectors/container_client.go @@ -36,6 +36,8 @@ import ( "github.com/moby/term" ) +var execCommand = exec.Command + type ContainerClient interface { CreateContainer(opts ContainerOpts) (string, error) StartContainer(containerId string) error @@ -75,6 +77,10 @@ func NewContainerClient(driver string) (ContainerClient, error) { } func NewDockerClient() (*containerClient, error) { + if err := ConfigureDockerHost(); err != nil { + return nil, err + } + cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation()) if err != nil { @@ -102,6 +108,29 @@ func PingDockerHost() error { return err } +func ConfigureDockerHost() error { + if os.Getenv("DOCKER_HOST") != "" { + return nil + } + + cmd := execCommand("docker", "context", "inspect", "--format", "{{.Endpoints.docker.Host}}") + out, err := cmd.Output() + if err != nil { + exitError, ok := err.(*exec.ExitError) + if ok && exitError.ExitCode() == 1 { + return nil + } + return fmt.Errorf("resolving docker context host: %w", err) + } + + dockerHost := strings.TrimSpace(string(out)) + if dockerHost != "" { + return os.Setenv("DOCKER_HOST", dockerHost) + } + + return nil +} + func ConfigurePodmanHost() error { switch runtime.GOOS { case "windows": diff --git a/pkg/connectors/container_client_test.go b/pkg/connectors/container_client_test.go new file mode 100644 index 00000000..aabb1aea --- /dev/null +++ b/pkg/connectors/container_client_test.go @@ -0,0 +1,112 @@ +package connectors + +import ( + "context" + "os" + "os/exec" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestConfigureDockerHost(t *testing.T) { + oldDockerHost := os.Getenv("DOCKER_HOST") + defer func() { + if oldDockerHost == "" { + os.Unsetenv("DOCKER_HOST") + } else { + os.Setenv("DOCKER_HOST", oldDockerHost) + } + }() + + oldExecCommand := execCommand + defer func() { + execCommand = oldExecCommand + }() + + t.Run("sets DOCKER_HOST from docker context", func(t *testing.T) { + os.Unsetenv("DOCKER_HOST") + + execCommand = func(name string, args ...string) *exec.Cmd { + cmd := exec.Command( + os.Args[0], + "-test.run=TestHelperProcess", + "--", + "npipe:////./pipe/dockerDesktopLinuxEngine", + ) + cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") + return cmd + } + + require.NoError(t, ConfigureDockerHost()) + assert.Equal(t, "npipe:////./pipe/dockerDesktopLinuxEngine", os.Getenv("DOCKER_HOST")) + }) + + t.Run("does not overwrite existing DOCKER_HOST", func(t *testing.T) { + require.NoError(t, os.Setenv("DOCKER_HOST", "existing-endpoint")) + + require.NoError(t, ConfigureDockerHost()) + assert.Equal(t, "existing-endpoint", os.Getenv("DOCKER_HOST")) + }) + + t.Run("returns error when docker context command fails", func(t *testing.T) { + os.Unsetenv("DOCKER_HOST") + + execCommand = func(name string, args ...string) *exec.Cmd { + cmd := exec.Command( + os.Args[0], + "-test.run=TestHelperProcessFailure", + ) + cmd.Env = append(os.Environ(), "GO_WANT_HELPER_PROCESS=1") + return cmd + } + + err := ConfigureDockerHost() + require.Error(t, err) + assert.Contains(t, err.Error(), "resolving docker context host") + }) +} +func TestNewDockerClient(t *testing.T) { + oldDockerHost := os.Getenv("DOCKER_HOST") + defer func() { + if oldDockerHost == "" { + os.Unsetenv("DOCKER_HOST") + } else { + os.Setenv("DOCKER_HOST", oldDockerHost) + } + }() + + os.Unsetenv("DOCKER_HOST") + + dockerClient, err := NewDockerClient() + require.NoError(t, err) + require.NotNil(t, dockerClient) + + defer dockerClient.cli.Close() + + ctx := context.Background() + _, err = dockerClient.cli.Ping(ctx) + require.NoError(t, err) +} +func TestHelperProcess(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + for i, arg := range os.Args { + if arg == "--" && i+1 < len(os.Args) { + os.Stdout.WriteString(os.Args[i+1]) + os.Exit(0) + } + } + + os.Exit(1) +} +func TestHelperProcessFailure(t *testing.T) { + if os.Getenv("GO_WANT_HELPER_PROCESS") != "1" { + return + } + + os.Exit(2) +}