mirror of
https://github.com/containers/podman.git
synced 2025-05-20 00:27:03 +08:00
360 lines
9.3 KiB
Go
360 lines
9.3 KiB
Go
package libpod
|
|
|
|
import (
|
|
"bufio"
|
|
"bytes"
|
|
"fmt"
|
|
"io/ioutil"
|
|
"os"
|
|
"os/exec"
|
|
"runtime"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/common/pkg/apparmor"
|
|
"github.com/containers/common/pkg/seccomp"
|
|
"github.com/containers/podman/v3/libpod/define"
|
|
"github.com/containers/podman/v3/libpod/linkmode"
|
|
"github.com/containers/podman/v3/pkg/cgroups"
|
|
registries2 "github.com/containers/podman/v3/pkg/registries"
|
|
"github.com/containers/podman/v3/pkg/rootless"
|
|
"github.com/containers/storage"
|
|
"github.com/containers/storage/pkg/system"
|
|
"github.com/opencontainers/selinux/go-selinux"
|
|
"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")
|
|
}
|
|
|
|
seccompProfilePath, err := DefaultSeccompPath()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting Seccomp profile path")
|
|
}
|
|
|
|
// CGroups version
|
|
unified, err := cgroups.IsCgroup2UnifiedMode()
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error reading cgroups mode")
|
|
}
|
|
|
|
// Get Map of all available controllers
|
|
availableControllers, err := cgroups.GetAvailableControllers(nil, unified)
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "error getting available cgroup controllers")
|
|
}
|
|
|
|
info := define.HostInfo{
|
|
Arch: runtime.GOARCH,
|
|
BuildahVersion: buildah.Version,
|
|
CgroupManager: r.config.Engine.CgroupManager,
|
|
CgroupControllers: availableControllers,
|
|
Linkmode: linkmode.Linkmode(),
|
|
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,
|
|
Security: define.SecurityInfo{
|
|
AppArmorEnabled: apparmor.IsEnabled(),
|
|
DefaultCapabilities: strings.Join(r.config.Containers.DefaultCapabilities, ","),
|
|
Rootless: rootless.IsRootless(),
|
|
SECCOMPEnabled: seccomp.IsEnabled(),
|
|
SECCOMPProfilePath: seccompProfilePath,
|
|
SELinuxEnabled: selinux.GetEnabled(),
|
|
},
|
|
Slirp4NetNS: define.SlirpInfo{},
|
|
SwapFree: mi.SwapFree,
|
|
SwapTotal: mi.SwapTotal,
|
|
}
|
|
|
|
cgroupVersion := "v1"
|
|
if unified {
|
|
cgroupVersion = "v2"
|
|
}
|
|
info.CGroupsVersion = cgroupVersion
|
|
|
|
if rootless.IsRootless() {
|
|
if path, err := exec.LookPath("slirp4netns"); err == nil {
|
|
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 Isn't 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++
|
|
case define.ContainerStatePaused:
|
|
paused++
|
|
default:
|
|
stopped++
|
|
}
|
|
}
|
|
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
|
|
}
|