Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
47 changes: 47 additions & 0 deletions cmd/compose/compose_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down Expand Up @@ -53,3 +59,44 @@ 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())
}

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")
}
17 changes: 13 additions & 4 deletions cmd/compose/up.go
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down Expand Up @@ -124,16 +125,16 @@ 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)

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

wonder if this should be inside runUp to avoid connecting to the API before all other validation is completed (at least at a glance it feels like a more natural place to validate the connection)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I checked this against the command path after your note. runUp is reached after the project has already been loaded by p.WithServices(...); the misleading is a directory error from #13649 is raised during that earlier project-loading path, so moving the ping into runUp would leave the reported case unchanged.

I kept the reachability check after local validation but before project loading, and moved the remaining --attach / --attach-dependencies conflict into validateFlags so that flag-only validation still wins before any Docker API call. Added coverage for both paths. If there is another pre-load owner you prefer for the connection check, I can adjust the placement.

}),
RunE: p.WithServices(dockerCli, func(ctx context.Context, project *types.Project, services []string) error {
create.ignoreOrphans = utils.StringToBool(project.Environment[ComposeIgnoreOrphans])
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)

Expand Down Expand Up @@ -186,6 +187,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 {
Expand All @@ -203,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")
}
Expand Down