summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podman/runlabel.go4
-rw-r--r--cmd/podman/shared/container.go4
-rw-r--r--cmd/podman/shared/funcs.go4
-rw-r--r--cmd/podman/shared/funcs_test.go12
-rw-r--r--cmd/podman/shared/parse/parse.go353
-rw-r--r--cmd/podman/shared/parse/parse_test.go99
-rw-r--r--cmd/podman/top.go9
-rw-r--r--docs/podman-pod-top.1.md2
-rw-r--r--docs/podman-top.1.md12
-rw-r--r--libpod/container_top_linux.go14
-rw-r--r--pkg/adapter/containers.go66
-rw-r--r--pkg/util/utils.go31
-rw-r--r--pkg/varlinkapi/images.go2
-rw-r--r--test/e2e/pod_top_test.go6
-rw-r--r--test/e2e/runlabel_test.go19
-rw-r--r--test/e2e/top_test.go28
16 files changed, 290 insertions, 375 deletions
diff --git a/cmd/podman/runlabel.go b/cmd/podman/runlabel.go
index f097cb693..c426817de 100644
--- a/cmd/podman/runlabel.go
+++ b/cmd/podman/runlabel.go
@@ -12,6 +12,7 @@ import (
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/util"
"github.com/containers/libpod/utils"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -145,7 +146,8 @@ func runlabelCmd(c *cliconfig.RunlabelValues) error {
return errors.Errorf("%s does not have a label of %s", runlabelImage, label)
}
- cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs)
+ globalOpts := util.GetGlobalOpts(c)
+ cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, c.Name, opts, extraArgs, globalOpts)
if err != nil {
return err
}
diff --git a/cmd/podman/shared/container.go b/cmd/podman/shared/container.go
index 9050fd2b9..fe447d10d 100644
--- a/cmd/podman/shared/container.go
+++ b/cmd/podman/shared/container.go
@@ -883,7 +883,7 @@ func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtim
}
// GenerateRunlabelCommand generates the command that will eventually be execucted by podman
-func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string) ([]string, []string, error) {
+func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string, globalOpts string) ([]string, []string, error) {
// If no name is provided, we use the image's basename instead
if name == "" {
baseName, err := image.GetImageBaseName(imageName)
@@ -896,7 +896,7 @@ func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]s
if len(extraArgs) > 0 {
runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " "))
}
- cmd, err := GenerateCommand(runLabel, imageName, name)
+ cmd, err := GenerateCommand(runLabel, imageName, name, globalOpts)
if err != nil {
return nil, nil, errors.Wrapf(err, "unable to generate command")
}
diff --git a/cmd/podman/shared/funcs.go b/cmd/podman/shared/funcs.go
index 70d041fd2..c189cceeb 100644
--- a/cmd/podman/shared/funcs.go
+++ b/cmd/podman/shared/funcs.go
@@ -41,7 +41,7 @@ func substituteCommand(cmd string) (string, error) {
}
// GenerateCommand takes a label (string) and converts it to an executable command
-func GenerateCommand(command, imageName, name string) ([]string, error) {
+func GenerateCommand(command, imageName, name, globalOpts string) ([]string, error) {
var (
newCommand []string
)
@@ -79,6 +79,8 @@ func GenerateCommand(command, imageName, name string) ([]string, error) {
newArg = fmt.Sprintf("NAME=%s", name)
case "$NAME":
newArg = name
+ case "$GLOBAL_OPTS":
+ newArg = globalOpts
default:
newArg = arg
}
diff --git a/cmd/podman/shared/funcs_test.go b/cmd/podman/shared/funcs_test.go
index 7506b9d9c..c05348242 100644
--- a/cmd/podman/shared/funcs_test.go
+++ b/cmd/podman/shared/funcs_test.go
@@ -20,7 +20,7 @@ var (
func TestGenerateCommand(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo \"hello world\""
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo hello world"
- newCommand, err := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "bar", "")
assert.Nil(t, err)
assert.Equal(t, "hello world", newCommand[11])
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
@@ -83,7 +83,7 @@ func TestGenerateCommandCheckSubstitution(t *testing.T) {
}
for _, test := range tests {
- newCommand, err := GenerateCommand(test.input, "foo", "bar")
+ newCommand, err := GenerateCommand(test.input, "foo", "bar", "")
if test.shouldFail {
assert.NotNil(t, err)
} else {
@@ -96,14 +96,14 @@ func TestGenerateCommandCheckSubstitution(t *testing.T) {
func TestGenerateCommandPath(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand, _ := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, _ := GenerateCommand(inputCommand, "foo", "bar", "")
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
func TestGenerateCommandNoSetName(t *testing.T) {
inputCommand := "docker run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name foo -e NAME=foo -e IMAGE=foo foo echo install"
- newCommand, err := GenerateCommand(inputCommand, "foo", "")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "", "")
assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
@@ -111,7 +111,7 @@ func TestGenerateCommandNoSetName(t *testing.T) {
func TestGenerateCommandNoName(t *testing.T) {
inputCommand := "docker run -it -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it -e IMAGE=foo foo echo install"
- newCommand, err := GenerateCommand(inputCommand, "foo", "")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "", "")
assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
@@ -119,7 +119,7 @@ func TestGenerateCommandNoName(t *testing.T) {
func TestGenerateCommandAlreadyPodman(t *testing.T) {
inputCommand := "podman run -it --name NAME -e NAME=NAME -e IMAGE=IMAGE IMAGE echo install"
correctCommand := "/proc/self/exe run -it --name bar -e NAME=bar -e IMAGE=foo foo echo install"
- newCommand, err := GenerateCommand(inputCommand, "foo", "bar")
+ newCommand, err := GenerateCommand(inputCommand, "foo", "bar", "")
assert.Nil(t, err)
assert.Equal(t, correctCommand, strings.Join(newCommand, " "))
}
diff --git a/cmd/podman/shared/parse/parse.go b/cmd/podman/shared/parse/parse.go
index a3751835b..7bc2652cb 100644
--- a/cmd/podman/shared/parse/parse.go
+++ b/cmd/podman/shared/parse/parse.go
@@ -5,15 +5,10 @@ package parse
import (
"bufio"
- "bytes"
- "encoding/json"
"fmt"
- "io/ioutil"
"net"
"os"
- "path"
"regexp"
- "strconv"
"strings"
"github.com/pkg/errors"
@@ -72,77 +67,6 @@ func validateIPAddress(val string) (string, error) {
return "", fmt.Errorf("%s is not an ip address", val)
}
-// validateAttach validates that the specified string is a valid attach option.
-// for attach flag
-func validateAttach(val string) (string, error) { //nolint
- s := strings.ToLower(val)
- for _, str := range []string{"stdin", "stdout", "stderr"} {
- if s == str {
- return s, nil
- }
- }
- return val, fmt.Errorf("valid streams are STDIN, STDOUT and STDERR")
-}
-
-// validate the blkioWeight falls in the range of 10 to 1000
-// for blkio-weight flag
-func validateBlkioWeight(val int64) (int64, error) { //nolint
- if val >= 10 && val <= 1000 {
- return val, nil
- }
- return -1, errors.Errorf("invalid blkio weight %q, should be between 10 and 1000", val)
-}
-
-func validatePath(val string, validator func(string) bool) (string, error) {
- var containerPath string
- var mode string
-
- if strings.Count(val, ":") > 2 {
- return val, fmt.Errorf("bad format for path: %s", val)
- }
-
- split := strings.SplitN(val, ":", 3)
- if split[0] == "" {
- return val, fmt.Errorf("bad format for path: %s", val)
- }
- switch len(split) {
- case 1:
- containerPath = split[0]
- val = path.Clean(containerPath)
- case 2:
- if isValid := validator(split[1]); isValid {
- containerPath = split[0]
- mode = split[1]
- val = fmt.Sprintf("%s:%s", path.Clean(containerPath), mode)
- } else {
- containerPath = split[1]
- val = fmt.Sprintf("%s:%s", split[0], path.Clean(containerPath))
- }
- case 3:
- containerPath = split[1]
- mode = split[2]
- if isValid := validator(split[2]); !isValid {
- return val, fmt.Errorf("bad mode specified: %s", mode)
- }
- val = fmt.Sprintf("%s:%s:%s", split[0], containerPath, mode)
- }
-
- if !path.IsAbs(containerPath) {
- return val, fmt.Errorf("%s is not an absolute path", containerPath)
- }
- return val, nil
-}
-
-// validateDNSSearch validates domain for resolvconf search configuration.
-// A zero length domain is represented by a dot (.).
-// for dns-search flag
-func validateDNSSearch(val string) (string, error) { //nolint
- if val = strings.Trim(val, " "); val == "." {
- return val, nil
- }
- return ValidateDomain(val)
-}
-
func ValidateDomain(val string) (string, error) {
if alphaRegexp.FindString(val) == "" {
return "", fmt.Errorf("%s is not a valid domain", val)
@@ -154,30 +78,6 @@ func ValidateDomain(val string) (string, error) {
return "", fmt.Errorf("%s is not a valid domain", val)
}
-// validateEnv validates an environment variable and returns it.
-// If no value is specified, it returns the current value using os.Getenv.
-// for env flag
-func validateEnv(val string) (string, error) { //nolint
- arr := strings.Split(val, "=")
- if len(arr) > 1 {
- return val, nil
- }
- if !doesEnvExist(val) {
- return val, nil
- }
- return fmt.Sprintf("%s=%s", val, os.Getenv(val)), nil
-}
-
-func doesEnvExist(name string) bool {
- for _, entry := range os.Environ() {
- parts := strings.SplitN(entry, "=", 2)
- if parts[0] == name {
- return true
- }
- }
- return false
-}
-
// reads a file of line terminated key=value pairs, and overrides any keys
// present in the file with additional pairs specified in the override parameter
// for env-file and labels-file flags
@@ -241,259 +141,6 @@ func parseEnvFile(env map[string]string, filename string) error {
return scanner.Err()
}
-// validateLabel validates that the specified string is a valid label, and returns it.
-// Labels are in the form on key=value.
-// for label flag
-func validateLabel(val string) (string, error) { //nolint
- if strings.Count(val, "=") < 1 {
- return "", fmt.Errorf("bad attribute format: %s", val)
- }
- return val, nil
-}
-
-// validateMACAddress validates a MAC address.
-// for mac-address flag
-func validateMACAddress(val string) (string, error) { //nolint
- _, err := net.ParseMAC(strings.TrimSpace(val))
- if err != nil {
- return "", err
- }
- return val, nil
-}
-
-// parseLoggingOpts validates the logDriver and logDriverOpts
-// for log-opt and log-driver flags
-func parseLoggingOpts(logDriver string, logDriverOpt []string) (map[string]string, error) { //nolint
- logOptsMap := convertKVStringsToMap(logDriverOpt)
- if logDriver == "none" && len(logDriverOpt) > 0 {
- return map[string]string{}, errors.Errorf("invalid logging opts for driver %s", logDriver)
- }
- return logOptsMap, nil
-}
-
-// parsePortSpecs receives port specs in the format of ip:public:private/proto and parses
-// these in to the internal types
-// for publish, publish-all, and expose flags
-func parsePortSpecs(ports []string) ([]*PortMapping, error) { //nolint
- var portMappings []*PortMapping
- for _, rawPort := range ports {
- portMapping, err := parsePortSpec(rawPort)
- if err != nil {
- return nil, err
- }
-
- portMappings = append(portMappings, portMapping...)
- }
- return portMappings, nil
-}
-
-func validateProto(proto string) bool {
- for _, availableProto := range []string{"tcp", "udp"} {
- if availableProto == proto {
- return true
- }
- }
- return false
-}
-
-// parsePortSpec parses a port specification string into a slice of PortMappings
-func parsePortSpec(rawPort string) ([]*PortMapping, error) {
- var proto string
- rawIP, hostPort, containerPort := splitParts(rawPort)
- proto, containerPort = splitProtoPort(containerPort)
-
- // Strip [] from IPV6 addresses
- ip, _, err := net.SplitHostPort(rawIP + ":")
- if err != nil {
- return nil, fmt.Errorf("Invalid ip address %v: %s", rawIP, err)
- }
- if ip != "" && net.ParseIP(ip) == nil {
- return nil, fmt.Errorf("Invalid ip address: %s", ip)
- }
- if containerPort == "" {
- return nil, fmt.Errorf("No port specified: %s<empty>", rawPort)
- }
-
- startPort, endPort, err := parsePortRange(containerPort)
- if err != nil {
- return nil, fmt.Errorf("Invalid containerPort: %s", containerPort)
- }
-
- var startHostPort, endHostPort uint64 = 0, 0
- if len(hostPort) > 0 {
- startHostPort, endHostPort, err = parsePortRange(hostPort)
- if err != nil {
- return nil, fmt.Errorf("Invalid hostPort: %s", hostPort)
- }
- }
-
- if hostPort != "" && (endPort-startPort) != (endHostPort-startHostPort) {
- // Allow host port range iff containerPort is not a range.
- // In this case, use the host port range as the dynamic
- // host port range to allocate into.
- if endPort != startPort {
- return nil, fmt.Errorf("Invalid ranges specified for container and host Ports: %s and %s", containerPort, hostPort)
- }
- }
-
- if !validateProto(strings.ToLower(proto)) {
- return nil, fmt.Errorf("invalid proto: %s", proto)
- }
-
- protocol := Protocol_TCP
- if strings.ToLower(proto) == "udp" {
- protocol = Protocol_UDP
- }
-
- var ports []*PortMapping
- for i := uint64(0); i <= (endPort - startPort); i++ {
- containerPort = strconv.FormatUint(startPort+i, 10)
- if len(hostPort) > 0 {
- hostPort = strconv.FormatUint(startHostPort+i, 10)
- }
- // Set hostPort to a range only if there is a single container port
- // and a dynamic host port.
- if startPort == endPort && startHostPort != endHostPort {
- hostPort = fmt.Sprintf("%s-%s", hostPort, strconv.FormatUint(endHostPort, 10))
- }
-
- ctrPort, err := strconv.ParseInt(containerPort, 10, 32)
- if err != nil {
- return nil, err
- }
- hPort, err := strconv.ParseInt(hostPort, 10, 32)
- if err != nil {
- return nil, err
- }
-
- port := &PortMapping{
- Protocol: protocol,
- ContainerPort: int32(ctrPort),
- HostPort: int32(hPort),
- HostIp: ip,
- }
-
- ports = append(ports, port)
- }
- return ports, nil
-}
-
-// parsePortRange parses and validates the specified string as a port-range (8000-9000)
-func parsePortRange(ports string) (uint64, uint64, error) {
- if ports == "" {
- return 0, 0, fmt.Errorf("empty string specified for ports")
- }
- if !strings.Contains(ports, "-") {
- start, err := strconv.ParseUint(ports, 10, 16)
- end := start
- return start, end, err
- }
-
- parts := strings.Split(ports, "-")
- start, err := strconv.ParseUint(parts[0], 10, 16)
- if err != nil {
- return 0, 0, err
- }
- end, err := strconv.ParseUint(parts[1], 10, 16)
- if err != nil {
- return 0, 0, err
- }
- if end < start {
- return 0, 0, fmt.Errorf("Invalid range specified for the Port: %s", ports)
- }
- return start, end, nil
-}
-
-// splitParts separates the different parts of rawPort
-func splitParts(rawport string) (string, string, string) {
- parts := strings.Split(rawport, ":")
- n := len(parts)
- containerport := parts[n-1]
-
- switch n {
- case 1:
- return "", "", containerport
- case 2:
- return "", parts[0], containerport
- case 3:
- return parts[0], parts[1], containerport
- default:
- return strings.Join(parts[:n-2], ":"), parts[n-2], containerport
- }
-}
-
-// splitProtoPort splits a port in the format of port/proto
-func splitProtoPort(rawPort string) (string, string) {
- parts := strings.Split(rawPort, "/")
- l := len(parts)
- if len(rawPort) == 0 || l == 0 || len(parts[0]) == 0 {
- return "", ""
- }
- if l == 1 {
- return "tcp", rawPort
- }
- if len(parts[1]) == 0 {
- return "tcp", parts[0]
- }
- return parts[1], parts[0]
-}
-
-// takes a local seccomp file and reads its file contents
-// for security-opt flag
-func parseSecurityOpts(securityOpts []string) ([]string, error) { //nolint
- for key, opt := range securityOpts {
- con := strings.SplitN(opt, "=", 2)
- if len(con) == 1 && con[0] != "no-new-privileges" {
- if strings.Index(opt, ":") != -1 {
- con = strings.SplitN(opt, ":", 2)
- } else {
- return securityOpts, fmt.Errorf("Invalid --security-opt: %q", opt)
- }
- }
- if con[0] == "seccomp" && con[1] != "unconfined" {
- f, err := ioutil.ReadFile(con[1])
- if err != nil {
- return securityOpts, fmt.Errorf("opening seccomp profile (%s) failed: %v", con[1], err)
- }
- b := bytes.NewBuffer(nil)
- if err := json.Compact(b, f); err != nil {
- return securityOpts, fmt.Errorf("compacting json for seccomp profile (%s) failed: %v", con[1], err)
- }
- securityOpts[key] = fmt.Sprintf("seccomp=%s", b.Bytes())
- }
- }
-
- return securityOpts, nil
-}
-
-// convertKVStringsToMap converts ["key=value"] to {"key":"value"}
-func convertKVStringsToMap(values []string) map[string]string {
- result := make(map[string]string, len(values))
- for _, value := range values {
- kv := strings.SplitN(value, "=", 2)
- if len(kv) == 1 {
- result[kv[0]] = ""
- } else {
- result[kv[0]] = kv[1]
- }
- }
-
- return result
-}
-
-// Takes a stringslice and converts to a uint32slice
-func stringSlicetoUint32Slice(inputSlice []string) ([]uint32, error) {
- var outputSlice []uint32
- for _, v := range inputSlice {
- u, err := strconv.ParseUint(v, 10, 32)
- if err != nil {
- return outputSlice, err
- }
- outputSlice = append(outputSlice, uint32(u))
- }
- return outputSlice, nil
-}
-
// ValidateFileName returns an error if filename contains ":"
// as it is currently not supported
func ValidateFileName(filename string) error {
diff --git a/cmd/podman/shared/parse/parse_test.go b/cmd/podman/shared/parse/parse_test.go
new file mode 100644
index 000000000..0a221c244
--- /dev/null
+++ b/cmd/podman/shared/parse/parse_test.go
@@ -0,0 +1,99 @@
+//nolint
+// most of these validate and parse functions have been taken from projectatomic/docker
+// and modified for cri-o
+package parse
+
+import (
+ "testing"
+)
+
+func TestValidateExtraHost(t *testing.T) {
+ type args struct {
+ val string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ //2001:0db8:85a3:0000:0000:8a2e:0370:7334
+ {name: "good-ipv4", args: args{val: "foobar:192.168.1.1"}, want: "foobar:192.168.1.1", wantErr: false},
+ {name: "bad-ipv4", args: args{val: "foobar:999.999.999.99"}, want: "", wantErr: true},
+ {name: "bad-ipv4", args: args{val: "foobar:999.999.999"}, want: "", wantErr: true},
+ {name: "noname-ipv4", args: args{val: "192.168.1.1"}, want: "", wantErr: true},
+ {name: "noname-ipv4", args: args{val: ":192.168.1.1"}, want: "", wantErr: true},
+ {name: "noip", args: args{val: "foobar:"}, want: "", wantErr: true},
+ {name: "noip", args: args{val: "foobar"}, want: "", wantErr: true},
+ {name: "good-ipv6", args: args{val: "foobar:2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "foobar:2001:0db8:85a3:0000:0000:8a2e:0370:7334", wantErr: false},
+ {name: "bad-ipv6", args: args{val: "foobar:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true},
+ {name: "bad-ipv6", args: args{val: "foobar:0db8:85a3:0000:0000:8a2e:0370:7334.0000.0000.000"}, want: "", wantErr: true},
+ {name: "noname-ipv6", args: args{val: "2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true},
+ {name: "noname-ipv6", args: args{val: ":2001:0db8:85a3:0000:0000:8a2e:0370:7334"}, want: "", wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := ValidateExtraHost(tt.args.val)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("ValidateExtraHost() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("ValidateExtraHost() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func Test_validateIPAddress(t *testing.T) {
+ type args struct {
+ val string
+ }
+ tests := []struct {
+ name string
+ args args
+ want string
+ wantErr bool
+ }{
+ {name: "ipv4-good", args: args{val: "192.168.1.1"}, want: "192.168.1.1", wantErr: false},
+ {name: "ipv4-bad", args: args{val: "192.168.1.1.1"}, want: "", wantErr: true},
+ {name: "ipv4-bad", args: args{val: "192."}, want: "", wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ got, err := validateIPAddress(tt.args.val)
+ if (err != nil) != tt.wantErr {
+ t.Errorf("validateIPAddress() error = %v, wantErr %v", err, tt.wantErr)
+ return
+ }
+ if got != tt.want {
+ t.Errorf("validateIPAddress() = %v, want %v", got, tt.want)
+ }
+ })
+ }
+}
+
+func TestValidateFileName(t *testing.T) {
+ type args struct {
+ filename string
+ }
+ tests := []struct {
+ name string
+ args args
+ wantErr bool
+ }{
+ {name: "good", args: args{filename: "/som/rand/path"}, wantErr: false},
+ {name: "good", args: args{filename: "som/rand/path"}, wantErr: false},
+ {name: "good", args: args{filename: "/"}, wantErr: false},
+ {name: "bad", args: args{filename: "/:"}, wantErr: true},
+ {name: "bad", args: args{filename: ":/"}, wantErr: true},
+ {name: "bad", args: args{filename: "/some/rand:/path"}, wantErr: true},
+ }
+ for _, tt := range tests {
+ t.Run(tt.name, func(t *testing.T) {
+ if err := ValidateFileName(tt.args.filename); (err != nil) != tt.wantErr {
+ t.Errorf("ValidateFileName() error = %v, wantErr %v", err, tt.wantErr)
+ }
+ })
+ }
+}
diff --git a/cmd/podman/top.go b/cmd/podman/top.go
index 2e0a22d92..8583eccb5 100644
--- a/cmd/podman/top.go
+++ b/cmd/podman/top.go
@@ -33,7 +33,7 @@ var (
%s`, getDescriptorString())
_topCommand = &cobra.Command{
- Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS]",
+ Use: "top [flags] CONTAINER [FORMAT-DESCRIPTORS|ARGS]",
Short: "Display the running processes of a container",
Long: topDescription,
RunE: func(cmd *cobra.Command, args []string) error {
@@ -42,9 +42,11 @@ var (
topCommand.Remote = remoteclient
return topCmd(&topCommand)
},
+ Args: cobra.ArbitraryArgs,
Example: `podman top ctrID
- podman top --latest
- podman top ctrID pid seccomp args %C`,
+podman top --latest
+podman top ctrID pid seccomp args %C
+podman top ctrID -eo user,pid,comm`,
}
)
@@ -53,6 +55,7 @@ func init() {
topCommand.SetHelpTemplate(HelpTemplate())
topCommand.SetUsageTemplate(UsageTemplate())
flags := topCommand.Flags()
+ flags.SetInterspersed(false)
flags.BoolVar(&topCommand.ListDescriptors, "list-descriptors", false, "")
flags.MarkHidden("list-descriptors")
flags.BoolVarP(&topCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
diff --git a/docs/podman-pod-top.1.md b/docs/podman-pod-top.1.md
index b235a70ad..fbab6bc09 100644
--- a/docs/podman-pod-top.1.md
+++ b/docs/podman-pod-top.1.md
@@ -7,7 +7,7 @@ podman\-pod\-top - Display the running processes of containers in a pod
**podman pod top** [*options*] *pod* [*format-descriptors*]
## DESCRIPTION
-Display the running process of containers in a pod. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process.
+Display the running processes of containers in a pod. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. The descriptors can either be passed as separated arguments or as a single comma-separated argument. Note that you can also specify options and or flags of ps(1); in this case, Podman will fallback to executing ps with the specified arguments and flags in the container.
## OPTIONS
diff --git a/docs/podman-top.1.md b/docs/podman-top.1.md
index 52d1238ef..74175b753 100644
--- a/docs/podman-top.1.md
+++ b/docs/podman-top.1.md
@@ -7,7 +7,7 @@ podman\-top - Display the running processes of a container
**podman top** [*options*] *container* [*format-descriptors*]
## DESCRIPTION
-Display the running process of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process.
+Display the running processes of the container. The *format-descriptors* are ps (1) compatible AIX format descriptors but extended to print additional information, such as the seccomp mode or the effective capabilities of a given process. The descriptors can either be passed as separated arguments or as a single comma-separated argument. Note that you can also specify options and or flags of ps(1); in this case, Podman will fallback to executing ps with the specified arguments and flags in the container.
## OPTIONS
@@ -83,12 +83,20 @@ root 8 1 0.000 11.386886562s pts/0 0s vi
The output can be controlled by specifying format descriptors as arguments after the container:
```
-$ sudo ./bin/podman top -l pid seccomp args %C
+$ podman top -l pid seccomp args %C
PID SECCOMP COMMAND %CPU
1 filter sh 0.000
8 filter vi /etc/ 0.000
```
+Podman will fallback to executing ps(1) in the container if an unknown descriptor is specified.
+
+```
+$ podman top -l -- aux
+USER PID PPID %CPU ELAPSED TTY TIME COMMAND
+root 1 0 0.000 1h2m12.497061672s ? 0s sleep 100000
+```
+
## SEE ALSO
podman(1), ps(1), seccomp(2), proc(5), capabilities(7)
diff --git a/libpod/container_top_linux.go b/libpod/container_top_linux.go
index b370495fe..392a7029e 100644
--- a/libpod/container_top_linux.go
+++ b/libpod/container_top_linux.go
@@ -20,14 +20,24 @@ func (c *Container) Top(descriptors []string) ([]string, error) {
if conStat != ContainerStateRunning {
return nil, errors.Errorf("top can only be used on running containers")
}
- return c.GetContainerPidInformation(descriptors)
+
+ // Also support comma-separated input.
+ psgoDescriptors := []string{}
+ for _, d := range descriptors {
+ for _, s := range strings.Split(d, ",") {
+ if s != "" {
+ psgoDescriptors = append(psgoDescriptors, s)
+ }
+ }
+ }
+ return c.GetContainerPidInformation(psgoDescriptors)
}
// GetContainerPidInformation returns process-related data of all processes in
// the container. The output data can be controlled via the `descriptors`
// argument which expects format descriptors and supports all AIXformat
// descriptors of ps (1) plus some additional ones to for instance inspect the
-// set of effective capabilities. Eeach element in the returned string slice
+// set of effective capabilities. Each element in the returned string slice
// is a tab-separated string.
//
// For more details, please refer to github.com/containers/psgo.
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index d575bc9b0..0721af773 100644
--- a/pkg/adapter/containers.go
+++ b/pkg/adapter/containers.go
@@ -3,6 +3,7 @@
package adapter
import (
+ "bufio"
"context"
"fmt"
"io/ioutil"
@@ -19,6 +20,7 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/systemdgen"
+ "github.com/containers/psgo"
"github.com/containers/storage"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
@@ -822,7 +824,69 @@ func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) {
if err != nil {
return nil, errors.Wrapf(err, "unable to lookup requested container")
}
- return container.Top(descriptors)
+
+ output, psgoErr := container.Top(descriptors)
+ if psgoErr == nil {
+ return output, nil
+ }
+
+ // If we encountered an ErrUnknownDescriptor error, fallback to executing
+ // ps(1). This ensures backwards compatibility to users depending on ps(1)
+ // and makes sure we're ~compatible with docker.
+ if errors.Cause(psgoErr) != psgo.ErrUnknownDescriptor {
+ return nil, psgoErr
+ }
+
+ output, err = r.execPS(container, descriptors)
+ if err != nil {
+ // Note: return psgoErr to guide users into using the AIX descriptors
+ // instead of using ps(1).
+ return nil, psgoErr
+ }
+
+ // Trick: filter the ps command from the output instead of
+ // checking/requiring PIDs in the output.
+ filtered := []string{}
+ cmd := strings.Join(descriptors, " ")
+ for _, line := range output {
+ if !strings.Contains(line, cmd) {
+ filtered = append(filtered, line)
+ }
+ }
+
+ return filtered, nil
+}
+
+func (r *LocalRuntime) execPS(c *libpod.Container, args []string) ([]string, error) {
+ rPipe, wPipe, err := os.Pipe()
+ if err != nil {
+ return nil, err
+ }
+ defer wPipe.Close()
+ defer rPipe.Close()
+
+ streams := new(libpod.AttachStreams)
+ streams.OutputStream = wPipe
+ streams.ErrorStream = wPipe
+ streams.InputStream = os.Stdin
+ streams.AttachOutput = true
+ streams.AttachError = true
+ streams.AttachInput = true
+
+ psOutput := []string{}
+ go func() {
+ scanner := bufio.NewScanner(rPipe)
+ for scanner.Scan() {
+ psOutput = append(psOutput, scanner.Text())
+ }
+ }()
+
+ cmd := append([]string{"ps"}, args...)
+ if err := c.Exec(false, false, []string{}, cmd, "", "", streams, 0); err != nil {
+ return nil, err
+ }
+
+ return psOutput, nil
}
// Prune removes stopped containers
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 14b0c2b55..2a52e5129 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -10,10 +10,12 @@ import (
"github.com/BurntSushi/toml"
"github.com/containers/image/types"
+ "github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/storage"
"github.com/containers/storage/pkg/idtools"
"github.com/opencontainers/image-spec/specs-go/v1"
"github.com/pkg/errors"
+ "github.com/spf13/pflag"
"golang.org/x/crypto/ssh/terminal"
)
@@ -252,3 +254,32 @@ func ParseInputTime(inputTime string) (time.Time, error) {
}
return time.Now().Add(-duration), nil
}
+
+// GetGlobalOpts checks all global flags and generates the command string
+func GetGlobalOpts(c *cliconfig.RunlabelValues) string {
+ globalFlags := map[string]bool{
+ "cgroup-manager": true, "cni-config-dir": true, "conmon": true, "default-mounts-file": true,
+ "hooks-dir": true, "namespace": true, "root": true, "runroot": true,
+ "runtime": true, "storage-driver": true, "storage-opt": true, "syslog": true,
+ "trace": true, "network-cmd-path": true, "config": true, "cpu-profile": true,
+ "log-level": true, "tmpdir": true}
+ const stringSliceType string = "stringSlice"
+
+ var optsCommand []string
+ c.PodmanCommand.Command.Flags().VisitAll(func(f *pflag.Flag) {
+ if !f.Changed {
+ return
+ }
+ if _, exist := globalFlags[f.Name]; exist {
+ if f.Value.Type() == stringSliceType {
+ flagValue := strings.TrimSuffix(strings.TrimPrefix(f.Value.String(), "["), "]")
+ for _, value := range strings.Split(flagValue, ",") {
+ optsCommand = append(optsCommand, fmt.Sprintf("--%s %s", f.Name, value))
+ }
+ } else {
+ optsCommand = append(optsCommand, fmt.Sprintf("--%s %s", f.Name, f.Value.String()))
+ }
+ }
+ })
+ return strings.Join(optsCommand, " ")
+}
diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go
index cecddf6b3..20f82a1c6 100644
--- a/pkg/varlinkapi/images.go
+++ b/pkg/varlinkapi/images.go
@@ -728,7 +728,7 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman.
return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label))
}
- cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs)
+ cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "")
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
diff --git a/test/e2e/pod_top_test.go b/test/e2e/pod_top_test.go
index 964ee075f..420e4aca9 100644
--- a/test/e2e/pod_top_test.go
+++ b/test/e2e/pod_top_test.go
@@ -93,7 +93,11 @@ var _ = Describe("Podman top", func() {
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(Equal(0))
- result := podmanTest.Podman([]string{"pod", "top", podid, "invalid"})
+ // We need to pass -eo to force executing ps in the Alpine container.
+ // Alpines stripped down ps(1) is accepting any kind of weird input in
+ // contrast to others, such that a `ps invalid` will silently ignore
+ // the wrong input and still print the -ef output instead.
+ result := podmanTest.Podman([]string{"pod", "top", podid, "-eo", "invalid"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(125))
})
diff --git a/test/e2e/runlabel_test.go b/test/e2e/runlabel_test.go
index b1d057bfd..f52a2b8fc 100644
--- a/test/e2e/runlabel_test.go
+++ b/test/e2e/runlabel_test.go
@@ -18,6 +18,11 @@ var LsDockerfile = `
FROM alpine:latest
LABEL RUN ls -la`
+var GlobalDockerfile = `
+FROM alpine:latest
+LABEL RUN echo \$GLOBAL_OPTS
+`
+
var _ = Describe("podman container runlabel", func() {
var (
tempdir string
@@ -78,4 +83,18 @@ var _ = Describe("podman container runlabel", func() {
Expect(result.ExitCode()).ToNot(Equal(0))
})
+
+ It("podman container runlabel global options", func() {
+ image := "podman-global-test:ls"
+ podmanTest.BuildImage(GlobalDockerfile, image, "false")
+ result := podmanTest.Podman([]string{"--syslog", "--log-level", "debug", "container", "runlabel", "RUN", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+
+ Expect(result.OutputToString()).To(ContainSubstring("--syslog true"))
+ Expect(result.OutputToString()).To(ContainSubstring("--log-level debug"))
+ result = podmanTest.Podman([]string{"rmi", image})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ })
})
diff --git a/test/e2e/top_test.go b/test/e2e/top_test.go
index 2d3a5629c..4c2cdb7b5 100644
--- a/test/e2e/top_test.go
+++ b/test/e2e/top_test.go
@@ -87,13 +87,39 @@ var _ = Describe("Podman top", func() {
Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
})
+ It("podman top with ps(1) options", func() {
+ session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"top", session.OutputToString(), "aux"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
+ })
+
+ It("podman top with comma-separated options", func() {
+ session := podmanTest.Podman([]string{"run", "-d", ALPINE, "top", "-d", "2"})
+ session.WaitWithDefaultTimeout()
+ Expect(session.ExitCode()).To(Equal(0))
+
+ result := podmanTest.Podman([]string{"top", session.OutputToString(), "user,pid,comm"})
+ result.WaitWithDefaultTimeout()
+ Expect(result.ExitCode()).To(Equal(0))
+ Expect(len(result.OutputToStringArray())).To(BeNumerically(">", 1))
+ })
+
It("podman top on container invalid options", func() {
top := podmanTest.RunTopContainer("")
top.WaitWithDefaultTimeout()
Expect(top.ExitCode()).To(Equal(0))
cid := top.OutputToString()
- result := podmanTest.Podman([]string{"top", cid, "invalid"})
+ // We need to pass -eo to force executing ps in the Alpine container.
+ // Alpines stripped down ps(1) is accepting any kind of weird input in
+ // contrast to others, such that a `ps invalid` will silently ignore
+ // the wrong input and still print the -ef output instead.
+ result := podmanTest.Podman([]string{"top", cid, "-eo", "invalid"})
result.WaitWithDefaultTimeout()
Expect(result.ExitCode()).To(Equal(125))
})