From 3a0b1d6c7973b5552792ca847e684839bc441c96 Mon Sep 17 00:00:00 2001 From: ScarabSystems Date: Sun, 26 Jul 2026 17:00:13 -0400 Subject: [PATCH 1/2] Surface Docker connection errors first Signed-off-by: ScarabSystems --- cmd/compose/compose_test.go | 32 ++++++++++++++++++++++++++++++++ cmd/compose/up.go | 11 ++++++++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/cmd/compose/compose_test.go b/cmd/compose/compose_test.go index 708929ff8cd..6a59e2d89c3 100644 --- a/cmd/compose/compose_test.go +++ b/cmd/compose/compose_test.go @@ -17,10 +17,16 @@ package compose import ( + "fmt" + "strings" "testing" "github.com/compose-spec/compose-go/v2/types" + "github.com/moby/moby/client" + "go.uber.org/mock/gomock" "gotest.tools/v3/assert" + + "github.com/docker/compose/v5/pkg/mocks" ) func TestFilterServices(t *testing.T) { @@ -53,3 +59,29 @@ func TestFilterServices(t *testing.T) { _, err = p.GetService("zot") assert.NilError(t, err) } + +func TestUpReturnsDockerConnectionErrorBeforeConfigPathFallback(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + wd := t.TempDir() + t.Chdir(wd) + t.Setenv("COMPOSE_FILE", "") + + apiClient := mocks.NewMockAPIClient(ctrl) + apiClient.EXPECT(). + Ping(gomock.Any(), client.PingOptions{NegotiateAPIVersion: true}). + Return(client.PingResult{}, fmt.Errorf("permission denied while trying to connect to the docker API at unix:///var/run/docker.sock")) + + cli := mocks.NewMockCli(ctrl) + cli.EXPECT().Client().Return(apiClient).AnyTimes() + + cmd := upCommand(&ProjectOptions{}, cli, &BackendOptions{}) + cmd.SetContext(t.Context()) + cmd.SetArgs([]string{"-d"}) + + err := cmd.Execute() + + assert.ErrorContains(t, err, "permission denied while trying to connect to the docker API") + assert.Assert(t, !strings.Contains(err.Error(), "is a directory"), err.Error()) +} diff --git a/cmd/compose/up.go b/cmd/compose/up.go index cda2678bbb2..6c7358d24a6 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -27,6 +27,7 @@ import ( "github.com/compose-spec/compose-go/v2/types" "github.com/docker/cli/cli/command" xprogress "github.com/moby/buildkit/util/progress/progressui" + "github.com/moby/moby/client" "github.com/sirupsen/logrus" "github.com/spf13/cobra" "github.com/spf13/pflag" @@ -124,7 +125,10 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend if !cmd.Flags().Changed("remove-orphans") { create.removeOrphans = utils.StringToBool(os.Getenv(ComposeRemoveOrphans)) } - return validateFlags(&up, &create) + if err := validateFlags(&up, &create); err != nil { + return err + } + return checkDockerConnection(ctx, dockerCli) }), RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error { create.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans]) @@ -186,6 +190,11 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend return upCmd } +func checkDockerConnection(ctx context.Context, dockerCli command.Cli) error { + _, err := dockerCli.Client().Ping(ctx, client.PingOptions{NegotiateAPIVersion: true}) + return err +} + //nolint:gocyclo func validateFlags(up *upOptions, create *createOptions) error { if up.waitTimeout < 0 { From 5bca94bd04e1c83f4b1b4ea3565c5f57d55949be Mon Sep 17 00:00:00 2001 From: ScarabSystems Date: Sun, 26 Jul 2026 17:35:04 -0400 Subject: [PATCH 2/2] Validate up attach flags before Docker ping Move the --attach/--attach-dependencies conflict into validateFlags so local flag validation still completes before the Docker API reachability check added for #13649. Add a regression test that exercises the conflict without expecting a Docker client call. Verification: - go test ./cmd/compose -run 'TestUp(ReturnsDockerConnectionErrorBeforeConfigPathFallback|ValidatesFlagConflictsBeforeDockerConnection)' -count=1 - go test ./cmd/compose ./pkg/compose -count=1 - make test - make build-and-e2e (fails on base-reproduced e2e tests: TestImageVolume, TestImageVolumeRecreateOnRebuild, TestComposeModel) Signed-off-by: ScarabSystems --- cmd/compose/compose_test.go | 15 +++++++++++++++ cmd/compose/up.go | 6 +++--- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/cmd/compose/compose_test.go b/cmd/compose/compose_test.go index 6a59e2d89c3..de5bc024278 100644 --- a/cmd/compose/compose_test.go +++ b/cmd/compose/compose_test.go @@ -85,3 +85,18 @@ func TestUpReturnsDockerConnectionErrorBeforeConfigPathFallback(t *testing.T) { assert.ErrorContains(t, err, "permission denied while trying to connect to the docker API") assert.Assert(t, !strings.Contains(err.Error(), "is a directory"), err.Error()) } + +func TestUpValidatesFlagConflictsBeforeDockerConnection(t *testing.T) { + ctrl := gomock.NewController(t) + defer ctrl.Finish() + + cli := mocks.NewMockCli(ctrl) + + cmd := upCommand(&ProjectOptions{}, cli, &BackendOptions{}) + cmd.SetContext(t.Context()) + cmd.SetArgs([]string{"--attach", "web", "--attach-dependencies"}) + + err := cmd.Execute() + + assert.Error(t, err, "cannot combine --attach and --attach-dependencies") +} diff --git a/cmd/compose/up.go b/cmd/compose/up.go index 6c7358d24a6..2046bd3bb4d 100644 --- a/cmd/compose/up.go +++ b/cmd/compose/up.go @@ -135,9 +135,6 @@ func upCommand(p *ProjectOptions, dockerCli command.Cli, backendOptions *Backend if create.ignoreOrphans && create.removeOrphans { return fmt.Errorf("cannot combine %s and --remove-orphans", ComposeIgnoreOrphans) } - if len(up.attach) != 0 && up.attachDependencies { - return errors.New("cannot combine --attach and --attach-dependencies") - } up.validateNavigationMenu(dockerCli) @@ -212,6 +209,9 @@ func validateFlags(up *upOptions, create *createOptions) error { } up.Detach = true } + if len(up.attach) != 0 && up.attachDependencies { + return errors.New("cannot combine --attach and --attach-dependencies") + } if create.Build && create.noBuild { return fmt.Errorf("--build and --no-build are incompatible") }