diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index abe0b0ea2ca..642867cba9f 100644 --- a/components/cli/cli/command/stack/deploy_composefile.go +++ b/components/cli/cli/command/stack/deploy_composefile.go @@ -13,6 +13,7 @@ import ( "github.com/docker/cli/cli/compose/loader" composetypes "github.com/docker/cli/cli/compose/types" "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/container" "github.com/docker/docker/api/types/swarm" apiclient "github.com/docker/docker/client" dockerclient "github.com/docker/docker/client" @@ -64,7 +65,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption serviceNetworks := getServicesDeclaredNetworks(config.Services) networks, externalNetworks := convert.Networks(namespace, config.Networks, serviceNetworks) - if err := validateExternalNetworks(ctx, dockerCli, externalNetworks); err != nil { + if err := validateExternalNetworks(ctx, dockerCli.Client(), externalNetworks); err != nil { return err } if err := createNetworks(ctx, dockerCli, namespace, networks); err != nil { @@ -75,7 +76,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption if err != nil { return err } - if err := createSecrets(ctx, dockerCli, namespace, secrets); err != nil { + if err := createSecrets(ctx, dockerCli, secrets); err != nil { return err } @@ -83,7 +84,7 @@ func deployCompose(ctx context.Context, dockerCli command.Cli, opts deployOption if err != nil { return err } - if err := createConfigs(ctx, dockerCli, namespace, configs); err != nil { + if err := createConfigs(ctx, dockerCli, configs); err != nil { return err } @@ -169,30 +170,26 @@ func getConfigFile(filename string) (*composetypes.ConfigFile, error) { func validateExternalNetworks( ctx context.Context, - dockerCli command.Cli, - externalNetworks []string) error { - client := dockerCli.Client() - + client dockerclient.NetworkAPIClient, + externalNetworks []string, +) error { for _, networkName := range externalNetworks { network, err := client.NetworkInspect(ctx, networkName, false) - if err != nil { - if dockerclient.IsErrNetworkNotFound(err) { - return errors.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName) - } + switch { + case dockerclient.IsErrNotFound(err): + return errors.Errorf("network %q is declared as external, but could not be found. You need to create a swarm-scoped network before the stack is deployed", networkName) + case err != nil: return err - } - if network.Scope != "swarm" { - return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of %q", networkName, network.Scope, "swarm") + case container.NetworkMode(networkName).IsUserDefined() && network.Scope != "swarm": + return errors.Errorf("network %q is declared as external, but it is not in the right scope: %q instead of \"swarm\"", networkName, network.Scope) } } - return nil } func createSecrets( ctx context.Context, dockerCli command.Cli, - namespace convert.Namespace, secrets []swarm.SecretSpec, ) error { client := dockerCli.Client() @@ -219,7 +216,6 @@ func createSecrets( func createConfigs( ctx context.Context, dockerCli command.Cli, - namespace convert.Namespace, configs []swarm.ConfigSpec, ) error { client := dockerCli.Client() diff --git a/components/cli/cli/command/stack/deploy_composefile_test.go b/components/cli/cli/command/stack/deploy_composefile_test.go index d5ef5463ff1..6d811ed208a 100644 --- a/components/cli/cli/command/stack/deploy_composefile_test.go +++ b/components/cli/cli/command/stack/deploy_composefile_test.go @@ -5,9 +5,14 @@ import ( "path/filepath" "testing" + "github.com/docker/cli/cli/internal/test/network" + "github.com/docker/docker/api/types" + "github.com/docker/docker/pkg/testutil" "github.com/docker/docker/pkg/testutil/tempfile" + "github.com/pkg/errors" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + "golang.org/x/net/context" ) func TestGetConfigDetails(t *testing.T) { @@ -26,3 +31,55 @@ services: assert.Len(t, details.ConfigFiles, 1) assert.Len(t, details.Environment, len(os.Environ())) } + +type notFound struct { + error +} + +func (n notFound) NotFound() bool { + return true +} + +func TestValidateExternalNetworks(t *testing.T) { + var testcases = []struct { + inspectResponse types.NetworkResource + inspectError error + expectedMsg string + network string + }{ + { + inspectError: notFound{}, + expectedMsg: "could not be found. You need to create a swarm-scoped network", + }, + { + inspectError: errors.New("Unexpected"), + expectedMsg: "Unexpected", + }, + { + network: "host", + }, + { + network: "user", + expectedMsg: "is not in the right scope", + }, + { + network: "user", + inspectResponse: types.NetworkResource{Scope: "swarm"}, + }, + } + + for _, testcase := range testcases { + fakeClient := &network.FakeClient{ + NetworkInspectFunc: func(_ context.Context, _ string, _ bool) (types.NetworkResource, error) { + return testcase.inspectResponse, testcase.inspectError + }, + } + networks := []string{testcase.network} + err := validateExternalNetworks(context.Background(), fakeClient, networks) + if testcase.expectedMsg == "" { + assert.NoError(t, err) + } else { + testutil.ErrorContains(t, err, testcase.expectedMsg) + } + } +} diff --git a/components/cli/cli/compose/convert/service.go b/components/cli/cli/compose/convert/service.go index 5a99dc57777..233a3762938 100644 --- a/components/cli/cli/compose/convert/service.go +++ b/components/cli/cli/compose/convert/service.go @@ -223,10 +223,16 @@ func convertServiceNetworks( if networkConfig.External.External { target = networkConfig.External.Name } - nets = append(nets, swarm.NetworkAttachmentConfig{ + netAttachConfig := swarm.NetworkAttachmentConfig{ Target: target, - Aliases: append(aliases, name), - }) + Aliases: aliases, + } + // Only add default aliases to user defined networks. Other networks do + // not support aliases. + if container.NetworkMode(target).IsUserDefined() { + netAttachConfig.Aliases = append(netAttachConfig.Aliases, name) + } + nets = append(nets, netAttachConfig) } sort.Sort(byNetworkTarget(nets)) diff --git a/components/cli/cli/internal/test/network/client.go b/components/cli/cli/internal/test/network/client.go new file mode 100644 index 00000000000..5f35cd4514a --- /dev/null +++ b/components/cli/cli/internal/test/network/client.go @@ -0,0 +1,56 @@ +package network + +import ( + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/filters" + "github.com/docker/docker/api/types/network" + "golang.org/x/net/context" +) + +// FakeClient is a fake NetworkAPIClient +type FakeClient struct { + NetworkInspectFunc func(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) +} + +// NetworkConnect fakes connecting to a network +func (c *FakeClient) NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error { + return nil +} + +// NetworkCreate fakes creating a network +func (c *FakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { + return types.NetworkCreateResponse{}, nil +} + +// NetworkDisconnect fakes disconencting from a network +func (c *FakeClient) NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error { + return nil +} + +// NetworkInspect fakes inspecting a network +func (c *FakeClient) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) { + if c.NetworkInspectFunc != nil { + return c.NetworkInspectFunc(ctx, networkID, verbose) + } + return types.NetworkResource{}, nil +} + +// NetworkInspectWithRaw fakes inspecting a network with a raw response +func (c *FakeClient) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) { + return types.NetworkResource{}, nil, nil +} + +// NetworkList fakes listing networks +func (c *FakeClient) NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) { + return nil, nil +} + +// NetworkRemove fakes removing networks +func (c *FakeClient) NetworkRemove(ctx context.Context, networkID string) error { + return nil +} + +// NetworksPrune fakes pruning networks +func (c *FakeClient) NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error) { + return types.NetworksPruneReport{}, nil +} diff --git a/components/cli/scripts/test/watch b/components/cli/scripts/test/watch index 61057e2430b..3c2b46bea1e 100755 --- a/components/cli/scripts/test/watch +++ b/components/cli/scripts/test/watch @@ -3,7 +3,7 @@ set -e filewatcher \ - -L 5 \ + -L 6 \ -x '**/*.swp' \ -x .git \ -x build \