diff --git a/cmd/kpod/common.go b/cmd/kpod/common.go index 91997e1f..ceb46401 100644 --- a/cmd/kpod/common.go +++ b/cmd/kpod/common.go @@ -136,11 +136,11 @@ func validateFlags(c *cli.Context, flags []cli.Flag) error { // Common flags shared between commands var createFlags = []cli.Flag{ - cli.StringSliceFlag{ // + cli.StringSliceFlag{ Name: "add-host", Usage: "Add a custom host-to-IP mapping (host:ip) (default [])", }, - cli.StringSliceFlag{ // + cli.StringSliceFlag{ Name: "attach, a", Usage: "Attach to STDIN, STDOUT or STDERR (default [])", }, @@ -164,12 +164,8 @@ var createFlags = []cli.Flag{ Name: "cgroup-parent", Usage: "Optional parent cgroup for the container", }, - cli.Int64Flag{ - Name: "cpu-count", - Usage: "Limit the number of CPUs available for execution by the container.", - }, - cli.StringFlag{ // - Name: "cid-file", + cli.StringFlag{ + Name: "cidfile", Usage: "Write the container ID to the file", }, cli.Uint64Flag{ @@ -208,7 +204,7 @@ var createFlags = []cli.Flag{ Name: "detach, d", Usage: "Run container in background and print container ID", }, - cli.StringFlag{ // + cli.StringFlag{ Name: "detach-keys", Usage: "Override the key sequence for detaching a container. Format is a single character `[a-Z]` or `ctrl-` where `` is one of: `a-z`, `@`, `^`, `[`, `,` or `_`", }, @@ -253,7 +249,7 @@ var createFlags = []cli.Flag{ Usage: "Set environment variables in container", }, cli.StringSliceFlag{ - Name: "env-file", // + Name: "env-file", Usage: "Read in a file of environment variables", }, cli.StringSliceFlag{ @@ -280,7 +276,7 @@ var createFlags = []cli.Flag{ Name: "ip6", Usage: "Container IPv6 address (e.g. 2001:db8::1b99)", }, - cli.StringFlag{ // + cli.StringFlag{ Name: "ipc", Usage: "IPC Namespace to use", }, @@ -292,7 +288,7 @@ var createFlags = []cli.Flag{ Name: "label", Usage: "Set metadata on container (default [])", }, - cli.StringSliceFlag{ // + cli.StringSliceFlag{ Name: "label-file", Usage: "Read in a line delimited file of labels (default [])", }, @@ -344,7 +340,7 @@ var createFlags = []cli.Flag{ Name: "network-alias", Usage: "Add network-scoped alias for the container (default [])", }, - cli.BoolFlag{ // + cli.BoolFlag{ Name: "oom-kill-disable", Usage: "Disable OOM Killer", }, diff --git a/cmd/kpod/create.go b/cmd/kpod/create.go index 067a2f6b..3f11f8a9 100644 --- a/cmd/kpod/create.go +++ b/cmd/kpod/create.go @@ -2,19 +2,13 @@ package main import ( "fmt" - - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/urfave/cli" - pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" - "strings" + "strconv" "github.com/docker/go-units" "github.com/kubernetes-incubator/cri-o/libpod" - ann "github.com/kubernetes-incubator/cri-o/pkg/annotations" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" - "strconv" + "github.com/pkg/errors" + "github.com/urfave/cli" + pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" ) type mountType string @@ -24,11 +18,9 @@ const ( // TypeBind is the type for mounting host dir TypeBind mountType = "bind" // TypeVolume is the type for remote storage volumes - TypeVolume mountType = "volume" + // TypeVolume mountType = "volume" // re-enable upon use // TypeTmpfs is the type for mounting tmpfs TypeTmpfs mountType = "tmpfs" - // TypeNamedPipe is the type for mounting Windows named pipes - TypeNamedPipe mountType = "npipe" ) var ( @@ -138,8 +130,8 @@ func createCmd(c *cli.Context) error { if err := validateFlags(c, createFlags); err != nil { return err } - //runtime, err := getRuntime(c) - runtime, err := libpod.NewRuntime() + + runtime, err := getRuntime(c) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -167,12 +159,17 @@ func createCmd(c *cli.Context) error { if err != nil { return err } - fmt.Println(imageName) imageID, err := createImage.GetImageID() if err != nil { return err } - ctr, err := runtime.NewContainer(runtimeSpec, libpod.WithRootFSFromImage(imageID, imageName, false)) + 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 } @@ -181,81 +178,47 @@ func createCmd(c *cli.Context) error { return err } - if c.String("cid-file") != "" { - libpod.WriteFile(ctr.ID(), c.String("cid-file")) - return nil + if c.String("cidfile") != "" { + libpod.WriteFile(ctr.ID(), c.String("cidfile")) + } else { + fmt.Printf("%s\n", ctr.ID()) } - fmt.Printf("%s\n", ctr.ID()) return nil } -/* The following funcs should land in parse.go */ -// -// -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 -} - // 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 env []string - var labelValues []string var uid, gid uint32 - sysctl := make(map[string]string) - labels := make(map[string]string) image := c.Args()[0] if len(c.Args()) < 1 { - return nil, errors.Errorf("you just provide an image name") + return nil, errors.Errorf("image name or ID is required") } if len(c.Args()) > 1 { command = c.Args()[1:] } // LABEL VARIABLES - // TODO where should labels be verified to be x=y format - labelValues, labelErr := readKVStrings(c.StringSlice("label-file"), c.StringSlice("label")) - if labelErr != nil { - return &createConfig{}, errors.Wrapf(labelErr, "unable to process labels from --label and label-file") + labels, err := getAllLabels(c) + if err != nil { + return &createConfig{}, errors.Wrapf(err, "unable to process labels") } - // Process KEY=VALUE stringslice in string map for WithLabels func - if len(labelValues) > 0 { - for _, i := range labelValues { - spliti := strings.Split(i, "=") - labels[spliti[0]] = spliti[1] - } - } - // ENVIRONMENT VARIABLES // TODO where should env variables be verified to be x=y format - env, err := readKVStrings(c.StringSlice("env-file"), c.StringSlice("env")) + env, err := getAllEnvironmentVariables(c) if err != nil { - return &createConfig{}, 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 &createConfig{}, errors.Wrapf(err, "unable to process environment variables") } - if len(c.StringSlice("sysctl")) > 0 { - for _, inputSysctl := range c.StringSlice("sysctl") { - values := strings.Split(inputSysctl, "=") - sysctl[values[0]] = values[1] - } + 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")) @@ -338,10 +301,9 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er 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"), - //cpuCount: c.Int64("cpu-count"), + 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"), @@ -354,6 +316,7 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er 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, @@ -366,534 +329,19 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er }, rm: c.Bool("rm"), securityOpts: c.StringSlice("security-opt"), - //shmSize: c.String("shm-size"), - 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, - //userns: c.String("userns"), - volumes: c.StringSlice("volume"), - volumesFrom: c.StringSlice("volumes-from"), - workDir: c.String("workdir"), + 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 } - -// Parses information needed to create a container into an OCI runtime spec -func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { - - //blkio, err := config.CreateBlockIO() - //if err != nil { - // return &spec.Spec{}, err - //} - - spec := config.GetDefaultLinuxSpec() - spec.Process.Cwd = config.workDir - spec.Process.Args = config.command - - if config.tty { - spec.Process.Terminal = config.tty - } - - if config.user != 0 { - // User and Group must go together - spec.Process.User.UID = config.user - spec.Process.User.GID = config.group - } - if len(config.groupAdd) > 0 { - spec.Process.User.AdditionalGids = config.groupAdd - } - if len(config.env) > 0 { - spec.Process.Env = config.env - } - //TODO - // Need examples of capacity additions so I can load that properly - - if config.readOnlyRootfs { - spec.Root.Readonly = config.readOnlyRootfs - } - - if config.hostname != "" { - spec.Hostname = config.hostname - } - - // BIND MOUNTS - if len(config.volumes) > 0 { - spec.Mounts = append(spec.Mounts, config.GetVolumeMounts()...) - } - // TMPFS MOUNTS - if len(config.tmpfs) > 0 { - spec.Mounts = append(spec.Mounts, config.GetTmpfsMounts()...) - } - - // RESOURCES - MEMORY - if len(config.sysctl) > 0 { - 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 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 - -} - -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 { - 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"}, - }, - } -} -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 -} - -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 a map[string]string - a = make(map[string]string) - a[ann.Annotations] = "" - a[ann.ContainerID] = "" - a[ann.ContainerName] = "" - a[ann.ContainerType] = "" - a[ann.Created] = "" - a[ann.HostName] = "" - a[ann.IP] = "" - a[ann.Image] = "" - a[ann.ImageName] = "" - a[ann.ImageRef] = "" - a[ann.KubeName] = "" - a[ann.Labels] = "" - a[ann.LogPath] = "" - a[ann.Metadata] = "" - a[ann.Name] = "" - a[ann.PrivilegedRuntime] = "" - a[ann.ResolvPath] = "" - a[ann.HostnamePath] = "" - a[ann.SandboxID] = "" - a[ann.SandboxName] = "" - a[ann.ShmPath] = "" - a[ann.MountPoint] = "" - a[ann.TrustedSandbox] = "" - a[ann.TTY] = "false" - a[ann.Stdin] = "" - a[ann.StdinOnce] = "" - a[ann.Volumes] = "" - - return a -} - -//GetTmpfsMounts 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 { - 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, - }) - } - return m -} - -func (c *createConfig) GetContainerCreateOptions(cli *cli.Context) ([]libpod.CtrCreateOption, error) { - /* - WithStorageConfig - WithImageConfig - WithSignaturePolicy - WithOCIRuntime - WithConmonPath - WithConmonEnv - WithCgroupManager - WithStaticDir - WithTmpDir - WithSELinux - WithPidsLimit // dont need - WithMaxLogSize - WithNoPivotRoot - WithRootFSFromPath - WithRootFSFromImage - WithStdin // done - WithSharedNamespaces - WithLabels //done - WithAnnotations // dont need - WithName // done - WithStopSignal - WithPodName - */ - 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.Info("appending name %s", c.name) - options = append(options, libpod.WithName(c.name)) - } - - return options, nil -} diff --git a/cmd/kpod/create_cli.go b/cmd/kpod/create_cli.go new file mode 100644 index 00000000..996155ba --- /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/parse.go b/cmd/kpod/parse.go index 6437a74a..e3143a79 100644 --- a/cmd/kpod/parse.go +++ b/cmd/kpod/parse.go @@ -1,3 +1,4 @@ +//nolint // most of these validate and parse functions have been taken from projectatomic/docker // and modified for cri-o package main @@ -34,7 +35,7 @@ var ( // 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) { +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 { @@ -58,7 +59,7 @@ func validateIPAddress(val string) (string, error) { // validateAttach validates that the specified string is a valid attach option. // for attach flag -func validateAttach(val string) (string, error) { +func validateAttach(val string) (string, error) { //nolint s := strings.ToLower(val) for _, str := range []string{"stdin", "stdout", "stderr"} { if s == str { @@ -70,7 +71,7 @@ func validateAttach(val string) (string, error) { // validate the blkioWeight falls in the range of 10 to 1000 // for blkio-weight flag -func validateBlkioWeight(val int64) (int64, error) { +func validateBlkioWeight(val int64) (int64, error) { //nolint if val >= 10 && val <= 1000 { return val, nil } @@ -113,7 +114,7 @@ func validateweightDevice(val string) (*weightDevice, error) { // parseDevice parses a device mapping string to a container.DeviceMapping struct // for device flag -func parseDevice(device string) (*pb.Device, error) { +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) @@ -256,7 +257,7 @@ func validateBpsDevice(val string) (*throttleDevice, error) { // 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) { +func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint split := strings.SplitN(val, ":", 2) if len(split) != 2 { return nil, fmt.Errorf("bad format: %s", val) @@ -281,7 +282,7 @@ func validateIOpsDevice(val string) (*throttleDevice, error) { // 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) { +func validateDNSSearch(val string) (string, error) { //nolint if val = strings.Trim(val, " "); val == "." { return val, nil } @@ -302,7 +303,7 @@ func validateDomain(val string) (string, error) { // 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) { +func validateEnv(val string) (string, error) { //nolint arr := strings.Split(val, "=") if len(arr) > 1 { return val, nil @@ -424,7 +425,7 @@ func (n NsIpc) Container() string { // 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) { +func validateLabel(val string) (string, error) { //nolint if strings.Count(val, "=") < 1 { return "", fmt.Errorf("bad attribute format: %s", val) } @@ -433,7 +434,7 @@ func validateLabel(val string) (string, error) { // validateMACAddress validates a MAC address. // for mac-address flag -func validateMACAddress(val string) (string, error) { +func validateMACAddress(val string) (string, error) { //nolint _, err := net.ParseMAC(strings.TrimSpace(val)) if err != nil { return "", err @@ -442,7 +443,7 @@ func validateMACAddress(val string) (string, error) { } // validateLink validates that the specified string has a valid link format (containerName:alias). -func validateLink(val string) (string, error) { +func validateLink(val string) (string, error) { //nolint if _, _, err := parseLink(val); err != nil { return val, err } @@ -473,7 +474,7 @@ func parseLink(val string) (string, string, error) { // parseLoggingOpts validates the logDriver and logDriverOpts // for log-opt and log-driver flags -func parseLoggingOpts(logDriver string, logDriverOpt []string) (map[string]string, error) { +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) @@ -528,7 +529,7 @@ func (n NsPid) Container() string { // 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) { +func parsePortSpecs(ports []string) ([]*pb.PortMapping, error) { //nolint var portMappings []*pb.PortMapping for _, rawPort := range ports { portMapping, err := parsePortSpec(rawPort) @@ -694,7 +695,7 @@ func splitProtoPort(rawPort string) (string, string) { // takes a local seccomp file and reads its file contents // for security-opt flag -func parseSecurityOpts(securityOpts []string) ([]string, error) { +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" { @@ -722,7 +723,7 @@ func parseSecurityOpts(securityOpts []string) ([]string, error) { // parses storage options per container into a map // for storage-opt flag -func parseStorageOpts(storageOpts []string) (map[string]string, error) { +func parseStorageOpts(storageOpts []string) (map[string]string, error) { //nolint m := make(map[string]string) for _, option := range storageOpts { if strings.Contains(option, "=") { @@ -738,7 +739,7 @@ func parseStorageOpts(storageOpts []string) (map[string]string, error) { // parseUser parses the the uid and gid in the format [:] // for user flag // FIXME: Issue from https://github.com/projectatomic/buildah/issues/66 -func parseUser(rootdir, userspec string) (specs.User, error) { +func parseUser(rootdir, userspec string) (specs.User, error) { //nolint var gid64 uint64 var gerr error = user.UnknownGroupError("error looking up group") @@ -870,3 +871,16 @@ func (n NsUts) Valid() bool { } 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 index 9c923e2b..070f5f48 100644 --- a/cmd/kpod/run.go +++ b/cmd/kpod/run.go @@ -24,7 +24,8 @@ func runCmd(c *cli.Context) error { if err := validateFlags(c, createFlags); err != nil { return err } - runtime, err := libpod.NewRuntime() + runtime, err := getRuntime(c) + if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -44,10 +45,10 @@ func runCmd(c *cli.Context) error { } runtimeSpec, err := createConfigToOCISpec(createConfig) - logrus.Debug("spec is ", runtimeSpec) if err != nil { return err } + logrus.Debug("spec is ", runtimeSpec) imageName, err := createImage.GetFQName() if err != nil { @@ -77,25 +78,21 @@ func runCmd(c *cli.Context) error { if err := ctr.Create(); err != nil { return err } - logrus.Debug("container storage created for ", ctr.ID()) + logrus.Debug("container storage created for %q", ctr.ID()) - if c.String("cid-file") != "" { - libpod.WriteFile(ctr.ID(), c.String("cid-file")) + 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 ", ctr.ID()) + 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 - keys := "" - if c.String("detach-keys") != "" { - keys = c.String("detach-keys") - } logrus.Debug("trying to attach to the container %s", ctr.ID()) - if err := ctr.Attach(false, keys); err != nil { + if err := ctr.Attach(false, c.String("detach-keys")); err != nil { return errors.Wrapf(err, "unable to attach to container %s", ctr.ID()) } } else { diff --git a/cmd/kpod/spec.go b/cmd/kpod/spec.go new file mode 100644 index 00000000..8cf96168 --- /dev/null +++ b/cmd/kpod/spec.go @@ -0,0 +1,490 @@ +package main + +import ( + "fmt" + "strings" + + "github.com/kubernetes-incubator/cri-o/libpod" + ann "github.com/kubernetes-incubator/cri-o/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 00000000..3e2e308c --- /dev/null +++ b/cmd/kpod/user.go @@ -0,0 +1,121 @@ +package main + +// #include +// #include +// #include +// #include +// #include +// #include +// 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)) +} diff --git a/libpod/container_attach.go b/libpod/container_attach.go index 6516a311..b2a40702 100644 --- a/libpod/container_attach.go +++ b/libpod/container_attach.go @@ -3,6 +3,7 @@ package libpod import ( "fmt" "io" + "net" "os" "path/filepath" "strconv" @@ -14,7 +15,6 @@ import ( "golang.org/x/sys/unix" "k8s.io/client-go/tools/remotecommand" kubecontainer "k8s.io/kubernetes/pkg/kubelet/container" - "net" ) /* Sync with stdpipe_t in conmon.c */ @@ -39,7 +39,6 @@ func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSi } oldTermState, err := term.SaveState(inputStream.Fd()) - if err != nil { return errors.Wrapf(err, "unable to save terminal state") } @@ -58,16 +57,15 @@ func (c *Container) attachContainerSocket(resize <-chan remotecommand.TerminalSi } kubecontainer.HandleResizing(resize, func(size remotecommand.TerminalSize) { - logrus.Debug("Got a resize event: %+v", size) + logrus.Debugf("Received a resize event: %+v", size) _, err := fmt.Fprintf(controlFile, "%d %d %d\n", 1, size.Height, size.Width) if err != nil { - logrus.Warn("Failed to write to control file to resize terminal: %v", err) + logrus.Warnf("Failed to write to control file to resize terminal: %v", err) } }) - attachSocketPath := filepath.Join(c.runtime.ociRuntime.socketsDir, c.ID(), "attach") - logrus.Debug("connecting to socket ", attachSocketPath) + logrus.Debug("connecting to socket ", c.attachSocketPath()) - conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: attachSocketPath, Net: "unixpacket"}) + conn, err := net.DialUnix("unixpacket", nil, &net.UnixAddr{Name: c.attachSocketPath(), Net: "unixpacket"}) if err != nil { return errors.Wrapf(err, "failed to connect to container's attach socket: %v") } @@ -117,7 +115,7 @@ func redirectResponseToOutputStreams(outputStream, errorStream io.Writer, conn i case AttachPipeStderr: dst = errorStream default: - logrus.Infof("Got unexpected attach type %+d", buf[0]) + logrus.Infof("Received unexpected attach type %+d", buf[0]) } if dst != nil { diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 6a4704ec..085bdc61 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -98,21 +98,21 @@ type imageDecomposeStruct struct { transport string } -func (k *KpodImage) assembleFqName() string { +func (k *Image) assembleFqName() string { return fmt.Sprintf("%s/%s:%s", k.Registry, k.ImageName, k.Tag) } -func (k *KpodImage) assembleFqNameTransport() string { +func (k *Image) assembleFqNameTransport() string { return fmt.Sprintf("%s%s/%s:%s", k.Transport, k.Registry, k.ImageName, k.Tag) } -//KpodImage describes basic attributes of an image -type KpodImage struct { - Name string - ID string - fqname string - hasImageLocal bool - Runtime +//Image describes basic attributes of an image +type Image struct { + Name string + ID string + fqname string + hasImageLocal bool + runtime *Runtime Registry string ImageName string Tag string @@ -123,20 +123,20 @@ type KpodImage struct { } // NewImage creates a new image object based on its name -func (r *Runtime) NewImage(name string) KpodImage { - return KpodImage{ +func (r *Runtime) NewImage(name string) Image { + return Image{ Name: name, - Runtime: *r, + runtime: r, } } // GetImageID returns the image ID of the image -func (k *KpodImage) GetImageID() (string, error) { +func (k *Image) GetImageID() (string, error) { if k.ID != "" { return k.ID, nil } image, _ := k.GetFQName() - img, err := k.Runtime.GetImage(image) + img, err := k.runtime.GetImage(image) if err != nil { return "", err } @@ -144,20 +144,19 @@ func (k *KpodImage) GetImageID() (string, error) { } // GetFQName returns the fully qualified image name if it can be determined -func (k *KpodImage) GetFQName() (string, error) { +func (k *Image) GetFQName() (string, error) { // Check if the fqname has already been found if k.fqname != "" { return k.fqname, nil } - err := k.Decompose() - if err != nil { + if err := k.Decompose(); err != nil { return "", err } k.fqname = k.assembleFqName() return k.fqname, nil } -func (k *KpodImage) findImageOnRegistry() error { +func (k *Image) findImageOnRegistry() error { searchRegistries, err := GetRegistries() if err != nil { @@ -178,14 +177,14 @@ func (k *KpodImage) findImageOnRegistry() error { } // GetManifest tries to GET an images manifest, returns nil on success and err on failure -func (k *KpodImage) GetManifest() error { +func (k *Image) GetManifest() error { pullRef, err := alltransports.ParseImageName(k.assembleFqNameTransport()) if err != nil { - return errors.Errorf("unable to parse '%s'", k.assembleFqName()) + return errors.Errorf("unable to parse1 '%s'", k.assembleFqName()) } imageSource, err := pullRef.NewImageSource(nil) if err != nil { - return errors.Errorf("unable to create new image source") + return errors.Wrapf(err, "unable to create new image source") } _, _, err = imageSource.GetManifest() if err == nil { @@ -195,7 +194,10 @@ func (k *KpodImage) GetManifest() error { } //Decompose breaks up an image name into its parts -func (k *KpodImage) Decompose() error { +func (k *Image) Decompose() error { + if k.beenDecomposed { + return nil + } k.beenDecomposed = true k.Transport = "docker://" decomposeName := k.Name @@ -209,7 +211,7 @@ func (k *KpodImage) Decompose() error { if k.Transport == "dir:" { return nil } - var imageError = fmt.Sprintf("unable to parse '%s'\n", k.Name) + var imageError = fmt.Sprintf("unable to parse '%s'\n", decomposeName) imgRef, err := reference.Parse(decomposeName) if err != nil { return errors.Wrapf(err, imageError) @@ -262,14 +264,14 @@ func (k *KpodImage) Decompose() error { } // HasImageLocal returns a bool true if the image is already pulled -func (k *KpodImage) HasImageLocal() bool { - _, err := k.Runtime.GetImage(k.Name) +func (k *Image) HasImageLocal() bool { + _, err := k.runtime.GetImage(k.Name) if err == nil { return true } fqname, _ := k.GetFQName() - _, err = k.Runtime.GetImage(fqname) + _, err = k.runtime.GetImage(fqname) if err == nil { return true } @@ -277,7 +279,7 @@ func (k *KpodImage) HasImageLocal() bool { } // HasLatest determines if we have the latest image local -func (k *KpodImage) HasLatest() (bool, error) { +func (k *Image) HasLatest() (bool, error) { if !k.HasImageLocal() { return false, nil } @@ -297,7 +299,7 @@ func (k *KpodImage) HasLatest() (bool, error) { } // Pull is a wrapper function to pull and image -func (k *KpodImage) Pull() error { +func (k *Image) Pull() error { // If the image hasn't been decomposed yet if !k.beenDecomposed { err := k.Decompose() @@ -305,7 +307,7 @@ func (k *KpodImage) Pull() error { return err } } - k.Runtime.PullImage(k.PullName, CopyOptions{Writer: os.Stdout, SignaturePolicyPath: k.Runtime.config.SignaturePolicyPath}) + k.runtime.PullImage(k.PullName, CopyOptions{Writer: os.Stdout, SignaturePolicyPath: k.runtime.config.SignaturePolicyPath}) return nil } diff --git a/libpod/storage.go b/libpod/storage.go index f0bf9e9c..5e18aaf5 100644 --- a/libpod/storage.go +++ b/libpod/storage.go @@ -57,7 +57,7 @@ func (metadata *RuntimeContainerMetadata) SetMountLabel(mountLabel string) { } // CreateContainerStorage creates the storage end of things. We already have the container spec created -// TO-DO We should be passing in an KpodImage object in the future. +// TO-DO We should be passing in an Image object in the future. func (r *storageService) CreateContainerStorage(systemContext *types.SystemContext, imageName, imageID, containerName, containerID, mountLabel string) (ContainerInfo, error) { var ref types.ImageReference if imageName == "" && imageID == "" { diff --git a/libpod/util.go b/libpod/util.go index 77f73468..0270af07 100644 --- a/libpod/util.go +++ b/libpod/util.go @@ -14,10 +14,10 @@ func WriteFile(content string, path string) error { } } f, err := os.Create(path) - defer f.Close() if err != nil { return err } + defer f.Close() f.WriteString(content) f.Sync() return nil diff --git a/test/kpod_create.bats b/test/kpod_create.bats new file mode 100644 index 00000000..46a460ec --- /dev/null +++ b/test/kpod_create.bats @@ -0,0 +1,24 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +ALPINE="docker.io/library/alpine:latest" + +@test "create a container based on local image" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} create docker.io/library/busybox:latest ls + echo "$output" + [ "$status" -eq 0 ] +} + +@test "create a container based on a remote image" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} create ${ALPINE} ls + echo "$output" + [ "$status" -eq 0 ] +} diff --git a/test/kpod_run.bats b/test/kpod_run.bats new file mode 100644 index 00000000..4f54c8df --- /dev/null +++ b/test/kpod_run.bats @@ -0,0 +1,24 @@ +#!/usr/bin/env bats + +load helpers + +function teardown() { + cleanup_test +} + +ALPINE="docker.io/library/alpine:latest" + +@test "run a container based on local image" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} pull docker.io/library/busybox:latest + echo "$output" + [ "$status" -eq 0 ] + run ${KPOD_BINARY} ${KPOD_OPTIONS} run docker.io/library/busybox:latest ls + echo "$output" + [ "$status" -eq 0 ] +} + +@test "run a container based on a remote image" { + run ${KPOD_BINARY} ${KPOD_OPTIONS} run ${ALPINE} ls + echo "$output" + [ "$status" -eq 0 ] +} diff --git a/transfer.md b/transfer.md index 12ea7432..95fd7a2e 100644 --- a/transfer.md +++ b/transfer.md @@ -41,7 +41,7 @@ There are other equivalents for these tools | :---: | :---: | | `docker attach` | [`kpod exec`](./docs/kpod-attach.1.md) ***| | `docker build` | [`buildah bud`](https://github.com/projectatomic/buildah/blob/master/docs/buildah-bud.md) | -| `docker cp` | [`kpod mount`](./docs/kpod-cp.1.md) *** | +| `docker cp` | [`kpod mount`](./docs/kpod-cp.1.md) **** | | `docker create` | [`kpod create`](./docs/kpod-create.1.md) | | `docker diff` | [`kpod diff`](./docs/kpod-diff.1.md) | | `docker export` | [`kpod export`](./docs/kpod-export.1.md) | @@ -64,7 +64,7 @@ There are other equivalents for these tools | `docker tag` | [`kpod tag`](./docs/kpod-tag.1.md) | | `docker unpause`| [`kpod unpause`](./docs/kpod-unpause.1.md)| | `docker version`| [`kpod version`](./docs/kpod-version.1.md)| -| `docker wait` | [`kpod wait`](./docs/kpod-wait.1.md)| +| `docker wait` | [`kpod wait`](./docs/kpod-wait.1.md) | *** Use `kpod exec` to enter a container and `kpod logs` to view the output of pid 1 of a container. **** Use mount to take advantage of the entire linux tool chain rather then just cp. Read [`here`](./docs/kpod-cp.1.md) for more information.