aboutsummaryrefslogtreecommitdiff
path: root/cmd
diff options
context:
space:
mode:
Diffstat (limited to 'cmd')
-rw-r--r--cmd/kpod/common.go319
-rw-r--r--cmd/kpod/create.go343
-rw-r--r--cmd/kpod/create_cli.go52
-rw-r--r--cmd/kpod/main.go6
-rw-r--r--cmd/kpod/parse.go886
-rw-r--r--cmd/kpod/run.go103
-rw-r--r--cmd/kpod/spec.go490
-rw-r--r--cmd/kpod/user.go121
8 files changed, 2318 insertions, 2 deletions
diff --git a/cmd/kpod/common.go b/cmd/kpod/common.go
index 338aee8ea..24f20f7ed 100644
--- a/cmd/kpod/common.go
+++ b/cmd/kpod/common.go
@@ -50,7 +50,7 @@ func getRuntime(c *cli.Context) (*libpod.Runtime, error) {
options.GraphDriverName = config.Storage
options.GraphDriverOptions = config.StorageOptions
- return libpod.NewRuntime(libpod.WithStorageConfig(options))
+ return libpod.NewRuntime(libpod.WithStorageConfig(options), libpod.WithConmonPath(config.Conmon), libpod.WithOCIRuntime(config.Runtime))
}
func shutdownStores() {
@@ -83,7 +83,9 @@ func getConfig(c *cli.Context) (*libkpod.Config, error) {
if c.GlobalIsSet("runroot") {
config.RunRoot = c.GlobalString("runroot")
}
-
+ if c.GlobalIsSet("conmon") {
+ config.Conmon = c.GlobalString("conmon")
+ }
if c.GlobalIsSet("storage-driver") {
config.Storage = c.GlobalString("storage-driver")
}
@@ -134,3 +136,316 @@ func validateFlags(c *cli.Context, flags []cli.Flag) error {
}
return nil
}
+
+// Common flags shared between commands
+var createFlags = []cli.Flag{
+ cli.StringSliceFlag{
+ Name: "add-host",
+ Usage: "Add a custom host-to-IP mapping (host:ip) (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "attach, a",
+ Usage: "Attach to STDIN, STDOUT or STDERR (default [])",
+ },
+ cli.StringFlag{
+ Name: "blkio-weight",
+ Usage: "Block IO weight (relative weight) accepts a weight value between 10 and 1000.",
+ },
+ cli.StringSliceFlag{
+ Name: "blkio-weight-device",
+ Usage: "Block IO weight (relative device weight, format: `DEVICE_NAME:WEIGHT`)",
+ },
+ cli.StringSliceFlag{
+ Name: "cap-add",
+ Usage: "Add capabilities to the container",
+ },
+ cli.StringSliceFlag{
+ Name: "cap-drop",
+ Usage: "Drop capabilities from the container",
+ },
+ cli.StringFlag{
+ Name: "cgroup-parent",
+ Usage: "Optional parent cgroup for the container",
+ },
+ cli.StringFlag{
+ Name: "cidfile",
+ Usage: "Write the container ID to the file",
+ },
+ cli.Uint64Flag{
+ Name: "cpu-period",
+ Usage: "Limit the CPU CFS (Completely Fair Scheduler) period",
+ },
+ cli.Int64Flag{
+ Name: "cpu-quota",
+ Usage: "Limit the CPU CFS (Completely Fair Scheduler) quota",
+ },
+ cli.Uint64Flag{
+ Name: "cpu-rt-period",
+ Usage: "Limit the CPU real-time period in microseconds",
+ },
+ cli.Int64Flag{
+ Name: "cpu-rt-runtime",
+ Usage: "Limit the CPU real-time runtime in microseconds",
+ },
+ cli.Uint64Flag{
+ Name: "cpu-shares",
+ Usage: "CPU shares (relative weight)",
+ },
+ cli.StringFlag{
+ Name: "cpus",
+ Usage: "Number of CPUs. The default is 0.000 which means no limit",
+ },
+ cli.StringFlag{
+ Name: "cpuset-cpus",
+ Usage: "CPUs in which to allow execution (0-3, 0,1)",
+ },
+ cli.StringFlag{
+ Name: "cpuset-mems",
+ Usage: "Memory nodes (MEMs) in which to allow execution (0-3, 0,1). Only effective on NUMA systems.",
+ },
+ cli.BoolFlag{
+ Name: "detach, d",
+ Usage: "Run container in background and print container ID",
+ },
+ cli.StringFlag{
+ Name: "detach-keys",
+ Usage: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-<value>` where `<value>` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`",
+ },
+ cli.StringSliceFlag{
+ Name: "device",
+ Usage: "Add a host device to the container (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "device-read-bps",
+ Usage: "Limit read rate (bytes per second) from a device (e.g. --device-read-bps=/dev/sda:1mb)",
+ },
+ cli.StringSliceFlag{
+ Name: "device-read-iops",
+ Usage: "Limit read rate (IO per second) from a device (e.g. --device-read-iops=/dev/sda:1000)",
+ },
+ cli.StringSliceFlag{
+ Name: "device-write-bps",
+ Usage: "Limit write rate (bytes per second) to a device (e.g. --device-write-bps=/dev/sda:1mb)",
+ },
+ cli.StringSliceFlag{
+ Name: "device-write-iops",
+ Usage: "Limit write rate (IO per second) to a device (e.g. --device-write-iops=/dev/sda:1000)",
+ },
+ cli.StringSliceFlag{
+ Name: "dns",
+ Usage: "Set custom DNS servers",
+ },
+ cli.StringSliceFlag{
+ Name: "dns-opt",
+ Usage: "Set custom DNS options",
+ },
+ cli.StringSliceFlag{
+ Name: "dns-search",
+ Usage: "Set custom DNS search domains",
+ },
+ cli.StringFlag{
+ Name: "entrypoint",
+ Usage: "Overwrite the default ENTRYPOINT of the image",
+ },
+ cli.StringSliceFlag{
+ Name: "env, e",
+ Usage: "Set environment variables in container",
+ },
+ cli.StringSliceFlag{
+ Name: "env-file",
+ Usage: "Read in a file of environment variables",
+ },
+ cli.StringSliceFlag{
+ Name: "expose",
+ Usage: "Expose a port or a range of ports (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "group-add",
+ Usage: "Add additional groups to join (default [])",
+ },
+ cli.StringFlag{
+ Name: "hostname",
+ Usage: "Set container hostname",
+ },
+ cli.BoolFlag{
+ Name: "interactive, i",
+ Usage: "Keep STDIN open even if not attached",
+ },
+ cli.StringFlag{
+ Name: "ip",
+ Usage: "Container IPv4 address (e.g. 172.23.0.9)",
+ },
+ cli.StringFlag{
+ Name: "ip6",
+ Usage: "Container IPv6 address (e.g. 2001:db8::1b99)",
+ },
+ cli.StringFlag{
+ Name: "ipc",
+ Usage: "IPC Namespace to use",
+ },
+ cli.StringFlag{
+ Name: "kernel-memory",
+ Usage: "Kernel memory limit (format: `<number>[<unit>]`, where unit = b, k, m or g)",
+ },
+ cli.StringSliceFlag{
+ Name: "label",
+ Usage: "Set metadata on container (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "label-file",
+ Usage: "Read in a line delimited file of labels (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "link-local-ip",
+ Usage: "Container IPv4/IPv6 link-local addresses (default [])",
+ },
+ cli.StringFlag{
+ Name: "log-driver",
+ Usage: "Logging driver for the container",
+ },
+ cli.StringSliceFlag{
+ Name: "log-opt",
+ Usage: "Logging driver options (default [])",
+ },
+ cli.StringFlag{
+ Name: "mac-address",
+ Usage: "Container MAC address (e.g. 92:d0:c6:0a:29:33)",
+ },
+ cli.StringFlag{
+ Name: "memory, m",
+ Usage: "Memory limit (format: <number>[<unit>], where unit = b, k, m or g)",
+ },
+ cli.StringFlag{
+ Name: "memory-reservation",
+ Usage: "Memory soft limit (format: <number>[<unit>], where unit = b, k, m or g)",
+ },
+ cli.StringFlag{
+ Name: "memory-swap",
+ Usage: "Swap limit equal to memory plus swap: '-1' to enable unlimited swap",
+ },
+ cli.Int64Flag{
+ Name: "memory-swappiness",
+ Usage: "Tune container memory swappiness (0 to 100) (default -1)",
+ },
+ cli.StringFlag{
+ Name: "name",
+ Usage: "Assign a name to the container",
+ },
+ cli.StringFlag{
+ Name: "net",
+ Usage: "Setup the network namespace",
+ },
+ cli.StringFlag{
+ Name: "network",
+ Usage: "Connect a container to a network (default 'default')",
+ },
+ cli.StringSliceFlag{
+ Name: "network-alias",
+ Usage: "Add network-scoped alias for the container (default [])",
+ },
+ cli.BoolFlag{
+ Name: "oom-kill-disable",
+ Usage: "Disable OOM Killer",
+ },
+ cli.StringFlag{
+ Name: "oom-score-adj",
+ Usage: "Tune the host's OOM preferences (-1000 to 1000)",
+ },
+ cli.StringFlag{
+ Name: "pid",
+ Usage: "PID Namespace to use",
+ },
+ cli.Int64Flag{
+ Name: "pids-limit",
+ Usage: "Tune container pids limit (set -1 for unlimited)",
+ },
+ cli.StringFlag{
+ Name: "pod",
+ Usage: "Run container in an existing pod",
+ },
+ cli.BoolFlag{
+ Name: "privileged",
+ Usage: "Give extended privileges to container",
+ },
+ cli.StringSliceFlag{
+ Name: "publish, p",
+ Usage: "Publish a container's port, or a range of ports, to the host (default [])",
+ },
+ cli.BoolFlag{
+ Name: "publish-all, P",
+ Usage: "Publish all exposed ports to random ports on the host interface",
+ },
+ cli.BoolFlag{
+ Name: "read-only",
+ Usage: "Make containers root filesystem read-only",
+ },
+ cli.BoolFlag{
+ Name: "rm",
+ Usage: "Remove container (and pod if created) after exit",
+ },
+ cli.StringSliceFlag{
+ Name: "security-opt",
+ Usage: "Security Options (default [])",
+ },
+ cli.StringFlag{
+ Name: "shm-size",
+ Usage: "Size of `/dev/shm`. The format is `<number><unit>`. default is 64 MB",
+ },
+ cli.BoolFlag{
+ Name: "sig-proxy",
+ Usage: "Proxy received signals to the process (default true)",
+ },
+ cli.StringFlag{
+ Name: "stop-signal",
+ Usage: "Signal to stop a container. Default is SIGTERM",
+ },
+ cli.IntFlag{
+ Name: "stop-timeout",
+ Usage: "Timeout (in seconds) to stop a container. Default is 10",
+ },
+ cli.StringSliceFlag{
+ Name: "storage-opt",
+ Usage: "Storage driver options per container (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "sysctl",
+ Usage: "Sysctl options (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "tmpfs",
+ Usage: "Mount a temporary filesystem (`tmpfs`) into a container (default [])",
+ },
+ cli.BoolFlag{
+ Name: "tty, t",
+ Usage: "Allocate a pseudo-TTY for container",
+ },
+ cli.StringSliceFlag{
+ Name: "ulimit",
+ Usage: "Ulimit options (default [])",
+ },
+ cli.StringFlag{
+ Name: "user, u",
+ Usage: "Username or UID (format: <name|uid>[:<group|gid>])",
+ },
+ cli.StringFlag{
+ Name: "userns",
+ Usage: "User namespace to use",
+ },
+ cli.StringFlag{
+ Name: "uts",
+ Usage: "UTS namespace to use",
+ },
+ cli.StringSliceFlag{
+ Name: "volume, v",
+ Usage: "Bind mount a volume into the container (default [])",
+ },
+ cli.StringSliceFlag{
+ Name: "volumes-from",
+ Usage: "Mount volumes from the specified container(s) (default [])",
+ },
+ cli.StringFlag{
+ Name: "workdir, w",
+ Usage: "Working `directory inside the container",
+ Value: "/",
+ },
+}
diff --git a/cmd/kpod/create.go b/cmd/kpod/create.go
new file mode 100644
index 000000000..2e79c883e
--- /dev/null
+++ b/cmd/kpod/create.go
@@ -0,0 +1,343 @@
+package main
+
+import (
+ "fmt"
+ "strconv"
+
+ "github.com/docker/go-units"
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+type mountType string
+
+// Type constants
+const (
+ // TypeBind is the type for mounting host dir
+ TypeBind mountType = "bind"
+ // TypeVolume is the type for remote storage volumes
+ // TypeVolume mountType = "volume" // re-enable upon use
+ // TypeTmpfs is the type for mounting tmpfs
+ TypeTmpfs mountType = "tmpfs"
+)
+
+var (
+ defaultEnvVariables = []string{"PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", "TERM=xterm"}
+)
+
+type createResourceConfig struct {
+ blkioDevice []string // blkio-weight-device
+ blkioWeight uint16 // blkio-weight
+ cpuPeriod uint64 // cpu-period
+ cpuQuota int64 // cpu-quota
+ cpuRtPeriod uint64 // cpu-rt-period
+ cpuRtRuntime int64 // cpu-rt-runtime
+ cpuShares uint64 // cpu-shares
+ cpus string // cpus
+ cpusetCpus string
+ cpusetMems string // cpuset-mems
+ deviceReadBps []string // device-read-bps
+ deviceReadIops []string // device-read-iops
+ deviceWriteBps []string // device-write-bps
+ deviceWriteIops []string // device-write-iops
+ disableOomKiller bool // oom-kill-disable
+ kernelMemory int64 // kernel-memory
+ memory int64 //memory
+ memoryReservation int64 // memory-reservation
+ memorySwap int64 //memory-swap
+ memorySwapiness uint64 // memory-swappiness
+ oomScoreAdj int //oom-score-adj
+ pidsLimit int64 // pids-limit
+ shmSize string
+ ulimit []string //ulimit
+}
+
+type createConfig struct {
+ args []string
+ capAdd []string // cap-add
+ capDrop []string // cap-drop
+ cidFile string
+ cgroupParent string // cgroup-parent
+ command []string
+ detach bool // detach
+ devices []*pb.Device // device
+ dnsOpt []string //dns-opt
+ dnsSearch []string //dns-search
+ dnsServers []string //dns
+ entrypoint string //entrypoint
+ env []string //env
+ expose []string //expose
+ groupAdd []uint32 // group-add
+ hostname string //hostname
+ image string
+ interactive bool //interactive
+ ip6Address string //ipv6
+ ipAddress string //ip
+ labels map[string]string //label
+ linkLocalIP []string // link-local-ip
+ logDriver string // log-driver
+ logDriverOpt []string // log-opt
+ macAddress string //mac-address
+ name string //name
+ network string //network
+ networkAlias []string //network-alias
+ nsIPC string // ipc
+ nsNet string //net
+ nsPID string //pid
+ nsUser string
+ pod string //pod
+ privileged bool //privileged
+ publish []string //publish
+ publishAll bool //publish-all
+ readOnlyRootfs bool //read-only
+ resources createResourceConfig
+ rm bool //rm
+ securityOpts []string //security-opt
+ sigProxy bool //sig-proxy
+ stopSignal string // stop-signal
+ stopTimeout int64 // stop-timeout
+ storageOpts []string //storage-opt
+ sysctl map[string]string //sysctl
+ tmpfs []string // tmpfs
+ tty bool //tty
+ user uint32 //user
+ group uint32 // group
+ volumes []string //volume
+ volumesFrom []string //volumes-from
+ workDir string //workdir
+}
+
+var createDescription = "Creates a new container from the given image or" +
+ " storage and prepares it for running the specified command. The" +
+ " container ID is then printed to stdout. You can then start it at" +
+ " any time with the kpod start <container_id> command. The container" +
+ " will be created with the initial state 'created'."
+
+var createCommand = cli.Command{
+ Name: "create",
+ Usage: "create but do not start a container",
+ Description: createDescription,
+ Flags: createFlags,
+ Action: createCmd,
+ ArgsUsage: "IMAGE [COMMAND [ARG...]]",
+}
+
+func createCmd(c *cli.Context) error {
+ // TODO should allow user to create based off a directory on the host not just image
+ // Need CLI support for this
+ if err := validateFlags(c, createFlags); err != nil {
+ return err
+ }
+
+ runtime, err := getRuntime(c)
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+
+ createConfig, err := parseCreateOpts(c, runtime)
+ if err != nil {
+ return err
+ }
+
+ // Deal with the image after all the args have been checked
+ createImage := runtime.NewImage(createConfig.image)
+ if !createImage.HasImageLocal() {
+ // The image wasnt found by the user input'd name or its fqname
+ // Pull the image
+ fmt.Printf("Trying to pull %s...", createImage.PullName)
+ createImage.Pull()
+ }
+
+ runtimeSpec, err := createConfigToOCISpec(createConfig)
+ if err != nil {
+ return err
+ }
+ defer runtime.Shutdown(false)
+ imageName, err := createImage.GetFQName()
+ if err != nil {
+ return err
+ }
+ imageID, err := createImage.GetImageID()
+ if err != nil {
+ return err
+ }
+ options, err := createConfig.GetContainerCreateOptions(c)
+ if err != nil {
+ return errors.Wrapf(err, "unable to parse new container options")
+ }
+ // Gather up the options for NewContainer which consist of With... funcs
+ options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false))
+ ctr, err := runtime.NewContainer(runtimeSpec, options...)
+ if err != nil {
+ return err
+ }
+
+ if c.String("cidfile") != "" {
+ libpod.WriteFile(ctr.ID(), c.String("cidfile"))
+ } else {
+ fmt.Printf("%s\n", ctr.ID())
+ }
+
+ return nil
+}
+
+// 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) (*createConfig, error) {
+ var command []string
+ var memoryLimit, memoryReservation, memorySwap, memoryKernel int64
+ var blkioWeight uint16
+ var uid, gid uint32
+
+ image := c.Args()[0]
+
+ if len(c.Args()) < 1 {
+ return nil, errors.Errorf("image name or ID is required")
+ }
+ if len(c.Args()) > 1 {
+ command = c.Args()[1:]
+ }
+
+ // LABEL VARIABLES
+ labels, err := getAllLabels(c)
+ if err != nil {
+ return &createConfig{}, errors.Wrapf(err, "unable to process labels")
+ }
+ // ENVIRONMENT VARIABLES
+ // TODO where should env variables be verified to be x=y format
+ env, err := getAllEnvironmentVariables(c)
+ if err != nil {
+ return &createConfig{}, errors.Wrapf(err, "unable to process environment variables")
+ }
+
+ sysctl, err := convertStringSliceToMap(c.StringSlice("sysctl"), "=")
+ if err != nil {
+ return &createConfig{}, errors.Wrapf(err, "sysctl values must be in the form of KEY=VALUE")
+ }
+
+ groupAdd, err := stringSlicetoUint32Slice(c.StringSlice("group-add"))
+ if err != nil {
+ return &createConfig{}, errors.Wrapf(err, "invalid value for groups provided")
+ }
+
+ if c.String("user") != "" {
+ // TODO
+ // We need to mount the imagefs and get the uid/gid
+ // For now, user zeros
+ uid = 0
+ gid = 0
+ }
+
+ if c.String("memory") != "" {
+ memoryLimit, err = units.RAMInBytes(c.String("memory"))
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for memory")
+ }
+ }
+ if c.String("memory-reservation") != "" {
+ memoryReservation, err = units.RAMInBytes(c.String("memory-reservation"))
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for memory-reservation")
+ }
+ }
+ if c.String("memory-swap") != "" {
+ memorySwap, err = units.RAMInBytes(c.String("memory-swap"))
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for memory-swap")
+ }
+ }
+ if c.String("kernel-memory") != "" {
+ memoryKernel, err = units.RAMInBytes(c.String("kernel-memory"))
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for kernel-memory")
+ }
+ }
+ if c.String("blkio-weight") != "" {
+ u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16)
+ if err != nil {
+ return nil, errors.Wrapf(err, "invalid value for blkio-weight")
+ }
+ blkioWeight = uint16(u)
+ }
+
+ config := &createConfig{
+ capAdd: c.StringSlice("cap-add"),
+ capDrop: c.StringSlice("cap-drop"),
+ cgroupParent: c.String("cgroup-parent"),
+ command: command,
+ detach: c.Bool("detach"),
+ dnsOpt: c.StringSlice("dns-opt"),
+ dnsSearch: c.StringSlice("dns-search"),
+ dnsServers: c.StringSlice("dns"),
+ entrypoint: c.String("entrypoint"),
+ env: env,
+ expose: c.StringSlice("env"),
+ groupAdd: groupAdd,
+ hostname: c.String("hostname"),
+ image: image,
+ interactive: c.Bool("interactive"),
+ ip6Address: c.String("ipv6"),
+ ipAddress: c.String("ip"),
+ labels: labels,
+ linkLocalIP: c.StringSlice("link-local-ip"),
+ logDriver: c.String("log-driver"),
+ logDriverOpt: c.StringSlice("log-opt"),
+ macAddress: c.String("mac-address"),
+ name: c.String("name"),
+ network: c.String("network"),
+ networkAlias: c.StringSlice("network-alias"),
+ nsIPC: c.String("ipc"),
+ nsNet: c.String("net"),
+ nsPID: c.String("pid"),
+ pod: c.String("pod"),
+ privileged: c.Bool("privileged"),
+ publish: c.StringSlice("publish"),
+ publishAll: c.Bool("publish-all"),
+ readOnlyRootfs: c.Bool("read-only"),
+ resources: createResourceConfig{
+ blkioWeight: blkioWeight,
+ blkioDevice: c.StringSlice("blkio-weight-device"),
+ cpuShares: c.Uint64("cpu-shares"),
+ cpuPeriod: c.Uint64("cpu-period"),
+ cpusetCpus: c.String("cpu-period"),
+ 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"),
+ deviceReadBps: c.StringSlice("device-read-bps"),
+ deviceReadIops: c.StringSlice("device-read-iops"),
+ deviceWriteBps: c.StringSlice("device-write-bps"),
+ deviceWriteIops: c.StringSlice("device-write-iops"),
+ disableOomKiller: c.Bool("oom-kill-disable"),
+ shmSize: c.String("shm-size"),
+ memory: memoryLimit,
+ memoryReservation: memoryReservation,
+ memorySwap: memorySwap,
+ memorySwapiness: c.Uint64("memory-swapiness"),
+ kernelMemory: memoryKernel,
+ oomScoreAdj: c.Int("oom-score-adj"),
+
+ pidsLimit: c.Int64("pids-limit"),
+ ulimit: c.StringSlice("ulimit"),
+ },
+ rm: c.Bool("rm"),
+ securityOpts: c.StringSlice("security-opt"),
+ sigProxy: c.Bool("sig-proxy"),
+ stopSignal: c.String("stop-signal"),
+ stopTimeout: c.Int64("stop-timeout"),
+ storageOpts: c.StringSlice("storage-opt"),
+ sysctl: sysctl,
+ tmpfs: c.StringSlice("tmpfs"),
+ tty: c.Bool("tty"),
+ user: uid,
+ group: gid,
+ volumes: c.StringSlice("volume"),
+ volumesFrom: c.StringSlice("volumes-from"),
+ workDir: c.String("workdir"),
+ }
+
+ return config, nil
+}
diff --git a/cmd/kpod/create_cli.go b/cmd/kpod/create_cli.go
new file mode 100644
index 000000000..996155ba0
--- /dev/null
+++ b/cmd/kpod/create_cli.go
@@ -0,0 +1,52 @@
+package main
+
+import (
+ "strings"
+
+ "github.com/pkg/errors"
+ "github.com/urfave/cli"
+)
+
+func getAllLabels(cli *cli.Context) (map[string]string, error) {
+ var labelValues []string
+ labels := make(map[string]string)
+ labelValues, labelErr := readKVStrings(cli.StringSlice("label-file"), cli.StringSlice("label"))
+ if labelErr != nil {
+ return labels, errors.Wrapf(labelErr, "unable to process labels from --label and label-file")
+ }
+ // Process KEY=VALUE stringslice in string map for WithLabels func
+ if len(labelValues) > 0 {
+ for _, i := range labelValues {
+ spliti := strings.Split(i, "=")
+ if len(spliti) > 1 {
+ return labels, errors.Errorf("labels must be in KEY=VALUE format: %s is invalid", i)
+ }
+ labels[spliti[0]] = spliti[1]
+ }
+ }
+ return labels, nil
+}
+
+func getAllEnvironmentVariables(cli *cli.Context) ([]string, error) {
+ env, err := readKVStrings(cli.StringSlice("env-file"), cli.StringSlice("env"))
+ if err != nil {
+ return []string{}, errors.Wrapf(err, "unable to process variables from --env and --env-file")
+ }
+ // Add default environment variables if nothing defined
+ if len(env) == 0 {
+ env = append(env, defaultEnvVariables...)
+ }
+ return env, nil
+}
+
+func convertStringSliceToMap(strSlice []string, delimiter 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)
+ }
+ sysctl[values[0]] = values[1]
+ }
+ return sysctl, nil
+}
diff --git a/cmd/kpod/main.go b/cmd/kpod/main.go
index 7745fbf3d..b8a7b0cb5 100644
--- a/cmd/kpod/main.go
+++ b/cmd/kpod/main.go
@@ -31,6 +31,7 @@ func main() {
app.Version = v
app.Commands = []cli.Command{
+ createCommand,
diffCommand,
exportCommand,
historyCommand,
@@ -50,6 +51,7 @@ func main() {
renameCommand,
rmCommand,
rmiCommand,
+ runCommand,
saveCommand,
statsCommand,
stopCommand,
@@ -93,6 +95,10 @@ func main() {
Usage: "path of a config file detailing container server configuration options",
},
cli.StringFlag{
+ Name: "conmon",
+ Usage: "path of the conmon binary",
+ },
+ cli.StringFlag{
Name: "log-level",
Usage: "log messages above specified level: debug, info, warn, error (default), fatal or panic",
Value: "error",
diff --git a/cmd/kpod/parse.go b/cmd/kpod/parse.go
new file mode 100644
index 000000000..e3143a793
--- /dev/null
+++ b/cmd/kpod/parse.go
@@ -0,0 +1,886 @@
+//nolint
+// most of these validate and parse functions have been taken from projectatomic/docker
+// and modified for cri-o
+package main
+
+import (
+ "bufio"
+ "bytes"
+ "encoding/json"
+ "fmt"
+ "io/ioutil"
+ "net"
+ "os"
+ "os/user"
+ "path"
+ "regexp"
+ "strconv"
+ "strings"
+
+ units "github.com/docker/go-units"
+ specs "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime"
+)
+
+// Note: for flags that are in the form <number><unit>, use the RAMInBytes function
+// from the units package in docker/go-units/size.go
+
+var (
+ whiteSpaces = " \t"
+ alphaRegexp = regexp.MustCompile(`[a-zA-Z]`)
+ domainRegexp = regexp.MustCompile(`^(:?(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9]))(:?\.(:?[a-zA-Z0-9]|(:?[a-zA-Z0-9][a-zA-Z0-9\-]*[a-zA-Z0-9])))*)\.?\s*$`)
+)
+
+// validateExtraHost validates that the specified string is a valid extrahost and returns it.
+// ExtraHost is in the form of name:ip where the ip has to be a valid ip (ipv4 or ipv6).
+// for add-host flag
+func validateExtraHost(val string) (string, error) { //nolint
+ // allow for IPv6 addresses in extra hosts by only splitting on first ":"
+ arr := strings.SplitN(val, ":", 2)
+ if len(arr) != 2 || len(arr[0]) == 0 {
+ return "", fmt.Errorf("bad format for add-host: %q", val)
+ }
+ if _, err := validateIPAddress(arr[1]); err != nil {
+ return "", fmt.Errorf("invalid IP address in add-host: %q", arr[1])
+ }
+ return val, nil
+}
+
+// validateIPAddress validates an Ip address.
+// for dns, ip, and ip6 flags also
+func validateIPAddress(val string) (string, error) {
+ var ip = net.ParseIP(strings.TrimSpace(val))
+ if ip != nil {
+ return ip.String(), nil
+ }
+ 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)
+}
+
+// weightDevice is a structure that holds device:weight pair
+type weightDevice struct {
+ path string
+ weight uint16
+}
+
+func (w *weightDevice) String() string {
+ return fmt.Sprintf("%s:%d", w.path, w.weight)
+}
+
+// validateweightDevice validates that the specified string has a valid device-weight format
+// for blkio-weight-device flag
+func validateweightDevice(val string) (*weightDevice, error) {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ weight, err := strconv.ParseUint(split[1], 10, 0)
+ if err != nil {
+ return nil, fmt.Errorf("invalid weight for device: %s", val)
+ }
+ if weight > 0 && (weight < 10 || weight > 1000) {
+ return nil, fmt.Errorf("invalid weight for device: %s", val)
+ }
+
+ return &weightDevice{
+ path: split[0],
+ weight: uint16(weight),
+ }, nil
+}
+
+// parseDevice parses a device mapping string to a container.DeviceMapping struct
+// for device flag
+func parseDevice(device string) (*pb.Device, error) { //nolint
+ _, err := validateDevice(device)
+ if err != nil {
+ return nil, errors.Wrapf(err, "device string not valid %q", device)
+ }
+
+ src := ""
+ dst := ""
+ permissions := "rwm"
+ arr := strings.Split(device, ":")
+ switch len(arr) {
+ case 3:
+ permissions = arr[2]
+ fallthrough
+ case 2:
+ if validDeviceMode(arr[1]) {
+ permissions = arr[1]
+ } else {
+ dst = arr[1]
+ }
+ fallthrough
+ case 1:
+ src = arr[0]
+ default:
+ return nil, fmt.Errorf("invalid device specification: %s", device)
+ }
+
+ if dst == "" {
+ dst = src
+ }
+
+ deviceMapping := &pb.Device{
+ ContainerPath: dst,
+ HostPath: src,
+ Permissions: permissions,
+ }
+ return deviceMapping, nil
+}
+
+// validDeviceMode checks if the mode for device is valid or not.
+// Valid mode is a composition of r (read), w (write), and m (mknod).
+func validDeviceMode(mode string) bool {
+ var legalDeviceMode = map[rune]bool{
+ 'r': true,
+ 'w': true,
+ 'm': true,
+ }
+ if mode == "" {
+ return false
+ }
+ for _, c := range mode {
+ if !legalDeviceMode[c] {
+ return false
+ }
+ legalDeviceMode[c] = false
+ }
+ return true
+}
+
+// validateDevice validates a path for devices
+// It will make sure 'val' is in the form:
+// [host-dir:]container-path[:mode]
+// It also validates the device mode.
+func validateDevice(val string) (string, error) {
+ return validatePath(val, validDeviceMode)
+}
+
+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
+}
+
+// throttleDevice is a structure that holds device:rate_per_second pair
+type throttleDevice struct {
+ path string
+ rate uint64
+}
+
+func (t *throttleDevice) String() string {
+ return fmt.Sprintf("%s:%d", t.path, t.rate)
+}
+
+// validateBpsDevice validates that the specified string has a valid device-rate format
+// for device-read-bps and device-write-bps flags
+func validateBpsDevice(val string) (*throttleDevice, error) {
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ rate, err := units.RAMInBytes(split[1])
+ if err != nil {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+ }
+ if rate < 0 {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val)
+ }
+
+ return &throttleDevice{
+ path: split[0],
+ rate: uint64(rate),
+ }, nil
+}
+
+// validateIOpsDevice validates that the specified string has a valid device-rate format
+// for device-write-iops and device-read-iops flags
+func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint
+ split := strings.SplitN(val, ":", 2)
+ if len(split) != 2 {
+ return nil, fmt.Errorf("bad format: %s", val)
+ }
+ if !strings.HasPrefix(split[0], "/dev/") {
+ return nil, fmt.Errorf("bad format for device path: %s", val)
+ }
+ rate, err := strconv.ParseUint(split[1], 10, 64)
+ if err != nil {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
+ }
+ if rate < 0 {
+ return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val)
+ }
+
+ return &throttleDevice{
+ path: split[0],
+ rate: uint64(rate),
+ }, 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)
+ }
+ ns := domainRegexp.FindSubmatch([]byte(val))
+ if len(ns) > 0 && len(ns[1]) < 255 {
+ return string(ns[1]), nil
+ }
+ 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
+func readKVStrings(files []string, override []string) ([]string, error) {
+ envVariables := []string{}
+ for _, ef := range files {
+ parsedVars, err := parseEnvFile(ef)
+ if err != nil {
+ return nil, err
+ }
+ envVariables = append(envVariables, parsedVars...)
+ }
+ // parse the '-e' and '--env' after, to allow override
+ envVariables = append(envVariables, override...)
+
+ return envVariables, nil
+}
+
+// parseEnvFile reads a file with environment variables enumerated by lines
+func parseEnvFile(filename string) ([]string, error) {
+ fh, err := os.Open(filename)
+ if err != nil {
+ return []string{}, err
+ }
+ defer fh.Close()
+
+ lines := []string{}
+ scanner := bufio.NewScanner(fh)
+ for scanner.Scan() {
+ // trim the line from all leading whitespace first
+ line := strings.TrimLeft(scanner.Text(), whiteSpaces)
+ // line is not empty, and not starting with '#'
+ if len(line) > 0 && !strings.HasPrefix(line, "#") {
+ data := strings.SplitN(line, "=", 2)
+
+ // trim the front of a variable, but nothing else
+ variable := strings.TrimLeft(data[0], whiteSpaces)
+ if strings.ContainsAny(variable, whiteSpaces) {
+ return []string{}, errors.Errorf("variable %q has white spaces, poorly formatted environment", variable)
+ }
+
+ if len(data) > 1 {
+
+ // pass the value through, no trimming
+ lines = append(lines, fmt.Sprintf("%s=%s", variable, data[1]))
+ } else {
+ // if only a pass-through variable is given, clean it up.
+ lines = append(lines, fmt.Sprintf("%s=%s", strings.TrimSpace(line), os.Getenv(line)))
+ }
+ }
+ }
+ return lines, scanner.Err()
+}
+
+// NsIpc represents the container ipc stack.
+// for ipc flag
+type NsIpc string
+
+// IsPrivate indicates whether the container uses its private ipc stack.
+func (n NsIpc) IsPrivate() bool {
+ return !(n.IsHost() || n.IsContainer())
+}
+
+// IsHost indicates whether the container uses the host's ipc stack.
+func (n NsIpc) IsHost() bool {
+ return n == "host"
+}
+
+// IsContainer indicates whether the container uses a container's ipc stack.
+func (n NsIpc) IsContainer() bool {
+ parts := strings.SplitN(string(n), ":", 2)
+ return len(parts) > 1 && parts[0] == "container"
+}
+
+// Valid indicates whether the ipc stack is valid.
+func (n NsIpc) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host":
+ case "container":
+ if len(parts) != 2 || parts[1] == "" {
+ return false
+ }
+ default:
+ return false
+ }
+ return true
+}
+
+// Container returns the name of the container ipc stack is going to be used.
+func (n NsIpc) Container() string {
+ parts := strings.SplitN(string(n), ":", 2)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ return ""
+}
+
+// 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
+}
+
+// validateLink validates that the specified string has a valid link format (containerName:alias).
+func validateLink(val string) (string, error) { //nolint
+ if _, _, err := parseLink(val); err != nil {
+ return val, err
+ }
+ return val, nil
+}
+
+// parseLink parses and validates the specified string as a link format (name:alias)
+func parseLink(val string) (string, string, error) {
+ if val == "" {
+ return "", "", fmt.Errorf("empty string specified for links")
+ }
+ arr := strings.Split(val, ":")
+ if len(arr) > 2 {
+ return "", "", fmt.Errorf("bad format for links: %s", val)
+ }
+ if len(arr) == 1 {
+ return val, val, nil
+ }
+ // This is kept because we can actually get a HostConfig with links
+ // from an already created container and the format is not `foo:bar`
+ // but `/foo:/c1/bar`
+ if strings.HasPrefix(arr[0], "/") {
+ _, alias := path.Split(arr[1])
+ return arr[0][1:], alias, nil
+ }
+ return arr[0], arr[1], 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
+}
+
+// NsPid represents the pid namespace of the container.
+//for pid flag
+type NsPid string
+
+// IsPrivate indicates whether the container uses its own new pid namespace.
+func (n NsPid) IsPrivate() bool {
+ return !(n.IsHost() || n.IsContainer())
+}
+
+// IsHost indicates whether the container uses the host's pid namespace.
+func (n NsPid) IsHost() bool {
+ return n == "host"
+}
+
+// IsContainer indicates whether the container uses a container's pid namespace.
+func (n NsPid) IsContainer() bool {
+ parts := strings.SplitN(string(n), ":", 2)
+ return len(parts) > 1 && parts[0] == "container"
+}
+
+// Valid indicates whether the pid namespace is valid.
+func (n NsPid) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host":
+ case "container":
+ if len(parts) != 2 || parts[1] == "" {
+ return false
+ }
+ default:
+ return false
+ }
+ return true
+}
+
+// Container returns the name of the container whose pid namespace is going to be used.
+func (n NsPid) Container() string {
+ parts := strings.SplitN(string(n), ":", 2)
+ if len(parts) > 1 {
+ return parts[1]
+ }
+ return ""
+}
+
+// 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) ([]*pb.PortMapping, error) { //nolint
+ var portMappings []*pb.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) ([]*pb.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 := pb.Protocol_TCP
+ if strings.ToLower(proto) == "udp" {
+ protocol = pb.Protocol_UDP
+ }
+
+ var ports []*pb.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 := &pb.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
+}
+
+// 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
+}
+
+// parseUser parses the the uid and gid in the format <name|uid>[:<group|gid>]
+// for user flag
+// FIXME: Issue from https://github.com/projectatomic/buildah/issues/66
+func parseUser(rootdir, userspec string) (specs.User, error) { //nolint
+ var gid64 uint64
+ var gerr error = user.UnknownGroupError("error looking up group")
+
+ spec := strings.SplitN(userspec, ":", 2)
+ userspec = spec[0]
+ groupspec := ""
+ if userspec == "" {
+ return specs.User{}, nil
+ }
+ if len(spec) > 1 {
+ groupspec = spec[1]
+ }
+
+ uid64, uerr := strconv.ParseUint(userspec, 10, 32)
+ if uerr == nil && groupspec == "" {
+ // We parsed the user name as a number, and there's no group
+ // component, so we need to look up the user's primary GID.
+ var name string
+ name, gid64, gerr = lookupGroupForUIDInContainer(rootdir, uid64)
+ if gerr == nil {
+ userspec = name
+ } else {
+ if userrec, err := user.LookupId(userspec); err == nil {
+ gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
+ userspec = userrec.Name
+ }
+ }
+ }
+ if uerr != nil {
+ uid64, gid64, uerr = lookupUserInContainer(rootdir, userspec)
+ gerr = uerr
+ }
+ if uerr != nil {
+ if userrec, err := user.Lookup(userspec); err == nil {
+ uid64, uerr = strconv.ParseUint(userrec.Uid, 10, 32)
+ gid64, gerr = strconv.ParseUint(userrec.Gid, 10, 32)
+ }
+ }
+
+ if groupspec != "" {
+ gid64, gerr = strconv.ParseUint(groupspec, 10, 32)
+ if gerr != nil {
+ gid64, gerr = lookupGroupInContainer(rootdir, groupspec)
+ }
+ if gerr != nil {
+ if group, err := user.LookupGroup(groupspec); err == nil {
+ gid64, gerr = strconv.ParseUint(group.Gid, 10, 32)
+ }
+ }
+ }
+
+ if uerr == nil && gerr == nil {
+ u := specs.User{
+ UID: uint32(uid64),
+ GID: uint32(gid64),
+ Username: userspec,
+ }
+ return u, nil
+ }
+
+ err := errors.Wrapf(uerr, "error determining run uid")
+ if uerr == nil {
+ err = errors.Wrapf(gerr, "error determining run gid")
+ }
+ return specs.User{}, err
+}
+
+// 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
+}
+
+// NsUser represents userns mode in the container.
+// for userns flag
+type NsUser string
+
+// IsHost indicates whether the container uses the host's userns.
+func (n NsUser) IsHost() bool {
+ return n == "host"
+}
+
+// IsPrivate indicates whether the container uses the a private userns.
+func (n NsUser) IsPrivate() bool {
+ return !(n.IsHost())
+}
+
+// Valid indicates whether the userns is valid.
+func (n NsUser) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host":
+ default:
+ return false
+ }
+ return true
+}
+
+// NsUts represents the UTS namespace of the container.
+// for uts flag
+type NsUts string
+
+// IsPrivate indicates whether the container uses its private UTS namespace.
+func (n NsUts) IsPrivate() bool {
+ return !(n.IsHost())
+}
+
+// IsHost indicates whether the container uses the host's UTS namespace.
+func (n NsUts) IsHost() bool {
+ return n == "host"
+}
+
+// Valid indicates whether the UTS namespace is valid.
+func (n NsUts) Valid() bool {
+ parts := strings.Split(string(n), ":")
+ switch mode := parts[0]; mode {
+ case "", "host":
+ default:
+ return false
+ }
+ return true
+}
+
+// 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
+}
diff --git a/cmd/kpod/run.go b/cmd/kpod/run.go
new file mode 100644
index 000000000..bf9375d95
--- /dev/null
+++ b/cmd/kpod/run.go
@@ -0,0 +1,103 @@
+package main
+
+import (
+ "fmt"
+
+ "github.com/projectatomic/libpod/libpod"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+)
+
+var runDescription = "Runs a command in a new container from the given image"
+
+var runCommand = cli.Command{
+ Name: "run",
+ Usage: "run a command in a new container",
+ Description: runDescription,
+ Flags: createFlags,
+ Action: runCmd,
+ ArgsUsage: "IMAGE [COMMAND [ARG...]]",
+}
+
+func runCmd(c *cli.Context) error {
+ if err := validateFlags(c, createFlags); err != nil {
+ return err
+ }
+ runtime, err := getRuntime(c)
+
+ if err != nil {
+ return errors.Wrapf(err, "error creating libpod runtime")
+ }
+
+ createConfig, err := parseCreateOpts(c, runtime)
+ if err != nil {
+ return err
+ }
+
+ createImage := runtime.NewImage(createConfig.image)
+
+ if !createImage.HasImageLocal() {
+ // The image wasnt found by the user input'd name or its fqname
+ // Pull the image
+ fmt.Printf("Trying to pull %s...", createImage.PullName)
+ createImage.Pull()
+ }
+
+ runtimeSpec, err := createConfigToOCISpec(createConfig)
+ if err != nil {
+ return err
+ }
+ defer runtime.Shutdown(false)
+ logrus.Debug("spec is ", runtimeSpec)
+
+ imageName, err := createImage.GetFQName()
+ if err != nil {
+ return err
+ }
+ logrus.Debug("imageName is ", imageName)
+
+ imageID, err := createImage.GetImageID()
+ if err != nil {
+ return err
+ }
+ logrus.Debug("imageID is ", imageID)
+
+ options, err := createConfig.GetContainerCreateOptions(c)
+ if err != nil {
+ return errors.Wrapf(err, "unable to parse new container options")
+ }
+ // Gather up the options for NewContainer which consist of With... funcs
+ options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false))
+ ctr, err := runtime.NewContainer(runtimeSpec, options...)
+ if err != nil {
+ return err
+ }
+
+ logrus.Debug("new container created ", ctr.ID())
+ if err := ctr.Create(); err != nil {
+ return err
+ }
+ logrus.Debug("container storage created for %q", ctr.ID())
+
+ if c.String("cidfile") != "" {
+ libpod.WriteFile(ctr.ID(), c.String("cidfile"))
+ return nil
+ }
+ // Start the container
+ if err := ctr.Start(); err != nil {
+ return errors.Wrapf(err, "unable to start container %q", ctr.ID())
+ }
+ logrus.Debug("started container ", ctr.ID())
+ if createConfig.tty {
+ // Attach to the running container
+ logrus.Debug("trying to attach to the container %s", ctr.ID())
+ if err := ctr.Attach(false, c.String("detach-keys")); err != nil {
+ return errors.Wrapf(err, "unable to attach to container %s", ctr.ID())
+ }
+ } else {
+ fmt.Printf("%s\n", ctr.ID())
+ }
+
+ return nil
+}
diff --git a/cmd/kpod/spec.go b/cmd/kpod/spec.go
new file mode 100644
index 000000000..4b05005bd
--- /dev/null
+++ b/cmd/kpod/spec.go
@@ -0,0 +1,490 @@
+package main
+
+import (
+ "fmt"
+ "strings"
+
+ "github.com/projectatomic/libpod/libpod"
+ ann "github.com/projectatomic/libpod/pkg/annotations"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+ "github.com/urfave/cli"
+ "golang.org/x/sys/unix"
+)
+
+// Parses information needed to create a container into an OCI runtime spec
+func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) {
+ spec := config.GetDefaultLinuxSpec()
+ spec.Process.Cwd = config.workDir
+ spec.Process.Args = config.command
+
+ spec.Process.Terminal = config.tty
+
+ // User and Group must go together
+ spec.Process.User.UID = config.user
+ spec.Process.User.GID = config.group
+ spec.Process.User.AdditionalGids = config.groupAdd
+
+ spec.Process.Env = config.env
+
+ //TODO
+ // Need examples of capacity additions so I can load that properly
+
+ spec.Root.Readonly = config.readOnlyRootfs
+ spec.Hostname = config.hostname
+
+ // BIND MOUNTS
+ spec.Mounts = append(spec.Mounts, config.GetVolumeMounts()...)
+
+ // TMPFS MOUNTS
+ spec.Mounts = append(spec.Mounts, config.GetTmpfsMounts()...)
+
+ // RESOURCES - MEMORY
+ spec.Linux.Sysctl = config.sysctl
+
+ if config.resources.memory != 0 {
+ spec.Linux.Resources.Memory.Limit = &config.resources.memory
+ }
+ if config.resources.memoryReservation != 0 {
+ spec.Linux.Resources.Memory.Reservation = &config.resources.memoryReservation
+ }
+ if config.resources.memorySwap != 0 {
+ spec.Linux.Resources.Memory.Swap = &config.resources.memorySwap
+ }
+ if config.resources.kernelMemory != 0 {
+ spec.Linux.Resources.Memory.Kernel = &config.resources.kernelMemory
+ }
+ if config.resources.memorySwapiness != 0 {
+ spec.Linux.Resources.Memory.Swappiness = &config.resources.memorySwapiness
+ }
+ if config.resources.disableOomKiller {
+ spec.Linux.Resources.Memory.DisableOOMKiller = &config.resources.disableOomKiller
+ }
+
+ // RESOURCES - CPU
+
+ if config.resources.cpuShares != 0 {
+ spec.Linux.Resources.CPU.Shares = &config.resources.cpuShares
+ }
+ if config.resources.cpuQuota != 0 {
+ spec.Linux.Resources.CPU.Quota = &config.resources.cpuQuota
+ }
+ if config.resources.cpuPeriod != 0 {
+ spec.Linux.Resources.CPU.Period = &config.resources.cpuPeriod
+ }
+ if config.resources.cpuRtRuntime != 0 {
+ spec.Linux.Resources.CPU.RealtimeRuntime = &config.resources.cpuRtRuntime
+ }
+ if config.resources.cpuRtPeriod != 0 {
+ spec.Linux.Resources.CPU.RealtimePeriod = &config.resources.cpuRtPeriod
+ }
+ if config.resources.cpus != "" {
+ spec.Linux.Resources.CPU.Cpus = config.resources.cpus
+ }
+ if config.resources.cpusetMems != "" {
+ spec.Linux.Resources.CPU.Mems = config.resources.cpusetMems
+ }
+
+ // RESOURCES - PIDS
+ if config.resources.pidsLimit != 0 {
+ spec.Linux.Resources.Pids.Limit = config.resources.pidsLimit
+ }
+
+ /*
+ Capabilities: &spec.LinuxCapabilities{
+ // Rlimits []PosixRlimit // Where does this come from
+ // Type string
+ // Hard uint64
+ // Limit uint64
+ // NoNewPrivileges bool // No user input for this
+ // ApparmorProfile string // No user input for this
+ OOMScoreAdj: &config.resources.oomScoreAdj,
+ // Selinuxlabel
+ },
+ Hooks: &spec.Hooks{},
+ //Annotations
+ Resources: &spec.LinuxResources{
+ Devices: config.GetDefaultDevices(),
+ BlockIO: &blkio,
+ //HugepageLimits:
+ Network: &spec.LinuxNetwork{
+ // ClassID *uint32
+ // Priorites []LinuxInterfacePriority
+ },
+ },
+ //CgroupsPath:
+ //Namespaces: []LinuxNamespace
+ //Devices
+ Seccomp: &spec.LinuxSeccomp{
+ // DefaultAction:
+ // Architectures
+ // Syscalls:
+ },
+ // RootfsPropagation
+ // MaskedPaths
+ // ReadonlyPaths:
+ // MountLabel
+ // IntelRdt
+ },
+ }
+ */
+ return &spec, nil
+}
+
+func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) {
+ bio := spec.LinuxBlockIO{}
+ bio.Weight = &c.resources.blkioWeight
+ if len(c.resources.blkioDevice) > 0 {
+ var lwds []spec.LinuxWeightDevice
+ for _, i := range c.resources.blkioDevice {
+ wd, err := validateweightDevice(i)
+ if err != nil {
+ return bio, errors.Wrapf(err, "invalid values for blkio-weight-device")
+ }
+ wdStat := getStatFromPath(wd.path)
+ lwd := spec.LinuxWeightDevice{
+ Weight: &wd.weight,
+ }
+ lwd.Major = int64(unix.Major(wdStat.Rdev))
+ lwd.Minor = int64(unix.Minor(wdStat.Rdev))
+ lwds = append(lwds, lwd)
+ }
+ }
+ if len(c.resources.deviceReadBps) > 0 {
+ readBps, err := makeThrottleArray(c.resources.deviceReadBps)
+ if err != nil {
+ return bio, err
+ }
+ bio.ThrottleReadBpsDevice = readBps
+ }
+ if len(c.resources.deviceWriteBps) > 0 {
+ writeBpds, err := makeThrottleArray(c.resources.deviceWriteBps)
+ if err != nil {
+ return bio, err
+ }
+ bio.ThrottleWriteBpsDevice = writeBpds
+ }
+ if len(c.resources.deviceReadIops) > 0 {
+ readIops, err := makeThrottleArray(c.resources.deviceReadIops)
+ if err != nil {
+ return bio, err
+ }
+ bio.ThrottleReadIOPSDevice = readIops
+ }
+ if len(c.resources.deviceWriteIops) > 0 {
+ writeIops, err := makeThrottleArray(c.resources.deviceWriteIops)
+ if err != nil {
+ return bio, err
+ }
+ bio.ThrottleWriteIOPSDevice = writeIops
+ }
+
+ return bio, nil
+}
+
+func (c *createConfig) GetDefaultMounts() []spec.Mount {
+ // Default to 64K default per man page
+ shmSize := "65536k"
+ if c.resources.shmSize != "" {
+ shmSize = c.resources.shmSize
+ }
+ return []spec.Mount{
+ {
+ Destination: "/proc",
+ Type: "proc",
+ Source: "proc",
+ Options: []string{"nosuid", "noexec", "nodev"},
+ },
+ {
+ Destination: "/dev",
+ Type: "tmpfs",
+ Source: "tmpfs",
+ Options: []string{"nosuid", "strictatime", "mode=755", "size=65536k"},
+ },
+ {
+ Destination: "/dev/pts",
+ Type: "devpts",
+ Source: "devpts",
+ Options: []string{"nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620", "gid=5"},
+ },
+ {
+ Destination: "/sys",
+ Type: "sysfs",
+ Source: "sysfs",
+ Options: []string{"nosuid", "noexec", "nodev", "ro"},
+ },
+ {
+ Destination: "/sys/fs/cgroup",
+ Type: "cgroup",
+ Source: "cgroup",
+ Options: []string{"ro", "nosuid", "noexec", "nodev"},
+ },
+ {
+ Destination: "/dev/mqueue",
+ Type: "mqueue",
+ Source: "mqueue",
+ Options: []string{"nosuid", "noexec", "nodev"},
+ },
+ {
+ Destination: "/dev/shm",
+ Type: "tmpfs",
+ Source: "shm",
+ Options: []string{"nosuid", "noexec", "nodev", "mode=1777", fmt.Sprintf("size=%s", shmSize)},
+ },
+ }
+}
+
+func iPtr(i int64) *int64 { return &i }
+
+func (c *createConfig) GetDefaultDevices() []spec.LinuxDeviceCgroup {
+ return []spec.LinuxDeviceCgroup{
+ {
+ Allow: false,
+ Access: "rwm",
+ },
+ {
+ Allow: true,
+ Type: "c",
+ Major: iPtr(1),
+ Minor: iPtr(5),
+ Access: "rwm",
+ },
+ {
+ Allow: true,
+ Type: "c",
+ Major: iPtr(1),
+ Minor: iPtr(3),
+ Access: "rwm",
+ },
+ {
+ Allow: true,
+ Type: "c",
+ Major: iPtr(1),
+ Minor: iPtr(9),
+ Access: "rwm",
+ },
+ {
+ Allow: true,
+ Type: "c",
+ Major: iPtr(1),
+ Minor: iPtr(8),
+ Access: "rwm",
+ },
+ {
+ Allow: true,
+ Type: "c",
+ Major: iPtr(5),
+ Minor: iPtr(0),
+ Access: "rwm",
+ },
+ {
+ Allow: true,
+ Type: "c",
+ Major: iPtr(5),
+ Minor: iPtr(1),
+ Access: "rwm",
+ },
+ {
+ Allow: false,
+ Type: "c",
+ Major: iPtr(10),
+ Minor: iPtr(229),
+ Access: "rwm",
+ },
+ }
+}
+
+func defaultCapabilities() []string {
+ return []string{
+ "CAP_CHOWN",
+ "CAP_DAC_OVERRIDE",
+ "CAP_FSETID",
+ "CAP_FOWNER",
+ "CAP_MKNOD",
+ "CAP_NET_RAW",
+ "CAP_SETGID",
+ "CAP_SETUID",
+ "CAP_SETFCAP",
+ "CAP_SETPCAP",
+ "CAP_NET_BIND_SERVICE",
+ "CAP_SYS_CHROOT",
+ "CAP_KILL",
+ "CAP_AUDIT_WRITE",
+ }
+}
+
+func (c *createConfig) GetDefaultLinuxSpec() spec.Spec {
+ s := spec.Spec{
+ Version: spec.Version,
+ Root: &spec.Root{},
+ }
+ s.Annotations = c.GetAnnotations()
+ s.Mounts = c.GetDefaultMounts()
+ s.Process = &spec.Process{
+ Capabilities: &spec.LinuxCapabilities{
+ Bounding: defaultCapabilities(),
+ Permitted: defaultCapabilities(),
+ Inheritable: defaultCapabilities(),
+ Effective: defaultCapabilities(),
+ },
+ }
+ s.Linux = &spec.Linux{
+ MaskedPaths: []string{
+ "/proc/kcore",
+ "/proc/latency_stats",
+ "/proc/timer_list",
+ "/proc/timer_stats",
+ "/proc/sched_debug",
+ },
+ ReadonlyPaths: []string{
+ "/proc/asound",
+ "/proc/bus",
+ "/proc/fs",
+ "/proc/irq",
+ "/proc/sys",
+ "/proc/sysrq-trigger",
+ },
+ Namespaces: []spec.LinuxNamespace{
+ {Type: "mount"},
+ {Type: "network"},
+ {Type: "uts"},
+ {Type: "pid"},
+ {Type: "ipc"},
+ },
+ Devices: []spec.LinuxDevice{},
+ Resources: &spec.LinuxResources{
+ Devices: c.GetDefaultDevices(),
+ },
+ }
+
+ return s
+}
+
+// GetAnnotations returns the all the annotations for the container
+func (c *createConfig) GetAnnotations() map[string]string {
+ a := getDefaultAnnotations()
+ // TODO
+ // Which annotations do we want added by default
+ if c.tty {
+ a["io.kubernetes.cri-o.TTY"] = "true"
+ }
+ return a
+}
+
+func getDefaultAnnotations() map[string]string {
+ var annotations map[string]string
+ annotations = make(map[string]string)
+ annotations[ann.Annotations] = ""
+ annotations[ann.ContainerID] = ""
+ annotations[ann.ContainerName] = ""
+ annotations[ann.ContainerType] = ""
+ annotations[ann.Created] = ""
+ annotations[ann.HostName] = ""
+ annotations[ann.IP] = ""
+ annotations[ann.Image] = ""
+ annotations[ann.ImageName] = ""
+ annotations[ann.ImageRef] = ""
+ annotations[ann.KubeName] = ""
+ annotations[ann.Labels] = ""
+ annotations[ann.LogPath] = ""
+ annotations[ann.Metadata] = ""
+ annotations[ann.Name] = ""
+ annotations[ann.PrivilegedRuntime] = ""
+ annotations[ann.ResolvPath] = ""
+ annotations[ann.HostnamePath] = ""
+ annotations[ann.SandboxID] = ""
+ annotations[ann.SandboxName] = ""
+ annotations[ann.ShmPath] = ""
+ annotations[ann.MountPoint] = ""
+ annotations[ann.TrustedSandbox] = ""
+ annotations[ann.TTY] = "false"
+ annotations[ann.Stdin] = ""
+ annotations[ann.StdinOnce] = ""
+ annotations[ann.Volumes] = ""
+
+ return annotations
+}
+
+//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
+func (c *createConfig) GetVolumeMounts() []spec.Mount {
+ var m []spec.Mount
+ var options []string
+ for _, i := range c.volumes {
+ // We need to handle SELinux options better here, specifically :Z
+ spliti := strings.Split(i, ":")
+ if len(spliti) > 2 {
+ options = strings.Split(spliti[2], ",")
+ }
+ // always add rbind bc mount ignores the bind filesystem when mounting
+ options = append(options, "rbind")
+ m = append(m, spec.Mount{
+ Destination: spliti[1],
+ Type: string(TypeBind),
+ Source: spliti[0],
+ Options: options,
+ })
+ }
+ return m
+}
+
+//GetTmpfsMounts takes user provided input for tmpfs mounts and creates Mount structs
+func (c *createConfig) GetTmpfsMounts() []spec.Mount {
+ var m []spec.Mount
+ for _, i := range c.tmpfs {
+ // Default options if nothing passed
+ options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"}
+ spliti := strings.Split(i, ":")
+ destPath := spliti[0]
+ if len(spliti) > 1 {
+ options = strings.Split(spliti[1], ",")
+ }
+ m = append(m, spec.Mount{
+ Destination: destPath,
+ Type: string(TypeTmpfs),
+ Options: options,
+ Source: string(TypeTmpfs),
+ })
+ }
+ return m
+}
+
+func (c *createConfig) GetContainerCreateOptions(cli *cli.Context) ([]libpod.CtrCreateOption, error) {
+ var options []libpod.CtrCreateOption
+
+ // Uncomment after talking to mheon about unimplemented funcs
+ // options = append(options, libpod.WithLabels(c.labels))
+
+ if c.interactive {
+ options = append(options, libpod.WithStdin())
+ }
+ if c.name != "" {
+ logrus.Debug("appending name %s", c.name)
+ options = append(options, libpod.WithName(c.name))
+ }
+
+ return options, nil
+}
+
+func getStatFromPath(path string) unix.Stat_t {
+ s := unix.Stat_t{}
+ _ = unix.Stat(path, &s)
+ return s
+}
+
+func makeThrottleArray(throttleInput []string) ([]spec.LinuxThrottleDevice, error) {
+ var ltds []spec.LinuxThrottleDevice
+ for _, i := range throttleInput {
+ t, err := validateBpsDevice(i)
+ if err != nil {
+ return []spec.LinuxThrottleDevice{}, err
+ }
+ ltd := spec.LinuxThrottleDevice{}
+ ltd.Rate = t.rate
+ ltdStat := getStatFromPath(t.path)
+ ltd.Major = int64(unix.Major(ltdStat.Rdev))
+ ltd.Minor = int64(unix.Major(ltdStat.Rdev))
+ ltds = append(ltds, ltd)
+ }
+ return ltds, nil
+}
diff --git a/cmd/kpod/user.go b/cmd/kpod/user.go
new file mode 100644
index 000000000..3e2e308c5
--- /dev/null
+++ b/cmd/kpod/user.go
@@ -0,0 +1,121 @@
+package main
+
+// #include <sys/types.h>
+// #include <grp.h>
+// #include <pwd.h>
+// #include <stdlib.h>
+// #include <stdio.h>
+// #include <string.h>
+// typedef FILE * pFILE;
+import "C"
+
+import (
+ "fmt"
+ "os/user"
+ "path/filepath"
+ "sync"
+ "syscall"
+ "unsafe"
+
+ "github.com/pkg/errors"
+)
+
+func fopenContainerFile(rootdir, filename string) (C.pFILE, error) {
+ var st, lst syscall.Stat_t
+
+ ctrfile := filepath.Join(rootdir, filename)
+ cctrfile := C.CString(ctrfile)
+ defer C.free(unsafe.Pointer(cctrfile))
+ mode := C.CString("r")
+ defer C.free(unsafe.Pointer(mode))
+ f, err := C.fopen(cctrfile, mode)
+ if f == nil || err != nil {
+ return nil, errors.Wrapf(err, "error opening %q", ctrfile)
+ }
+ if err = syscall.Fstat(int(C.fileno(f)), &st); err != nil {
+ return nil, errors.Wrapf(err, "fstat(%q)", ctrfile)
+ }
+ if err = syscall.Lstat(ctrfile, &lst); err != nil {
+ return nil, errors.Wrapf(err, "lstat(%q)", ctrfile)
+ }
+ if st.Dev != lst.Dev || st.Ino != lst.Ino {
+ return nil, errors.Errorf("%q is not a regular file", ctrfile)
+ }
+ return f, nil
+}
+
+var (
+ lookupUser, lookupGroup sync.Mutex
+)
+
+func lookupUserInContainer(rootdir, username string) (uint64, uint64, error) {
+ name := C.CString(username)
+ defer C.free(unsafe.Pointer(name))
+
+ f, err := fopenContainerFile(rootdir, "/etc/passwd")
+ if err != nil {
+ return 0, 0, err
+ }
+ defer C.fclose(f)
+
+ lookupUser.Lock()
+ defer lookupUser.Unlock()
+
+ pwd := C.fgetpwent(f)
+ for pwd != nil {
+ if C.strcmp(pwd.pw_name, name) != 0 {
+ pwd = C.fgetpwent(f)
+ continue
+ }
+ return uint64(pwd.pw_uid), uint64(pwd.pw_gid), nil
+ }
+
+ return 0, 0, user.UnknownUserError(fmt.Sprintf("error looking up user %q", username))
+}
+
+func lookupGroupForUIDInContainer(rootdir string, userid uint64) (string, uint64, error) {
+ f, err := fopenContainerFile(rootdir, "/etc/passwd")
+ if err != nil {
+ return "", 0, err
+ }
+ defer C.fclose(f)
+
+ lookupUser.Lock()
+ defer lookupUser.Unlock()
+
+ pwd := C.fgetpwent(f)
+ for pwd != nil {
+ if uint64(pwd.pw_uid) != userid {
+ pwd = C.fgetpwent(f)
+ continue
+ }
+ return C.GoString(pwd.pw_name), uint64(pwd.pw_gid), nil
+ }
+
+ return "", 0, user.UnknownUserError(fmt.Sprintf("error looking up user with UID %d", userid))
+}
+
+func lookupGroupInContainer(rootdir, groupname string) (uint64, error) {
+ name := C.CString(groupname)
+ defer C.free(unsafe.Pointer(name))
+
+ f, err := fopenContainerFile(rootdir, "/etc/group")
+ if err != nil {
+ return 0, err
+ }
+ defer C.fclose(f)
+
+ lookupGroup.Lock()
+ defer lookupGroup.Unlock()
+
+ grp := C.fgetgrent(f)
+ for grp != nil {
+ if C.strcmp(grp.gr_name, name) != 0 {
+ grp = C.fgetgrent(f)
+ continue
+ }
+ return uint64(grp.gr_gid), nil
+ }
+
+ return 0, user.UnknownGroupError(fmt.Sprintf("error looking up group %q", groupname))
+}