1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206 207 208 209 210 211 212 213 214 215 216 217 218 219 220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 322 323
|
//go:build !remote
package libpod
import (
"bufio"
"bytes"
"errors"
"fmt"
"math"
"os"
"runtime"
"strings"
"syscall"
"time"
"github.com/containers/buildah"
"github.com/containers/buildah/pkg/parse"
"github.com/containers/buildah/pkg/util"
"github.com/containers/podman/v5/libpod/define"
"github.com/containers/podman/v5/libpod/linkmode"
"github.com/sirupsen/logrus"
"go.podman.io/common/pkg/version"
"go.podman.io/image/v5/pkg/sysregistriesv2"
"go.podman.io/storage"
"go.podman.io/storage/pkg/system"
)
// 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, fmt.Errorf("getting version info: %w", err)
}
info.Version = versionInfo
// get host information
hostInfo, err := r.hostInfo()
if err != nil {
return nil, fmt.Errorf("getting host info: %w", err)
}
info.Host = hostInfo
// get store information
storeInfo, err := r.storeInfo()
if err != nil {
return nil, fmt.Errorf("getting store info: %w", err)
}
info.Store = storeInfo
registries := make(map[string]any)
sys := r.SystemContext()
data, err := sysregistriesv2.GetRegistries(sys)
if err != nil {
return nil, fmt.Errorf("getting registries: %w", err)
}
for _, reg := range data {
registries[reg.Prefix] = reg
}
regs, err := sysregistriesv2.UnqualifiedSearchRegistries(sys)
if err != nil {
return nil, fmt.Errorf("getting registries: %w", err)
}
if len(regs) > 0 {
registries["search"] = regs
}
volumePlugins := make([]string, 0, len(r.config.Engine.VolumePlugins)+1)
// the local driver always exists
volumePlugins = append(volumePlugins, "local")
for plugin := range r.config.Engine.VolumePlugins {
volumePlugins = append(volumePlugins, plugin)
}
info.Plugins.Volume = volumePlugins
info.Plugins.Network = r.network.Drivers()
info.Plugins.Log = logDrivers
info.Registries = registries
return &info, nil
}
// top-level "host" info
func (r *Runtime) hostInfo() (*define.HostInfo, error) {
// let's 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, fmt.Errorf("reading memory info: %w", err)
}
hostDistributionInfo := r.GetHostDistributionInfo()
kv, err := util.ReadKernelVersion()
if err != nil {
return nil, fmt.Errorf("reading kernel version: %w", err)
}
host, err := os.Hostname()
if err != nil {
return nil, fmt.Errorf("getting hostname: %w", err)
}
cpuUtil, err := getCPUUtilization()
if err != nil {
return nil, err
}
locksFree, err := r.lockManager.AvailableLocks()
if err != nil {
return nil, fmt.Errorf("getting free locks: %w", err)
}
info := define.HostInfo{
Arch: runtime.GOARCH,
BuildahVersion: buildah.Version,
DatabaseBackend: r.config.Engine.DBBackend,
Linkmode: linkmode.Linkmode(),
CPUs: runtime.NumCPU(),
CPUUtilization: cpuUtil,
Distribution: hostDistributionInfo,
LogDriver: r.config.Containers.LogDriver,
EventLogger: r.eventer.String(),
FreeLocks: locksFree,
Hostname: host,
Kernel: kv,
MemFree: mi.MemFree,
MemTotal: mi.MemTotal,
NetworkBackend: r.config.Network.NetworkBackend,
NetworkBackendInfo: r.network.NetworkInfo(),
OS: runtime.GOOS,
RootlessNetworkCmd: r.config.Network.DefaultRootlessNetworkCmd,
SwapFree: mi.SwapFree,
SwapTotal: mi.SwapTotal,
}
platform := parse.DefaultPlatform()
pArr := strings.Split(platform, "/")
if len(pArr) == 3 {
info.Variant = pArr[2]
}
if err := r.setPlatformHostInfo(&info); err != nil {
return nil, err
}
conmonInfo, ociruntimeInfo, err := r.defaultOCIRuntime.RuntimeInfo()
if err != nil {
logrus.Errorf("Getting info on OCI runtime %s: %v", r.defaultOCIRuntime.Name(), err)
} else {
info.Conmon = conmonInfo
info.OCIRuntime = ociruntimeInfo
}
duration, err := util.ReadUptime()
if err != nil {
return nil, fmt.Errorf("reading up time: %w", err)
}
uptime := struct {
hours float64
minutes float64
seconds float64
}{
hours: duration.Truncate(time.Hour).Hours(),
minutes: duration.Truncate(time.Minute).Minutes(),
seconds: duration.Truncate(time.Second).Seconds(),
}
// Could not find a humanize-formatter for time.Duration
var buffer bytes.Buffer
buffer.WriteString(fmt.Sprintf("%.0fh %.0fm %.2fs",
uptime.hours,
math.Mod(uptime.minutes, 60),
math.Mod(uptime.seconds, 60),
))
if int64(uptime.hours) > 0 {
buffer.WriteString(fmt.Sprintf(" (Approximately %.2f days)", uptime.hours/24))
}
info.Uptime = buffer.String()
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.Is(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) {
// let's say storage driver in use, number of images, number of containers
configFile, err := storage.DefaultConfigFile()
if err != nil {
return nil, err
}
images, err := r.store.Images()
if err != nil {
return nil, fmt.Errorf("getting number of images: %w", err)
}
conInfo, err := r.getContainerStoreInfo()
if err != nil {
return nil, err
}
imageInfo := define.ImageStore{Number: len(images)}
var grStats syscall.Statfs_t
if err := syscall.Statfs(r.store.GraphRoot(), &grStats); err != nil {
return nil, fmt.Errorf("unable to collect graph root usage for %q: %w", r.store.GraphRoot(), err)
}
bsize := uint64(grStats.Bsize) //nolint:unconvert,nolintlint // Bsize is not always uint64 on Linux.
allocated := bsize * grStats.Blocks
info := define.StoreInfo{
ImageStore: imageInfo,
ImageCopyTmpDir: os.Getenv("TMPDIR"),
ContainerStore: conInfo,
GraphRoot: r.store.GraphRoot(),
GraphRootAllocated: allocated,
GraphRootUsed: allocated - (bsize * grStats.Bfree),
RunRoot: r.store.RunRoot(),
GraphDriverName: r.store.GraphDriverName(),
GraphOptions: nil,
VolumePath: r.config.Engine.VolumePath,
ConfigFile: configFile,
TransientStore: r.store.TransientStore(),
}
graphOptions := map[string]any{}
for _, o := range r.store.GraphOptions() {
split := strings.SplitN(o, "=", 2)
switch {
case strings.HasSuffix(split[0], "mount_program"):
ver, err := version.Program(split[1])
if err != nil {
logrus.Warnf("Failed to retrieve program version for %s: %v", split[1], err)
}
program := map[string]any{}
program["Executable"] = split[1]
program["Version"] = ver
program["Package"] = version.Package(split[1])
graphOptions[split[0]] = program
case strings.HasSuffix(split[0], "imagestore"):
key := strings.ReplaceAll(split[0], "imagestore", "additionalImageStores")
if graphOptions[key] == nil {
graphOptions[key] = []string{split[1]}
} else {
graphOptions[key] = append(graphOptions[key].([]string), split[1])
}
// Fallthrough to include the `imagestore` key to avoid breaking
// Podman v5 API. Should be removed in Podman v6.0.0.
fallthrough
default:
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
}
// 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 after, ok := strings.CutPrefix(l.Text(), "ID="); ok {
dist.Distribution = strings.Trim(after, "\"")
}
if after, ok := strings.CutPrefix(l.Text(), "VARIANT_ID="); ok {
dist.Variant = strings.Trim(after, "\"")
}
if after, ok := strings.CutPrefix(l.Text(), "VERSION_ID="); ok {
dist.Version = strings.Trim(after, "\"")
}
if after, ok := strings.CutPrefix(l.Text(), "VERSION_CODENAME="); ok {
dist.Codename = strings.Trim(after, "\"")
}
}
return dist
}
|