From 2340ac7ad56f4722678522eca454cd3984412bff Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Wed, 4 Feb 2026 13:33:38 +0530 Subject: [PATCH 1/2] Fix panic when Docker is unresponsive (Issue #312) --- pkg/docker/client.go | 8 ++++++ pkg/docker/client_test.go | 28 ++++++++++++++++++++ pkg/docker/containers.go | 54 +++++++++++++++++++++++++++++++-------- pkg/docker/images.go | 21 ++++++++++++--- pkg/docker/network.go | 18 ++++++++++--- pkg/docker/volumes.go | 6 ++++- 6 files changed, 117 insertions(+), 18 deletions(-) create mode 100644 pkg/docker/client_test.go diff --git a/pkg/docker/client.go b/pkg/docker/client.go index d861d4768..5c41aeb50 100644 --- a/pkg/docker/client.go +++ b/pkg/docker/client.go @@ -40,6 +40,14 @@ type dockerClient struct { apiClient func() client.APIClient } +func (c *dockerClient) client() (client.APIClient, error) { + cli := c.apiClient() + if cli == nil { + return nil, fmt.Errorf("docker client is not available. Please ensure Docker Desktop is running.") + } + return cli, nil +} + func NewClient(cli command.Cli) Client { return &dockerClient{ apiClient: sync.OnceValue(func() client.APIClient { diff --git a/pkg/docker/client_test.go b/pkg/docker/client_test.go new file mode 100644 index 000000000..98be4295b --- /dev/null +++ b/pkg/docker/client_test.go @@ -0,0 +1,28 @@ +package docker + +import ( + "context" + "strings" + "testing" + + "github.com/docker/docker/client" +) + +func TestClientSafeError(t *testing.T) { + // Direct initialization with a nil client factory + c := &dockerClient{ + apiClient: func() client.APIClient { return nil }, + } + + // This should NOT panic anymore + err := c.PullImage(context.Background(), "hello-world") + + if err == nil { + t.Fatal("Expected an error, got nil") + } + + expectedErr := "docker client is not available" + if !strings.Contains(err.Error(), expectedErr) { + t.Errorf("Expected error to contain %q, got %q", expectedErr, err.Error()) + } +} diff --git a/pkg/docker/containers.go b/pkg/docker/containers.go index 1ccd0b64e..45d80867c 100644 --- a/pkg/docker/containers.go +++ b/pkg/docker/containers.go @@ -12,8 +12,12 @@ import ( "github.com/docker/docker/api/types/network" ) -func (c *dockerClient) ContainerExists(ctx context.Context, container string) (bool, container.InspectResponse, error) { - response, err := c.apiClient().ContainerInspect(ctx, container) +func (c *dockerClient) ContainerExists(ctx context.Context, containerName string) (bool, container.InspectResponse, error) { + cli, err := c.client() + if err != nil { + return false, container.InspectResponse{}, err + } + response, err := cli.ContainerInspect(ctx, containerName) if cerrdefs.IsNotFound(err) { return false, response, nil } @@ -22,18 +26,27 @@ func (c *dockerClient) ContainerExists(ctx context.Context, container string) (b } func (c *dockerClient) RemoveContainer(ctx context.Context, containerID string, force bool) error { - return c.apiClient().ContainerRemove(ctx, containerID, container.RemoveOptions{ + cli, err := c.client() + if err != nil { + return err + } + return cli.ContainerRemove(ctx, containerID, container.RemoveOptions{ Force: force, }) } func (c *dockerClient) StartContainer(ctx context.Context, containerID string, containerConfig container.Config, hostConfig container.HostConfig, networkingConfig network.NetworkingConfig) error { - resp, err := c.apiClient().ContainerCreate(ctx, &containerConfig, &hostConfig, &networkingConfig, nil, containerID) + cli, err := c.client() + if err != nil { + return err + } + + resp, err := cli.ContainerCreate(ctx, &containerConfig, &hostConfig, &networkingConfig, nil, containerID) if err != nil { return fmt.Errorf("creating container: %w", err) } - if err := c.apiClient().ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { + if err := cli.ContainerStart(ctx, resp.ID, container.StartOptions{}); err != nil { return fmt.Errorf("starting container: %w", err) } @@ -41,17 +54,29 @@ func (c *dockerClient) StartContainer(ctx context.Context, containerID string, c } func (c *dockerClient) StopContainer(ctx context.Context, containerID string, timeout int) error { - return c.apiClient().ContainerStop(ctx, containerID, container.StopOptions{ + cli, err := c.client() + if err != nil { + return err + } + return cli.ContainerStop(ctx, containerID, container.StopOptions{ Timeout: &timeout, }) } func (c *dockerClient) InspectContainer(ctx context.Context, containerID string) (container.InspectResponse, error) { - return c.apiClient().ContainerInspect(ctx, containerID) + cli, err := c.client() + if err != nil { + return container.InspectResponse{}, err + } + return cli.ContainerInspect(ctx, containerID) } func (c *dockerClient) FindContainerByLabel(ctx context.Context, label string) (string, error) { - containers, err := c.apiClient().ContainerList(ctx, container.ListOptions{ + cli, err := c.client() + if err != nil { + return "", err + } + containers, err := cli.ContainerList(ctx, container.ListOptions{ Filters: filters.NewArgs(filters.Arg("label", label)), }) if err != nil { @@ -66,7 +91,11 @@ func (c *dockerClient) FindContainerByLabel(ctx context.Context, label string) ( } func (c *dockerClient) FindAllContainersByLabel(ctx context.Context, label string) ([]string, error) { - containers, err := c.apiClient().ContainerList(ctx, container.ListOptions{ + cli, err := c.client() + if err != nil { + return nil, err + } + containers, err := cli.ContainerList(ctx, container.ListOptions{ Filters: filters.NewArgs(filters.Arg("label", label)), }) if err != nil { @@ -92,7 +121,12 @@ func (c *dockerClient) FindAllContainersByLabel(ctx context.Context, label strin func (c *dockerClient) ReadLogs(ctx context.Context, containerID string, options container.LogsOptions) (io.ReadCloser, error) { const streamHeaderSize = 8 - rc, err := c.apiClient().ContainerLogs(ctx, containerID, options) + cli, err := c.client() + if err != nil { + return nil, err + } + + rc, err := cli.ContainerLogs(ctx, containerID, options) if err != nil { return nil, err } diff --git a/pkg/docker/images.go b/pkg/docker/images.go index 6466e313b..ee33b060d 100644 --- a/pkg/docker/images.go +++ b/pkg/docker/images.go @@ -16,7 +16,11 @@ import ( ) func (c *dockerClient) ImageExists(ctx context.Context, name string) (bool, error) { - _, err := c.apiClient().ContainerInspect(ctx, name) + cli, err := c.client() + if err != nil { + return false, err + } + _, err = cli.ContainerInspect(ctx, name) if cerrdefs.IsNotFound(err) { return false, nil } @@ -48,11 +52,20 @@ func (c *dockerClient) PullImage(ctx context.Context, name string) error { } func (c *dockerClient) InspectImage(ctx context.Context, name string) (image.InspectResponse, error) { - return c.apiClient().ImageInspect(ctx, name) + cli, err := c.client() + if err != nil { + return image.InspectResponse{}, err + } + return cli.ImageInspect(ctx, name) } func (c *dockerClient) pullImage(ctx context.Context, imageName string, registryAuthFn func() string) error { - inspect, err := c.apiClient().ImageInspect(ctx, imageName) + cli, err := c.client() + if err != nil { + return err + } + + inspect, err := cli.ImageInspect(ctx, imageName) if err != nil && !cerrdefs.IsNotFound(err) { return fmt.Errorf("inspecting docker image %s: %w", imageName, err) } @@ -80,7 +93,7 @@ func (c *dockerClient) pullImage(ctx context.Context, imageName string, registry pullOptions.RegistryAuth = registryAuthFn() } - response, err := c.apiClient().ImagePull(ctx, imageName, pullOptions) + response, err := cli.ImagePull(ctx, imageName, pullOptions) if err != nil { fmt.Fprintf(os.Stderr, " - warning: pulling docker image %s: %v", imageName, err) return nil diff --git a/pkg/docker/network.go b/pkg/docker/network.go index 0eba95bb6..799172bc6 100644 --- a/pkg/docker/network.go +++ b/pkg/docker/network.go @@ -7,7 +7,11 @@ import ( ) func (c *dockerClient) CreateNetwork(ctx context.Context, name string, internal bool, labels map[string]string) error { - _, err := c.apiClient().NetworkCreate(ctx, name, network.CreateOptions{ + cli, err := c.client() + if err != nil { + return err + } + _, err = cli.NetworkCreate(ctx, name, network.CreateOptions{ Internal: internal, Labels: labels, }) @@ -19,11 +23,19 @@ func (c *dockerClient) CreateNetwork(ctx context.Context, name string, internal } func (c *dockerClient) RemoveNetwork(ctx context.Context, name string) error { - return c.apiClient().NetworkRemove(ctx, name) + cli, err := c.client() + if err != nil { + return err + } + return cli.NetworkRemove(ctx, name) } func (c *dockerClient) ConnectNetwork(ctx context.Context, networkName, container, hostname string) error { - return c.apiClient().NetworkConnect(ctx, networkName, container, &network.EndpointSettings{ + cli, err := c.client() + if err != nil { + return err + } + return cli.NetworkConnect(ctx, networkName, container, &network.EndpointSettings{ Aliases: []string{hostname}, }) } diff --git a/pkg/docker/volumes.go b/pkg/docker/volumes.go index 833bf370f..f68724205 100644 --- a/pkg/docker/volumes.go +++ b/pkg/docker/volumes.go @@ -7,5 +7,9 @@ import ( ) func (c *dockerClient) InspectVolume(ctx context.Context, name string) (volume.Volume, error) { - return c.apiClient().VolumeInspect(ctx, name) + cli, err := c.client() + if err != nil { + return volume.Volume{}, err + } + return cli.VolumeInspect(ctx, name) } From f8d69228b4e69fd43090de3b6c016ce40765b662 Mon Sep 17 00:00:00 2001 From: aviralgarg05 Date: Thu, 5 Feb 2026 13:34:14 +0530 Subject: [PATCH 2/2] fix: use ImageInspect instead of ContainerInspect in ImageExists --- pkg/docker/images.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkg/docker/images.go b/pkg/docker/images.go index ee33b060d..54e6b6114 100644 --- a/pkg/docker/images.go +++ b/pkg/docker/images.go @@ -20,7 +20,7 @@ func (c *dockerClient) ImageExists(ctx context.Context, name string) (bool, erro if err != nil { return false, err } - _, err = cli.ContainerInspect(ctx, name) + _, err = cli.ImageInspect(ctx, name) if cerrdefs.IsNotFound(err) { return false, nil }