Skip to content
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
1 change: 1 addition & 0 deletions docs/source/markdown/podman-info.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ podman\-info - Display Podman related system information
## DESCRIPTION

Displays information pertinent to the host, current storage stats, configured container registries, and build of podman.
Host information includes configured CDI spec directories and resolved CDI devices when present.


## OPTIONS
Expand Down
8 changes: 8 additions & 0 deletions libpod/define/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ type HostInfo struct {
CgroupManager string `json:"cgroupManager"`
CgroupsVersion string `json:"cgroupVersion"`
CgroupControllers []string `json:"cgroupControllers"`
CDISpecDirs []string `json:"cdiSpecDirs"`
DiscoveredDevices []DeviceInfo `json:"discoveredDevices,omitempty"`
Conmon *ConmonInfo `json:"conmon"`
CPUs int `json:"cpus"`
CPUUtilization *CPUUsage `json:"cpuUtilization"`
Expand Down Expand Up @@ -72,6 +74,12 @@ type HostInfo struct {
EmulatedArchitectures []string `json:"emulatedArchitectures,omitempty"`
}

// DeviceInfo describes a device discovered by a device source.
type DeviceInfo struct {
Source string `json:"source"`
ID string `json:"id"`
}

// RemoteSocket describes information about the API socket
type RemoteSocket struct {
Path string `json:"path,omitempty"`
Expand Down
29 changes: 29 additions & 0 deletions libpod/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import (
"go.podman.io/podman/v6/libpod/define"
"go.podman.io/podman/v6/libpod/linkmode"
"go.podman.io/storage/pkg/system"
"tags.cncf.io/container-device-interface/pkg/cdi"
)

// Info returns the store and host information
Expand Down Expand Up @@ -130,6 +131,7 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
SwapFree: mi.SwapFree,
SwapTotal: mi.SwapTotal,
}
info.CDISpecDirs, info.DiscoveredDevices = r.cdiInfo()
platform := parse.DefaultPlatform()
pArr := strings.Split(platform, "/")
if len(pArr) == 3 {
Expand Down Expand Up @@ -177,6 +179,33 @@ func (r *Runtime) hostInfo() (*define.HostInfo, error) {
return &info, nil
}

func (r *Runtime) cdiInfo() ([]string, []define.DeviceInfo) {
registry, err := cdi.NewCache(
cdi.WithSpecDirs(r.config.Engine.CdiSpecDirs.Get()...),
cdi.WithAutoRefresh(false),
)
if err != nil {
logrus.Debugf("Creating CDI registry for info: %v", err)
Comment thread
Honny1 marked this conversation as resolved.
return r.config.Engine.CdiSpecDirs.Get(), nil
}
if err := registry.Refresh(); err != nil {
logrus.Debugf("The following error was triggered when refreshing the CDI registry for info: %v", err)
}

return registry.GetSpecDirectories(), cdiDeviceInfo(registry.ListDevices())
}

func cdiDeviceInfo(deviceNames []string) []define.DeviceInfo {
devices := make([]define.DeviceInfo, 0, len(deviceNames))
for _, device := range deviceNames {
devices = append(devices, define.DeviceInfo{
Source: "cdi",
ID: device,
})
}
return devices
}

func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) {
var paused, running, stopped int
cs := define.ContainerStore{}
Expand Down
13 changes: 13 additions & 0 deletions pkg/api/handlers/compat/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -62,6 +62,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
CPUSet: sysInfo.Cpuset,
CPUShares: sysInfo.CPUShares,
CgroupDriver: configInfo.Engine.CgroupManager,
CDISpecDirs: infoData.Host.CDISpecDirs,
ContainerdCommit: dockerSystem.Commit{},
Containers: infoData.Store.ContainerStore.Number,
ContainersPaused: stateInfo[define.ContainerStatePaused],
Expand All @@ -70,6 +71,7 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
Debug: log.IsLevelEnabled(log.DebugLevel),
DefaultAddressPools: getDefaultAddressPools(configInfo),
DefaultRuntime: configInfo.Engine.OCIRuntime,
DiscoveredDevices: getDiscoveredDevices(infoData.Host.DiscoveredDevices),
DockerRootDir: infoData.Store.GraphRoot,
Driver: infoData.Store.GraphDriverName,
DriverStatus: getGraphStatus(infoData.Store.GraphStatus),
Expand Down Expand Up @@ -132,6 +134,17 @@ func GetInfo(w http.ResponseWriter, r *http.Request) {
utils.WriteResponse(w, http.StatusOK, info)
}

func getDiscoveredDevices(discoveredDevices []define.DeviceInfo) []dockerSystem.DeviceInfo {
devices := make([]dockerSystem.DeviceInfo, 0, len(discoveredDevices))
for _, device := range discoveredDevices {
devices = append(devices, dockerSystem.DeviceInfo{
Source: device.Source,
ID: device.ID,
})
}
return devices
}

func getServiceConfig(runtime *libpod.Runtime) *registry.ServiceConfig {
var indexConfs map[string]*registry.IndexInfo

Expand Down
136 changes: 80 additions & 56 deletions test/e2e/info_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,22 +47,16 @@ var _ = Describe("Podman Info", func() {
})

It("podman info --format GO template", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Store.GraphRoot}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
podmanTest.PodmanExitCleanly("info", "--format", "{{.Store.GraphRoot}}")
})

It("podman info --format GO template", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Registries}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Registries}}")
Expect(session.OutputToString()).To(ContainSubstring("registry"))
})

