From 8a627e82e66ba3e3ea1ff1676f85204f09e14d08 Mon Sep 17 00:00:00 2001 From: Rogan Lynch Date: Fri, 5 Dec 2025 12:31:26 -0800 Subject: [PATCH 1/3] feat: minimal Docker Desktop fix - API version 1.44 Fix integration tests for Docker Desktop compatibility by forcing a modern Docker API version (1.44) instead of the default 1.25. Changes: - integration/scenario.go: Explicitly create Docker client with API 1.44 - .dockerignore: Exclude test artifacts and build outputs This is the minimal fix - no complex Docker Desktop detection needed. The root cause was dockertest.NewPool() defaulting to API 1.25, which modern Docker daemons (v1.52+) reject. Total changes: ~10 lines --- .dockerignore | 7 +++++++ integration/scenario.go | 5 ++++- 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/.dockerignore b/.dockerignore index 9ea3e4a4..78011dd5 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,3 +21,10 @@ LICENSE node_modules/ package-lock.json package.json + +# Ignore test artifacts and build outputs +control_logs/ +headscale +hi +cmd/hi/hi +logs/ diff --git a/integration/scenario.go b/integration/scenario.go index d584a3ef..e22026fd 100644 --- a/integration/scenario.go +++ b/integration/scenario.go @@ -161,10 +161,13 @@ func (s *Scenario) prefixedNetworkName(name string) string { // NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with // a set of Users and TailscaleClients. func NewScenario(spec ScenarioSpec) (*Scenario, error) { - pool, err := dockertest.NewPool("") + // dockertest.NewPool("") defaults to an old client version (1.25) which is rejected by newer Docker daemons. + // We must force a newer version (1.44) compatible with the host Docker environment. + client, err := docker.NewVersionedClient("unix:///var/run/docker.sock", "1.44") if err != nil { return nil, fmt.Errorf("could not connect to docker: %w", err) } + pool := &dockertest.Pool{Client: client} // Opportunity to clean up unreferenced networks. // This might be a no op, but it is worth a try as we sometime From aac1cc87bedd6f0e92aaa763ebb49ee92e623478 Mon Sep 17 00:00:00 2001 From: Rogan Lynch Date: Fri, 12 Dec 2025 19:55:59 -0800 Subject: [PATCH 2/3] feat: implement dynamic Docker API version negotiation Replace hardcoded API version 1.44 with automatic version detection. The previous approach had issues: - dockertest.NewPool("") auto-negotiated to API 1.25 (too old) - Modern Docker daemons reject old versions causing broken pipe errors - Hardcoded 1.44 worked but wasn't portable across Docker installations New approach: - Query Docker daemon's /version endpoint to get MinAPIVersion - Use the server's minimum supported version (e.g., 1.44 for Docker Desktop) - Fallback to 1.41 if MinAPIVersion not available - Respects DOCKER_HOST environment variable This ensures compatibility across Docker Desktop, Colima, and other installations while avoiding the broken pipe errors from outdated API versions. --- integration/scenario.go | 49 ++++++++++++++++++++++++++++++++++++++--- 1 file changed, 46 insertions(+), 3 deletions(-) diff --git a/integration/scenario.go b/integration/scenario.go index e22026fd..8fa6c80c 100644 --- a/integration/scenario.go +++ b/integration/scenario.go @@ -158,12 +158,55 @@ func (s *Scenario) prefixedNetworkName(name string) string { return s.testHashPrefix + "-" + name } +// createDockerClient creates a Docker client with automatic API version negotiation. +// This queries the Docker daemon to determine the supported API version range and selects +// an appropriate version, ensuring compatibility across different Docker installations. +func createDockerClient() (*docker.Client, error) { + // Determine the Docker endpoint (socket path) + endpoint := "unix:///var/run/docker.sock" + if dockerHost := os.Getenv("DOCKER_HOST"); dockerHost != "" { + endpoint = dockerHost + } + + // First, create a temporary client to query the server's API version. + // We use an empty version string which allows us to call the /version endpoint. + tmpClient, err := docker.NewVersionedClient(endpoint, "") + if err != nil { + return nil, fmt.Errorf("failed to create temporary Docker client: %w", err) + } + tmpClient.SkipServerVersionCheck = true // Allow querying any server + + // Query the server's API version information + env, err := tmpClient.Version() + if err != nil { + return nil, fmt.Errorf("failed to query Docker server version: %w", err) + } + + // Get the server's minimum supported API version + // This is the safe version to use that the server will accept + serverMinVersion := env.Get("MinAPIVersion") + if serverMinVersion == "" { + // Fallback to a reasonable default if MinAPIVersion not available + serverMinVersion = "1.41" + } + + // Now create the actual client with the negotiated version + client, err := docker.NewVersionedClient(endpoint, serverMinVersion) + if err != nil { + return nil, fmt.Errorf("failed to create Docker client with version %s: %w", serverMinVersion, err) + } + client.SkipServerVersionCheck = false + + return client, nil +} + // NewScenario creates a test Scenario which can be used to bootstraps a ControlServer with // a set of Users and TailscaleClients. func NewScenario(spec ScenarioSpec) (*Scenario, error) { - // dockertest.NewPool("") defaults to an old client version (1.25) which is rejected by newer Docker daemons. - // We must force a newer version (1.44) compatible with the host Docker environment. - client, err := docker.NewVersionedClient("unix:///var/run/docker.sock", "1.44") + // Create a Docker client with automatic API version negotiation. + // This ensures compatibility across different Docker installations by automatically + // selecting the highest mutually supported API version between client and server. + client, err := createDockerClient() if err != nil { return nil, fmt.Errorf("could not connect to docker: %w", err) } From 37162267d9c0e1cb08e748a105c2c9e002ac8541 Mon Sep 17 00:00:00 2001 From: Rogan Lynch Date: Fri, 12 Dec 2025 19:56:31 -0800 Subject: [PATCH 3/3] revert: remove .dockerignore changes to avoid side effects --- .dockerignore | 7 ------- 1 file changed, 7 deletions(-) diff --git a/.dockerignore b/.dockerignore index 78011dd5..9ea3e4a4 100644 --- a/.dockerignore +++ b/.dockerignore @@ -21,10 +21,3 @@ LICENSE node_modules/ package-lock.json package.json - -# Ignore test artifacts and build outputs -control_logs/ -headscale -hi -cmd/hi/hi -logs/