From 1bb09dd9f9c3584fdb31abb537068040f46a2825 Mon Sep 17 00:00:00 2001 From: Ye Sijun Date: Sat, 23 Jul 2022 14:13:47 +0800 Subject: [PATCH] support network prune Signed-off-by: Ye Sijun --- README.md | 12 +- cmd/nerdctl/image_prune.go | 15 +-- cmd/nerdctl/network.go | 1 + cmd/nerdctl/network_prune.go | 161 ++++++++++++++++++++++++ cmd/nerdctl/network_prune_linux_test.go | 38 ++++++ 5 files changed, 219 insertions(+), 8 deletions(-) create mode 100644 cmd/nerdctl/network_prune.go create mode 100644 cmd/nerdctl/network_prune_linux_test.go diff --git a/README.md b/README.md index 0a31ac99aab..ca89d9c1c7d 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,7 @@ It does not necessarily mean that the corresponding features are missing in cont - [:whale: nerdctl network ls](#whale-nerdctl-network-ls) - [:whale: nerdctl network inspect](#whale-nerdctl-network-inspect) - [:whale: nerdctl network rm](#whale-nerdctl-network-rm) + - [:whale: nerdctl network prune](#whale-nerdctl-network-prune) - [Volume management](#volume-management) - [:whale: nerdctl volume create](#whale-nerdctl-volume-create) - [:whale: nerdctl volume ls](#whale-nerdctl-volume-ls) @@ -1034,6 +1035,16 @@ Remove one or more networks Usage: `nerdctl network rm NETWORK [NETWORK...]` +### :whale: nerdctl network prune +Remove all unused networks + +Usage: `nerdctl network prune [OPTIONS]` + +Flags: +- :whale: `-f, --force`: Do not prompt for confirmation + +Unimplemented `docker network prune` flags: `--filter` + ## Volume management ### :whale: nerdctl volume create Create a volume @@ -1433,7 +1444,6 @@ Image: Network management: - `docker network connect` - `docker network disconnect` -- `docker network prune` Registry: - `docker search` diff --git a/cmd/nerdctl/image_prune.go b/cmd/nerdctl/image_prune.go index a863873fc13..b8e88292a09 100644 --- a/cmd/nerdctl/image_prune.go +++ b/cmd/nerdctl/image_prune.go @@ -42,12 +42,6 @@ func newImagePruneCommand() *cobra.Command { } func imagePruneAction(cmd *cobra.Command, _ []string) error { - client, ctx, cancel, err := newClient(cmd) - if err != nil { - return err - } - defer cancel() - all, err := cmd.Flags().GetBool("all") if err != nil { return err @@ -76,6 +70,13 @@ func imagePruneAction(cmd *cobra.Command, _ []string) error { return nil } } + + client, ctx, cancel, err := newClient(cmd) + if err != nil { + return err + } + defer cancel() + var ( imageStore = client.ImageService() contentStore = client.ContentStore() @@ -109,7 +110,7 @@ func imagePruneAction(cmd *cobra.Command, _ []string) error { } fmt.Fprintf(cmd.OutOrStdout(), "Untagged: %s\n", image.Name) for _, digest := range digests { - fmt.Fprintf(cmd.OutOrStdout(), "Deleted: %s\n", digest) + fmt.Fprintf(cmd.OutOrStdout(), "deleted: %s\n", digest) } } return nil diff --git a/cmd/nerdctl/network.go b/cmd/nerdctl/network.go index b474f1af627..cc9b38d5f51 100644 --- a/cmd/nerdctl/network.go +++ b/cmd/nerdctl/network.go @@ -34,6 +34,7 @@ func newNetworkCommand() *cobra.Command { newNetworkInspectCommand(), newNetworkCreateCommand(), newNetworkRmCommand(), + newNetworkPruneCommand(), ) return networkCommand } diff --git a/cmd/nerdctl/network_prune.go b/cmd/nerdctl/network_prune.go new file mode 100644 index 00000000000..3654d617e6a --- /dev/null +++ b/cmd/nerdctl/network_prune.go @@ -0,0 +1,161 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package main + +import ( + "context" + "encoding/json" + "fmt" + "strings" + + "github.com/containerd/containerd" + "github.com/containerd/nerdctl/pkg/labels" + "github.com/containerd/nerdctl/pkg/netutil" + "github.com/containerd/nerdctl/pkg/netutil/nettype" + "github.com/sirupsen/logrus" + "github.com/spf13/cobra" +) + +func newNetworkPruneCommand() *cobra.Command { + networkPruneCommand := &cobra.Command{ + Use: "prune [flags]", + Short: "Remove all unused networks", + Args: cobra.NoArgs, + RunE: networkPruneAction, + SilenceUsage: true, + SilenceErrors: true, + } + networkPruneCommand.Flags().BoolP("force", "f", false, "Do not prompt for confirmation") + return networkPruneCommand +} + +func networkPruneAction(cmd *cobra.Command, args []string) error { + cniPath, err := cmd.Flags().GetString("cni-path") + if err != nil { + return err + } + cniNetconfpath, err := cmd.Flags().GetString("cni-netconfpath") + if err != nil { + return err + } + force, err := cmd.Flags().GetBool("force") + if err != nil { + return err + } + + if !force { + var confirm string + msg := "This will remove all custom networks not used by at least one container." + msg += "\nAre you sure you want to continue? [y/N] " + + fmt.Fprintf(cmd.OutOrStdout(), "WARNING! %s", msg) + fmt.Fscanf(cmd.InOrStdin(), "%s", &confirm) + + if strings.ToLower(confirm) != "y" { + return nil + } + } + + e, err := netutil.NewCNIEnv(cniPath, cniNetconfpath) + if err != nil { + return err + } + + client, ctx, cancel, err := newClient(cmd) + if err != nil { + return err + } + defer cancel() + + containers, err := client.Containers(ctx) + if err != nil { + return err + } + + usedNetworks, err := usedNetworks(ctx, containers) + if err != nil { + return err + } + + var removedNetworks []string // nolint: prealloc + for _, net := range e.Networks { + if net.Name == "host" || net.Name == "none" { + continue + } + if net.NerdctlID == nil || net.File == "" { + continue + } + if _, ok := usedNetworks[net.Name]; ok { + continue + } + if err := e.RemoveNetwork(net); err != nil { + logrus.WithError(err).Errorf("failed to remove network %s", net.Name) + continue + } + removedNetworks = append(removedNetworks, net.Name) + } + + if len(removedNetworks) > 0 { + fmt.Fprintln(cmd.OutOrStdout(), "Deleted Networks:") + for _, name := range removedNetworks { + fmt.Fprintln(cmd.OutOrStdout(), name) + } + } + return nil +} + +func usedNetworks(ctx context.Context, containers []containerd.Container) (map[string]struct{}, error) { + used := make(map[string]struct{}) + for _, c := range containers { + task, err := c.Task(ctx, nil) + if err != nil { + return nil, err + } + status, err := task.Status(ctx) + if err != nil { + return nil, err + } + switch status.Status { + case containerd.Paused, containerd.Running: + default: + continue + } + l, err := c.Labels(ctx) + if err != nil { + return nil, err + } + networkJSON, ok := l[labels.Networks] + if !ok { + continue + } + var networks []string + if err := json.Unmarshal([]byte(networkJSON), &networks); err != nil { + return nil, err + } + netType, err := nettype.Detect(networks) + if err != nil { + return nil, err + } + if netType != nettype.CNI { + continue + } + for _, n := range networks { + used[n] = struct{}{} + } + } + return used, nil +} diff --git a/cmd/nerdctl/network_prune_linux_test.go b/cmd/nerdctl/network_prune_linux_test.go new file mode 100644 index 00000000000..d872d540b26 --- /dev/null +++ b/cmd/nerdctl/network_prune_linux_test.go @@ -0,0 +1,38 @@ +/* + Copyright The containerd Authors. + + Licensed under the Apache License, Version 2.0 (the "License"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an "AS IS" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. +*/ + +package main + +import ( + "testing" + + "github.com/containerd/nerdctl/pkg/testutil" +) + +func TestNetworkPrune(t *testing.T) { + base := testutil.NewBase(t) + testNetwork := testutil.Identifier(t) + base.Cmd("network", "create", testNetwork).AssertOK() + defer base.Cmd("network", "prune", "-f").Run() + + tID := testutil.Identifier(t) + base.Cmd("run", "-d", "--net", testNetwork, "--name", tID, testutil.NginxAlpineImage).AssertOK() + defer base.Cmd("rm", "-f", tID).Run() + + base.Cmd("network", "prune", "-f").AssertNoOut(testNetwork) + base.Cmd("stop", tID).AssertOK() + base.Cmd("network", "prune", "-f").AssertOutContains(testNetwork) +}