From 3c9d134d0534a242aed7765529def90ac9b9fa89 Mon Sep 17 00:00:00 2001 From: Jesse Duffield Date: Sat, 17 Jan 2026 16:59:13 +1100 Subject: [PATCH] Fix version lock issue --- pkg/commands/docker.go | 17 +++++++++- pkg/commands/docker_test.go | 63 +++++++++++++++++++++++++++++++++++++ 2 files changed, 79 insertions(+), 1 deletion(-) create mode 100644 pkg/commands/docker_test.go diff --git a/pkg/commands/docker.go b/pkg/commands/docker.go index 35afef2f..9d86d97d 100644 --- a/pkg/commands/docker.go +++ b/pkg/commands/docker.go @@ -70,6 +70,21 @@ func (c *DockerCommand) NewCommandObject(obj CommandObject) CommandObject { return defaultObj } +// newDockerClient creates a Docker client with the given host. +// We avoid using client.FromEnv because it includes WithVersionFromEnv() which +// sets manualOverride=true when DOCKER_API_VERSION is set, preventing API version +// negotiation even when WithAPIVersionNegotiation() is specified. +// Instead, we explicitly configure only what we need, and rely on proper +// API version negotiation to support older Docker daemons. +// See https://github.com/jesseduffield/lazydocker/issues/715 +func newDockerClient(dockerHost string) (*client.Client, error) { + return client.NewClientWithOpts( + client.WithTLSClientConfigFromEnv(), + client.WithAPIVersionNegotiation(), + client.WithHost(dockerHost), + ) +} + // NewDockerCommand it runs docker commands func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.TranslationSet, config *config.AppConfig, errorChan chan error) (*DockerCommand, error) { dockerHost, err := determineDockerHost() @@ -96,7 +111,7 @@ func NewDockerCommand(log *logrus.Entry, osCommand *OSCommand, tr *i18n.Translat dockerHost = dockerHostFromEnv } - cli, err := client.NewClientWithOpts(client.FromEnv, client.WithAPIVersionNegotiation(), client.WithHost(dockerHost)) + cli, err := newDockerClient(dockerHost) if err != nil { ogLog.Fatal(err) } diff --git a/pkg/commands/docker_test.go b/pkg/commands/docker_test.go new file mode 100644 index 00000000..0d459686 --- /dev/null +++ b/pkg/commands/docker_test.go @@ -0,0 +1,63 @@ +package commands + +import ( + "os" + "testing" + + "github.com/docker/docker/client" + "github.com/stretchr/testify/assert" +) + +// TestNewDockerClientVersionNegotiation verifies that newDockerClient allows +// API version negotiation even when DOCKER_API_VERSION is set. +// +// This is a regression test for https://github.com/jesseduffield/lazydocker/issues/715 +// where users got "client version 1.25 is too old" errors because FromEnv() +// includes WithVersionFromEnv() which sets manualOverride=true, preventing +// API version negotiation. +func TestNewDockerClientVersionNegotiation(t *testing.T) { + // Save original env var and restore after test + originalAPIVersion := os.Getenv("DOCKER_API_VERSION") + defer func() { + if originalAPIVersion == "" { + os.Unsetenv("DOCKER_API_VERSION") + } else { + os.Setenv("DOCKER_API_VERSION", originalAPIVersion) + } + }() + + // Set DOCKER_API_VERSION to an old version that would cause + // "client version 1.25 is too old" errors if negotiation is disabled + os.Setenv("DOCKER_API_VERSION", "1.25") + + t.Run("FromEnv locks version preventing negotiation", func(t *testing.T) { + // This demonstrates the problematic behavior we're avoiding. + // When using FromEnv with DOCKER_API_VERSION set, the client + // version gets locked to 1.25 and negotiation is disabled. + cli, err := client.NewClientWithOpts( + client.FromEnv, + client.WithAPIVersionNegotiation(), + ) + assert.NoError(t, err) + defer cli.Close() + + // Version is locked to the env var value + assert.Equal(t, "1.25", cli.ClientVersion()) + }) + + t.Run("newDockerClient allows version negotiation", func(t *testing.T) { + // Test the actual production function. + // Use DefaultDockerHost for cross-platform compatibility + // (unix socket on Linux/macOS, named pipe on Windows). + cli, err := newDockerClient(client.DefaultDockerHost) + assert.NoError(t, err) + defer cli.Close() + + // Version is NOT locked to the env var value (1.25). + // Instead, it uses the library's default version and will negotiate + // with the server on first request. This is the key difference that + // fixes the "version too old" error. + assert.NotEqual(t, "1.25", cli.ClientVersion(), + "client version should not be locked to DOCKER_API_VERSION env var") + }) +}