summaryrefslogtreecommitdiff
path: root/cmd/podman
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/podman')
-rw-r--r--cmd/podman/common.go5
-rw-r--r--cmd/podman/create.go131
-rw-r--r--cmd/podman/create_cli.go43
-rw-r--r--cmd/podman/create_cli_test.go12
-rw-r--r--cmd/podman/parse.go15
-rw-r--r--cmd/podman/ps.go144
-rw-r--r--cmd/podman/rm.go7
-rw-r--r--cmd/podman/run.go4
-rw-r--r--cmd/podman/run_test.go54
-rw-r--r--cmd/podman/spec.go211
-rw-r--r--cmd/podman/stats.go49
11 files changed, 481 insertions, 194 deletions
diff --git a/cmd/podman/common.go b/cmd/podman/common.go
index e0ef43782..ae4bbd65d 100644
--- a/cmd/podman/common.go
+++ b/cmd/podman/common.go
@@ -173,7 +173,7 @@ var createFlags = []cli.Flag{
Name: "cpu-shares",
Usage: "CPU shares (relative weight)",
},
- cli.StringFlag{
+ cli.Float64Flag{
Name: "cpus",
Usage: "Number of CPUs. The default is 0.000 which means no limit",
},
@@ -316,7 +316,8 @@ var createFlags = []cli.Flag{
},
cli.StringFlag{
Name: "net",
- Usage: "Setup the network namespace",
+ Usage: "Connect a container to a network (alias for --network)",
+ Value: "bridge",
},
cli.StringFlag{
Name: "network",
diff --git a/cmd/podman/create.go b/cmd/podman/create.go
index ead2f6735..80cb7f432 100644
--- a/cmd/podman/create.go
+++ b/cmd/podman/create.go
@@ -4,6 +4,7 @@ import (
"encoding/json"
"fmt"
"io"
+ "net"
"os"
"strconv"
"strings"
@@ -47,7 +48,7 @@ type createResourceConfig struct {
CPURtPeriod uint64 // cpu-rt-period
CPURtRuntime int64 // cpu-rt-runtime
CPUShares uint64 // cpu-shares
- CPUs string // cpus
+ CPUs float64 // cpus
CPUsetCPUs string
CPUsetMems string // cpuset-mems
DeviceReadBps []string // device-read-bps
@@ -83,6 +84,7 @@ type createConfig struct {
Env map[string]string //env
ExposedPorts map[nat.Port]struct{}
GroupAdd []uint32 // group-add
+ HostAdd []string //add-host
Hostname string //hostname
Image string
ImageID string
@@ -114,7 +116,6 @@ type createConfig struct {
SigProxy bool //sig-proxy
StopSignal syscall.Signal // stop-signal
StopTimeout uint // stop-timeout
- StorageOpts []string //storage-opt
Sysctl map[string]string //sysctl
Tmpfs []string // tmpfs
Tty bool //tty
@@ -171,6 +172,9 @@ func createCmd(c *cli.Context) error {
defer runtime.Shutdown(false)
imageName, _, data, err := imageData(c, runtime, c.Args()[0])
+ if err != nil {
+ return err
+ }
createConfig, err := parseCreateOpts(c, runtime, imageName, data)
if err != nil {
return err
@@ -216,8 +220,6 @@ func createCmd(c *cli.Context) error {
return nil
}
-const seccompDefaultPath = "/etc/crio/seccomp.json"
-
func parseSecurityOpt(config *createConfig, securityOpts []string) error {
var (
labelOpts []string
@@ -267,28 +269,56 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error {
}
if config.SeccompProfilePath == "" {
- if _, err := os.Stat(seccompDefaultPath); err != nil {
+ if _, err := os.Stat(libpod.SeccompOverridePath); err == nil {
+ config.SeccompProfilePath = libpod.SeccompOverridePath
+ } else {
if !os.IsNotExist(err) {
- return errors.Wrapf(err, "can't check if %q exists", seccompDefaultPath)
+ return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompOverridePath)
+ }
+ if _, err := os.Stat(libpod.SeccompDefaultPath); err != nil {
+ if !os.IsNotExist(err) {
+ return errors.Wrapf(err, "can't check if %q exists", libpod.SeccompDefaultPath)
+ }
+ } else {
+ config.SeccompProfilePath = libpod.SeccompDefaultPath
}
- } else {
- config.SeccompProfilePath = seccompDefaultPath
}
}
config.ProcessLabel, config.MountLabel, err = label.InitLabels(labelOpts)
return err
}
-func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
- // TODO Handle exposed ports from image
- // Currently ignoring imageExposedPorts
+// isPortInPortBindings determines if an exposed host port is in user
+// provided ports
+func isPortInPortBindings(pb map[nat.Port][]nat.PortBinding, port nat.Port) bool {
+ var hostPorts []string
+ for _, i := range pb {
+ hostPorts = append(hostPorts, i[0].HostPort)
+ }
+ return libpod.StringInSlice(port.Port(), hostPorts)
+}
- ports, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish"))
+func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[nat.Port]struct{}, map[nat.Port][]nat.PortBinding, error) {
+ var exposedPorts []string
+ var ports map[nat.Port]struct{}
+ ports = make(map[nat.Port]struct{})
+ _, portBindings, err := nat.ParsePortSpecs(c.StringSlice("publish"))
if err != nil {
return nil, nil, err
}
- for _, e := range c.StringSlice("expose") {
+ // Parse the ports from the image itself
+ for i := range imageExposedPorts {
+ fields := strings.Split(i, "/")
+ if len(fields) > 2 {
+ return nil, nil, errors.Errorf("invalid exposed port format in image")
+ }
+ exposedPorts = append(exposedPorts, fields[0])
+ }
+
+ // Add the ports from the image to the ports from the user
+ exposedPorts = append(exposedPorts, c.StringSlice("expose")...)
+ for _, e := range exposedPorts {
// Merge in exposed ports to the map of published ports
if strings.Contains(e, ":") {
return nil, nil, fmt.Errorf("invalid port format for --expose: %s", e)
@@ -306,6 +336,28 @@ func exposedPorts(c *cli.Context, imageExposedPorts map[string]struct{}) (map[na
if err != nil {
return nil, nil, err
}
+ // check if the port in question is already being used
+ if isPortInPortBindings(portBindings, p) {
+ return nil, nil, errors.Errorf("host port %s already used in --publish option", p.Port())
+ }
+
+ if c.Bool("publish-all") {
+ l, err := net.Listen("tcp", ":0")
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "unable to get free port")
+ }
+ _, randomPort, err := net.SplitHostPort(l.Addr().String())
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "unable to determine free port")
+ }
+ rp, err := strconv.Atoi(randomPort)
+ if err != nil {
+ return nil, nil, errors.Wrapf(err, "unable to convert random port to int")
+ }
+ logrus.Debug(fmt.Sprintf("Using random host port %s with container port %d", randomPort, p.Int()))
+ portBindings[p] = CreatePortBinding(rp, "")
+ continue
+ }
if _, exists := ports[p]; !exists {
ports[p] = struct{}{}
}
@@ -371,7 +423,6 @@ func imageData(c *cli.Context, runtime *libpod.Runtime, image string) (string, s
// Parses CLI options related to container creation into a config which can be
// parsed into an OCI runtime spec
func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string, data *libpod.ImageData) (*createConfig, error) {
- //imageName, imageID, data, err := imageData(c, runtime, image)
var command []string
var memoryLimit, memoryReservation, memorySwap, memoryKernel int64
var blkioWeight uint16
@@ -382,9 +433,9 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
command = c.Args()[1:]
}
- sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=")
+ sysctl, err := validateSysctl(c.StringSlice("sysctl"))
if err != nil {
- return nil, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE")
+ return nil, errors.Wrapf(err, "invalid value for sysctl")
}
groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add"))
@@ -444,6 +495,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
if c.Bool("detach") && c.Bool("rm") {
return nil, errors.Errorf("--rm and --detach can not be specified together")
}
+ if c.Int64("cpu-period") != 0 && c.Float64("cpus") > 0 {
+ return nil, errors.Errorf("--cpu-period and --cpus cannot be set together")
+ }
+ if c.Int64("cpu-quota") != 0 && c.Float64("cpus") > 0 {
+ return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together")
+ }
utsMode := container.UTSMode(c.String("uts"))
if !utsMode.Valid() {
@@ -536,6 +593,29 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
if err != nil {
return nil, errors.Wrapf(err, "unable to translate --shm-size")
}
+ // Network
+ // Both --network and --net have default values of 'bridge'
+ // --net only overrides --network when --network is not explicitly
+ // set and --net is.
+ if c.IsSet("network") && c.IsSet("net") {
+ return nil, errors.Errorf("cannot use --network and --net together. use only --network instead")
+ }
+ networkMode := c.String("network")
+ if !c.IsSet("network") && c.IsSet("net") {
+ networkMode = c.String("net")
+ }
+
+ // Verify the additional hosts are in correct format
+ for _, host := range c.StringSlice("add-host") {
+ if _, err := validateExtraHost(host); err != nil {
+ return nil, err
+ }
+ }
+
+ // Check for . and dns-search domains
+ if libpod.StringInSlice(".", c.StringSlice("dns-search")) && len(c.StringSlice("dns-search")) > 1 {
+ return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'")
+ }
config := &createConfig{
Runtime: runtime,
@@ -553,6 +633,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
ExposedPorts: ports,
GroupAdd: groupAdd,
Hostname: c.String("hostname"),
+ HostAdd: c.StringSlice("add-host"),
Image: imageName,
ImageID: imageID,
Interactive: c.Bool("interactive"),
@@ -564,10 +645,10 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
LogDriverOpt: c.StringSlice("log-opt"),
MacAddress: c.String("mac-address"),
Name: c.String("name"),
- Network: c.String("network"),
+ Network: networkMode,
NetworkAlias: c.StringSlice("network-alias"),
IpcMode: ipcMode,
- NetMode: container.NetworkMode(c.String("network")),
+ NetMode: container.NetworkMode(networkMode),
UtsMode: utsMode,
PidMode: pidMode,
Pod: c.String("pod"),
@@ -582,12 +663,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
BlkioWeightDevice: c.StringSlice("blkio-weight-device"),
CPUShares: c.Uint64("cpu-shares"),
CPUPeriod: c.Uint64("cpu-period"),
- CPUsetCPUs: c.String("cpu-period"),
+ CPUsetCPUs: c.String("cpuset-cpus"),
CPUsetMems: c.String("cpuset-mems"),
CPUQuota: c.Int64("cpu-quota"),
CPURtPeriod: c.Uint64("cpu-rt-period"),
CPURtRuntime: c.Int64("cpu-rt-runtime"),
- CPUs: c.String("cpus"),
+ CPUs: c.Float64("cpus"),
DeviceReadBps: c.StringSlice("device-read-bps"),
DeviceReadIOps: c.StringSlice("device-read-iops"),
DeviceWriteBps: c.StringSlice("device-write-bps"),
@@ -609,7 +690,6 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
SigProxy: c.Bool("sig-proxy"),
StopSignal: stopSignal,
StopTimeout: c.Uint("stop-timeout"),
- StorageOpts: c.StringSlice("storage-opt"),
Sysctl: sysctl,
Tmpfs: c.StringSlice("tmpfs"),
Tty: tty,
@@ -633,3 +713,12 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime, imageName string,
}
return config, nil
}
+
+//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs
+func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding {
+ pb := nat.PortBinding{
+ HostPort: strconv.Itoa(hostPort),
+ }
+ pb.HostIP = hostIP
+ return []nat.PortBinding{pb}
+}
diff --git a/cmd/podman/create_cli.go b/cmd/podman/create_cli.go
index 0cc265e92..24856feb8 100644
--- a/cmd/podman/create_cli.go
+++ b/cmd/podman/create_cli.go
@@ -24,14 +24,45 @@ func getAllLabels(labelFile, inputLabels []string) (map[string]string, error) {
return labels, nil
}
-func convertStringSliceToMap(strSlice []string, delimiter string) (map[string]string, error) {
+// validateSysctl validates a sysctl and returns it.
+func validateSysctl(strSlice []string) (map[string]string, error) {
sysctl := make(map[string]string)
- for _, inputSysctl := range strSlice {
- values := strings.Split(inputSysctl, delimiter)
- if len(values) < 2 {
- return sysctl, errors.Errorf("%s in an invalid sysctl value", inputSysctl)
+ validSysctlMap := map[string]bool{
+ "kernel.msgmax": true,
+ "kernel.msgmnb": true,
+ "kernel.msgmni": true,
+ "kernel.sem": true,
+ "kernel.shmall": true,
+ "kernel.shmmax": true,
+ "kernel.shmmni": true,
+ "kernel.shm_rmid_forced": true,
+ }
+ validSysctlPrefixes := []string{
+ "net.",
+ "fs.mqueue.",
+ }
+
+ for _, val := range strSlice {
+ foundMatch := false
+ arr := strings.Split(val, "=")
+ if len(arr) < 2 {
+ return nil, errors.Errorf("%s is invalid, sysctl values must be in the form of KEY=VALUE", val)
+ }
+ if validSysctlMap[arr[0]] {
+ sysctl[arr[0]] = arr[1]
+ continue
+ }
+
+ for _, prefix := range validSysctlPrefixes {
+ if strings.HasPrefix(arr[0], prefix) {
+ sysctl[arr[0]] = arr[1]
+ foundMatch = true
+ break
+ }
+ }
+ if !foundMatch {
+ return nil, errors.Errorf("sysctl '%s' is not whitelisted", arr[0])
}
- sysctl[values[0]] = values[1]
}
return sysctl, nil
}
diff --git a/cmd/podman/create_cli_test.go b/cmd/podman/create_cli_test.go
index 63a1e5dd3..fa128c8e6 100644
--- a/cmd/podman/create_cli_test.go
+++ b/cmd/podman/create_cli_test.go
@@ -28,15 +28,15 @@ func createTmpFile(content []byte) (string, error) {
return tmpfile.Name(), nil
}
-func TestConvertStringSliceToMap(t *testing.T) {
- strSlice := []string{"BLAU=BLUE", "GELB=YELLOW"}
- result, _ := convertStringSliceToMap(strSlice, "=")
- assert.Equal(t, result["BLAU"], "BLUE")
+func TestValidateSysctl(t *testing.T) {
+ strSlice := []string{"net.core.test1=4", "kernel.msgmax=2"}
+ result, _ := validateSysctl(strSlice)
+ assert.Equal(t, result["net.core.test1"], "4")
}
-func TestConvertStringSliceToMapBadData(t *testing.T) {
+func TestValidateSysctlBadSysctl(t *testing.T) {
strSlice := []string{"BLAU=BLUE", "GELB^YELLOW"}
- _, err := convertStringSliceToMap(strSlice, "=")
+ _, err := validateSysctl(strSlice)
assert.Error(t, err)
}
diff --git a/cmd/podman/parse.go b/cmd/podman/parse.go
index bb45d08c4..33988a3b6 100644
--- a/cmd/podman/parse.go
+++ b/cmd/podman/parse.go
@@ -696,21 +696,6 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { //nolint
return securityOpts, nil
}
-// parses storage options per container into a map
-// for storage-opt flag
-func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolint
- m := make(map[string]string)
- for _, option := range storageOpts {
- if strings.Contains(option, "=") {
- opt := strings.SplitN(option, "=", 2)
- m[opt[0]] = opt[1]
- } else {
- return nil, errors.Errorf("invalid storage option %q", option)
- }
- }
- return m, nil
-}
-
// convertKVStringsToMap converts ["key=value"] to {"key":"value"}
func convertKVStringsToMap(values []string) map[string]string {
result := make(map[string]string, len(values))
diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go
index c674c9d1e..ef5d40c43 100644
--- a/cmd/podman/ps.go
+++ b/cmd/podman/ps.go
@@ -1,6 +1,7 @@
package main
import (
+ "encoding/json"
"fmt"
"os"
"path/filepath"
@@ -10,15 +11,19 @@ import (
"strings"
"time"
+ "github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/formats"
"github.com/projectatomic/libpod/libpod"
+ "github.com/sirupsen/logrus"
"github.com/urfave/cli"
"k8s.io/apimachinery/pkg/fields"
)
+const mountTruncLength = 12
+
type psOptions struct {
all bool
filter string
@@ -62,12 +67,13 @@ type psJSONParams struct {
ID string `json:"id"`
Image string `json:"image"`
ImageID string `json:"image_id"`
- Command string `json:"command"`
+ Command []string `json:"command"`
CreatedAt time.Time `json:"createdAt"`
RunningFor time.Duration `json:"runningFor"`
Status string `json:"status"`
Ports map[string]struct{} `json:"ports"`
- Size uint `json:"size"`
+ RootFsSize int64 `json:"rootFsSize"`
+ RWSize int64 `json:"rwSize"`
Names string `json:"names"`
Labels fields.Set `json:"labels"`
Mounts []specs.Mount `json:"mounts"`
@@ -241,7 +247,7 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru
switch filter {
case "id":
return func(c *libpod.Container) bool {
- return c.ID() == filterValue
+ return strings.Contains(c.ID(), filterValue)
}, nil
case "label":
return func(c *libpod.Container) bool {
@@ -254,7 +260,7 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru
}, nil
case "name":
return func(c *libpod.Container) bool {
- return c.Name() == filterValue
+ return strings.Contains(c.Name(), filterValue)
}, nil
case "exited":
exitCode, err := strconv.ParseInt(filterValue, 10, 32)
@@ -277,14 +283,18 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru
if err != nil {
return false
}
- return status.String() == filterValue
+ state := status.String()
+ if status == libpod.ContainerStateConfigured {
+ state = "created"
+ }
+ return state == filterValue
}, nil
case "ancestor":
// This needs to refine to match docker
// - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant.
return func(c *libpod.Container) bool {
containerConfig := c.Config()
- if containerConfig.RootfsImageID == filterValue || containerConfig.RootfsImageName == filterValue {
+ if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) {
return true
}
return false
@@ -315,8 +325,21 @@ func generateContainerFilterFuncs(filter, filterValue string, runtime *libpod.Ru
//- volume=(<volume-name>|<mount-point-destination>)
return func(c *libpod.Container) bool {
containerConfig := c.Config()
- //TODO We need to still lookup against volumes too
- return containerConfig.MountLabel == filterValue
+ var dest string
+ arr := strings.Split(filterValue, ":")
+ source := arr[0]
+ if len(arr) == 2 {
+ dest = arr[1]
+ }
+ for _, mount := range containerConfig.Spec.Mounts {
+ if dest != "" && (mount.Source == source && mount.Destination == dest) {
+ return true
+ }
+ if dest == "" && mount.Source == source {
+ return true
+ }
+ }
+ return false
}, nil
}
return nil, errors.Errorf("%s is an invalid filter", filter)
@@ -393,15 +416,32 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
runningFor := units.HumanDuration(time.Since(conConfig.CreatedTime))
createdAt := runningFor + " ago"
imageName := conConfig.RootfsImageName
+ rootFsSize, err := ctr.RootFsSize()
+ if err != nil {
+ logrus.Errorf("error getting root fs size for %q: %v", ctr.ID(), err)
+ }
+ rwSize, err := ctr.RWSize()
+ if err != nil {
+ logrus.Errorf("error getting rw size for %q: %v", ctr.ID(), err)
+ }
+
+ var createArtifact createConfig
+ artifact, err := ctr.GetArtifact("create-config")
+ if err == nil {
+ if err := json.Unmarshal(artifact, &createArtifact); err != nil {
+ return nil, err
+ }
+ } else {
+ logrus.Errorf("couldn't get some ps information, error getting artifact %q: %v", ctr.ID(), err)
+ }
// TODO We currently dont have the ability to get many of
// these data items. Uncomment as progress is made
- //command := getStrFromSquareBrackets(ctr.ImageCreatedBy)
command := strings.Join(ctr.Spec().Process.Args, " ")
- //mounts := getMounts(ctr.Mounts, opts.noTrunc)
- //ports := getPorts(ctr.Config.ExposedPorts)
- //size := units.HumanSize(float64(ctr.SizeRootFs))
+ ports := getPorts(ctr.Config().PortMappings)
+ mounts := getMounts(createArtifact.Volumes, opts.noTrunc)
+ size := units.HumanSizeWithPrecision(float64(rwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(rootFsSize), 3) + ")"
labels := formatLabels(ctr.Labels())
ns := getNamespaces(pid)
@@ -412,7 +452,7 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
status = "Up " + runningFor + " ago"
case libpod.ContainerStatePaused:
status = "Paused"
- case libpod.ContainerStateCreated:
+ case libpod.ContainerStateCreated, libpod.ContainerStateConfigured:
status = "Created"
default:
status = "Dead"
@@ -433,19 +473,19 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
CreatedAt: createdAt,
RunningFor: runningFor,
Status: status,
- //Ports: ports,
- //Size: size,
- Names: ctr.Name(),
- Labels: labels,
- //Mounts: mounts,
- PID: pid,
- Cgroup: ns.Cgroup,
- IPC: ns.IPC,
- MNT: ns.MNT,
- NET: ns.NET,
- PIDNS: ns.PID,
- User: ns.User,
- UTS: ns.UTS,
+ Ports: ports,
+ Size: size,
+ Names: ctr.Name(),
+ Labels: labels,
+ Mounts: mounts,
+ PID: pid,
+ Cgroup: ns.Cgroup,
+ IPC: ns.IPC,
+ MNT: ns.MNT,
+ NET: ns.NET,
+ PIDNS: ns.PID,
+ User: ns.User,
+ UTS: ns.UTS,
}
psOutput = append(psOutput, params)
}
@@ -499,19 +539,28 @@ func getJSONOutput(containers []*libpod.Container, nSpace bool) ([]psJSONParams,
if err != nil {
return psOutput, errors.Wrapf(err, "unable to obtain container state for JSON output")
}
+ rootFsSize, err := ctr.RootFsSize()
+ if err != nil {
+ logrus.Errorf("error getting root fs size for %q: %v", ctr.ID(), err)
+ }
+ rwSize, err := ctr.RWSize()
+ if err != nil {
+ logrus.Errorf("error getting rw size for %q: %v", ctr.ID(), err)
+ }
+
params := psJSONParams{
// TODO When we have ability to obtain the commented out data, we need
// TODO to add it
- ID: ctr.ID(),
- Image: cc.RootfsImageName,
- ImageID: cc.RootfsImageID,
- //Command: getStrFromSquareBrackets(ctr.ImageCreatedBy),
- Command: strings.Join(ctr.Spec().Process.Args, " "),
+ ID: ctr.ID(),
+ Image: cc.RootfsImageName,
+ ImageID: cc.RootfsImageID,
+ Command: ctr.Spec().Process.Args,
CreatedAt: cc.CreatedTime,
RunningFor: time.Since(cc.CreatedTime),
Status: conState.String(),
//Ports: cc.Spec.Linux.Resources.Network.
- //Size: ctr.SizeRootFs,
+ RootFsSize: rootFsSize,
+ RWSize: rwSize,
Names: cc.Name,
Labels: cc.Labels,
Mounts: cc.Spec.Mounts,
@@ -570,37 +619,36 @@ func formatLabels(labels map[string]string) string {
return ""
}
-/*
// getMounts converts the volumes mounted to a string of the form "mount1, mount2"
// it truncates it if noTrunc is false
-func getMounts(mounts []specs.Mount, noTrunc bool) string {
+func getMounts(mounts []string, noTrunc bool) string {
var arr []string
if len(mounts) == 0 {
return ""
}
for _, mount := range mounts {
- if noTrunc {
- arr = append(arr, mount.Source)
+ splitArr := strings.Split(mount, ":")
+ if len(splitArr[0]) > mountTruncLength && !noTrunc {
+ arr = append(arr, splitArr[0][:mountTruncLength]+"...")
continue
}
- tempArr := strings.SplitAfter(mount.Source, "/")
- if len(tempArr) >= 3 {
- arr = append(arr, strings.Join(tempArr[:3], ""))
- } else {
- arr = append(arr, mount.Source)
- }
+ arr = append(arr, splitArr[0])
}
return strings.Join(arr, ",")
}
+
// getPorts converts the ports used to a string of the from "port1, port2"
-func getPorts(ports map[string]struct{}) string {
- var arr []string
+func getPorts(ports []ocicni.PortMapping) string {
+ var portDisplay []string
if len(ports) == 0 {
return ""
}
- for key := range ports {
- arr = append(arr, key)
+ for _, v := range ports {
+ hostIP := v.HostIP
+ if hostIP == "" {
+ hostIP = "0.0.0.0"
+ }
+ portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol))
}
- return strings.Join(arr, ",")
+ return strings.Join(portDisplay, ", ")
}
-*/
diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go
index 8dd3475c0..182089e8e 100644
--- a/cmd/podman/rm.go
+++ b/cmd/podman/rm.go
@@ -80,13 +80,6 @@ func rmCmd(c *cli.Context) error {
}
}
for _, container := range delContainers {
- if err != nil {
- if lastError != nil {
- fmt.Fprintln(os.Stderr, lastError)
- }
- lastError = errors.Wrapf(err, "failed to find container %s", container.ID())
- continue
- }
err = runtime.RemoveContainer(container, c.Bool("force"))
if err != nil {
if lastError != nil {
diff --git a/cmd/podman/run.go b/cmd/podman/run.go
index eecfe87b3..97f60cdbf 100644
--- a/cmd/podman/run.go
+++ b/cmd/podman/run.go
@@ -46,6 +46,10 @@ func runCmd(c *cli.Context) error {
}
imageName, _, data, err := imageData(c, runtime, c.Args()[0])
+ if err != nil {
+ return err
+ }
+
createConfig, err := parseCreateOpts(c, runtime, imageName, data)
if err != nil {
return err
diff --git a/cmd/podman/run_test.go b/cmd/podman/run_test.go
index 622e75d3e..b82df86db 100644
--- a/cmd/podman/run_test.go
+++ b/cmd/podman/run_test.go
@@ -3,6 +3,7 @@ package main
import (
"testing"
+ units "github.com/docker/go-units"
ociv1 "github.com/opencontainers/image-spec/specs-go/v1"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/projectatomic/libpod/libpod"
@@ -65,11 +66,24 @@ func createCLI() cli.App {
return a
}
-func getRuntimeSpec(c *cli.Context) *spec.Spec {
- runtime, _ := getRuntime(c)
- createConfig, _ := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData())
- runtimeSpec, _ := createConfigToOCISpec(createConfig)
- return runtimeSpec
+func getRuntimeSpec(c *cli.Context) (*spec.Spec, error) {
+ /*
+ TODO: This test has never worked. Need to install content
+ runtime, err := getRuntime(c)
+ if err != nil {
+ return nil, err
+ }
+ createConfig, err := parseCreateOpts(c, runtime, "alpine", generateAlpineImageData())
+ */
+ createConfig, err := parseCreateOpts(c, nil, "alpine", generateAlpineImageData())
+ if err != nil {
+ return nil, err
+ }
+ runtimeSpec, err := createConfigToOCISpec(createConfig)
+ if err != nil {
+ return nil, err
+ }
+ return runtimeSpec, nil
}
// TestPIDsLimit verifies the inputed pid-limit is correctly defined in the spec
@@ -77,6 +91,34 @@ func TestPIDsLimit(t *testing.T) {
a := createCLI()
args := []string{"--pids-limit", "22"}
a.Run(append(cmd, args...))
- runtimeSpec := getRuntimeSpec(CLI)
+ runtimeSpec, err := getRuntimeSpec(CLI)
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
assert.Equal(t, runtimeSpec.Linux.Resources.Pids.Limit, int64(22))
}
+
+// TestBLKIOWeightDevice verifies the inputed blkio weigh device is correctly defined in the spec
+func TestBLKIOWeightDevice(t *testing.T) {
+ a := createCLI()
+ args := []string{"--blkio-weight-device", "/dev/sda:100"}
+ a.Run(append(cmd, args...))
+ runtimeSpec, err := getRuntimeSpec(CLI)
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+ assert.Equal(t, *runtimeSpec.Linux.Resources.BlockIO.WeightDevice[0].Weight, uint16(100))
+}
+
+// TestMemorySwap verifies that the inputed memory swap is correctly defined in the spec
+func TestMemorySwap(t *testing.T) {
+ a := createCLI()
+ args := []string{"--memory-swap", "45m", "--memory", "40m"}
+ a.Run(append(cmd, args...))
+ runtimeSpec, err := getRuntimeSpec(CLI)
+ if err != nil {
+ t.Fatalf(err.Error())
+ }
+ mem, _ := units.RAMInBytes("45m")
+ assert.Equal(t, *runtimeSpec.Linux.Resources.Memory.Swap, mem)
+}
diff --git a/cmd/podman/spec.go b/cmd/podman/spec.go
index d630b2f50..cb9efdcb2 100644
--- a/cmd/podman/spec.go
+++ b/cmd/podman/spec.go
@@ -1,14 +1,14 @@
package main
import (
- "encoding/json"
- "fmt"
"io/ioutil"
+ "strconv"
"strings"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/docker/daemon/caps"
"github.com/docker/docker/pkg/mount"
+ "github.com/docker/docker/profiles/seccomp"
"github.com/docker/go-units"
"github.com/opencontainers/runc/libcontainer/devices"
spec "github.com/opencontainers/runtime-spec/specs-go"
@@ -21,6 +21,8 @@ import (
"golang.org/x/sys/unix"
)
+const cpuPeriod = 100000
+
func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) {
if !config.Privileged {
for _, mp := range []string{
@@ -51,21 +53,10 @@ func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator)
func addPidNS(config *createConfig, g *generate.Generator) error {
pidMode := config.PidMode
if pidMode.IsHost() {
- return g.RemoveLinuxNamespace(libpod.PIDNamespace)
+ return g.RemoveLinuxNamespace(string(spec.PIDNamespace))
}
if pidMode.IsContainer() {
- ctr, err := config.Runtime.LookupContainer(pidMode.Container())
- if err != nil {
- return errors.Wrapf(err, "container %q not found", pidMode.Container())
- }
- pid, err := ctr.PID()
- if err != nil {
- return errors.Wrapf(err, "Failed to get pid of container %q", pidMode.Container())
- }
- pidNsPath := fmt.Sprintf("/proc/%d/ns/pid", pid)
- if err := g.AddOrReplaceLinuxNamespace(libpod.PIDNamespace, pidNsPath); err != nil {
- return err
- }
+ logrus.Debug("using container pidmode")
}
return nil
}
@@ -74,7 +65,7 @@ func addNetNS(config *createConfig, g *generate.Generator) error {
netMode := config.NetMode
if netMode.IsHost() {
logrus.Debug("Using host netmode")
- return g.RemoveLinuxNamespace(libpod.NetNamespace)
+ return g.RemoveLinuxNamespace(spec.NetworkNamespace)
} else if netMode.IsNone() {
logrus.Debug("Using none netmode")
return nil
@@ -83,18 +74,6 @@ func addNetNS(config *createConfig, g *generate.Generator) error {
return nil
} else if netMode.IsContainer() {
logrus.Debug("Using container netmode")
- ctr, err := config.Runtime.LookupContainer(netMode.ConnectedContainer())
- if err != nil {
- return errors.Wrapf(err, "container %q not found", netMode.ConnectedContainer())
- }
- pid, err := ctr.PID()
- if err != nil {
- return errors.Wrapf(err, "Failed to get pid of container %q", netMode.ConnectedContainer())
- }
- nsPath := fmt.Sprintf("/proc/%d/ns/net", pid)
- if err := g.AddOrReplaceLinuxNamespace(libpod.NetNamespace, nsPath); err != nil {
- return err
- }
} else {
return errors.Errorf("unknown network mode")
}
@@ -104,7 +83,7 @@ func addNetNS(config *createConfig, g *generate.Generator) error {
func addUTSNS(config *createConfig, g *generate.Generator) error {
utsMode := config.UtsMode
if utsMode.IsHost() {
- return g.RemoveLinuxNamespace(libpod.UTSNamespace)
+ return g.RemoveLinuxNamespace(spec.UTSNamespace)
}
return nil
}
@@ -112,21 +91,10 @@ func addUTSNS(config *createConfig, g *generate.Generator) error {
func addIpcNS(config *createConfig, g *generate.Generator) error {
ipcMode := config.IpcMode
if ipcMode.IsHost() {
- return g.RemoveLinuxNamespace(libpod.IPCNamespace)
+ return g.RemoveLinuxNamespace(spec.IPCNamespace)
}
if ipcMode.IsContainer() {
- ctr, err := config.Runtime.LookupContainer(ipcMode.Container())
- if err != nil {
- return errors.Wrapf(err, "container %q not found", ipcMode.Container())
- }
- pid, err := ctr.PID()
- if err != nil {
- return errors.Wrapf(err, "Failed to get pid of container %q", ipcMode.Container())
- }
- nsPath := fmt.Sprintf("/proc/%d/ns/ipc", pid)
- if err := g.AddOrReplaceLinuxNamespace(libpod.IPCNamespace, nsPath); err != nil {
- return err
- }
+ logrus.Debug("Using container ipcmode")
}
return nil
@@ -143,7 +111,7 @@ func addRlimits(config *createConfig, g *generate.Generator) error {
return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u)
}
- g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Soft), uint64(ul.Hard))
+ g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft))
}
return nil
}
@@ -210,10 +178,8 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
if config.Hostname != "" {
g.AddProcessEnv("HOSTNAME", config.Hostname)
}
-
- for _, sysctl := range config.Sysctl {
- s := strings.SplitN(sysctl, "=", 2)
- g.AddLinuxSysctl(s[0], s[1])
+ for sysctlKey, sysctlVal := range config.Sysctl {
+ g.AddLinuxSysctl(sysctlKey, sysctlVal)
}
// RESOURCES - MEMORY
@@ -236,7 +202,6 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
g.SetProcessOOMScoreAdj(config.Resources.OomScoreAdj)
// RESOURCES - CPU
-
if config.Resources.CPUShares != 0 {
g.SetLinuxResourcesCPUShares(config.Resources.CPUShares)
}
@@ -246,14 +211,18 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
if config.Resources.CPUPeriod != 0 {
g.SetLinuxResourcesCPUPeriod(config.Resources.CPUPeriod)
}
+ if config.Resources.CPUs != 0 {
+ g.SetLinuxResourcesCPUPeriod(cpuPeriod)
+ g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * cpuPeriod))
+ }
if config.Resources.CPURtRuntime != 0 {
g.SetLinuxResourcesCPURealtimeRuntime(config.Resources.CPURtRuntime)
}
if config.Resources.CPURtPeriod != 0 {
g.SetLinuxResourcesCPURealtimePeriod(config.Resources.CPURtPeriod)
}
- if config.Resources.CPUs != "" {
- g.SetLinuxResourcesCPUCpus(config.Resources.CPUs)
+ if config.Resources.CPUsetCPUs != "" {
+ g.SetLinuxResourcesCPUCpus(config.Resources.CPUsetCPUs)
}
if config.Resources.CPUsetMems != "" {
g.SetLinuxResourcesCPUMems(config.Resources.CPUsetMems)
@@ -322,16 +291,31 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
}
configSpec := g.Spec()
- if config.SeccompProfilePath != "" && config.SeccompProfilePath != "unconfined" {
- seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
- if err != nil {
- return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
- }
- var seccompConfig spec.LinuxSeccomp
- if err := json.Unmarshal(seccompProfile, &seccompConfig); err != nil {
- return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.SeccompProfilePath)
+ // HANDLE CAPABILITIES
+ // NOTE: Must happen before SECCOMP
+ if err := setupCapabilities(config, configSpec); err != nil {
+ return nil, err
+ }
+
+ // HANDLE SECCOMP
+ if config.SeccompProfilePath != "unconfined" {
+ if config.SeccompProfilePath != "" {
+ seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
+ }
+ seccompConfig, err := seccomp.LoadProfile(string(seccompProfile), configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ }
+ configSpec.Linux.Seccomp = seccompConfig
+ } else {
+ seccompConfig, err := seccomp.GetDefaultProfile(configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath)
+ }
+ configSpec.Linux.Seccomp = seccompConfig
}
- configSpec.Linux.Seccomp = &seccompConfig
}
// BIND MOUNTS
@@ -351,9 +335,13 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
}
}
- // HANDLE CAPABILITIES
- if err := setupCapabilities(config, configSpec); err != nil {
- return nil, err
+ // BLOCK IO
+ blkio, err := config.CreateBlockIO()
+ if err != nil {
+ return nil, errors.Wrapf(err, "error creating block io")
+ }
+ if blkio != nil {
+ configSpec.Linux.Resources.BlockIO = blkio
}
/*
@@ -383,8 +371,8 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
return configSpec, nil
}
-func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) {
- bio := spec.LinuxBlockIO{}
+func (c *createConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) {
+ bio := &spec.LinuxBlockIO{}
bio.Weight = &c.Resources.BlkioWeight
if len(c.Resources.BlkioWeightDevice) > 0 {
var lwds []spec.LinuxWeightDevice
@@ -401,6 +389,7 @@ func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) {
lwd.Minor = int64(unix.Minor(wdStat.Rdev))
lwds = append(lwds, lwd)
}
+ bio.WeightDevice = lwds
}
if len(c.Resources.DeviceReadBps) > 0 {
readBps, err := makeThrottleArray(c.Resources.DeviceReadBps)
@@ -430,7 +419,6 @@ func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) {
}
bio.ThrottleWriteIOPSDevice = writeIOps
}
-
return bio, nil
}
@@ -556,6 +544,8 @@ func (c *createConfig) GetTmpfsMounts() []spec.Mount {
func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
+ var portBindings []ocicni.PortMapping
+ var err error
// Uncomment after talking to mheon about unimplemented funcs
// options = append(options, libpod.WithLabels(c.labels))
@@ -567,12 +557,57 @@ func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, er
logrus.Debugf("appending name %s", c.Name)
options = append(options, libpod.WithName(c.Name))
}
- // TODO parse ports into libpod format and include
- if !c.NetMode.IsHost() {
- options = append(options, libpod.WithNetNS([]ocicni.PortMapping{}))
+
+ // TODO deal with ports defined in image metadata
+ if len(c.PortBindings) > 0 || len(c.ExposedPorts) > 0 {
+ portBindings, err = c.CreatePortBindings()
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to create port bindings")
+ }
+ }
+
+ if c.NetMode.IsContainer() {
+ connectedCtr, err := c.Runtime.LookupContainer(c.NetMode.ConnectedContainer())
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", c.NetMode.ConnectedContainer())
+ }
+ options = append(options, libpod.WithNetNSFrom(connectedCtr))
+ } else if !c.NetMode.IsHost() {
+ options = append(options, libpod.WithNetNS(portBindings))
+ }
+
+ if c.PidMode.IsContainer() {
+ connectedCtr, err := c.Runtime.LookupContainer(c.PidMode.Container())
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container())
+ }
+
+ options = append(options, libpod.WithPIDNSFrom(connectedCtr))
+ }
+ if c.IpcMode.IsContainer() {
+ connectedCtr, err := c.Runtime.LookupContainer(c.IpcMode.Container())
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container())
+ }
+
+ options = append(options, libpod.WithIPCNSFrom(connectedCtr))
}
+
options = append(options, libpod.WithStopSignal(c.StopSignal))
options = append(options, libpod.WithStopTimeout(c.StopTimeout))
+ if len(c.DNSSearch) > 0 {
+ options = append(options, libpod.WithDNSSearch(c.DNSSearch))
+ }
+ if len(c.DNSServers) > 0 {
+ options = append(options, libpod.WithDNS(c.DNSServers))
+ }
+ if len(c.DNSOpt) > 0 {
+ options = append(options, libpod.WithDNSOption(c.DNSOpt))
+ }
+ if len(c.HostAdd) > 0 {
+ options = append(options, libpod.WithHosts(c.HostAdd))
+ }
+
return options, nil
}
@@ -598,3 +633,43 @@ func makeThrottleArray(throttleInput []string) ([]spec.LinuxThrottleDevice, erro
}
return ltds, nil
}
+
+// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands
+func (c *createConfig) CreatePortBindings() ([]ocicni.PortMapping, error) {
+ var portBindings []ocicni.PortMapping
+ for containerPb, hostPb := range c.PortBindings {
+ var pm ocicni.PortMapping
+ pm.ContainerPort = int32(containerPb.Int())
+ for _, i := range hostPb {
+ var hostPort int
+ var err error
+ pm.HostIP = i.HostIP
+ if i.HostPort == "" {
+ hostPort = containerPb.Int()
+ } else {
+ hostPort, err = strconv.Atoi(i.HostPort)
+ if err != nil {
+ return nil, errors.Wrapf(err, "unable to convert host port to integer")
+ }
+ }
+
+ pm.HostPort = int32(hostPort)
+ // CNI requires us to make both udp and tcp structs
+ pm.Protocol = "udp"
+ portBindings = append(portBindings, pm)
+ pm.Protocol = "tcp"
+ portBindings = append(portBindings, pm)
+ }
+ }
+ for j := range c.ExposedPorts {
+ var expose ocicni.PortMapping
+ expose.HostPort = int32(j.Int())
+ expose.ContainerPort = int32(j.Int())
+ // CNI requires us to make both udp and tcp structs
+ expose.Protocol = "udp"
+ portBindings = append(portBindings, expose)
+ expose.Protocol = "tcp"
+ portBindings = append(portBindings, expose)
+ }
+ return portBindings, nil
+}
diff --git a/cmd/podman/stats.go b/cmd/podman/stats.go
index d2ffd8a75..871eb9e2f 100644
--- a/cmd/podman/stats.go
+++ b/cmd/podman/stats.go
@@ -22,7 +22,7 @@ type statsOutputParams struct {
MemPerc string `json:"mem_percent"`
NetIO string `json:"netio"`
BlockIO string `json:"blocki"`
- PIDS uint64 `json:"pids"`
+ PIDS string `json:"pids"`
}
var (
@@ -62,14 +62,22 @@ func statsCmd(c *cli.Context) error {
}
all := c.Bool("all")
-
- if c.Bool("latest") && all {
- return errors.Errorf("--all and --latest cannot be used together")
+ latest := c.Bool("latest")
+ ctr := 0
+ if all {
+ ctr += 1
+ }
+ if latest {
+ ctr += 1
+ }
+ if len(c.Args()) > 0 {
+ ctr += 1
}
- if c.Bool("latest") && len(c.Args()) > 0 {
- return errors.Errorf("no container names are allowed with --latest")
+ if ctr > 1 {
+ return errors.Errorf("--all, --latest and containers cannot be used together")
}
+
runtime, err := getRuntime(c)
if err != nil {
return errors.Wrapf(err, "could not get runtime")
@@ -91,19 +99,19 @@ func statsCmd(c *cli.Context) error {
format = genStatsFormat()
}
+ containerFunc = runtime.GetRunningContainers
if len(c.Args()) > 0 {
containerFunc = func() ([]*libpod.Container, error) { return runtime.GetContainersByList(c.Args()) }
- } else if c.Bool("latest") {
+ } else if latest {
containerFunc = func() ([]*libpod.Container, error) {
- var ctrs []*libpod.Container
lastCtr, err := runtime.GetLatestContainer()
- ctrs = append(ctrs, lastCtr)
- return ctrs, err
+ if err != nil {
+ return nil, err
+ }
+ return []*libpod.Container{lastCtr}, nil
}
} else if all {
containerFunc = runtime.GetAllContainers
- } else {
- containerFunc = runtime.GetRunningContainers
}
ctrs, err = containerFunc()
@@ -215,18 +223,29 @@ func (i *statsOutputParams) headerMap() map[string]string {
}
func combineHumanValues(a, b uint64) string {
+ if a == 0 && b == 0 {
+ return "-- / --"
+ }
return fmt.Sprintf("%s / %s", units.HumanSize(float64(a)), units.HumanSize(float64(b)))
}
func floatToPercentString(f float64) string {
strippedFloat, err := libpod.RemoveScientificNotationFromFloat(f)
- if err != nil {
+ if err != nil || strippedFloat == 0 {
// If things go bazinga, return a safe value
- return "0.00 %"
+ return "--"
}
return fmt.Sprintf("%.2f", strippedFloat) + "%"
}
+func pidsToString(pid uint64) string {
+ if pid == 0 {
+ // If things go bazinga, return a safe value
+ return "--"
+ }
+ return fmt.Sprintf("%d", pid)
+}
+
func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams {
return statsOutputParams{
Container: stats.ContainerID[:12],
@@ -236,6 +255,6 @@ func getStatsOutputParams(stats *libpod.ContainerStats) statsOutputParams {
MemPerc: floatToPercentString(stats.MemPerc),
NetIO: combineHumanValues(stats.NetInput, stats.NetOutput),
BlockIO: combineHumanValues(stats.BlockInput, stats.BlockOutput),
- PIDS: stats.PIDs,
+ PIDS: pidsToString(stats.PIDs),
}
}