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..54e6b6114 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.ImageInspect(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) }