podman network inspect: include running containers

Like docker podman network inspect should output the information of
running container with their ip/mac address on this network.
However the output format is not docker compatible as this cannot
include all the info we have and the previous output was already not
compatible so this is not new.

New example output:
```
[
     {
          ...
          "containers": {
               "7c0d295779cee4a6db7adc07a99e635909413a390eeab9f951edbc4aac406bf1": {
                    "name": "c2",
                    "interfaces": {
                         "eth0": {
                              "subnets": [
                                   {
                                        "ipnet": "10.89.0.4/24",
                                        "gateway": "10.89.0.1"
                                   },
                                   {
                                        "ipnet": "fda3:b4da:da1e:7e9d::4/64",
                                        "gateway": "fda3:b4da:da1e:7e9d::1"
                                   }
                              ],
                              "mac_address": "1a:bd:ca:ea:4b:3a"
                         }
                    }
               },
               "b17c6651ae6d9cc7d5825968e01d6b1e67f44460bb0c140bcc32bd9d436ac11d": {
                    "name": "c1",
                    "interfaces": {
                         "eth0": {
                              "subnets": [
                                   {
                                        "ipnet": "10.89.0.3/24",
                                        "gateway": "10.89.0.1"
                                   },
                                   {
                                        "ipnet": "fda3:b4da:da1e:7e9d::3/64",
                                        "gateway": "fda3:b4da:da1e:7e9d::1"
                                   }
                              ],
                              "mac_address": "f6:50:e6:22:d9:55"
                         }
                    }
               }
          }
     }
]
```

Fixes #14126
Fixes https://issues.redhat.com/browse/RHEL-3153

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2024-02-28 14:28:47 +01:00
parent 031e7a15b0
commit 5952486df8
15 changed files with 155 additions and 65 deletions

View File

@ -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
}

View File

@ -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 {

View File

@ -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 |

View File

@ -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

View File

@ -434,7 +434,7 @@ type networkRmResponse struct {
// swagger:response
type networkInspectResponse struct {
// in:body
Body types.Network
Body entities.NetworkInspectReport
}
// Network list

View File

@ -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

View File

@ -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)

View File

@ -82,3 +82,6 @@ type NetworkPruneReport = entitiesTypes.NetworkPruneReport
type NetworkPruneOptions struct {
Filters map[string][]string
}
type NetworkInspectReport = entitiesTypes.NetworkInspectReport
type NetworkContainerInfo = entitiesTypes.NetworkContainerInfo

View File

@ -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"`
}

View File

@ -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
}

View File

@ -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)

View File

@ -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))

View File

@ -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))

View File

@ -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))

View File

@ -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