diff --git a/components/cli/cli/command/service/update.go b/components/cli/cli/command/service/update.go index 85ac89aa52..5e3bc7c458 100644 --- a/components/cli/cli/command/service/update.go +++ b/components/cli/cli/command/service/update.go @@ -131,7 +131,7 @@ func runUpdate(dockerCli *command.DockerCli, flags *pflag.FlagSet, options *serv // Rollback can't be combined with other flags. otherFlagsPassed := false flags.VisitAll(func(f *pflag.Flag) { - if f.Name == "rollback" { + if f.Name == "rollback" || f.Name == "detach" || f.Name == "quiet" { return } if flags.Changed(f.Name) { diff --git a/components/cli/cli/command/stack/deploy_composefile.go b/components/cli/cli/command/stack/deploy_composefile.go index abe0b0ea2c..642867cba9 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 d5ef5463ff..6d811ed208 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 5a99dc5777..233a376293 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/compose/loader/loader.go b/components/cli/cli/compose/loader/loader.go index 0ed8ce87d7..f12a7f0eea 100644 --- a/components/cli/cli/compose/loader/loader.go +++ b/components/cli/cli/compose/loader/loader.go @@ -3,6 +3,7 @@ package loader import ( "fmt" "path" + "path/filepath" "reflect" "sort" "strings" @@ -371,7 +372,16 @@ func resolveVolumePaths(volumes []types.ServiceVolumeConfig, workingDir string, continue } - volume.Source = absPath(workingDir, expandUser(volume.Source, lookupEnv)) + filePath := expandUser(volume.Source, lookupEnv) + // Check for a Unix absolute path first, to handle a Windows client + // with a Unix daemon. This handles a Windows client connecting to a + // Unix daemon. Note that this is not required for Docker for Windows + // when specifying a local Windows path, because Docker for Windows + // translates the Windows path into a valid path within the VM. + if !path.IsAbs(filePath) { + filePath = absPath(workingDir, filePath) + } + volume.Source = filePath volumes[i] = volume } } @@ -492,11 +502,11 @@ func LoadConfigObjs(source map[string]interface{}, workingDir string) (map[strin return configs, nil } -func absPath(workingDir string, filepath string) string { - if path.IsAbs(filepath) { - return filepath +func absPath(workingDir string, filePath string) string { + if filepath.IsAbs(filePath) { + return filePath } - return path.Join(workingDir, filepath) + return filepath.Join(workingDir, filePath) } func transformMapStringString(data interface{}) (interface{}, error) { 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 0000000000..5f35cd4514 --- /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 61057e2430..3c2b46bea1 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 \