diff --git a/cmd/podman/common/completion.go b/cmd/podman/common/completion.go index a8e9fc0102..9427e7535b 100644 --- a/cmd/podman/common/completion.go +++ b/cmd/podman/common/completion.go @@ -1298,7 +1298,7 @@ func getEntityType(cmd *cobra.Command, args []string, o interface{}) interface{} } // network logic if networks, _ := getNetworks(cmd, args[0], completeDefault); len(networks) > 0 { - return &types.Network{} + return &entities.NetworkInspectReport{} } return o } diff --git a/cmd/podman/networks/inspect.go b/cmd/podman/networks/inspect.go index 6bb10287b5..f2962fdeca 100644 --- a/cmd/podman/networks/inspect.go +++ b/cmd/podman/networks/inspect.go @@ -1,7 +1,6 @@ package network import ( - "github.com/containers/common/libnetwork/types" "github.com/containers/podman/v5/cmd/podman/common" "github.com/containers/podman/v5/cmd/podman/inspect" "github.com/containers/podman/v5/cmd/podman/registry" @@ -33,7 +32,7 @@ func init() { formatFlagName := "format" flags.StringVarP(&inspectOpts.Format, formatFlagName, "f", "", "Pretty-print network to JSON or using a Go template") - _ = networkinspectCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&types.Network{})) + _ = networkinspectCommand.RegisterFlagCompletionFunc(formatFlagName, common.AutocompleteFormat(&entities.NetworkInspectReport{})) } func networkInspect(_ *cobra.Command, args []string) error { diff --git a/docs/source/markdown/podman-network-inspect.1.md b/docs/source/markdown/podman-network-inspect.1.md index 7a3f0445b9..2350764485 100644 --- a/docs/source/markdown/podman-network-inspect.1.md +++ b/docs/source/markdown/podman-network-inspect.1.md @@ -16,6 +16,7 @@ Pretty-print networks to JSON or using a Go template. | **Placeholder** | **Description** | |--------------------|-------------------------------------------| +| .Containers ... | Running containers on this network. | | .Created ... | Timestamp when the network was created | | .DNSEnabled | Network has dns enabled (boolean) | | .Driver | Network driver | @@ -25,6 +26,7 @@ Pretty-print networks to JSON or using a Go template. | .IPv6Enabled | Network has ipv6 subnet (boolean) | | .Labels ... | Network labels | | .Name | Network name | +| .Network ... | Nested Network type | | .NetworkDNSServers | Array of DNS servers used in this network | | .NetworkInterface | Name of the network interface on the host | | .Options ... | Network options | diff --git a/pkg/api/handlers/compat/networks.go b/pkg/api/handlers/compat/networks.go index a727225692..d7ea086201 100644 --- a/pkg/api/handlers/compat/networks.go +++ b/pkg/api/handlers/compat/networks.go @@ -22,36 +22,6 @@ import ( "github.com/sirupsen/logrus" ) -type containerNetStatus struct { - name string - id string - status map[string]nettypes.StatusBlock -} - -func getContainerNetStatuses(rt *libpod.Runtime) ([]containerNetStatus, error) { - cons, err := rt.GetAllContainers() - if err != nil { - return nil, err - } - statuses := make([]containerNetStatus, 0, len(cons)) - for _, con := range cons { - status, err := con.GetNetworkStatus() - if err != nil { - if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { - continue - } - return nil, err - } - - statuses = append(statuses, containerNetStatus{ - id: con.ID(), - name: con.Name(), - status: status, - }) - } - return statuses, nil -} - func normalizeNetworkName(rt *libpod.Runtime, name string) (string, bool) { if name == nettypes.BridgeNetworkDriver { return rt.Network().DefaultNetworkName(), true @@ -86,7 +56,8 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { utils.NetworkNotFound(w, name, err) return } - statuses, err := getContainerNetStatuses(runtime) + ic := abi.ContainerEngine{Libpod: runtime} + statuses, err := ic.GetContainerNetStatuses() if err != nil { utils.InternalServerError(w, err) return @@ -95,10 +66,10 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) { utils.WriteResponse(w, http.StatusOK, report) } -func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, statuses []containerNetStatus, network *nettypes.Network, changeDefaultName bool) *types.NetworkResource { +func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, statuses []abi.ContainerNetStatus, network *nettypes.Network, changeDefaultName bool) *types.NetworkResource { containerEndpoints := make(map[string]types.EndpointResource, len(statuses)) for _, st := range statuses { - if netData, ok := st.status[network.Name]; ok { + if netData, ok := st.Status[network.Name]; ok { ipv4Address := "" ipv6Address := "" macAddr := "" @@ -116,12 +87,12 @@ func convertLibpodNetworktoDockerNetwork(runtime *libpod.Runtime, statuses []con break } containerEndpoint := types.EndpointResource{ - Name: st.name, + Name: st.Name, MacAddress: macAddr, IPv4Address: ipv4Address, IPv6Address: ipv6Address, } - containerEndpoints[st.id] = containerEndpoint + containerEndpoints[st.ID] = containerEndpoint } } ipamConfigs := make([]dockerNetwork.IPAMConfig, 0, len(network.Subnets)) @@ -192,7 +163,7 @@ func ListNetworks(w http.ResponseWriter, r *http.Request) { utils.InternalServerError(w, err) return } - statuses, err := getContainerNetStatuses(runtime) + statuses, err := ic.GetContainerNetStatuses() if err != nil { utils.InternalServerError(w, err) return diff --git a/pkg/api/handlers/swagger/responses.go b/pkg/api/handlers/swagger/responses.go index ee8704dea8..6c52a00588 100644 --- a/pkg/api/handlers/swagger/responses.go +++ b/pkg/api/handlers/swagger/responses.go @@ -434,7 +434,7 @@ type networkRmResponse struct { // swagger:response type networkInspectResponse struct { // in:body - Body types.Network + Body entities.NetworkInspectReport } // Network list diff --git a/pkg/bindings/network/network.go b/pkg/bindings/network/network.go index 0ff425313c..a0512d5ec4 100644 --- a/pkg/bindings/network/network.go +++ b/pkg/bindings/network/network.go @@ -70,8 +70,8 @@ func Update(ctx context.Context, netNameOrID string, options *UpdateOptions) err } // Inspect returns information about a network configuration -func Inspect(ctx context.Context, nameOrID string, _ *InspectOptions) (types.Network, error) { - var net types.Network +func Inspect(ctx context.Context, nameOrID string, _ *InspectOptions) (entitiesTypes.NetworkInspectReport, error) { + var net entitiesTypes.NetworkInspectReport conn, err := bindings.GetClient(ctx) if err != nil { return net, err diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 5c9c53517f..15cf309bf2 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -70,7 +70,7 @@ type ContainerEngine interface { //nolint:interfacebloat NetworkUpdate(ctx context.Context, networkname string, options NetworkUpdateOptions) error NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error NetworkExists(ctx context.Context, networkname string) (*BoolReport, error) - NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]netTypes.Network, []error, error) + NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error) NetworkList(ctx context.Context, options NetworkListOptions) ([]netTypes.Network, error) NetworkPrune(ctx context.Context, options NetworkPruneOptions) ([]*NetworkPruneReport, error) NetworkReload(ctx context.Context, names []string, options NetworkReloadOptions) ([]*NetworkReloadReport, error) diff --git a/pkg/domain/entities/network.go b/pkg/domain/entities/network.go index c2e223233c..1edc335c3b 100644 --- a/pkg/domain/entities/network.go +++ b/pkg/domain/entities/network.go @@ -82,3 +82,6 @@ type NetworkPruneReport = entitiesTypes.NetworkPruneReport type NetworkPruneOptions struct { Filters map[string][]string } + +type NetworkInspectReport = entitiesTypes.NetworkInspectReport +type NetworkContainerInfo = entitiesTypes.NetworkContainerInfo diff --git a/pkg/domain/entities/types/network.go b/pkg/domain/entities/types/network.go index 8c547ee94d..ae233b8232 100644 --- a/pkg/domain/entities/types/network.go +++ b/pkg/domain/entities/types/network.go @@ -35,3 +35,17 @@ type NetworkRmReport struct { type NetworkCreateReport struct { Name string } + +type NetworkInspectReport struct { + commonTypes.Network + + Containers map[string]NetworkContainerInfo `json:"containers"` +} + +type NetworkContainerInfo struct { + // Name of the container + Name string `json:"name"` + + // Interfaces configured for this container with their addresses + Interfaces map[string]commonTypes.NetInterface `json:"interfaces,omitempty"` +} diff --git a/pkg/domain/infra/abi/network.go b/pkg/domain/infra/abi/network.go index c5309ed8d1..3db382448d 100644 --- a/pkg/domain/infra/abi/network.go +++ b/pkg/domain/infra/abi/network.go @@ -64,9 +64,13 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, options entities.Net return nets, err } -func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]types.Network, []error, error) { +func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, options entities.InspectOptions) ([]entities.NetworkInspectReport, []error, error) { var errs []error - networks := make([]types.Network, 0, len(namesOrIds)) + statuses, err := ic.GetContainerNetStatuses() + if err != nil { + return nil, nil, fmt.Errorf("failed to get network status for containers: %w", err) + } + networks := make([]entities.NetworkInspectReport, 0, len(namesOrIds)) for _, name := range namesOrIds { net, err := ic.Libpod.Network().NetworkInspect(name) if err != nil { @@ -77,7 +81,22 @@ func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []stri return nil, nil, fmt.Errorf("inspecting network %s: %w", name, err) } } - networks = append(networks, net) + containerMap := make(map[string]entities.NetworkContainerInfo) + for _, st := range statuses { + // Make sure to only show the info for the correct network + if sb, ok := st.Status[net.Name]; ok { + containerMap[st.ID] = entities.NetworkContainerInfo{ + Name: st.Name, + Interfaces: sb.Interfaces, + } + } + } + + netReport := entities.NetworkInspectReport{ + Network: net, + Containers: containerMap, + } + networks = append(networks, netReport) } return networks, errs, nil } @@ -243,3 +262,36 @@ func (ic *ContainerEngine) createDanglingFilterFunc(wantDangling bool) (types.Fi return wantDangling }, nil } + +type ContainerNetStatus struct { + // Name of the container + Name string + // ID of the container + ID string + // Status contains the net status, the key is the network name + Status map[string]types.StatusBlock +} + +func (ic *ContainerEngine) GetContainerNetStatuses() ([]ContainerNetStatus, error) { + cons, err := ic.Libpod.GetAllContainers() + if err != nil { + return nil, err + } + statuses := make([]ContainerNetStatus, 0, len(cons)) + for _, con := range cons { + status, err := con.GetNetworkStatus() + if err != nil { + if errors.Is(err, define.ErrNoSuchCtr) || errors.Is(err, define.ErrCtrRemoved) { + continue + } + return nil, err + } + + statuses = append(statuses, ContainerNetStatus{ + ID: con.ID(), + Name: con.Name(), + Status: status, + }) + } + return statuses, nil +} diff --git a/pkg/domain/infra/tunnel/network.go b/pkg/domain/infra/tunnel/network.go index e09e247a3b..bd48f6dcaa 100644 --- a/pkg/domain/infra/tunnel/network.go +++ b/pkg/domain/infra/tunnel/network.go @@ -22,9 +22,9 @@ func (ic *ContainerEngine) NetworkList(ctx context.Context, opts entities.Networ return network.List(ic.ClientCtx, options) } -func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, opts entities.InspectOptions) ([]types.Network, []error, error) { +func (ic *ContainerEngine) NetworkInspect(ctx context.Context, namesOrIds []string, opts entities.InspectOptions) ([]entities.NetworkInspectReport, []error, error) { var ( - reports = make([]types.Network, 0, len(namesOrIds)) + reports = make([]entities.NetworkInspectReport, 0, len(namesOrIds)) errs = []error{} ) options := new(network.InspectOptions) diff --git a/test/e2e/network_create_test.go b/test/e2e/network_create_test.go index 764f97cdbb..28edc41be3 100644 --- a/test/e2e/network_create_test.go +++ b/test/e2e/network_create_test.go @@ -5,6 +5,7 @@ import ( "net" "github.com/containers/common/libnetwork/types" + "github.com/containers/podman/v5/pkg/domain/entities" . "github.com/containers/podman/v5/test/utils" "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo/v2" @@ -32,7 +33,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -84,7 +85,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -125,7 +126,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -168,7 +169,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -213,7 +214,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -254,7 +255,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -284,7 +285,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -323,7 +324,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -711,7 +712,7 @@ var _ = Describe("Podman network create", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) diff --git a/test/e2e/network_test.go b/test/e2e/network_test.go index 1117eb7685..338d9b4d5b 100644 --- a/test/e2e/network_test.go +++ b/test/e2e/network_test.go @@ -6,7 +6,7 @@ import ( "path/filepath" "time" - "github.com/containers/common/libnetwork/types" + "github.com/containers/podman/v5/pkg/domain/entities" . "github.com/containers/podman/v5/test/utils" "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo/v2" @@ -530,7 +530,7 @@ var _ = Describe("Podman network", func() { Expect(inspect).Should(ExitCleanly()) // JSON the network configuration into something usable - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -556,7 +556,7 @@ var _ = Describe("Podman network", func() { inspect.WaitWithDefaultTimeout() Expect(inspect).Should(ExitCleanly()) - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -584,7 +584,7 @@ var _ = Describe("Podman network", func() { inspect.WaitWithDefaultTimeout() Expect(inspect).Should(ExitCleanly()) - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -627,7 +627,7 @@ var _ = Describe("Podman network", func() { inspect.WaitWithDefaultTimeout() Expect(inspect).Should(ExitCleanly()) - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(inspect.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index b854e76010..6f036295d4 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -10,7 +10,7 @@ import ( "syscall" "github.com/containernetworking/plugins/pkg/ns" - "github.com/containers/common/libnetwork/types" + "github.com/containers/podman/v5/pkg/domain/entities" . "github.com/containers/podman/v5/test/utils" "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo/v2" @@ -36,7 +36,7 @@ var _ = Describe("Podman run networking", func() { session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(net) Expect(session).Should(ExitCleanly()) - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(session.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) @@ -83,7 +83,7 @@ var _ = Describe("Podman run networking", func() { session.WaitWithDefaultTimeout() defer podmanTest.removeNetwork(net) Expect(session).Should(ExitCleanly()) - var results []types.Network + var results []entities.NetworkInspectReport err := json.Unmarshal([]byte(session.OutputToString()), &results) Expect(err).ToNot(HaveOccurred()) Expect(results).To(HaveLen(1)) diff --git a/test/system/500-networking.bats b/test/system/500-networking.bats index fc6c881124..174d2e4d47 100644 --- a/test/system/500-networking.bats +++ b/test/system/500-networking.bats @@ -953,4 +953,52 @@ EOF assert "$output" = $hostname "/etc/hostname with --uts=host --net=host must be equal to 'uname -n'" } +@test "podman network inspect running containers" { + local cname1=c1-$(random_string 10) + local cname2=c2-$(random_string 10) + local cname3=c3-$(random_string 10) + + local netname=net-$(random_string 10) + local subnet=$(random_rfc1918_subnet) + + run_podman network create --subnet "${subnet}.0/24" $netname + + run_podman network inspect --format "{{json .Containers}}" $netname + assert "$output" == "{}" "no containers on the network" + + run_podman create --name $cname1 --network $netname $IMAGE top + cid1="$output" + run_podman create --name $cname2 --network $netname $IMAGE top + cid2="$output" + + # containers should only be part of the output when they are running + run_podman network inspect --format "{{json .Containers}}" $netname + assert "$output" == "{}" "no running containers on the network" + + # start the containers to setup the network info + run_podman start $cname1 $cname2 + + # also run a third container on different network (should not be part of inspect then) + run_podman run -d --name $cname3 --network podman $IMAGE top + cid3="$output" + + # Map ordering is not deterministic so we check each container one by one + local expect="\{\"name\":\"$cname1\",\"interfaces\":\{\"eth0\":\{\"subnets\":\[\{\"ipnet\":\"${subnet}.2/24\"\,\"gateway\":\"${subnet}.1\"\}\],\"mac_address\":\"[0-9a-f]{2}:.*\"\}\}\}" + run_podman network inspect --format "{{json (index .Containers \"$cid1\")}}" $netname + assert "$output" =~ "$expect" "container 1 on the network" + + local expect="\{\"name\":\"$cname2\",\"interfaces\":\{\"eth0\":\{\"subnets\":\[\{\"ipnet\":\"${subnet}.3/24\"\,\"gateway\":\"${subnet}.1\"\}\],\"mac_address\":\"[0-9a-f]{2}:.*\"\}\}\}" + run_podman network inspect --format "{{json (index .Containers \"$cid2\")}}" $netname + assert "$output" =~ "$expect" "container 2 on the network" + + # container 3 should not be part of the inspect, index does not error if the key does not + # exists so just make sure the cid3 and cname3 are not in the json. + run_podman network inspect --format "{{json .Containers}}" $netname + assert "$output" !~ "$cid3" "container 3 on the network (cid)" + assert "$output" !~ "$cname3" "container 3 on the network (name)" + + run_podman rm -f -t0 $cname1 $cname2 $cname3 + run_podman network rm $netname +} + # vim: filetype=sh