It("podman info --format GO template plugins", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Plugins}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Plugins}}")
Expect(session.OutputToString()).To(ContainSubstring("local"))
Expect(session.OutputToString()).To(ContainSubstring("journald"))
Expect(session.OutputToString()).To(ContainSubstring("bridge"))
Expand All @@ -72,10 +66,7 @@ var _ = Describe("Podman Info", func() {
SkipIfNotRootless("test of rootless_storage_path is only meaningful as rootless")
SkipIfRemote("Only tests storage on local client")
configPath := filepath.Join(podmanTest.TempDir, ".config", "containers", "storage.conf")
os.Setenv("CONTAINERS_STORAGE_CONF", configPath)
defer func() {
os.Unsetenv("CONTAINERS_STORAGE_CONF")
}()
GinkgoT().Setenv("CONTAINERS_STORAGE_CONF", configPath)
err := os.RemoveAll(filepath.Dir(configPath))
Expect(err).ToNot(HaveOccurred())

Expand Down Expand Up @@ -104,38 +95,30 @@ var _ = Describe("Podman Info", func() {
})

It("check RemoteSocket ", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.RemoteSocket.Path}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.RemoteSocket.Path}}")
switch podmanTest.RemoteSocketScheme {
case "unix":
Expect(session.OutputToString()).To(MatchRegexp("/run/.*podman.*sock"))
case "tcp":
Expect(session.OutputToString()).To(MatchRegexp("tcp://127.0.0.1:.*"))
}

session = podmanTest.Podman([]string{"info", "--format", "{{.Host.ServiceIsRemote}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.ServiceIsRemote}}")
if podmanTest.RemoteTest {
Expect(session.OutputToString()).To(Equal("true"))
} else {
Expect(session.OutputToString()).To(Equal("false"))
}

if IsRemote() {
session = podmanTest.Podman([]string{"info", "--format", "{{.Host.RemoteSocket.Exists}}"})
session.WaitWithDefaultTimeout()
Expect(session).Should(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.RemoteSocket.Exists}}")
Expect(session.OutputToString()).To(Equal("true"))
}
})

