mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

There is a potential of a race condition where a container is removed while podman is looking up information on the total containers. This can cause podman info to fail with an error "no such container". This change ignores the failure. Signed-off-by: Daniel J Walsh <dwalsh@redhat.com>
334 lines
8.4 KiB
Go
334 lines
8.4 KiB
Go
package libpod
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/libpod/libpod/define"
|
|
"github.com/containers/libpod/pkg/cgroups"
|
|
registries2 "github.com/containers/libpod/pkg/registries"
|
|
"github.com/containers/libpod/pkg/rootless"
|
|
"github.com/containers/storage"
|
|
"github.com/containers/storage/pkg/system"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// Info returns the store and host information
|
|
func (r *Runtime) info() (*define.Info, error) {
|
|
info := define.Info{}
|
|
versionInfo, err := define.GetVersion()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting version info")
|
|
}
|
|
info.Version = versionInfo
|
|
// get host information
|
|
hostInfo, err := r.hostInfo()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting host info")
|
|
}
|
|
info.Host = hostInfo
|
|
|
|
// get store information
|
|
storeInfo, err := r.storeInfo()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting store info")
|
|
}
|
|
info.Store = storeInfo
|
|
registries := make(map[string]interface{})
|
|
data, err := registries2.GetRegistriesData()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting registries")
|
|
}
|
|
for _, reg := range data {
|
|
registries[reg.Prefix] = reg
|
|
}
|
|
regs, err := registries2.GetRegistries()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting registries")
|
|
}
|
|
if len(regs) > 0 {
|
|
registries["search"] = regs
|
|
}
|
|
|
|
info.Registries = registries
|
|
return &info, nil
|
|
}
|
|
|
|
// top-level "host" info
|
|
func (r *Runtime) hostInfo() (*define.HostInfo, error) {
|
|
// lets say OS, arch, number of cpus, amount of memory, maybe os distribution/version, hostname, kernel version, uptime
|
|
mi, err := system.ReadMemInfo()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading memory info")
|
|
}
|
|
|
|
hostDistributionInfo := r.GetHostDistributionInfo()
|
|
|
|
kv, err := readKernelVersion()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading kernel version")
|
|
}
|
|
|
|
host, err := os.Hostname()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting hostname")
|
|
}
|
|
info := define.HostInfo{
|
|
Arch: runtime.GOARCH,
|
|
BuildahVersion: buildah.Version,
|
|
CPUs: runtime.NumCPU(),
|
|
Distribution: hostDistributionInfo,
|
|
EventLogger: r.eventer.String(),
|
|
Hostname: host,
|
|
IDMappings: define.IDMappings{},
|
|
Kernel: kv,
|
|
MemFree: mi.MemFree,
|
|
MemTotal: mi.MemTotal,
|
|
OS: runtime.GOOS,
|
|
Rootless: rootless.IsRootless(),
|
|
Slirp4NetNS: define.SlirpInfo{},
|
|
SwapFree: mi.SwapFree,
|
|
SwapTotal: mi.SwapTotal,
|
|
}
|
|
|
|
// CGroups version
|
|
unified, err := cgroups.IsCgroup2UnifiedMode()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading cgroups mode")
|
|
}
|
|
cgroupVersion := "v1"
|
|
if unified {
|
|
cgroupVersion = "v2"
|
|
}
|
|
info.CGroupsVersion = cgroupVersion
|
|
|
|
if rootless.IsRootless() {
|
|
if path, err := exec.LookPath("slirp4netns"); err == nil {
|
|
logrus.Warnf("Failed to retrieve program version for %s: %v", path, err)
|
|
version, err := programVersion(path)
|
|
if err != nil {
|
|
logrus.Warnf("Failed to retrieve program version for %s: %v", path, err)
|
|
}
|
|
program := define.SlirpInfo{
|
|
Executable: path,
|
|
Package: packageVersion(path),
|
|
Version: version,
|
|
}
|
|
info.Slirp4NetNS = program
|
|
}
|
|
uidmappings, err := rootless.ReadMappingsProc("/proc/self/uid_map")
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading uid mappings")
|
|
}
|
|
gidmappings, err := rootless.ReadMappingsProc("/proc/self/gid_map")
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading gid mappings")
|
|
}
|
|
idmappings := define.IDMappings{
|
|
GIDMap: gidmappings,
|
|
UIDMap: uidmappings,
|
|
}
|
|
info.IDMappings = idmappings
|
|
}
|
|
|
|
conmonInfo, ociruntimeInfo, err := r.defaultOCIRuntime.RuntimeInfo()
|
|
if err != nil {
|
|
logrus.Errorf("Error getting info on OCI runtime %s: %v", r.defaultOCIRuntime.Name(), err)
|
|
} else {
|
|
info.Conmon = conmonInfo
|
|
info.OCIRuntime = ociruntimeInfo
|
|
}
|
|
|
|
up, err := readUptime()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading up time")
|
|
}
|
|
// Convert uptime in seconds to a human-readable format
|
|
upSeconds := up + "s"
|
|
upDuration, err := time.ParseDuration(upSeconds)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error parsing system uptime")
|
|
}
|
|
|
|
// TODO Isnt there a simple lib for this, something like humantime?
|
|
hoursFound := false
|
|
var timeBuffer bytes.Buffer
|
|
var hoursBuffer bytes.Buffer
|
|
for _, elem := range upDuration.String() {
|
|
timeBuffer.WriteRune(elem)
|
|
if elem == 'h' || elem == 'm' {
|
|
timeBuffer.WriteRune(' ')
|
|
if elem == 'h' {
|
|
hoursFound = true
|
|
}
|
|
}
|
|
if !hoursFound {
|
|
hoursBuffer.WriteRune(elem)
|
|
}
|
|
}
|
|
|
|
info.Uptime = timeBuffer.String()
|
|
if hoursFound {
|
|
hours, err := strconv.ParseFloat(hoursBuffer.String(), 64)
|
|
if err == nil {
|
|
days := hours / 24
|
|
info.Uptime = fmt.Sprintf("%s (Approximately %.2f days)", info.Uptime, days)
|
|
}
|
|
}
|
|
|
|
return &info, nil
|
|
}
|
|
|
|
func (r *Runtime) getContainerStoreInfo() (define.ContainerStore, error) {
|
|
var (
|
|
paused, running, stopped int
|
|
)
|
|
cs := define.ContainerStore{}
|
|
cons, err := r.GetAllContainers()
|
|
if err != nil {
|
|
return cs, err
|
|
}
|
|
cs.Number = len(cons)
|
|
for _, con := range cons {
|
|
state, err := con.State()
|
|
if err != nil {
|
|
if errors.Cause(err) == define.ErrNoSuchCtr {
|
|
// container was probably removed
|
|
cs.Number--
|
|
continue
|
|
}
|
|
return cs, err
|
|
}
|
|
switch state {
|
|
case define.ContainerStateRunning:
|
|
running += 1
|
|
case define.ContainerStatePaused:
|
|
paused += 1
|
|
default:
|
|
stopped += 1
|
|
}
|
|
}
|
|
cs.Paused = paused
|
|
cs.Stopped = stopped
|
|
cs.Running = running
|
|
return cs, nil
|
|
}
|
|
|
|
// top-level "store" info
|
|
func (r *Runtime) storeInfo() (*define.StoreInfo, error) {
|
|
// lets say storage driver in use, number of images, number of containers
|
|
configFile, err := storage.DefaultConfigFile(rootless.IsRootless())
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
images, err := r.store.Images()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting number of images")
|
|
}
|
|
conInfo, err := r.getContainerStoreInfo()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
imageInfo := define.ImageStore{Number: len(images)}
|
|
|
|
info := define.StoreInfo{
|
|
ImageStore: imageInfo,
|
|
ContainerStore: conInfo,
|
|
GraphRoot: r.store.GraphRoot(),
|
|
RunRoot: r.store.RunRoot(),
|
|
GraphDriverName: r.store.GraphDriverName(),
|
|
GraphOptions: nil,
|
|
VolumePath: r.config.Engine.VolumePath,
|
|
ConfigFile: configFile,
|
|
}
|
|
graphOptions := map[string]interface{}{}
|
|
for _, o := range r.store.GraphOptions() {
|
|
split := strings.SplitN(o, "=", 2)
|
|
if strings.HasSuffix(split[0], "mount_program") {
|
|
version, err := programVersion(split[1])
|
|
if err != nil {
|
|
logrus.Warnf("Failed to retrieve program version for %s: %v", split[1], err)
|
|
}
|
|
program := map[string]interface{}{}
|
|
program["Executable"] = split[1]
|
|
program["Version"] = version
|
|
program["Package"] = packageVersion(split[1])
|
|
graphOptions[split[0]] = program
|
|
} else {
|
|
graphOptions[split[0]] = split[1]
|
|
}
|
|
}
|
|
info.GraphOptions = graphOptions
|
|
|
|
statusPairs, err := r.store.Status()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
status := map[string]string{}
|
|
for _, pair := range statusPairs {
|
|
status[pair[0]] = pair[1]
|
|
}
|
|
info.GraphStatus = status
|
|
return &info, nil
|
|
}
|
|
|
|
func readKernelVersion() (string, error) {
|
|
buf, err := ioutil.ReadFile("/proc/version")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
f := bytes.Fields(buf)
|
|
if len(f) < 2 {
|
|
return string(bytes.TrimSpace(buf)), nil
|
|
}
|
|
return string(f[2]), nil
|
|
}
|
|
|
|
func readUptime() (string, error) {
|
|
buf, err := ioutil.ReadFile("/proc/uptime")
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
f := bytes.Fields(buf)
|
|
if len(f) < 1 {
|
|
return "", fmt.Errorf("invalid uptime")
|
|
}
|
|
return string(f[0]), nil
|
|
}
|
|
|
|
// GetHostDistributionInfo returns a map containing the host's distribution and version
|
|
func (r *Runtime) GetHostDistributionInfo() define.DistributionInfo {
|
|
// Populate values in case we cannot find the values
|
|
// or the file
|
|
dist := define.DistributionInfo{
|
|
Distribution: "unknown",
|
|
Version: "unknown",
|
|
}
|
|
f, err := os.Open("/etc/os-release")
|
|
if err != nil {
|
|
return dist
|
|
}
|
|
defer f.Close()
|
|
|
|
l := bufio.NewScanner(f)
|
|
for l.Scan() {
|
|
if strings.HasPrefix(l.Text(), "ID=") {
|
|
dist.Distribution = strings.TrimPrefix(l.Text(), "ID=")
|
|
}
|
|
if strings.HasPrefix(l.Text(), "VERSION_ID=") {
|
|
dist.Version = strings.Trim(strings.TrimPrefix(l.Text(), "VERSION_ID="), "\"")
|
|
}
|
|
}
|
|
return dist
|
|
}
|