diff options
Diffstat (limited to 'cmd/podman')
-rw-r--r-- | cmd/podman/common.go | 5 | ||||
-rw-r--r-- | cmd/podman/create.go | 131 | ||||
-rw-r--r-- | cmd/podman/create_cli.go | 43 | ||||
-rw-r--r-- | cmd/podman/create_cli_test.go | 12 | ||||
-rw-r--r-- | cmd/podman/parse.go | 15 | ||||
-rw-r--r-- | cmd/podman/ps.go | 144 | ||||
-rw-r--r-- | cmd/podman/rm.go | 7 | ||||
-rw-r--r-- | cmd/podman/run.go | 4 | ||||
-rw-r--r-- | cmd/podman/run_test.go | 54 | ||||
-rw-r--r-- | cmd/podman/spec.go | 211 | ||||
-rw-r--r-- | cmd/podman/stats.go | 49 |
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), } } |