Add --force option to network rm subcommand

The code is similar to that used by the volume rm subcommand, however,
one difference I noticed was VolumeRemove takes the force flag/option
was a parameter. This isn't the case for NetworkRemove.

To get NetworkRemove to take a similar parameter, this would require
modifying the Docker daemon. For now this isn't a route I wish to take
when the code can be arrange to mimic the same behavior.

Co-authored-by: Sebastiaan van Stijn <github@gone.nl>
Signed-off-by: Conner Crosby <conner@cavcrosby.tech>
This commit is contained in:
Conner Crosby 2022-04-08 22:43:28 -04:00 committed by Sebastiaan van Stijn
parent 1a2daffc51
commit 0ea587b0d7
No known key found for this signature in database
GPG Key ID: 76698F39D527CE8C
5 changed files with 128 additions and 5 deletions

View File

@ -13,6 +13,7 @@ type fakeClient struct {
networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error) networkCreateFunc func(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
networkConnectFunc func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error networkConnectFunc func(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error networkDisconnectFunc func(ctx context.Context, networkID, container string, force bool) error
networkRemoveFunc func(ctx context.Context, networkID string) error
networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error) networkListFunc func(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
} }
@ -43,3 +44,14 @@ func (c *fakeClient) NetworkList(ctx context.Context, options types.NetworkListO
} }
return []types.NetworkResource{}, nil return []types.NetworkResource{}, nil
} }
func (c *fakeClient) NetworkRemove(ctx context.Context, networkID string) error {
if c.networkRemoveFunc != nil {
return c.networkRemoveFunc(ctx, networkID)
}
return nil
}
func (c *fakeClient) NetworkInspectWithRaw(ctx context.Context, network string, options types.NetworkInspectOptions) (types.NetworkResource, []byte, error) {
return types.NetworkResource{}, nil, nil
}

View File

@ -7,19 +7,30 @@ import (
"github.com/docker/cli/cli" "github.com/docker/cli/cli"
"github.com/docker/cli/cli/command" "github.com/docker/cli/cli/command"
"github.com/docker/docker/api/types" "github.com/docker/docker/api/types"
"github.com/docker/docker/errdefs"
"github.com/spf13/cobra" "github.com/spf13/cobra"
) )
type removeOptions struct {
force bool
}
func newRemoveCommand(dockerCli command.Cli) *cobra.Command { func newRemoveCommand(dockerCli command.Cli) *cobra.Command {
return &cobra.Command{ var opts removeOptions
cmd := &cobra.Command{
Use: "rm NETWORK [NETWORK...]", Use: "rm NETWORK [NETWORK...]",
Aliases: []string{"remove"}, Aliases: []string{"remove"},
Short: "Remove one or more networks", Short: "Remove one or more networks",
Args: cli.RequiresMinArgs(1), Args: cli.RequiresMinArgs(1),
RunE: func(cmd *cobra.Command, args []string) error { RunE: func(cmd *cobra.Command, args []string) error {
return runRemove(dockerCli, args) return runRemove(dockerCli, args, &opts)
}, },
} }
flags := cmd.Flags()
flags.BoolVarP(&opts.force, "force", "f", false, "Do not error if the network does not exist")
return cmd
} }
const ingressWarning = "WARNING! Before removing the routing-mesh network, " + const ingressWarning = "WARNING! Before removing the routing-mesh network, " +
@ -27,7 +38,7 @@ const ingressWarning = "WARNING! Before removing the routing-mesh network, " +
"Otherwise, removal may not be effective and functionality of newly create " + "Otherwise, removal may not be effective and functionality of newly create " +
"ingress networks will be impaired.\nAre you sure you want to continue?" "ingress networks will be impaired.\nAre you sure you want to continue?"
func runRemove(dockerCli command.Cli, networks []string) error { func runRemove(dockerCli command.Cli, networks []string, opts *removeOptions) error {
client := dockerCli.Client() client := dockerCli.Client()
ctx := context.Background() ctx := context.Background()
status := 0 status := 0
@ -39,6 +50,9 @@ func runRemove(dockerCli command.Cli, networks []string) error {
continue continue
} }
if err := client.NetworkRemove(ctx, name); err != nil { if err := client.NetworkRemove(ctx, name); err != nil {
if opts.force && errdefs.IsNotFound(err) {
continue
}
fmt.Fprintf(dockerCli.Err(), "%s\n", err) fmt.Fprintf(dockerCli.Err(), "%s\n", err)
status = 1 status = 1
continue continue

View File

@ -0,0 +1,97 @@
package network
import (
"context"
"io"
"testing"
"github.com/docker/cli/internal/test"
"github.com/docker/docker/errdefs"
"github.com/pkg/errors"
"gotest.tools/v3/assert"
is "gotest.tools/v3/assert/cmp"
)
func TestNetworkRemoveForce(t *testing.T) {
tests := []struct {
doc string
args []string
expectedErr string
}{
{
doc: "existing network",
args: []string{"existing-network"},
},
{
doc: "existing network (forced)",
args: []string{"--force", "existing-network"},
},
{
doc: "non-existing network",
args: []string{"no-such-network"},
expectedErr: "no such network: no-such-network",
},
{
doc: "non-existing network (forced)",
args: []string{"--force", "no-such-network"},
},
{
doc: "in-use network",
args: []string{"in-use-network"},
expectedErr: "network is in use",
},
{
doc: "in-use network (forced)",
args: []string{"--force", "in-use-network"},
expectedErr: "network is in use",
},
{
doc: "multiple networks",
args: []string{"existing-network", "no-such-network"},
expectedErr: "no such network: no-such-network",
},
{
doc: "multiple networks (forced)",
args: []string{"--force", "existing-network", "no-such-network"},
},
{
doc: "multiple networks 2 (forced)",
args: []string{"--force", "existing-network", "no-such-network", "in-use-network"},
expectedErr: "network is in use",
},
}
for _, tc := range tests {
tc := tc
t.Run(tc.doc, func(t *testing.T) {
fakeCli := test.NewFakeCli(&fakeClient{
networkRemoveFunc: func(ctx context.Context, networkID string) error {
switch networkID {
case "no-such-network":
return errdefs.NotFound(errors.New("no such network: no-such-network"))
case "in-use-network":
return errdefs.Forbidden(errors.New("network is in use"))
case "existing-network":
return nil
default:
return nil
}
},
})
cmd := newRemoveCommand(fakeCli)
cmd.SetOut(io.Discard)
cmd.SetErr(fakeCli.ErrBuffer())
cmd.SetArgs(tc.args)
err := cmd.Execute()
if tc.expectedErr == "" {
assert.NilError(t, err)
} else {
assert.Check(t, is.Contains(fakeCli.ErrBuffer().String(), tc.expectedErr))
assert.ErrorContains(t, err, "Code: 1")
}
})
}
}

View File

@ -3495,7 +3495,7 @@ _docker_network_prune() {
_docker_network_rm() { _docker_network_rm() {
case "$cur" in case "$cur" in
-*) -*)
COMPREPLY=( $( compgen -W "--help" -- "$cur" ) ) COMPREPLY=( $( compgen -W "--force -f --help" -- "$cur" ) )
;; ;;
*) *)
__docker_complete_networks --filter type=custom __docker_complete_networks --filter type=custom

View File

@ -15,7 +15,7 @@ Aliases:
rm, remove rm, remove
Options: Options:
--help Print usage -f, --force Do not error if the network does not exist
``` ```
## Description ## Description