It("Podman info must contain cgroupControllers with RelevantControllers", func() {
SkipIfRootless("Hard to tell which controllers are going to be enabled for rootless")
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.CgroupControllers}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.CgroupControllers}}")
Expect(session.OutputToString()).To(ContainSubstring("memory"))
Expect(session.OutputToString()).To(ContainSubstring("pids"))
})
Expand All @@ -149,28 +132,22 @@ var _ = Describe("Podman Info", func() {
}
Fail("CIRRUS_CI is set, but CI_DESIRED_RUNTIME is not! See #14912")
}
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.OCIRuntime.Name}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.OCIRuntime.Name}}")
Expect(session.OutputToString()).To(Equal(want))
})

It("Podman info: check desired network backend", func() {
session := podmanTest.Podman([]string{"info", "--format", "{{.Host.NetworkBackend}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.NetworkBackend}}")
Expect(session.OutputToString()).To(Equal("netavark"))

session = podmanTest.Podman([]string{"info", "--format", "{{.Host.NetworkBackendInfo.Backend}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.NetworkBackendInfo.Backend}}")
Expect(session.OutputToString()).To(Equal("netavark"))
})

It("Podman info: check default network from configuration", func() {
configPath := filepath.Join(podmanTest.TempDir, "containers.conf")

os.Setenv("CONTAINERS_CONF_OVERRIDE", configPath)
GinkgoT().Setenv("CONTAINERS_CONF_OVERRIDE", configPath)

customNetName := "my-custom-test-network"
configContent := fmt.Sprintf("[network]\ndefault_network=%q\n", customNetName)
Expand All @@ -179,12 +156,71 @@ var _ = Describe("Podman Info", func() {
Expect(err).ToNot(HaveOccurred())
podmanTest.RestartRemoteService()

session := podmanTest.Podman([]string{"info", "--format", "{{.Host.NetworkBackendInfo.DefaultNetwork}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.NetworkBackendInfo.DefaultNetwork}}")
Expect(session.OutputToString()).To(Equal(customNetName))
})

It("Podman info: check CDI spec dirs and devices from configuration", func() {
cdiDir := filepath.Join(podmanTest.TempDir, "cdi")
Expect(os.MkdirAll(cdiDir, os.ModePerm)).To(Succeed())

cdiSpec := []byte(`{
"cdiVersion": "0.3.0",
"kind": "vendor.com/device",
"devices": [
{
"name": "myKmsg",
"containerEdits": {
"env": ["PODMAN_CDI_INFO_TEST=1"]
}
}
]
}`)
Expect(os.WriteFile(filepath.Join(cdiDir, "device.json"), cdiSpec, os.ModePerm)).To(Succeed())

configPath := filepath.Join(podmanTest.TempDir, "containers.conf")
configContent := fmt.Sprintf("[engine]\ncdi_spec_dirs = [%q]\n", cdiDir)
err := os.WriteFile(configPath, []byte(configContent), os.ModePerm)
Expect(err).ToNot(HaveOccurred())

GinkgoT().Setenv("CONTAINERS_CONF_OVERRIDE", configPath)
podmanTest.RestartRemoteService()

session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Host.CDISpecDirs}} {{.Host.DiscoveredDevices}}")

Expect(session.OutputToString()).To(ContainSubstring(cdiDir))
Expect(session.OutputToString()).To(ContainSubstring("vendor.com/device=myKmsg"))
})

It("Podman info: check CDI spec dirs and devices with --cdi-spec-dir", func() {
SkipIfRemote("The --cdi-spec-dir flag is not supported for remote")

cdiDir := filepath.Join(podmanTest.TempDir, "cdi")
Expect(os.MkdirAll(cdiDir, os.ModePerm)).To(Succeed())

cdiSpec := []byte(`{
"cdiVersion": "0.3.0",
"kind": "vendor.com/device",
"devices": [
{
"name": "myKmsg",
"containerEdits": {
"env": ["PODMAN_CDI_INFO_TEST=1"]
}
}
]
}`)
Expect(os.WriteFile(filepath.Join(cdiDir, "device.json"), cdiSpec, os.ModePerm)).To(Succeed())

session := podmanTest.PodmanExitCleanly("--cdi-spec-dir", cdiDir, "info", "--format", "{{.Host.CDISpecDirs}} {{.Host.DiscoveredDevices}}")
Expect(session.OutputToString()).To(ContainSubstring(cdiDir))
Expect(session.OutputToString()).To(ContainSubstring("vendor.com/device=myKmsg"))

session = podmanTest.PodmanExitCleanly("--cdi-spec-dir", cdiDir, "system", "info", "--format", "{{.Host.CDISpecDirs}} {{.Host.DiscoveredDevices}}")
Expect(session.OutputToString()).To(ContainSubstring(cdiDir))
Expect(session.OutputToString()).To(ContainSubstring("vendor.com/device=myKmsg"))
})

It("Podman info: check desired storage driver", func() {
// defined in .cirrus.yml
want := os.Getenv("CI_DESIRED_STORAGE")
Expand All @@ -194,9 +230,7 @@ var _ = Describe("Podman Info", func() {
}
Fail("CIRRUS_CI is set, but CI_DESIRED_STORAGE is not! See #20161")
}
session := podmanTest.Podman([]string{"info", "--format", "{{.Store.GraphDriverName}}"})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session := podmanTest.PodmanExitCleanly("info", "--format", "{{.Store.GraphDriverName}}")
Expect(session.OutputToString()).To(Equal(want), ".Store.GraphDriverName from podman info")

// Confirm desired setting of composefs
Expand All @@ -205,9 +239,7 @@ var _ = Describe("Podman Info", func() {
if os.Getenv("CI_DESIRED_COMPOSEFS") != "" {
expect = "true"
}
session = podmanTest.Podman([]string{"info", "--format", `{{index .Store.GraphOptions "overlay.use_composefs"}}`})
session.WaitWithDefaultTimeout()
Expect(session).To(ExitCleanly())
session = podmanTest.PodmanExitCleanly("info", "--format", `{{index .Store.GraphOptions "overlay.use_composefs"}}`)
Expect(session.OutputToString()).To(Equal(expect), ".Store.GraphOptions -> overlay.use_composefs")
}
})
Expand All @@ -216,19 +248,13 @@ var _ = Describe("Podman Info", func() {
// This should not run on architectures and OSes that use the file locks backend.
// Which, for now, is Linux + RISCV and FreeBSD, neither of which are in CI - so
// no skips.
info1 := podmanTest.Podman([]string{"info", "--format", "{{ .Host.FreeLocks }}"})
info1.WaitWithDefaultTimeout()
Expect(info1).To(ExitCleanly())
info1 := podmanTest.PodmanExitCleanly("info", "--format", "{{ .Host.FreeLocks }}")
free1, err := strconv.Atoi(info1.OutputToString())
Expect(err).To(Not(HaveOccurred()))

ctr := podmanTest.Podman([]string{"create", ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr).To(ExitCleanly())
podmanTest.PodmanExitCleanly("create", ALPINE, "top")

info2 := podmanTest.Podman([]string{"info", "--format", "{{ .Host.FreeLocks }}"})
info2.WaitWithDefaultTimeout()
Expect(info2).To(ExitCleanly())
info2 := podmanTest.PodmanExitCleanly("info", "--format", "{{ .Host.FreeLocks }}")
free2, err := strconv.Atoi(info2.OutputToString())
Expect(err).To(Not(HaveOccurred()))

Expand All @@ -251,9 +277,7 @@ var _ = Describe("Podman Info", func() {
})

It("Podman info: check client information", func() {
info := podmanTest.Podman([]string{"info", "--format", "{{ .Client }}"})
info.WaitWithDefaultTimeout()
Expect(info).To(ExitCleanly())
info := podmanTest.PodmanExitCleanly("info", "--format", "{{ .Client }}")
// client info should only appear when using the remote client
if IsRemote() {
Expect(info.OutputToString()).ToNot(Equal("<nil>"))
Expand Down