Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for removing a network by id #1581

Merged
merged 3 commits into from
Dec 4, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -1118,7 +1118,7 @@ Flags:
Unimplemented `docker network inspect` flags: `--verbose`

### :whale: nerdctl network rm
Remove one or more networks
Remove one or more networks by name or identifier

:warning network removal will fail if there are containers attached to it.

Expand Down
46 changes: 30 additions & 16 deletions cmd/nerdctl/network_rm.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,10 @@
package main

import (
"context"
"fmt"

"github.com/containerd/nerdctl/pkg/idutil/netwalker"
"github.com/containerd/nerdctl/pkg/netutil"

"github.com/spf13/cobra"
Expand Down Expand Up @@ -65,29 +67,41 @@ func networkRmAction(cmd *cobra.Command, args []string) error {
if err != nil {
return err
}
netMap := e.NetworkMap()

walker := netwalker.NetworkWalker{
Client: e,
OnFound: func(ctx context.Context, found netwalker.Found) error {
if found.MatchCount > 1 {
return fmt.Errorf("multiple IDs found with provided prefix: %s", found.Req)
}
if value, ok := usedNetworkInfo[found.Network.Name]; ok {
return fmt.Errorf("network %q is in use by container %q", found.Req, value)
}
if found.Network.NerdctlID == nil {
return fmt.Errorf("%s is managed outside nerdctl and cannot be removed", found.Req)
}
if found.Network.File == "" {
return fmt.Errorf("%s is a pre-defined network and cannot be removed", found.Req)
}
if err := e.RemoveNetwork(found.Network); err != nil {
return err
}
fmt.Fprintln(cmd.OutOrStdout(), found.Req)
return nil
},
}

for _, name := range args {
if name == "host" || name == "none" {
return fmt.Errorf("pseudo network %q cannot be removed", name)
}
net, ok := netMap[name]
if !ok {
return fmt.Errorf("no such network: %s", name)
}
if value, ok := usedNetworkInfo[name]; ok {
return fmt.Errorf("network %q is in use by container %q", name, value)
}
if net.NerdctlID == nil {
return fmt.Errorf("%s is managed outside nerdctl and cannot be removed", name)
}
if net.File == "" {
return fmt.Errorf("%s is a pre-defined network and cannot be removed", name)
}
if err := e.RemoveNetwork(net); err != nil {

n, err := walker.Walk(cmd.Context(), name)
if err != nil {
return err
} else if n == 0 {
return fmt.Errorf("no such network %s", name)
}
fmt.Fprintln(cmd.OutOrStdout(), name)
}
return nil
}
Expand Down
50 changes: 50 additions & 0 deletions cmd/nerdctl/network_rm_linux_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -66,3 +66,53 @@ func TestNetworkRemoveWhenLinkWithContainer(t *testing.T) {
defer base.Cmd("rm", "-f", tID).Run()
base.Cmd("network", "rm", networkName).AssertFail()
}

func TestNetworkRemoveById(t *testing.T) {
t.Parallel()
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
}
base := testutil.NewBase(t)
networkName := testutil.Identifier(t)

base.Cmd("network", "create", networkName).AssertOK()
defer base.Cmd("network", "rm", networkName).Run()

networkID := base.InspectNetwork(networkName).ID

tID := testutil.Identifier(t)
base.Cmd("run", "--rm", "--net", networkName, "--name", tID, testutil.CommonImage).AssertOK()

_, err := netlink.LinkByName("br-" + networkID[:12])
assert.NilError(t, err)

base.Cmd("network", "rm", networkID).AssertOK()
Zheaoli marked this conversation as resolved.
Show resolved Hide resolved

_, err = netlink.LinkByName("br-" + networkID[:12])
assert.Error(t, err, "Link not found")
}

func TestNetworkRemoveByShortId(t *testing.T) {
t.Parallel()
if rootlessutil.IsRootless() {
t.Skip("test skipped for remove rootless network")
}
base := testutil.NewBase(t)
networkName := testutil.Identifier(t)

base.Cmd("network", "create", networkName).AssertOK()
defer base.Cmd("network", "rm", networkName).Run()

networkID := base.InspectNetwork(networkName).ID

tID := testutil.Identifier(t)
base.Cmd("run", "--rm", "--net", networkName, "--name", tID, testutil.CommonImage).AssertOK()

_, err := netlink.LinkByName("br-" + networkID[:12])
assert.NilError(t, err)

base.Cmd("network", "rm", networkID[:12]).AssertOK()

_, err = netlink.LinkByName("br-" + networkID[:12])
assert.Error(t, err, "Link not found")
}
76 changes: 76 additions & 0 deletions pkg/idutil/netwalker/netwalker.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/*
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 netwalker

import (
"context"
"fmt"
"regexp"

"github.com/containerd/nerdctl/pkg/netutil"
)

type Found struct {
Network *netutil.NetworkConfig
Req string // The raw request string. name, short ID, or long ID.
MatchIndex int // Begins with 0, up to MatchCount - 1.
MatchCount int // 1 on exact match. > 1 on ambiguous match. Never be <= 0.
}

type OnFound func(ctx context.Context, found Found) error

type NetworkWalker struct {
Client *netutil.CNIEnv
OnFound OnFound
}

// Walk walks networks and calls w.OnFound .
// Req is name, short ID, or long ID.
// Returns the number of the found entries.
func (w *NetworkWalker) Walk(ctx context.Context, req string) (int, error) {
longIDExp, err := regexp.Compile(fmt.Sprintf("^sha256:%s.*", regexp.QuoteMeta(req)))
if err != nil {
return 0, err
}

shortIDExp, err := regexp.Compile(fmt.Sprintf("^%s", regexp.QuoteMeta(req)))
if err != nil {
return 0, err
}

networks := []*netutil.NetworkConfig{}
for _, n := range w.Client.Networks {
if n.Name == req || longIDExp.Match([]byte(*n.NerdctlID)) || shortIDExp.Match([]byte(*n.NerdctlID)) {
networks = append(networks, n)
}
}

matchCount := len(networks)

for i, network := range networks {
f := Found{
Network: network,
Req: req,
MatchIndex: i,
MatchCount: matchCount,
}
if e := w.OnFound(ctx, f); e != nil {
return -1, e
}
}
return matchCount, nil
}
30 changes: 15 additions & 15 deletions pkg/netutil/netutil.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ import (
type CNIEnv struct {
Path string
NetconfPath string
Networks []*networkConfig
Networks []*NetworkConfig
}

type CNIEnvOpt func(e *CNIEnv) error
Expand Down Expand Up @@ -124,8 +124,8 @@ func NewCNIEnv(cniPath, cniConfPath string, opts ...CNIEnvOpt) (*CNIEnv, error)
return &e, nil
}

func (e *CNIEnv) NetworkMap() map[string]*networkConfig { //nolint:revive
m := make(map[string]*networkConfig, len(e.Networks))
func (e *CNIEnv) NetworkMap() map[string]*NetworkConfig { //nolint:revive
m := make(map[string]*NetworkConfig, len(e.Networks))
for _, n := range e.Networks {
m[n.Name] = n
}
Expand All @@ -143,7 +143,7 @@ func (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) {
return usedSubnets, nil
}

type networkConfig struct {
type NetworkConfig struct {
*libcni.NetworkConfigList
NerdctlID *string
NerdctlLabels *map[string]string
Expand All @@ -170,8 +170,8 @@ type CreateOptions struct {
Labels []string
}

func (e *CNIEnv) CreateNetwork(opts CreateOptions) (*networkConfig, error) { //nolint:revive
var net *networkConfig
func (e *CNIEnv) CreateNetwork(opts CreateOptions) (*NetworkConfig, error) { //nolint:revive
var net *NetworkConfig
if _, ok := e.NetworkMap()[opts.Name]; ok {
return nil, errdefs.ErrAlreadyExists
}
Expand Down Expand Up @@ -201,7 +201,7 @@ func (e *CNIEnv) CreateNetwork(opts CreateOptions) (*networkConfig, error) { //n
return net, nil
}

func (e *CNIEnv) RemoveNetwork(net *networkConfig) error {
func (e *CNIEnv) RemoveNetwork(net *NetworkConfig) error {
fn := func() error {
if err := os.RemoveAll(net.File); err != nil {
return err
Expand Down Expand Up @@ -232,9 +232,9 @@ func (e *CNIEnv) ensureDefaultNetworkConfig() error {
return nil
}

// generateNetworkConfig creates networkConfig.
// generateNetworkConfig creates NetworkConfig.
// generateNetworkConfig does not fill "File" field.
func (e *CNIEnv) generateNetworkConfig(name string, labels []string, plugins []CNIPlugin) (*networkConfig, error) {
func (e *CNIEnv) generateNetworkConfig(name string, labels []string, plugins []CNIPlugin) (*NetworkConfig, error) {
if name == "" || len(plugins) == 0 {
return nil, errdefs.ErrInvalidArgument
}
Expand Down Expand Up @@ -264,16 +264,16 @@ func (e *CNIEnv) generateNetworkConfig(name string, labels []string, plugins []C
if err != nil {
return nil, err
}
return &networkConfig{
return &NetworkConfig{
NetworkConfigList: l,
NerdctlID: &id,
NerdctlLabels: &labelsMap,
File: "",
}, nil
}

// writeNetworkConfig writes networkConfig file to cni config path.
func (e *CNIEnv) writeNetworkConfig(net *networkConfig) error {
// writeNetworkConfig writes NetworkConfig file to cni config path.
func (e *CNIEnv) writeNetworkConfig(net *NetworkConfig) error {
filename := filepath.Join(e.NetconfPath, "nerdctl-"+net.Name+".conflist")
if _, err := os.Stat(filename); err == nil {
return errdefs.ErrAlreadyExists
Expand All @@ -285,8 +285,8 @@ func (e *CNIEnv) writeNetworkConfig(net *networkConfig) error {
}

// networkConfigList loads config from dir if dir exists.
func (e *CNIEnv) networkConfigList() ([]*networkConfig, error) {
l := []*networkConfig{}
func (e *CNIEnv) networkConfigList() ([]*NetworkConfig, error) {
l := []*NetworkConfig{}
fileNames, err := libcni.ConfFiles(e.NetconfPath, []string{".conf", ".conflist", ".json"})
if err != nil {
return nil, err
Expand All @@ -310,7 +310,7 @@ func (e *CNIEnv) networkConfigList() ([]*networkConfig, error) {
}
}
id, labels := nerdctlIDLabels(lcl.Bytes)
l = append(l, &networkConfig{
l = append(l, &NetworkConfig{
NetworkConfigList: lcl,
NerdctlID: id,
NerdctlLabels: labels,
Expand Down
4 changes: 2 additions & 2 deletions pkg/netutil/netutil_unix.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ const (
DefaultIPAMDriver = "host-local"
)

func (n *networkConfig) subnets() []*net.IPNet {
func (n *NetworkConfig) subnets() []*net.IPNet {
var subnets []*net.IPNet
if len(n.Plugins) > 0 && n.Plugins[0].Network.Type == "bridge" {
var bridge bridgeConfig
Expand All @@ -69,7 +69,7 @@ func (n *networkConfig) subnets() []*net.IPNet {
return subnets
}

func (n *networkConfig) clean() error {
func (n *NetworkConfig) clean() error {
// Remove the bridge network interface on the host.
if len(n.Plugins) > 0 && n.Plugins[0].Network.Type == "bridge" {
var bridge bridgeConfig
Expand Down
4 changes: 2 additions & 2 deletions pkg/netutil/netutil_windows.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ const (
DefaultCIDR = "10.4.0.0/24"
)

func (n *networkConfig) subnets() []*net.IPNet {
func (n *NetworkConfig) subnets() []*net.IPNet {
var subnets []*net.IPNet
if n.Plugins[0].Network.Type == "nat" {
var nat natConfig
Expand All @@ -49,7 +49,7 @@ func (n *networkConfig) subnets() []*net.IPNet {
return subnets
}

func (n *networkConfig) clean() error {
func (n *NetworkConfig) clean() error {
return nil
}

Expand Down