diff --git a/cli/command/network/client_test.go b/cli/command/network/client_test.go new file mode 100644 index 0000000000..1a5ee4ed81 --- /dev/null +++ b/cli/command/network/client_test.go @@ -0,0 +1,19 @@ +package network + +import ( + "github.com/docker/docker/api/types" + "github.com/docker/docker/client" + "golang.org/x/net/context" +) + +type fakeClient struct { + client.Client + networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) +} + +func (c *fakeClient) NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) { + if c.networkCreateFunc != nil { + return c.networkCreateFunc(ctx, name, options) + } + return types.NetworkCreateResponse{}, nil +} diff --git a/cli/command/network/cmd.go b/cli/command/network/cmd.go index 97bcca2a8d..48edf1c4e3 100644 --- a/cli/command/network/cmd.go +++ b/cli/command/network/cmd.go @@ -8,8 +8,7 @@ import ( ) // NewNetworkCommand returns a cobra command for `network` subcommands -// nolint: interfacer -func NewNetworkCommand(dockerCli *command.DockerCli) *cobra.Command { +func NewNetworkCommand(dockerCli command.Cli) *cobra.Command { cmd := &cobra.Command{ Use: "network", Short: "Manage networks", diff --git a/cli/command/network/connect.go b/cli/command/network/connect.go index 19bd3c56aa..9e925c3444 100644 --- a/cli/command/network/connect.go +++ b/cli/command/network/connect.go @@ -19,7 +19,7 @@ type connectOptions struct { linklocalips []string } -func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command { +func newConnectCommand(dockerCli command.Cli) *cobra.Command { options := connectOptions{ links: opts.NewListOpts(opts.ValidateLink), } @@ -45,7 +45,7 @@ func newConnectCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runConnect(dockerCli *command.DockerCli, options connectOptions) error { +func runConnect(dockerCli command.Cli, options connectOptions) error { client := dockerCli.Client() epConfig := &network.EndpointSettings{ diff --git a/cli/command/network/create.go b/cli/command/network/create.go index ed6d2de7bd..33bbdef5be 100644 --- a/cli/command/network/create.go +++ b/cli/command/network/create.go @@ -36,7 +36,7 @@ type createOptions struct { ipamOpt opts.MapOpts } -func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { +func newCreateCommand(dockerCli command.Cli) *cobra.Command { options := createOptions{ driverOpts: *opts.NewMapOpts(nil, nil), labels: opts.NewListOpts(opts.ValidateEnv), @@ -82,7 +82,7 @@ func newCreateCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runCreate(dockerCli *command.DockerCli, options createOptions) error { +func runCreate(dockerCli command.Cli, options createOptions) error { client := dockerCli.Client() ipamCfg, err := consolidateIpam(options.ipamSubnet, options.ipamIPRange, options.ipamGateway, options.ipamAux.GetAll()) @@ -232,13 +232,13 @@ func subnetMatches(subnet, data string) (bool, error) { _, s, err := net.ParseCIDR(subnet) if err != nil { - return false, errors.Errorf("Invalid subnet %s : %v", s, err) + return false, errors.Wrap(err, "invalid subnet") } if strings.Contains(data, "/") { ip, _, err = net.ParseCIDR(data) if err != nil { - return false, errors.Errorf("Invalid cidr %s : %v", data, err) + return false, err } } else { ip = net.ParseIP(data) diff --git a/cli/command/network/create_test.go b/cli/command/network/create_test.go new file mode 100644 index 0000000000..c3c114ba9b --- /dev/null +++ b/cli/command/network/create_test.go @@ -0,0 +1,178 @@ +package network + +import ( + "bytes" + "io/ioutil" + "strings" + "testing" + + "github.com/docker/cli/cli/internal/test" + "github.com/docker/docker/api/types" + "github.com/docker/docker/api/types/network" + "github.com/docker/docker/pkg/testutil" + "github.com/pkg/errors" + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + "golang.org/x/net/context" +) + +func TestNetworkCreateErrors(t *testing.T) { + testCases := []struct { + args []string + flags map[string]string + networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) + expectedError string + }{ + { + expectedError: "exactly 1 argument", + }, + { + args: []string{"toto"}, + networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) { + return types.NetworkCreateResponse{}, errors.Errorf("error creating network") + }, + expectedError: "error creating network", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "255.255.0.0/24", + "gateway": "255.0.255.0/24", + "subnet": "10.1.2.0.30.50", + }, + expectedError: "invalid CIDR address: 10.1.2.0.30.50", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "255.255.0.0.30/24", + "gateway": "255.0.255.0/24", + "subnet": "255.0.0.0/24", + }, + expectedError: "invalid CIDR address: 255.255.0.0.30/24", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "gateway": "255.0.0.0/24", + }, + expectedError: "every ip-range or gateway must have a corresponding subnet", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "255.0.0.0/24", + }, + expectedError: "every ip-range or gateway must have a corresponding subnet", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "255.0.0.0/24", + "gateway": "255.0.0.0/24", + }, + expectedError: "every ip-range or gateway must have a corresponding subnet", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "255.255.0.0/24", + "gateway": "255.0.255.0/24", + "subnet": "10.1.2.0/23,10.1.3.248/30", + }, + expectedError: "multiple overlapping subnet configuration is not supported", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "192.168.1.0/24,192.168.1.200/24", + "gateway": "192.168.1.1,192.168.1.4", + "subnet": "192.168.2.0/24,192.168.1.250/24", + }, + expectedError: "cannot configure multiple ranges (192.168.1.200/24, 192.168.1.0/24) on the same subnet (192.168.1.250/24)", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "255.255.200.0/24,255.255.120.0/24", + "gateway": "255.0.255.0/24", + "subnet": "255.255.255.0/24,255.255.0.255/24", + }, + expectedError: "no matching subnet for range 255.255.200.0/24", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "192.168.1.0/24", + "gateway": "192.168.1.1,192.168.1.4", + "subnet": "192.168.2.0/24,192.168.1.250/24", + }, + expectedError: "cannot configure multiple gateways (192.168.1.4, 192.168.1.1) for the same subnet (192.168.1.250/24)", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "ip-range": "192.168.1.0/24", + "gateway": "192.168.4.1,192.168.5.4", + "subnet": "192.168.2.0/24,192.168.1.250/24", + }, + expectedError: "no matching subnet for gateway 192.168.4.1", + }, + { + args: []string{"toto"}, + flags: map[string]string{ + "gateway": "255.255.0.0/24", + "subnet": "255.255.0.0/24", + "aux-address": "255.255.0.30/24", + }, + expectedError: "no matching subnet for aux-address", + }, + } + + for _, tc := range testCases { + buf := new(bytes.Buffer) + cmd := newCreateCommand( + test.NewFakeCli(&fakeClient{ + networkCreateFunc: tc.networkCreateFunc, + }, buf), + ) + cmd.SetArgs(tc.args) + for key, value := range tc.flags { + require.NoError(t, cmd.Flags().Set(key, value)) + } + cmd.SetOutput(ioutil.Discard) + testutil.ErrorContains(t, cmd.Execute(), tc.expectedError) + + } +} +func TestNetworkCreateWithFlags(t *testing.T) { + expectedDriver := "foo" + expectedOpts := []network.IPAMConfig{ + { + "192.168.4.0/24", + "192.168.4.0/24", + "192.168.4.1/24", + map[string]string{}, + }, + } + buf := new(bytes.Buffer) + cli := test.NewFakeCli(&fakeClient{ + networkCreateFunc: func(ctx context.Context, name string, createBody types.NetworkCreate) (types.NetworkCreateResponse, error) { + assert.Equal(t, expectedDriver, createBody.Driver, "not expected driver error") + assert.Equal(t, expectedOpts, createBody.IPAM.Config, "not expected driver error") + return types.NetworkCreateResponse{ + ID: name, + }, nil + }, + }, buf) + args := []string{"banana"} + cmd := newCreateCommand(cli) + + cmd.SetArgs(args) + cmd.Flags().Set("driver", "foo") + cmd.Flags().Set("ip-range", "192.168.4.0/24") + cmd.Flags().Set("gateway", "192.168.4.1/24") + cmd.Flags().Set("subnet", "192.168.4.0/24") + assert.NoError(t, cmd.Execute()) + assert.Equal(t, "banana", strings.TrimSpace(buf.String())) +} diff --git a/cli/command/network/disconnect.go b/cli/command/network/disconnect.go index 0f7d7c2b98..ab866cf2c1 100644 --- a/cli/command/network/disconnect.go +++ b/cli/command/network/disconnect.go @@ -14,7 +14,7 @@ type disconnectOptions struct { force bool } -func newDisconnectCommand(dockerCli *command.DockerCli) *cobra.Command { +func newDisconnectCommand(dockerCli command.Cli) *cobra.Command { opts := disconnectOptions{} cmd := &cobra.Command{ @@ -34,7 +34,7 @@ func newDisconnectCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runDisconnect(dockerCli *command.DockerCli, opts disconnectOptions) error { +func runDisconnect(dockerCli command.Cli, opts disconnectOptions) error { client := dockerCli.Client() return client.NetworkDisconnect(context.Background(), opts.network, opts.container, opts.force) diff --git a/cli/command/network/inspect.go b/cli/command/network/inspect.go index 9856c04e4c..e4c2e5fb89 100644 --- a/cli/command/network/inspect.go +++ b/cli/command/network/inspect.go @@ -16,7 +16,7 @@ type inspectOptions struct { verbose bool } -func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { +func newInspectCommand(dockerCli command.Cli) *cobra.Command { var opts inspectOptions cmd := &cobra.Command{ @@ -35,7 +35,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error { +func runInspect(dockerCli command.Cli, opts inspectOptions) error { client := dockerCli.Client() ctx := context.Background() diff --git a/cli/command/network/list.go b/cli/command/network/list.go index 79a860f9b3..8a7c1f261d 100644 --- a/cli/command/network/list.go +++ b/cli/command/network/list.go @@ -25,7 +25,7 @@ type listOptions struct { filter opts.FilterOpt } -func newListCommand(dockerCli *command.DockerCli) *cobra.Command { +func newListCommand(dockerCli command.Cli) *cobra.Command { options := listOptions{filter: opts.NewFilterOpt()} cmd := &cobra.Command{ @@ -47,7 +47,7 @@ func newListCommand(dockerCli *command.DockerCli) *cobra.Command { return cmd } -func runList(dockerCli *command.DockerCli, options listOptions) error { +func runList(dockerCli command.Cli, options listOptions) error { client := dockerCli.Client() listOptions := types.NetworkListOptions{Filters: options.filter.Value()} networkResources, err := client.NetworkList(context.Background(), listOptions) diff --git a/cli/command/network/remove.go b/cli/command/network/remove.go index 7dfe8da2f7..3de7bdad38 100644 --- a/cli/command/network/remove.go +++ b/cli/command/network/remove.go @@ -11,7 +11,7 @@ import ( "github.com/spf13/cobra" ) -func newRemoveCommand(dockerCli *command.DockerCli) *cobra.Command { +func newRemoveCommand(dockerCli command.Cli) *cobra.Command { return &cobra.Command{ Use: "rm NETWORK [NETWORK...]", Aliases: []string{"remove"}, @@ -28,7 +28,7 @@ const ingressWarning = "WARNING! Before removing the routing-mesh network, " + "Otherwise, removal may not be effective and functionality of newly create " + "ingress networks will be impaired.\nAre you sure you want to continue?" -func runRemove(dockerCli *command.DockerCli, networks []string) error { +func runRemove(dockerCli command.Cli, networks []string) error { client := dockerCli.Client() ctx := context.Background() status := 0