From a7d46bcfdf94310020294b407ce7b80384746a32 Mon Sep 17 00:00:00 2001 From: Tushar Gupta Date: Sat, 29 Mar 2025 02:05:07 +0530 Subject: [PATCH] feat: Add "Containers" propery in the "nerdctl network inspect" command. Signed-off-by: Tushar Gupta --- cmd/nerdctl/network/network_inspect.go | 14 ++++++- cmd/nerdctl/network/network_inspect_test.go | 37 +++++++++++++++++++ pkg/cmd/network/inspect.go | 28 +++++++++++++- pkg/inspecttypes/dockercompat/dockercompat.go | 28 ++++++++++++-- pkg/inspecttypes/native/network.go | 3 +- 5 files changed, 102 insertions(+), 8 deletions(-) diff --git a/cmd/nerdctl/network/network_inspect.go b/cmd/nerdctl/network/network_inspect.go index adc1a96507d..c61fe893eca 100644 --- a/cmd/nerdctl/network/network_inspect.go +++ b/cmd/nerdctl/network/network_inspect.go @@ -22,6 +22,7 @@ import ( "github.com/containerd/nerdctl/v2/cmd/nerdctl/completion" "github.com/containerd/nerdctl/v2/cmd/nerdctl/helpers" "github.com/containerd/nerdctl/v2/pkg/api/types" + "github.com/containerd/nerdctl/v2/pkg/clientutil" "github.com/containerd/nerdctl/v2/pkg/cmd/network" ) @@ -59,13 +60,22 @@ func inspectAction(cmd *cobra.Command, args []string) error { if err != nil { return err } - return network.Inspect(cmd.Context(), types.NetworkInspectOptions{ + + options := types.NetworkInspectOptions{ GOptions: globalOptions, Mode: mode, Format: format, Networks: args, Stdout: cmd.OutOrStdout(), - }) + } + + client, ctx, cancel, err := clientutil.NewClient(cmd.Context(), options.GOptions.Namespace, options.GOptions.Address) + if err != nil { + return err + } + defer cancel() + + return network.Inspect(ctx, client, options) } func networkInspectShellComplete(cmd *cobra.Command, args []string, toComplete string) ([]string, cobra.ShellCompDirective) { diff --git a/cmd/nerdctl/network/network_inspect_test.go b/cmd/nerdctl/network/network_inspect_test.go index cb89f25f2d4..0694d1f0a9b 100644 --- a/cmd/nerdctl/network/network_inspect_test.go +++ b/cmd/nerdctl/network/network_inspect_test.go @@ -29,6 +29,7 @@ import ( "github.com/containerd/nerdctl/mod/tigron/test" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" + "github.com/containerd/nerdctl/v2/pkg/testutil" "github.com/containerd/nerdctl/v2/pkg/testutil/nerdtest" ) @@ -278,6 +279,42 @@ func TestNetworkInspect(t *testing.T) { } }, }, + { + Description: "Verify that only active containers appear in the network inspect output", + Setup: func(data test.Data, helpers test.Helpers) { + helpers.Ensure("network", "create", data.Identifier("nginx-network-1")) + helpers.Ensure("network", "create", data.Identifier("nginx-network-2")) + helpers.Ensure("create", "--name", data.Identifier("nginx-container-1"), "--network", data.Identifier("nginx-network-1"), testutil.NginxAlpineImage) + helpers.Ensure("create", "--name", data.Identifier("nginx-container-2"), "--network", data.Identifier("nginx-network-1"), testutil.NginxAlpineImage) + helpers.Ensure("create", "--name", data.Identifier("nginx-container-on-diff-network"), "--network", data.Identifier("nginx-network-2"), testutil.NginxAlpineImage) + helpers.Ensure("start", data.Identifier("nginx-container-1"), data.Identifier("nginx-container-on-diff-network")) + data.Set("nginx-container-1-id", strings.Trim(helpers.Capture("inspect", data.Identifier("nginx-container-1"), "--format", "{{.Id}}"), "\n")) + }, + Cleanup: func(data test.Data, helpers test.Helpers) { + helpers.Anyhow("rm", "-f", data.Identifier("nginx-container-1")) + helpers.Anyhow("rm", "-f", data.Identifier("nginx-container-2")) + helpers.Anyhow("rm", "-f", data.Identifier("nginx-container-on-diff-network")) + helpers.Anyhow("network", "remove", data.Identifier("nginx-network-1")) + helpers.Anyhow("network", "remove", data.Identifier("nginx-network-2")) + }, + Command: func(data test.Data, helpers test.Helpers) test.TestableCommand { + return helpers.Command("network", "inspect", data.Identifier("nginx-network-1")) + }, + 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.Identifier("nginx-network-1")) + // Assert only the "running" containers on the same network are returned. + assert.Equal(t, 1, len(dc[0].Containers), "Expected a single container as per configuration, but got multiple.") + assert.Equal(t, data.Identifier("nginx-container-1"), dc[0].Containers[data.Get("nginx-container-1-id")].Name) + }, + } + }, + }, } testCase.Run(t) diff --git a/pkg/cmd/network/inspect.go b/pkg/cmd/network/inspect.go index 5fae28028f3..0a9090b95aa 100644 --- a/pkg/cmd/network/inspect.go +++ b/pkg/cmd/network/inspect.go @@ -22,16 +22,19 @@ import ( "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/containerinspector" "github.com/containerd/nerdctl/v2/pkg/formatter" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/dockercompat" "github.com/containerd/nerdctl/v2/pkg/inspecttypes/native" + "github.com/containerd/nerdctl/v2/pkg/labels" "github.com/containerd/nerdctl/v2/pkg/netutil" ) -func Inspect(ctx context.Context, options types.NetworkInspectOptions) error { +func Inspect(ctx context.Context, client *containerd.Client, options types.NetworkInspectOptions) error { if options.Mode != "native" && options.Mode != "dockercompat" { return fmt.Errorf("unknown mode %q", options.Mode) } @@ -53,12 +56,35 @@ func Inspect(ctx context.Context, options types.NetworkInspectOptions) error { errs = append(errs, fmt.Errorf("no network found matching: %s", req)) continue } + network := netList[0] + var filters = []string{fmt.Sprintf("labels.%q==%q", labels.Networks, []string{network.Name})} + + filteredContainers, err := client.Containers(ctx, filters...) + + if err != nil { + return err + } + + var containers []*native.Container + + for _, container := range filteredContainers { + nativeContainer, err := containerinspector.Inspect(ctx, container) + if err != nil { + continue + } + if nativeContainer.Process == nil || nativeContainer.Process.Status.Status != containerd.Running { + continue + } + containers = append(containers, nativeContainer) + } + r := &native.Network{ CNI: json.RawMessage(network.Bytes), NerdctlID: network.NerdctlID, NerdctlLabels: network.NerdctlLabels, File: network.File, + Containers: containers, } switch options.Mode { case "native": diff --git a/pkg/inspecttypes/dockercompat/dockercompat.go b/pkg/inspecttypes/dockercompat/dockercompat.go index e5b797e786d..f04b4d7401a 100644 --- a/pkg/inspecttypes/dockercompat/dockercompat.go +++ b/pkg/inspecttypes/dockercompat/dockercompat.go @@ -884,13 +884,22 @@ type IPAM struct { // Network mimics a `docker network inspect` object. // From https://github.com/moby/moby/blob/v20.10.7/api/types/types.go#L430-L448 type Network struct { - Name string `json:"Name"` - ID string `json:"Id,omitempty"` // optional in nerdctl - IPAM IPAM `json:"IPAM,omitempty"` - Labels map[string]string `json:"Labels"` + Name string `json:"Name"` + ID string `json:"Id,omitempty"` // optional in nerdctl + IPAM IPAM `json:"IPAM,omitempty"` + Labels map[string]string `json:"Labels"` + Containers map[string]EndpointResource `json:"Containers"` // Containers contains endpoints belonging to the network // Scope, Driver, etc. are omitted } +type EndpointResource struct { + Name string `json:"Name"` + // EndpointID string `json:"EndpointID"` + // MacAddress string `json:"MacAddress"` + // IPv4Address string `json:"IPv4Address"` + // IPv6Address string `json:"IPv6Address"` +} + type structuredCNI struct { Name string `json:"name"` Plugins []struct { @@ -930,6 +939,17 @@ func NetworkFromNative(n *native.Network) (*Network, error) { res.Labels = *n.NerdctlLabels } + res.Containers = make(map[string]EndpointResource) + for _, container := range n.Containers { + res.Containers[container.ID] = EndpointResource{ + Name: container.Labels[labels.Name], + // EndpointID: container.EndpointID, + // MacAddress: container.MacAddress, + // IPv4Address: container.IPv4Address, + // IPv6Address: container.IPv6Address, + } + } + return &res, nil } diff --git a/pkg/inspecttypes/native/network.go b/pkg/inspecttypes/native/network.go index 18d5e96bb1e..f0e84d5974a 100644 --- a/pkg/inspecttypes/native/network.go +++ b/pkg/inspecttypes/native/network.go @@ -18,10 +18,11 @@ package native import "encoding/json" -// Network corresponds to pkg/netutil.NetworkConfigList +// Network corresponds to pkg/netutil.NetworkConfig type Network struct { CNI json.RawMessage `json:"CNI,omitempty"` NerdctlID *string `json:"NerdctlID"` NerdctlLabels *map[string]string `json:"NerdctlLabels,omitempty"` File string `json:"File,omitempty"` + Containers []*Container `json:"Containers"` }