From 2e3b6365cff2851b9c05b59d0ce11ff463f3357f Mon Sep 17 00:00:00 2001 From: apostasie Date: Sun, 6 Oct 2024 18:01:29 -0700 Subject: [PATCH 1/2] Fix netwalker issues Current implementation of netwalker (henceforth network inspect and remove) suffers from many issues, rooting from mixing names and identifiers in the same namespace, faulty erroring logic, and overly complicated design making debugging and reasonning harder. This commit removes the walker and replaces it with a simpler approach. Next commit will add tests. Signed-off-by: apostasie --- cmd/nerdctl/completion/completion.go | 6 +- pkg/cmd/container/kill.go | 12 +- pkg/cmd/network/inspect.go | 71 ++++++----- pkg/cmd/network/remove.go | 73 +++++++---- .../container_network_manager.go | 13 +- pkg/idutil/netwalker/netwalker.go | 119 ------------------ pkg/netutil/netutil.go | 92 +++++++++++--- pkg/netutil/netutil_unix.go | 4 +- pkg/ocihook/ocihook.go | 12 +- 9 files changed, 181 insertions(+), 221 deletions(-) delete mode 100644 pkg/idutil/netwalker/netwalker.go diff --git a/cmd/nerdctl/completion/completion.go b/cmd/nerdctl/completion/completion.go index ea7352d8561..7718c1bb063 100644 --- a/cmd/nerdctl/completion/completion.go +++ b/cmd/nerdctl/completion/completion.go @@ -124,9 +124,13 @@ func NetworkNames(cmd *cobra.Command, exclude []string) ([]string, cobra.ShellCo if err != nil { return nil, cobra.ShellCompDirectiveError } - for netName := range netConfigs { + for netName, network := range netConfigs { if _, ok := excludeMap[netName]; !ok { candidates = append(candidates, netName) + if network.NerdctlID != nil { + candidates = append(candidates, *network.NerdctlID) + candidates = append(candidates, (*network.NerdctlID)[0:12]) + } } } for _, s := range []string{"host", "none"} { diff --git a/pkg/cmd/container/kill.go b/pkg/cmd/container/kill.go index 50c2900bfbd..4e53b4f9e3b 100644 --- a/pkg/cmd/container/kill.go +++ b/pkg/cmd/container/kill.go @@ -161,16 +161,12 @@ func cleanupNetwork(ctx context.Context, container containerd.Container, globalO cniOpts := []gocni.Opt{ gocni.WithPluginDir([]string{globalOpts.CNIPath}), } - netMap, err := e.NetworkMap() - if err != nil { - return err - } + var netw *netutil.NetworkConfig for _, netstr := range networks { - net, ok := netMap[netstr] - if !ok { - return fmt.Errorf("no such network: %q", netstr) + if netw, err = e.NetworkByNameOrID(netstr); err != nil { + return err } - cniOpts = append(cniOpts, gocni.WithConfListBytes(net.Bytes)) + cniOpts = append(cniOpts, gocni.WithConfListBytes(netw.Bytes)) } cni, err := gocni.New(cniOpts...) if err != nil { diff --git a/pkg/cmd/network/inspect.go b/pkg/cmd/network/inspect.go index 6389be63755..5fae28028f3 100644 --- a/pkg/cmd/network/inspect.go +++ b/pkg/cmd/network/inspect.go @@ -19,62 +19,71 @@ package network import ( "context" "encoding/json" + "errors" "fmt" "github.com/containerd/log" "github.com/containerd/nerdctl/v2/pkg/api/types" "github.com/containerd/nerdctl/v2/pkg/formatter" - "github.com/containerd/nerdctl/v2/pkg/idutil/netwalker" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/native" "github.com/containerd/nerdctl/v2/pkg/netutil" ) func Inspect(ctx context.Context, options types.NetworkInspectOptions) error { - globalOptions := options.GOptions - e, err := netutil.NewCNIEnv(globalOptions.CNIPath, globalOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace)) + if options.Mode != "native" && options.Mode != "dockercompat" { + return fmt.Errorf("unknown mode %q", options.Mode) + } + cniEnv, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace)) if err != nil { return err } - if options.Mode != "native" && options.Mode != "dockercompat" { - return fmt.Errorf("unknown mode %q", options.Mode) - } var result []interface{} - 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) - } - r := &native.Network{ - CNI: json.RawMessage(found.Network.Bytes), - NerdctlID: found.Network.NerdctlID, - NerdctlLabels: found.Network.NerdctlLabels, - File: found.Network.File, - } - switch options.Mode { - case "native": - result = append(result, r) - case "dockercompat": - compat, err := dockercompat.NetworkFromNative(r) - if err != nil { - return err - } - result = append(result, compat) + netLists, errs := cniEnv.ListNetworksMatch(options.Networks, true) + + for req, netList := range netLists { + if len(netList) > 1 { + errs = append(errs, fmt.Errorf("multiple IDs found with provided prefix: %s", req)) + continue + } + if len(netList) == 0 { + errs = append(errs, fmt.Errorf("no network found matching: %s", req)) + continue + } + network := netList[0] + r := &native.Network{ + CNI: json.RawMessage(network.Bytes), + NerdctlID: network.NerdctlID, + NerdctlLabels: network.NerdctlLabels, + File: network.File, + } + switch options.Mode { + case "native": + result = append(result, r) + case "dockercompat": + compat, err := dockercompat.NetworkFromNative(r) + if err != nil { + return err } - return nil - }, + result = append(result, compat) + } } - // `network inspect` doesn't support pseudo network. - err = walker.WalkAll(ctx, options.Networks, true, false) if len(result) > 0 { if formatErr := formatter.FormatSlice(options.Format, options.Stdout, result); formatErr != nil { log.G(ctx).Error(formatErr) } + err = nil + } else { + err = errors.New("unable to find any network matching the provided request") + } + + for _, unErr := range errs { + log.G(ctx).Error(unErr) } + return err } diff --git a/pkg/cmd/network/remove.go b/pkg/cmd/network/remove.go index 66ad7ca1f33..41731d3f40c 100644 --- a/pkg/cmd/network/remove.go +++ b/pkg/cmd/network/remove.go @@ -18,17 +18,18 @@ package network import ( "context" + "errors" "fmt" containerd "github.com/containerd/containerd/v2/client" + "github.com/containerd/log" "github.com/containerd/nerdctl/v2/pkg/api/types" - "github.com/containerd/nerdctl/v2/pkg/idutil/netwalker" "github.com/containerd/nerdctl/v2/pkg/netutil" ) func Remove(ctx context.Context, client *containerd.Client, options types.NetworkRemoveOptions) error { - e, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace)) + cniEnv, err := netutil.NewCNIEnv(options.GOptions.CNIPath, options.GOptions.CNINetConfPath, netutil.WithNamespace(options.GOptions.Namespace)) if err != nil { return err } @@ -38,28 +39,52 @@ func Remove(ctx context.Context, client *containerd.Client, options types.Networ return err } - 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(options.Stdout, found.Req) - return nil - }, + var result []string + netLists, errs := cniEnv.ListNetworksMatch(options.Networks, false) + + for req, netList := range netLists { + if len(netList) > 1 { + errs = append(errs, fmt.Errorf("multiple IDs found with provided prefix: %s", req)) + continue + } + if len(netList) == 0 { + errs = append(errs, fmt.Errorf("no network found matching: %s", req)) + continue + } + network := netList[0] + if value, ok := usedNetworkInfo[network.Name]; ok { + errs = append(errs, fmt.Errorf("network %q is in use by container %q", req, value)) + continue + } + if network.Name == "bridge" { + errs = append(errs, errors.New("cannot remove pre-defined network bridge")) + continue + } + if network.File == "" { + errs = append(errs, fmt.Errorf("%s is a pre-defined network and cannot be removed", req)) + continue + } + if network.NerdctlID == nil { + errs = append(errs, fmt.Errorf("%s is managed outside nerdctl and cannot be removed", req)) + continue + } + if err := cniEnv.RemoveNetwork(network); err != nil { + errs = append(errs, err) + } else { + result = append(result, req) + } + } + for _, unErr := range errs { + log.G(ctx).Error(unErr) + } + if len(result) > 0 { + for _, id := range result { + fmt.Fprintln(options.Stdout, id) + } + err = nil + } else { + err = errors.New("no network could be removed") } - return walker.WalkAll(ctx, options.Networks, true, false) + return err } diff --git a/pkg/containerutil/container_network_manager.go b/pkg/containerutil/container_network_manager.go index 6a97adc47ef..7f7b97b13e4 100644 --- a/pkg/containerutil/container_network_manager.go +++ b/pkg/containerutil/container_network_manager.go @@ -667,17 +667,14 @@ func writeEtcHostnameForContainer(globalOptions types.GlobalCommandOptions, host // from the networkSlice is of a type within supportedTypes. // nolint:unused func verifyNetworkTypes(env *netutil.CNIEnv, networkSlice []string, supportedTypes []string) (map[string]*netutil.NetworkConfig, error) { - netMap, err := env.NetworkMap() - if err != nil { - return nil, err - } - res := make(map[string]*netutil.NetworkConfig, len(networkSlice)) + var netConfig *netutil.NetworkConfig + var err error for _, netstr := range networkSlice { - netConfig, ok := netMap[netstr] - if !ok { - return nil, fmt.Errorf("network %s not found", netstr) + if netConfig, err = env.NetworkByNameOrID(netstr); err != nil { + return nil, err } + netType := netConfig.Plugins[0].Network.Type if supportedTypes != nil && !strutil.InStringSlice(supportedTypes, netType) { return nil, fmt.Errorf("network type %q is not supported for network mapping %q, must be one of: %v", netType, netstr, supportedTypes) diff --git a/pkg/idutil/netwalker/netwalker.go b/pkg/idutil/netwalker/netwalker.go deleted file mode 100644 index 40debd9bfd2..00000000000 --- a/pkg/idutil/netwalker/netwalker.go +++ /dev/null @@ -1,119 +0,0 @@ -/* - 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" - "strings" - - "github.com/containerd/nerdctl/v2/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 - } - - idFilterF := func(n *netutil.NetworkConfig) bool { - if n.NerdctlID == nil { - // External network - return n.Name == req - } - return n.Name == req || longIDExp.Match([]byte(*n.NerdctlID)) || shortIDExp.Match([]byte(*n.NerdctlID)) - } - networks, err := w.Client.FilterNetworks(idFilterF) - if err != nil { - return 0, err - } - - 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 -} - -// WalkAll calls `Walk` for each req in `reqs`. -// -// It can be used when the matchCount is not important (e.g., only care if there -// is any error or if matchCount == 0 (not found error) when walking all reqs). -// If `forceAll`, it calls `Walk` on every req -// and return all errors joined by `\n`. If not `forceAll`, it returns the first error -// encountered while calling `Walk`. -// `allowSeudoNetwork` allows seudo network (host, none) to be passed to `Walk`, otherwise -// an error is recorded for it. -func (w *NetworkWalker) WalkAll(ctx context.Context, reqs []string, forceAll, allowSeudoNetwork bool) error { - var errs []string - for _, req := range reqs { - if !allowSeudoNetwork && (req == "host" || req == "none") { - err := fmt.Errorf("pseudo network not allowed: %s", req) - if !forceAll { - return err - } - errs = append(errs, err.Error()) - } else { - n, err := w.Walk(ctx, req) - if err == nil && n == 0 { - err = fmt.Errorf("no such network: %s", req) - } - if err != nil { - if !forceAll { - return err - } - errs = append(errs, err.Error()) - } - } - } - if len(errs) > 0 { - return fmt.Errorf("%d errors:\n%s", len(errs), strings.Join(errs, "\n")) - } - return nil -} diff --git a/pkg/netutil/netutil.go b/pkg/netutil/netutil.go index bafc1385c3e..81badc8edf1 100644 --- a/pkg/netutil/netutil.go +++ b/pkg/netutil/netutil.go @@ -53,6 +53,48 @@ type CNIEnv struct { type CNIEnvOpt func(e *CNIEnv) error +func (e *CNIEnv) ListNetworksMatch(reqs []string, allowPseudoNetwork bool) (list map[string][]*NetworkConfig, errs []error) { + var err error + + var networkConfigs []*NetworkConfig + err = lockutil.WithDirLock(e.NetconfPath, func() error { + networkConfigs, err = e.networkConfigList() + return err + }) + if err != nil { + return nil, []error{err} + } + + list = make(map[string][]*NetworkConfig) + for _, req := range reqs { + if !allowPseudoNetwork && (req == "host" || req == "none") { + errs = append(errs, fmt.Errorf("pseudo network not allowed: %s", req)) + continue + } + + result := []*NetworkConfig{} + // First match by name + for _, networkConfig := range networkConfigs { + if networkConfig.Name == req { + result = append(result, networkConfig) + } + } + // If nothing, try to match the id + if len(result) == 0 { + for _, networkConfig := range networkConfigs { + if networkConfig.NerdctlID != nil { + if len(req) <= len((*networkConfig.NerdctlID)) && (*networkConfig.NerdctlID)[0:len(req)] == req { + result = append(result, networkConfig) + } + } + } + } + list[req] = result + } + + return list, errs +} + func UsedNetworks(ctx context.Context, client *containerd.Client) (map[string][]string, error) { nsService := client.NamespaceService() nsList, err := nsService.List(ctx) @@ -188,19 +230,29 @@ func (e *CNIEnv) NetworkMap() (map[string]*NetworkConfig, error) { //nolint:revi log.L.Warnf("duplicate network name %q, %#v will get superseded by %#v", n.Name, original, n) } m[n.Name] = n - if n.NerdctlID != nil { - id := *n.NerdctlID - m[id] = n - if len(id) > 12 { - id = id[:12] - m[id] = n - } - } } return m, nil } -func (e *CNIEnv) FilterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkConfig, error) { +func (e *CNIEnv) NetworkByNameOrID(key string) (*NetworkConfig, error) { + networks, err := e.networkConfigList() + if err != nil { + return nil, err + } + + for _, n := range networks { + if n.Name == key { + return n, nil + } + if n.NerdctlID != nil && (*n.NerdctlID == key || (*n.NerdctlID)[0:12] == key) { + return n, nil + } + } + + return nil, fmt.Errorf("no such network: %q", key) +} + +func (e *CNIEnv) filterNetworks(filterf func(*NetworkConfig) bool) ([]*NetworkConfig, error) { networkConfigs, err := e.networkConfigList() if err != nil { return nil, err @@ -230,8 +282,8 @@ func (e *CNIEnv) usedSubnets() ([]*net.IPNet, error) { if err != nil { return nil, err } - for _, net := range networkConfigs { - usedSubnets = append(usedSubnets, net.subnets()...) + for _, netConf := range networkConfigs { + usedSubnets = append(usedSubnets, netConf.subnets()...) } return usedSubnets, nil } @@ -252,7 +304,7 @@ type cniNetworkConfig struct { } func (e *CNIEnv) CreateNetwork(opts types.NetworkCreateOptions) (*NetworkConfig, error) { //nolint:revive - var net *NetworkConfig + var netConf *NetworkConfig fn := func() error { netMap, err := e.NetworkMap() @@ -272,17 +324,17 @@ func (e *CNIEnv) CreateNetwork(opts types.NetworkCreateOptions) (*NetworkConfig, if err != nil { return err } - net, err = e.generateNetworkConfig(opts.Name, opts.Labels, plugins) + netConf, err = e.generateNetworkConfig(opts.Name, opts.Labels, plugins) if err != nil { return err } - return e.writeNetworkConfig(net) + return e.writeNetworkConfig(netConf) } err := lockutil.WithDirLock(e.NetconfPath, fn) if err != nil { return nil, err } - return net, nil + return netConf, nil } func (e *CNIEnv) RemoveNetwork(net *NetworkConfig) error { @@ -309,7 +361,7 @@ func (e *CNIEnv) GetDefaultNetworkConfig() (*NetworkConfig, error) { } return false } - labelMatches, err := e.FilterNetworks(defaultLabelFilterF) + labelMatches, err := e.filterNetworks(defaultLabelFilterF) if err != nil { return nil, err } @@ -324,7 +376,7 @@ func (e *CNIEnv) GetDefaultNetworkConfig() (*NetworkConfig, error) { defaultNameFilterF := func(nc *NetworkConfig) bool { return nc.Name == DefaultNetworkName } - nameMatches, err := e.FilterNetworks(defaultNameFilterF) + nameMatches, err := e.filterNetworks(defaultNameFilterF) if err != nil { return nil, err } @@ -460,11 +512,11 @@ func (e *CNIEnv) networkConfigList() ([]*NetworkConfig, error) { return nil, err } } - id, labels := nerdctlIDLabels(lcl.Bytes) + id, lbls := nerdctlIDLabels(lcl.Bytes) l = append(l, &NetworkConfig{ NetworkConfigList: lcl, NerdctlID: id, - NerdctlLabels: labels, + NerdctlLabels: lbls, File: fileName, }) } @@ -567,7 +619,7 @@ func structToMap(in interface{}) (map[string]interface{}, error) { } // ParseMTU parses the mtu option -func ParseMTU(mtu string) (int, error) { +func parseMTU(mtu string) (int, error) { if mtu == "" { return 0, nil // default } diff --git a/pkg/netutil/netutil_unix.go b/pkg/netutil/netutil_unix.go index df62145f632..eba2a5e3b8b 100644 --- a/pkg/netutil/netutil_unix.go +++ b/pkg/netutil/netutil_unix.go @@ -102,7 +102,7 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string] for opt, v := range opts { switch opt { case "mtu", "com.docker.network.driver.mtu": - mtu, err = ParseMTU(v) + mtu, err = parseMTU(v) if err != nil { return nil, err } @@ -148,7 +148,7 @@ func (e *CNIEnv) generateCNIPlugins(driver string, name string, ipam map[string] for opt, v := range opts { switch opt { case "mtu", "com.docker.network.driver.mtu": - mtu, err = ParseMTU(v) + mtu, err = parseMTU(v) if err != nil { return nil, err } diff --git a/pkg/ocihook/ocihook.go b/pkg/ocihook/ocihook.go index e67ec17c677..ffa98041611 100644 --- a/pkg/ocihook/ocihook.go +++ b/pkg/ocihook/ocihook.go @@ -159,16 +159,12 @@ func newHandlerOpts(state *specs.State, dataStore, cniPath, cniNetconfPath strin cniOpts := []gocni.Opt{ gocni.WithPluginDir([]string{cniPath}), } - netMap, err := e.NetworkMap() - if err != nil { - return nil, err - } + var netw *netutil.NetworkConfig for _, netstr := range networks { - net, ok := netMap[netstr] - if !ok { - return nil, fmt.Errorf("no such network: %q", netstr) + if netw, err = e.NetworkByNameOrID(netstr); err != nil { + return nil, err } - cniOpts = append(cniOpts, gocni.WithConfListBytes(net.Bytes)) + cniOpts = append(cniOpts, gocni.WithConfListBytes(netw.Bytes)) o.cniNames = append(o.cniNames, netstr) } o.cni, err = gocni.New(cniOpts...) From 1fe34f86cfb6be5f178eaf474dbb907aa178d0f3 Mon Sep 17 00:00:00 2001 From: apostasie Date: Sun, 6 Oct 2024 18:03:36 -0700 Subject: [PATCH 2/2] Tests for net inspect Signed-off-by: apostasie --- cmd/nerdctl/network/network_inspect_test.go | 129 +++++++++++++++++++- 1 file changed, 126 insertions(+), 3 deletions(-) diff --git a/cmd/nerdctl/network/network_inspect_test.go b/cmd/nerdctl/network/network_inspect_test.go index 4aa6147d063..5dfe9b88612 100644 --- a/cmd/nerdctl/network/network_inspect_test.go +++ b/cmd/nerdctl/network/network_inspect_test.go @@ -19,6 +19,7 @@ package network import ( "encoding/json" "errors" + "strings" "testing" "gotest.tools/v3/assert" @@ -38,6 +39,128 @@ func TestNetworkInspect(t *testing.T) { ) testGroup := &test.Group{ + { + Description: "non existent network", + Command: test.RunCommand("network", "inspect", "nonexistent"), + // FIXME: where is this error even comin from? + Expected: test.Expects(1, []error{errors.New("no network found matching")}, nil), + }, + { + Description: "invalid name network", + Command: test.RunCommand("network", "inspect", "∞"), + // FIXME: this is not even a valid identifier + Expected: test.Expects(1, []error{errors.New("no network found matching")}, nil), + }, + { + Description: "none", + Require: nerdtest.NerdctlNeedsFixing, + Command: test.RunCommand("network", "inspect", "none"), + Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, "none") + }), + }, + { + Description: "host", + Require: nerdtest.NerdctlNeedsFixing, + Command: test.RunCommand("network", "inspect", "host"), + Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, "host") + }), + }, + { + Description: "bridge", + Require: test.Not(test.Windows), + Command: test.RunCommand("network", "inspect", "bridge"), + Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, "bridge") + }), + }, + { + Description: "custom", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", "custom") + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("network", "remove", "custom") + }, + Command: test.RunCommand("network", "inspect", "custom"), + Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, "custom") + }), + }, + { + Description: "match exact id", + Require: test.Not(test.Windows), + Command: func(data test.Data, helpers test.Helpers) test.Command { + id := strings.TrimSpace(helpers.Capture("network", "inspect", "bridge", "--format", "{{ .Id }}")) + return helpers.Command("network", "inspect", id) + }, + Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, "bridge") + }), + }, + { + Description: "match part of id", + Require: test.Not(test.Windows), + Command: func(data test.Data, helpers test.Helpers) test.Command { + id := strings.TrimSpace(helpers.Capture("network", "inspect", "bridge", "--format", "{{ .Id }}")) + return helpers.Command("network", "inspect", id[0:25]) + }, + Expected: test.Expects(0, nil, func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, "bridge") + }), + }, + { + Description: "using another net short id", + Require: test.Not(test.Windows), + Setup: func(data test.Data, helpers test.Helpers) { + id := strings.TrimSpace(helpers.Capture("network", "inspect", "bridge", "--format", "{{ .Id }}")) + helpers.Ensure("network", "create", id[0:12]) + data.Set("netname", id[0:12]) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + id := strings.TrimSpace(helpers.Capture("network", "inspect", "bridge", "--format", "{{ .Id }}")) + helpers.Anyhow("network", "remove", id[0:12]) + }, + Command: func(data test.Data, helpers test.Helpers) test.Command { + return helpers.Command("network", "inspect", data.Get("netname")) + }, + Expected: func(data test.Data, helpers test.Helpers) *test.Expected { + return &test.Expected{ + Output: func(stdout string, info string, t *testing.T) { + var dc []dockercompat.Network + err := json.Unmarshal([]byte(stdout), &dc) + assert.NilError(t, err, "Unable to unmarshal output\n"+info) + assert.Equal(t, 1, len(dc), "Unexpectedly got multiple results\n"+info) + assert.Equal(t, dc[0].Name, data.Get("netname")) + }, + } + }, + }, { Description: "Test network inspect", // IPAMConfig is not implemented on Windows yet @@ -88,16 +211,16 @@ func TestNetworkInspect(t *testing.T) { return &test.Expected{ ExitCode: 0, Output: func(stdout string, info string, t *testing.T) { - cmd := helpers.Command().Clear().WithBinary("nerdctl").WithArgs("--namespace", data.Identifier()) + cmd := helpers.CustomCommand("nerdctl", "--namespace", data.Identifier()) cmd.Clone().WithArgs("network", "inspect", data.Identifier()).Run(&test.Expected{ ExitCode: 1, - Errors: []error{errors.New("no such network")}, + Errors: []error{errors.New("no network found")}, }) cmd.Clone().WithArgs("network", "remove", data.Identifier()).Run(&test.Expected{ ExitCode: 1, - Errors: []error{errors.New("no such network")}, + Errors: []error{errors.New("no network found")}, }) cmd.Clone().WithArgs("network", "ls").Run(&test.Expected{