diff --git a/cmd/kpod/create.go b/cmd/kpod/create.go index fc6e519fac03..3548ad7dfc64 100644 --- a/cmd/kpod/create.go +++ b/cmd/kpod/create.go @@ -1,6 +1,7 @@ package main import ( + "encoding/json" "fmt" "os" "strconv" @@ -36,91 +37,92 @@ var ( ) type createResourceConfig struct { - blkioWeight uint16 // blkio-weight - blkioWeightDevice []string // blkio-weight-device - 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 - memorySwappiness int // memory-swappiness - oomScoreAdj int //oom-score-adj - pidsLimit int64 // pids-limit - shmSize string - ulimit []string //ulimit + BlkioWeight uint16 // blkio-weight + BlkioWeightDevice []string // blkio-weight-device + 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 + MemorySwappiness int // memory-swappiness + OomScoreAdj int //oom-score-adj + PidsLimit int64 // pids-limit + ShmSize string + Ulimit []string //ulimit } type createConfig struct { - runtime *libpod.Runtime - 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 map[string]string //env - expose []string //expose - groupAdd []uint32 // group-add - hostname string //hostname - image string - interactive bool //interactive - ipcMode container.IpcMode //ipc - 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 - netMode container.NetworkMode //net - network string //network - networkAlias []string //network-alias - pidMode container.PidMode //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 - shmDir string - 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 - utsMode container.UTSMode //uts - volumes []string //volume - workDir string //workdir - mountLabel string //SecurityOpts - processLabel string //SecurityOpts - noNewPrivileges bool //SecurityOpts - apparmorProfile string //SecurityOpts - seccompProfilePath string //SecurityOpts + Runtime *libpod.Runtime + 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 map[string]string //env + Expose []string //expose + GroupAdd []uint32 // group-add + Hostname string //hostname + Image string + Interactive bool //interactive + IpcMode container.IpcMode //ipc + 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 + NetMode container.NetworkMode //net + Network string //network + NetworkAlias []string //network-alias + PidMode container.PidMode //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 + ShmDir string + 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 + UtsMode container.UTSMode //uts + Volumes []string //volume + WorkDir string //workdir + MountLabel string //SecurityOpts + ProcessLabel string //SecurityOpts + NoNewPrivileges bool //SecurityOpts + ApparmorProfile string //SecurityOpts + SeccompProfilePath string //SecurityOpts + SecurityOpts []string } var createDescription = "Creates a new container from the given image or" + @@ -160,7 +162,7 @@ func createCmd(c *cli.Context) error { } // Deal with the image after all the args have been checked - createImage := runtime.NewImage(createConfig.image) + createImage := runtime.NewImage(createConfig.Image) createImage.LocalName, _ = createImage.GetLocalImageName() if createImage.LocalName == "" { // The image wasnt found by the user input'd name or its fqname @@ -203,13 +205,21 @@ func createCmd(c *cli.Context) error { } // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) - options = append(options, libpod.WithSELinuxLabels(createConfig.processLabel, createConfig.mountLabel)) - options = append(options, libpod.WithShmDir(createConfig.shmDir)) + options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) + options = append(options, libpod.WithShmDir(createConfig.ShmDir)) ctr, err := runtime.NewContainer(runtimeSpec, options...) if err != nil { return err } + createConfigJSON, err := json.Marshal(createConfig) + if err != nil { + return err + } + if err := ctr.AddArtifact("create-config", createConfigJSON); err != nil { + return err + } + logrus.Debug("new container created ", ctr.ID()) if c.String("cidfile") != "" { @@ -229,29 +239,29 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error { err error ) - if config.pidMode.IsHost() { + if config.PidMode.IsHost() { labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.pidMode.IsContainer() { - ctr, err := config.runtime.LookupContainer(config.pidMode.Container()) + } else if config.PidMode.IsContainer() { + ctr, err := config.Runtime.LookupContainer(config.PidMode.Container()) if err != nil { - return errors.Wrapf(err, "container %q not found", config.pidMode.Container()) + return errors.Wrapf(err, "container %q not found", config.PidMode.Container()) } labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) } - if config.ipcMode.IsHost() { + if config.IpcMode.IsHost() { labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if config.ipcMode.IsContainer() { - ctr, err := config.runtime.LookupContainer(config.ipcMode.Container()) + } else if config.IpcMode.IsContainer() { + ctr, err := config.Runtime.LookupContainer(config.IpcMode.Container()) if err != nil { - return errors.Wrapf(err, "container %q not found", config.ipcMode.Container()) + return errors.Wrapf(err, "container %q not found", config.IpcMode.Container()) } labelOpts = append(labelOpts, label.DupSecOpt(ctr.ProcessLabel())...) } for _, opt := range securityOpts { if opt == "no-new-privileges" { - config.noNewPrivileges = true + config.NoNewPrivileges = true } else { con := strings.SplitN(opt, "=", 2) if len(con) != 2 { @@ -262,25 +272,25 @@ func parseSecurityOpt(config *createConfig, securityOpts []string) error { case "label": labelOpts = append(labelOpts, con[1]) case "apparmor": - config.apparmorProfile = con[1] + config.ApparmorProfile = con[1] case "seccomp": - config.seccompProfilePath = con[1] + config.SeccompProfilePath = con[1] default: return fmt.Errorf("Invalid --security-opt 2: %q", opt) } } } - if config.seccompProfilePath == "" { + if config.SeccompProfilePath == "" { if _, err := os.Stat(seccompDefaultPath); err != nil { if !os.IsNotExist(err) { return errors.Wrapf(err, "can't check if %q exists", seccompDefaultPath) } } else { - config.seccompProfilePath = seccompDefaultPath + config.SeccompProfilePath = seccompDefaultPath } } - config.processLabel, config.mountLabel, err = label.InitLabels(labelOpts) + config.ProcessLabel, config.MountLabel, err = label.InitLabels(labelOpts) return err } @@ -403,88 +413,89 @@ func parseCreateOpts(c *cli.Context, runtime *libpod.Runtime) (*createConfig, er } config := &createConfig{ - runtime: runtime, - 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("expose"), - 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"), - ipcMode: ipcMode, - netMode: container.NetworkMode(c.String("network")), - utsMode: utsMode, - pidMode: pidMode, - 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, - blkioWeightDevice: 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, - memorySwappiness: c.Int("memory-swappiness"), - kernelMemory: memoryKernel, - oomScoreAdj: c.Int("oom-score-adj"), - - pidsLimit: c.Int64("pids-limit"), - ulimit: c.StringSlice("ulimit"), + Runtime: runtime, + 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("expose"), + 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"), + IpcMode: ipcMode, + NetMode: container.NetworkMode(c.String("network")), + UtsMode: utsMode, + PidMode: pidMode, + 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, + BlkioWeightDevice: 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, + MemorySwappiness: c.Int("memory-swappiness"), + KernelMemory: memoryKernel, + OomScoreAdj: c.Int("oom-score-adj"), + + PidsLimit: c.Int64("pids-limit"), + Ulimit: c.StringSlice("ulimit"), }, - rm: c.Bool("rm"), - shmDir: shmDir, - 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: tty, - user: uid, - group: gid, - volumes: c.StringSlice("volume"), - workDir: c.String("workdir"), - } - - if !config.privileged { + Rm: c.Bool("rm"), + ShmDir: shmDir, + 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: tty, + User: uid, + Group: gid, + Volumes: c.StringSlice("volume"), + WorkDir: c.String("workdir"), + } + + if !config.Privileged { if err := parseSecurityOpt(config, c.StringSlice("security-opt")); err != nil { return nil, err } } + config.SecurityOpts = c.StringSlice("security-opt") warnings, err := verifyContainerResources(config, false) if err != nil { return nil, err diff --git a/cmd/kpod/create_cli.go b/cmd/kpod/create_cli.go index 9686b89a7987..a162cb319fc0 100644 --- a/cmd/kpod/create_cli.go +++ b/cmd/kpod/create_cli.go @@ -111,131 +111,131 @@ func verifyContainerResources(config *createConfig, update bool) ([]string, erro sysInfo := sysinfo.New(true) // memory subsystem checks and adjustments - if config.resources.memory != 0 && config.resources.memory < linuxMinMemory { + if config.Resources.Memory != 0 && config.Resources.Memory < linuxMinMemory { return warnings, fmt.Errorf("minimum memory limit allowed is 4MB") } - if config.resources.memory > 0 && !sysInfo.MemoryLimit { + if config.Resources.Memory > 0 && !sysInfo.MemoryLimit { warnings = addWarning(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.resources.memory = 0 - config.resources.memorySwap = -1 + config.Resources.Memory = 0 + config.Resources.MemorySwap = -1 } - if config.resources.memory > 0 && config.resources.memorySwap != -1 && !sysInfo.SwapLimit { + if config.Resources.Memory > 0 && config.Resources.MemorySwap != -1 && !sysInfo.SwapLimit { warnings = addWarning(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.") - config.resources.memorySwap = -1 + config.Resources.MemorySwap = -1 } - if config.resources.memory > 0 && config.resources.memorySwap > 0 && config.resources.memorySwap < config.resources.memory { + if config.Resources.Memory > 0 && config.Resources.MemorySwap > 0 && config.Resources.MemorySwap < config.Resources.Memory { return warnings, fmt.Errorf("minimum memoryswap limit should be larger than memory limit, see usage") } - if config.resources.memory == 0 && config.resources.memorySwap > 0 && !update { - return warnings, fmt.Errorf("you should always set the Memory limit when using Memoryswap limit, see usage") + if config.Resources.Memory == 0 && config.Resources.MemorySwap > 0 && !update { + return warnings, fmt.Errorf("you should always set the memory limit when using memoryswap limit, see usage") } - if config.resources.memorySwappiness != -1 { + if config.Resources.MemorySwappiness != -1 { if !sysInfo.MemorySwappiness { msg := "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded." warnings = addWarning(warnings, msg) - config.resources.memorySwappiness = -1 + config.Resources.MemorySwappiness = -1 } else { - swappiness := config.resources.memorySwappiness + swappiness := config.Resources.MemorySwappiness if swappiness < -1 || swappiness > 100 { return warnings, fmt.Errorf("invalid value: %v, valid memory swappiness range is 0-100", swappiness) } } } - if config.resources.memoryReservation > 0 && !sysInfo.MemoryReservation { + if config.Resources.MemoryReservation > 0 && !sysInfo.MemoryReservation { warnings = addWarning(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.resources.memoryReservation = 0 + config.Resources.MemoryReservation = 0 } - if config.resources.memoryReservation > 0 && config.resources.memoryReservation < linuxMinMemory { + if config.Resources.MemoryReservation > 0 && config.Resources.MemoryReservation < linuxMinMemory { return warnings, fmt.Errorf("minimum memory reservation allowed is 4MB") } - if config.resources.memory > 0 && config.resources.memoryReservation > 0 && config.resources.memory < config.resources.memoryReservation { + if config.Resources.Memory > 0 && config.Resources.MemoryReservation > 0 && config.Resources.Memory < config.Resources.MemoryReservation { return warnings, fmt.Errorf("minimum memory limit can not be less than memory reservation limit, see usage") } - if config.resources.kernelMemory > 0 && !sysInfo.KernelMemory { + if config.Resources.KernelMemory > 0 && !sysInfo.KernelMemory { warnings = addWarning(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.") - config.resources.kernelMemory = 0 + config.Resources.KernelMemory = 0 } - if config.resources.kernelMemory > 0 && config.resources.kernelMemory < linuxMinMemory { + if config.Resources.KernelMemory > 0 && config.Resources.KernelMemory < linuxMinMemory { return warnings, fmt.Errorf("minimum kernel memory limit allowed is 4MB") } - if config.resources.disableOomKiller == true && !sysInfo.OomKillDisable { + if config.Resources.DisableOomKiller == true && !sysInfo.OomKillDisable { // only produce warnings if the setting wasn't to *disable* the OOM Kill; no point // warning the caller if they already wanted the feature to be off warnings = addWarning(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.") - config.resources.disableOomKiller = false + config.Resources.DisableOomKiller = false } - if config.resources.pidsLimit != 0 && !sysInfo.PidsLimit { + if config.Resources.PidsLimit != 0 && !sysInfo.PidsLimit { warnings = addWarning(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.") - config.resources.pidsLimit = 0 + config.Resources.PidsLimit = 0 } - if config.resources.cpuShares > 0 && !sysInfo.CPUShares { + if config.Resources.CpuShares > 0 && !sysInfo.CPUShares { warnings = addWarning(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.") - config.resources.cpuShares = 0 + config.Resources.CpuShares = 0 } - if config.resources.cpuPeriod > 0 && !sysInfo.CPUCfsPeriod { + if config.Resources.CpuPeriod > 0 && !sysInfo.CPUCfsPeriod { warnings = addWarning(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.") - config.resources.cpuPeriod = 0 + config.Resources.CpuPeriod = 0 } - if config.resources.cpuPeriod != 0 && (config.resources.cpuPeriod < 1000 || config.resources.cpuPeriod > 1000000) { + if config.Resources.CpuPeriod != 0 && (config.Resources.CpuPeriod < 1000 || config.Resources.CpuPeriod > 1000000) { return warnings, fmt.Errorf("CPU cfs period can not be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)") } - if config.resources.cpuQuota > 0 && !sysInfo.CPUCfsQuota { + if config.Resources.CpuQuota > 0 && !sysInfo.CPUCfsQuota { warnings = addWarning(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.") - config.resources.cpuQuota = 0 + config.Resources.CpuQuota = 0 } - if config.resources.cpuQuota > 0 && config.resources.cpuQuota < 1000 { + if config.Resources.CpuQuota > 0 && config.Resources.CpuQuota < 1000 { return warnings, fmt.Errorf("CPU cfs quota can not be less than 1ms (i.e. 1000)") } // cpuset subsystem checks and adjustments - if (config.resources.cpusetCpus != "" || config.resources.cpusetMems != "") && !sysInfo.Cpuset { + if (config.Resources.CpusetCpus != "" || config.Resources.CpusetMems != "") && !sysInfo.Cpuset { warnings = addWarning(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. Cpuset discarded.") - config.resources.cpusetCpus = "" - config.resources.cpusetMems = "" + config.Resources.CpusetCpus = "" + config.Resources.CpusetMems = "" } - cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(config.resources.cpusetCpus) + cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(config.Resources.CpusetCpus) if err != nil { - return warnings, fmt.Errorf("invalid value %s for cpuset cpus", config.resources.cpusetCpus) + return warnings, fmt.Errorf("invalid value %s for cpuset cpus", config.Resources.CpusetCpus) } if !cpusAvailable { - return warnings, fmt.Errorf("requested CPUs are not available - requested %s, available: %s", config.resources.cpusetCpus, sysInfo.Cpus) + return warnings, fmt.Errorf("requested CPUs are not available - requested %s, available: %s", config.Resources.CpusetCpus, sysInfo.Cpus) } - memsAvailable, err := sysInfo.IsCpusetMemsAvailable(config.resources.cpusetMems) + memsAvailable, err := sysInfo.IsCpusetMemsAvailable(config.Resources.CpusetMems) if err != nil { - return warnings, fmt.Errorf("invalid value %s for cpuset mems", config.resources.cpusetMems) + return warnings, fmt.Errorf("invalid value %s for cpuset mems", config.Resources.CpusetMems) } if !memsAvailable { - return warnings, fmt.Errorf("requested memory nodes are not available - requested %s, available: %s", config.resources.cpusetMems, sysInfo.Mems) + return warnings, fmt.Errorf("requested memory nodes are not available - requested %s, available: %s", config.Resources.CpusetMems, sysInfo.Mems) } // blkio subsystem checks and adjustments - if config.resources.blkioWeight > 0 && !sysInfo.BlkioWeight { + if config.Resources.BlkioWeight > 0 && !sysInfo.BlkioWeight { warnings = addWarning(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.") - config.resources.blkioWeight = 0 + config.Resources.BlkioWeight = 0 } - if config.resources.blkioWeight > 0 && (config.resources.blkioWeight < 10 || config.resources.blkioWeight > 1000) { + if config.Resources.BlkioWeight > 0 && (config.Resources.BlkioWeight < 10 || config.Resources.BlkioWeight > 1000) { return warnings, fmt.Errorf("range of blkio weight is from 10 to 1000") } - if len(config.resources.blkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { + if len(config.Resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { warnings = addWarning(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.") - config.resources.blkioWeightDevice = []string{} + config.Resources.BlkioWeightDevice = []string{} } - if len(config.resources.deviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { + if len(config.Resources.DeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded") - config.resources.deviceReadBps = []string{} + config.Resources.DeviceReadBps = []string{} } - if len(config.resources.deviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { + if len(config.Resources.DeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.") - config.resources.deviceWriteBps = []string{} + config.Resources.DeviceWriteBps = []string{} } - if len(config.resources.deviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { + if len(config.Resources.DeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { warnings = addWarning(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.") - config.resources.deviceReadIOps = []string{} + config.Resources.DeviceReadIOps = []string{} } - if len(config.resources.deviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { + if len(config.Resources.DeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { warnings = addWarning(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.") - config.resources.deviceWriteIOps = []string{} + config.Resources.DeviceWriteIOps = []string{} } return warnings, nil diff --git a/cmd/kpod/images.go b/cmd/kpod/images.go index 7b020c984d5f..1cf69c96df82 100644 --- a/cmd/kpod/images.go +++ b/cmd/kpod/images.go @@ -6,7 +6,6 @@ import ( "strings" "time" - "github.com/containers/image/types" "github.com/containers/storage" "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" @@ -208,18 +207,18 @@ func getImagesTemplateOutput(runtime *libpod.Runtime, images []*storage.Image, o } } - info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img) - if info != nil { - createdTime = info.Created + imgData, _ := runtime.GetImageInspectInfo(*img) + if imgData != nil { + createdTime = *imgData.Created } params := imagesTemplateParams{ Repository: repository, Tag: tag, ID: imageID, - Digest: imageDigest, + Digest: imgData.Digest, Created: units.HumanDuration(time.Since((createdTime))) + " ago", - Size: units.HumanSizeWithPrecision(float64(size), 3), + Size: units.HumanSizeWithPrecision(float64(imgData.Size), 3), } imagesOutput = append(imagesOutput, params) } @@ -231,17 +230,17 @@ func getImagesJSONOutput(runtime *libpod.Runtime, images []*storage.Image) (imag for _, img := range images { createdTime := img.Created - info, imageDigest, size, _ := runtime.InfoAndDigestAndSize(*img) - if info != nil { - createdTime = info.Created + imgData, _ := runtime.GetImageInspectInfo(*img) + if imgData != nil { + createdTime = *imgData.Created } params := imagesJSONParams{ ID: img.ID, Name: img.Names, - Digest: imageDigest, + Digest: imgData.Digest, Created: createdTime, - Size: size, + Size: imgData.Size, } imagesOutput = append(imagesOutput, params) } @@ -274,7 +273,7 @@ func generateImagesOutput(runtime *libpod.Runtime, images []*storage.Image, opts func generateImagesFilter(params *libpod.ImageFilterParams, filterType string) libpod.ImageFilter { switch filterType { case "label": - return func(image *storage.Image, info *types.ImageInspectInfo) bool { + return func(image *storage.Image, info *libpod.ImageData) bool { if params == nil || params.Label == "" { return true } @@ -291,21 +290,21 @@ func generateImagesFilter(params *libpod.ImageFilterParams, filterType string) l return false } case "before-image": - return func(image *storage.Image, info *types.ImageInspectInfo) bool { + return func(image *storage.Image, info *libpod.ImageData) bool { if params == nil || params.BeforeImage.IsZero() { return true } return info.Created.Before(params.BeforeImage) } case "since-image": - return func(image *storage.Image, info *types.ImageInspectInfo) bool { + return func(image *storage.Image, info *libpod.ImageData) bool { if params == nil || params.SinceImage.IsZero() { return true } return info.Created.After(params.SinceImage) } case "dangling": - return func(image *storage.Image, info *types.ImageInspectInfo) bool { + return func(image *storage.Image, info *libpod.ImageData) bool { if params == nil || params.Dangling == "" { return true } @@ -318,14 +317,14 @@ func generateImagesFilter(params *libpod.ImageFilterParams, filterType string) l return false } case "reference": - return func(image *storage.Image, info *types.ImageInspectInfo) bool { + return func(image *storage.Image, info *libpod.ImageData) bool { if params == nil || params.ReferencePattern == "" { return true } return libpod.MatchesReference(params.ImageName, params.ReferencePattern) } case "image-input": - return func(image *storage.Image, info *types.ImageInspectInfo) bool { + return func(image *storage.Image, info *libpod.ImageData) bool { if params == nil || params.ImageInput == "" { return true } diff --git a/cmd/kpod/inspect.go b/cmd/kpod/inspect.go index a70e285ac3e8..e2f9ec97eca6 100644 --- a/cmd/kpod/inspect.go +++ b/cmd/kpod/inspect.go @@ -1,10 +1,12 @@ package main import ( + "encoding/json" + + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/projectatomic/libpod/cmd/kpod/formats" - "github.com/projectatomic/libpod/libkpod" - "github.com/projectatomic/libpod/libpod/images" + "github.com/projectatomic/libpod/libpod" "github.com/urfave/cli" ) @@ -53,56 +55,63 @@ func inspectCmd(c *cli.Context) error { return err } - itemType := c.String("type") - size := c.Bool("size") + runtime, err := getRuntime(c) + if err != nil { + return errors.Wrapf(err, "error creating libpod runtime") + } + defer runtime.Shutdown(false) - switch itemType { - case inspectTypeContainer: - case inspectTypeImage: - case inspectAll: - default: + if c.String("type") != inspectTypeContainer && c.String("type") != inspectTypeImage && c.String("type") != inspectAll { return errors.Errorf("the only recognized types are %q, %q, and %q", inspectTypeContainer, inspectTypeImage, inspectAll) } name := args[0] - config, err := getConfig(c) - if err != nil { - return errors.Wrapf(err, "Could not get config") - } - server, err := libkpod.New(config) - if err != nil { - return errors.Wrapf(err, "could not get container server") - } - defer server.Shutdown() - if err = server.Update(); err != nil { - return errors.Wrapf(err, "could not update list of containers") - } - outputFormat := c.String("format") var data interface{} - switch itemType { + switch c.String("type") { case inspectTypeContainer: - data, err = server.GetContainerData(name, size) + ctr, err := runtime.LookupContainer(name) + if err != nil { + return errors.Wrapf(err, "error looking up container %q", name) + } + libpodInspectData, err := ctr.Inspect(c.Bool("size")) + if err != nil { + return errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + } + data, err = getCtrInspectInfo(ctr, libpodInspectData) if err != nil { - return errors.Wrapf(err, "error parsing container data") + return errors.Wrapf(err, "error parsing container data %q", ctr.ID()) } case inspectTypeImage: - data, err = images.GetData(server.Store(), name) + image, err := runtime.GetImage(name) if err != nil { - return errors.Wrapf(err, "error parsing image data") + return errors.Wrapf(err, "error getting image %q", name) + } + data, err = runtime.GetImageInspectInfo(*image) + if err != nil { + return errors.Wrapf(err, "error parsing image data %q", image.ID) } case inspectAll: - ctrData, err := server.GetContainerData(name, size) + ctr, err := runtime.LookupContainer(name) if err != nil { - imgData, err := images.GetData(server.Store(), name) + image, err := runtime.GetImage(name) if err != nil { - return errors.Wrapf(err, "error parsing container or image data") + return errors.Wrapf(err, "error getting image %q", name) + } + data, err = runtime.GetImageInspectInfo(*image) + if err != nil { + return errors.Wrapf(err, "error parsing image data %q", image.ID) } - data = imgData - } else { - data = ctrData + libpodInspectData, err := ctr.Inspect(c.Bool("size")) + if err != nil { + return errors.Wrapf(err, "error getting libpod container inspect data %q", ctr.ID) + } + data, err = getCtrInspectInfo(ctr, libpodInspectData) + if err != nil { + return errors.Wrapf(err, "error parsing container data %q", ctr.ID) + } } } @@ -118,3 +127,236 @@ func inspectCmd(c *cli.Context) error { formats.Writer(out).Out() return nil } + +func getCtrInspectInfo(ctr *libpod.Container, ctrInspectData *libpod.ContainerInspectData) (*ContainerData, error) { + config := ctr.Config() + spec := config.Spec + + cpus, mems, period, quota, realtimePeriod, realtimeRuntime, shares := getCPUInfo(spec) + blkioWeight, blkioWeightDevice, blkioReadBps, blkioWriteBps, blkioReadIOPS, blkioeWriteIOPS := getBLKIOInfo(spec) + memKernel, memReservation, memSwap, memSwappiness, memDisableOOMKiller := getMemoryInfo(spec) + pidsLimit := getPidsInfo(spec) + cgroup := getCgroup(spec) + + artifact, err := ctr.GetArtifact("create-config") + if err != nil { + return nil, errors.Wrapf(err, "error getting artifact %q", ctr.ID()) + } + var createArtifact createConfig + if err := json.Unmarshal(artifact, &createArtifact); err != nil { + return nil, err + } + + data := &ContainerData{ + CtrInspectData: ctrInspectData, + HostConfig: &HostConfig{ + ConsoleSize: spec.Process.ConsoleSize, + OomScoreAdj: spec.Process.OOMScoreAdj, + CPUShares: shares, + BlkioWeight: blkioWeight, + BlkioWeightDevice: blkioWeightDevice, + BlkioDeviceReadBps: blkioReadBps, + BlkioDeviceWriteBps: blkioWriteBps, + BlkioDeviceReadIOps: blkioReadIOPS, + BlkioDeviceWriteIOps: blkioeWriteIOPS, + CPUPeriod: period, + CPUQuota: quota, + CPURealtimePeriod: realtimePeriod, + CPURealtimeRuntime: realtimeRuntime, + CPUSetCpus: cpus, + CPUSetMems: mems, + Devices: spec.Linux.Devices, + KernelMemory: memKernel, + MemoryReservation: memReservation, + MemorySwap: memSwap, + MemorySwappiness: memSwappiness, + OomKillDisable: memDisableOOMKiller, + PidsLimit: pidsLimit, + Privileged: spec.Process.NoNewPrivileges, + ReadonlyRootfs: spec.Root.Readonly, + Runtime: ctr.RuntimeName(), + NetworkMode: string(createArtifact.NetMode), + IpcMode: string(createArtifact.IpcMode), + Cgroup: cgroup, + UTSMode: string(createArtifact.UtsMode), + UsernsMode: createArtifact.NsUser, + GroupAdd: spec.Process.User.AdditionalGids, + ContainerIDFile: createArtifact.CidFile, + AutoRemove: createArtifact.Rm, + CapAdd: createArtifact.CapAdd, + CapDrop: createArtifact.CapDrop, + DNS: createArtifact.DnsServers, + DNSOptions: createArtifact.DnsOpt, + DNSSearch: createArtifact.DnsSearch, + PidMode: string(createArtifact.PidMode), + CgroupParent: createArtifact.CgroupParent, + ShmSize: createArtifact.Resources.ShmSize, + Memory: createArtifact.Resources.Memory, + Ulimits: createArtifact.Resources.Ulimit, + SecurityOpt: createArtifact.SecurityOpts, + }, + Config: &CtrConfig{ + Hostname: spec.Hostname, + User: spec.Process.User, + Env: spec.Process.Env, + Image: config.RootfsImageName, + WorkingDir: spec.Process.Cwd, + Labels: config.Labels, + Annotations: spec.Annotations, + Tty: spec.Process.Terminal, + OpenStdin: config.Stdin, + StopSignal: config.StopSignal, + Cmd: config.Spec.Process.Args, + Entrypoint: createArtifact.Entrypoint, + }, + } + return data, nil +} + +func getCPUInfo(spec *specs.Spec) (string, string, *uint64, *int64, *uint64, *int64, *uint64) { + if spec.Linux.Resources == nil { + return "", "", nil, nil, nil, nil, nil + } + cpu := spec.Linux.Resources.CPU + if cpu == nil { + return "", "", nil, nil, nil, nil, nil + } + return cpu.Cpus, cpu.Mems, cpu.Period, cpu.Quota, cpu.RealtimePeriod, cpu.RealtimeRuntime, cpu.Shares +} + +func getBLKIOInfo(spec *specs.Spec) (*uint16, []specs.LinuxWeightDevice, []specs.LinuxThrottleDevice, []specs.LinuxThrottleDevice, []specs.LinuxThrottleDevice, []specs.LinuxThrottleDevice) { + if spec.Linux.Resources == nil { + return nil, nil, nil, nil, nil, nil + } + blkio := spec.Linux.Resources.BlockIO + if blkio == nil { + return nil, nil, nil, nil, nil, nil + } + return blkio.Weight, blkio.WeightDevice, blkio.ThrottleReadBpsDevice, blkio.ThrottleWriteBpsDevice, blkio.ThrottleReadIOPSDevice, blkio.ThrottleWriteIOPSDevice +} + +func getMemoryInfo(spec *specs.Spec) (*int64, *int64, *int64, *uint64, *bool) { + if spec.Linux.Resources == nil { + return nil, nil, nil, nil, nil + } + memory := spec.Linux.Resources.Memory + if memory == nil { + return nil, nil, nil, nil, nil + } + return memory.Kernel, memory.Reservation, memory.Swap, memory.Swappiness, memory.DisableOOMKiller +} + +func getPidsInfo(spec *specs.Spec) *int64 { + if spec.Linux.Resources == nil { + return nil + } + pids := spec.Linux.Resources.Pids + if pids == nil { + return nil + } + return &pids.Limit +} + +func getCgroup(spec *specs.Spec) string { + cgroup := "host" + for _, ns := range spec.Linux.Namespaces { + if ns.Type == specs.CgroupNamespace && ns.Path != "" { + cgroup = "container" + } + } + return cgroup +} + +// ContainerData holds the kpod inspect data for a container +type ContainerData struct { + CtrInspectData *libpod.ContainerInspectData `json:"CtrInspectData"` + HostConfig *HostConfig `json:"HostConfig"` + Config *CtrConfig `json:"Config"` +} + +// LogConfig holds the log information for a container +type LogConfig struct { + Type string `json:"Type"` // TODO + Config map[string]string `json:"Config"` //idk type, TODO +} + +// HostConfig represents the host configuration for the container +type HostConfig struct { + ContainerIDFile string `json:"ContainerIDFile"` + LogConfig *LogConfig `json:"LogConfig"` //TODO + NetworkMode string `json:"NetworkMode"` + PortBindings map[string]struct{} `json:"PortBindings"` //TODO + AutoRemove bool `json:"AutoRemove"` + CapAdd []string `json:"CapAdd"` + CapDrop []string `json:"CapDrop"` + DNS []string `json:"DNS"` + DNSOptions []string `json:"DNSOptions"` + DNSSearch []string `json:"DNSSearch"` + ExtraHosts []string `json:"ExtraHosts"` + GroupAdd []uint32 `json:"GroupAdd"` + IpcMode string `json:"IpcMode"` + Cgroup string `json:"Cgroup"` + OomScoreAdj *int `json:"OomScoreAdj"` + PidMode string `json:"PidMode"` + Privileged bool `json:"Privileged"` + PublishAllPorts bool `json:"PublishAllPorts"` //TODO + ReadonlyRootfs bool `json:"ReadonlyRootfs"` + SecurityOpt []string `json:"SecurityOpt"` + UTSMode string `json:"UTSMode"` + UsernsMode string `json:"UsernsMode"` + ShmSize string `json:"ShmSize"` + Runtime string `json:"Runtime"` + ConsoleSize *specs.Box `json:"ConsoleSize"` + Isolation string `json:"Isolation"` //TODO + CPUShares *uint64 `json:"CPUSShares"` + Memory int64 `json:"Memory"` + NanoCpus int `json:"NanoCpus"` //check type, TODO + CgroupParent string `json:"CgroupParent"` + BlkioWeight *uint16 `json:"BlkioWeight"` + BlkioWeightDevice []specs.LinuxWeightDevice `json:"BlkioWeightDevice"` + BlkioDeviceReadBps []specs.LinuxThrottleDevice `json:"BlkioDeviceReadBps"` + BlkioDeviceWriteBps []specs.LinuxThrottleDevice `json:"BlkioDeviceWriteBps"` + BlkioDeviceReadIOps []specs.LinuxThrottleDevice `json:"BlkioDeviceReadIOps"` + BlkioDeviceWriteIOps []specs.LinuxThrottleDevice `json:"BlkioDeviceWriteIOps"` + CPUPeriod *uint64 `json:"CPUPeriod"` + CPUQuota *int64 `json:"CPUQuota"` + CPURealtimePeriod *uint64 `json:"CPURealtimePeriod"` + CPURealtimeRuntime *int64 `json:"CPURealtimeRuntime"` + CPUSetCpus string `json:"CPUSetCpus"` + CPUSetMems string `json:"CPUSetMems"` + Devices []specs.LinuxDevice `json:"Devices"` + DiskQuota int `json:"DiskQuota"` //check type, TODO + KernelMemory *int64 `json:"KernelMemory"` + MemoryReservation *int64 `json:"MemoryReservation"` + MemorySwap *int64 `json:"MemorySwap"` + MemorySwappiness *uint64 `json:"MemorySwappiness"` + OomKillDisable *bool `json:"OomKillDisable"` + PidsLimit *int64 `json:"PidsLimit"` + Ulimits []string `json:"Ulimits"` + CPUCount int `json:"CPUCount"` //check type, TODO + CPUPercent int `json:"CPUPercent"` //check type, TODO + IOMaximumIOps int `json:"IOMaximumIOps"` //check type, TODO + IOMaximumBandwidth int `json:"IOMaximumBandwidth"` //check type, TODO +} + +// CtrConfig holds information about the container configuration +type CtrConfig struct { + Hostname string `json:"Hostname"` + DomainName string `json:"Domainname"` //TODO + User specs.User `json:"User"` + AttachStdin bool `json:"AttachStdin"` //TODO + AttachStdout bool `json:"AttachStdout"` //TODO + AttachStderr bool `json:"AttachStderr"` //TODO + Tty bool `json:"Tty"` + OpenStdin bool `json:"OpenStdin"` + StdinOnce bool `json:"StdinOnce"` //TODO + Env []string `json:"Env"` + Cmd []string `json:"Cmd"` + Image string `json:"Image"` + Volumes map[string]struct{} `json:"Volumes"` + WorkingDir string `json:"WorkingDir"` + Entrypoint string `json:"Entrypoint"` + Labels map[string]string `json:"Labels"` + Annotations map[string]string `json:"Annotations"` + StopSignal uint `json:"StopSignal"` +} diff --git a/cmd/kpod/run.go b/cmd/kpod/run.go index 7e078f66aa2d..6142983ad1f8 100644 --- a/cmd/kpod/run.go +++ b/cmd/kpod/run.go @@ -39,7 +39,7 @@ func runCmd(c *cli.Context) error { return err } - createImage := runtime.NewImage(createConfig.image) + createImage := runtime.NewImage(createConfig.Image) createImage.LocalName, _ = createImage.GetLocalImageName() if createImage.LocalName == "" { // The image wasnt found by the user input'd name or its fqname @@ -89,8 +89,8 @@ func runCmd(c *cli.Context) error { // Gather up the options for NewContainer which consist of With... funcs options = append(options, libpod.WithRootFSFromImage(imageID, imageName, false)) - options = append(options, libpod.WithSELinuxLabels(createConfig.processLabel, createConfig.mountLabel)) - options = append(options, libpod.WithShmDir(createConfig.shmDir)) + options = append(options, libpod.WithSELinuxLabels(createConfig.ProcessLabel, createConfig.MountLabel)) + options = append(options, libpod.WithShmDir(createConfig.ShmDir)) ctr, err := runtime.NewContainer(runtimeSpec, options...) if err != nil { return err @@ -114,7 +114,7 @@ func runCmd(c *cli.Context) error { // to finish before exiting main var wg sync.WaitGroup - if !createConfig.detach { + if !createConfig.Detach { // We increment the wg counter because we need to do the attach wg.Add(1) // Attach to the running container @@ -133,13 +133,13 @@ func runCmd(c *cli.Context) error { if err := ctr.Start(); err != nil { return errors.Wrapf(err, "unable to start container %q", ctr.ID()) } - if createConfig.detach { + if createConfig.Detach { fmt.Printf("%s\n", ctr.ID()) return nil } wg.Wait() - if createConfig.rm { + if createConfig.Rm { return runtime.RemoveContainer(ctr, true) } return ctr.CleanupStorage() diff --git a/cmd/kpod/spec.go b/cmd/kpod/spec.go index b200ed77a56b..4e00f04ff32a 100644 --- a/cmd/kpod/spec.go +++ b/cmd/kpod/spec.go @@ -20,7 +20,7 @@ import ( ) func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) { - if !config.privileged { + if !config.Privileged { for _, mp := range []string{ "/proc/kcore", "/proc/latency_stats", @@ -47,12 +47,12 @@ func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) } func addPidNS(config *createConfig, g *generate.Generator) error { - pidMode := config.pidMode + pidMode := config.PidMode if pidMode.IsHost() { return g.RemoveLinuxNamespace(libpod.PIDNamespace) } if pidMode.IsContainer() { - ctr, err := config.runtime.LookupContainer(pidMode.Container()) + ctr, err := config.Runtime.LookupContainer(pidMode.Container()) if err != nil { return errors.Wrapf(err, "container %q not found", pidMode.Container()) } @@ -69,7 +69,7 @@ func addPidNS(config *createConfig, g *generate.Generator) error { } func addNetNS(config *createConfig, g *generate.Generator) error { - netMode := config.netMode + netMode := config.NetMode if netMode.IsHost() { return g.RemoveLinuxNamespace(libpod.NetNamespace) } @@ -80,7 +80,7 @@ func addNetNS(config *createConfig, g *generate.Generator) error { return libpod.ErrNotImplemented } if netMode.IsContainer() { - ctr, err := config.runtime.LookupContainer(netMode.ConnectedContainer()) + ctr, err := config.Runtime.LookupContainer(netMode.ConnectedContainer()) if err != nil { return errors.Wrapf(err, "container %q not found", netMode.ConnectedContainer()) } @@ -97,7 +97,7 @@ func addNetNS(config *createConfig, g *generate.Generator) error { } func addUTSNS(config *createConfig, g *generate.Generator) error { - utsMode := config.utsMode + utsMode := config.UtsMode if utsMode.IsHost() { return g.RemoveLinuxNamespace(libpod.UTSNamespace) } @@ -105,12 +105,12 @@ func addUTSNS(config *createConfig, g *generate.Generator) error { } func addIpcNS(config *createConfig, g *generate.Generator) error { - ipcMode := config.ipcMode + ipcMode := config.IpcMode if ipcMode.IsHost() { return g.RemoveLinuxNamespace(libpod.IPCNamespace) } if ipcMode.IsContainer() { - ctr, err := config.runtime.LookupContainer(ipcMode.Container()) + ctr, err := config.Runtime.LookupContainer(ipcMode.Container()) if err != nil { return errors.Wrapf(err, "container %q not found", ipcMode.Container()) } @@ -133,7 +133,7 @@ func addRlimits(config *createConfig, g *generate.Generator) error { err error ) - for _, u := range config.resources.ulimit { + for _, u := range config.Resources.Ulimit { if ul, err = units.ParseUlimit(u); err != nil { return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) } @@ -146,10 +146,10 @@ func addRlimits(config *createConfig, g *generate.Generator) error { func setupCapabilities(config *createConfig, configSpec *spec.Spec) error { var err error var caplist []string - if config.privileged { + if config.Privileged { caplist = caps.GetAllCapabilities() } else { - caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.capAdd, config.capDrop) + caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.CapAdd, config.CapDrop) if err != nil { return err } @@ -166,85 +166,85 @@ func setupCapabilities(config *createConfig, configSpec *spec.Spec) error { func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { g := generate.New() g.AddCgroupsMount("ro") - g.SetProcessCwd(config.workDir) - g.SetProcessArgs(config.command) - g.SetProcessTerminal(config.tty) + g.SetProcessCwd(config.WorkDir) + g.SetProcessArgs(config.Command) + g.SetProcessTerminal(config.Tty) // User and Group must go together - g.SetProcessUID(config.user) - g.SetProcessGID(config.group) - for _, gid := range config.groupAdd { + g.SetProcessUID(config.User) + g.SetProcessGID(config.Group) + for _, gid := range config.GroupAdd { g.AddProcessAdditionalGid(gid) } for key, val := range config.GetAnnotations() { g.AddAnnotation(key, val) } - g.SetRootReadonly(config.readOnlyRootfs) - g.SetHostname(config.hostname) - if config.hostname != "" { - g.AddProcessEnv("HOSTNAME", config.hostname) + g.SetRootReadonly(config.ReadOnlyRootfs) + g.SetHostname(config.Hostname) + if config.Hostname != "" { + g.AddProcessEnv("HOSTNAME", config.Hostname) } - for _, sysctl := range config.sysctl { + for _, sysctl := range config.Sysctl { s := strings.SplitN(sysctl, "=", 2) g.AddLinuxSysctl(s[0], s[1]) } // RESOURCES - MEMORY - if config.resources.memory != 0 { - g.SetLinuxResourcesMemoryLimit(config.resources.memory) + if config.Resources.Memory != 0 { + g.SetLinuxResourcesMemoryLimit(config.Resources.Memory) } - if config.resources.memoryReservation != 0 { - g.SetLinuxResourcesMemoryReservation(config.resources.memoryReservation) + if config.Resources.MemoryReservation != 0 { + g.SetLinuxResourcesMemoryReservation(config.Resources.MemoryReservation) } - if config.resources.memorySwap != 0 { - g.SetLinuxResourcesMemorySwap(config.resources.memorySwap) + if config.Resources.MemorySwap != 0 { + g.SetLinuxResourcesMemorySwap(config.Resources.MemorySwap) } - if config.resources.kernelMemory != 0 { - g.SetLinuxResourcesMemoryKernel(config.resources.kernelMemory) + if config.Resources.KernelMemory != 0 { + g.SetLinuxResourcesMemoryKernel(config.Resources.KernelMemory) } - if config.resources.memorySwappiness != -1 { - g.SetLinuxResourcesMemorySwappiness(uint64(config.resources.memorySwappiness)) + if config.Resources.MemorySwappiness != -1 { + g.SetLinuxResourcesMemorySwappiness(uint64(config.Resources.MemorySwappiness)) } - g.SetLinuxResourcesMemoryDisableOOMKiller(config.resources.disableOomKiller) - g.SetProcessOOMScoreAdj(config.resources.oomScoreAdj) + g.SetLinuxResourcesMemoryDisableOOMKiller(config.Resources.DisableOomKiller) + g.SetProcessOOMScoreAdj(config.Resources.OomScoreAdj) // RESOURCES - CPU - if config.resources.cpuShares != 0 { - g.SetLinuxResourcesCPUShares(config.resources.cpuShares) + if config.Resources.CpuShares != 0 { + g.SetLinuxResourcesCPUShares(config.Resources.CpuShares) } - if config.resources.cpuQuota != 0 { - g.SetLinuxResourcesCPUQuota(config.resources.cpuQuota) + if config.Resources.CpuQuota != 0 { + g.SetLinuxResourcesCPUQuota(config.Resources.CpuQuota) } - if config.resources.cpuPeriod != 0 { - g.SetLinuxResourcesCPUPeriod(config.resources.cpuPeriod) + if config.Resources.CpuPeriod != 0 { + g.SetLinuxResourcesCPUPeriod(config.Resources.CpuPeriod) } - if config.resources.cpuRtRuntime != 0 { - g.SetLinuxResourcesCPURealtimeRuntime(config.resources.cpuRtRuntime) + if config.Resources.CpuRtRuntime != 0 { + g.SetLinuxResourcesCPURealtimeRuntime(config.Resources.CpuRtRuntime) } - if config.resources.cpuRtPeriod != 0 { - g.SetLinuxResourcesCPURealtimePeriod(config.resources.cpuRtPeriod) + if config.Resources.CpuRtPeriod != 0 { + g.SetLinuxResourcesCPURealtimePeriod(config.Resources.CpuRtPeriod) } - if config.resources.cpus != "" { - g.SetLinuxResourcesCPUCpus(config.resources.cpus) + if config.Resources.Cpus != "" { + g.SetLinuxResourcesCPUCpus(config.Resources.Cpus) } - if config.resources.cpusetMems != "" { - g.SetLinuxResourcesCPUMems(config.resources.cpusetMems) + if config.Resources.CpusetMems != "" { + g.SetLinuxResourcesCPUMems(config.Resources.CpusetMems) } // SECURITY OPTS - g.SetProcessNoNewPrivileges(config.noNewPrivileges) - g.SetProcessApparmorProfile(config.apparmorProfile) - g.SetProcessSelinuxLabel(config.processLabel) - g.SetLinuxMountLabel(config.mountLabel) + g.SetProcessNoNewPrivileges(config.NoNewPrivileges) + g.SetProcessApparmorProfile(config.ApparmorProfile) + g.SetProcessSelinuxLabel(config.ProcessLabel) + g.SetLinuxMountLabel(config.MountLabel) blockAccessToKernelFilesystems(config, &g) // RESOURCES - PIDS - if config.resources.pidsLimit != 0 { - g.SetLinuxResourcesPidsLimit(config.resources.pidsLimit) + if config.Resources.PidsLimit != 0 { + g.SetLinuxResourcesPidsLimit(config.Resources.PidsLimit) } - for _, i := range config.tmpfs { + for _, i := range config.Tmpfs { options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"} spliti := strings.SplitN(i, ":", 2) if len(spliti) > 1 { @@ -257,7 +257,7 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { g.AddTmpfsMount(spliti[0], options) } - for name, val := range config.env { + for name, val := range config.Env { g.AddProcessEnv(name, val) } @@ -282,14 +282,14 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { } configSpec := g.Spec() - if config.seccompProfilePath != "" && config.seccompProfilePath != "unconfined" { - seccompProfile, err := ioutil.ReadFile(config.seccompProfilePath) + if config.SeccompProfilePath != "" && config.SeccompProfilePath != "unconfined" { + seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) if err != nil { - return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.seccompProfilePath) + return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath) } var seccompConfig spec.LinuxSeccomp if err := json.Unmarshal(seccompProfile, &seccompConfig); err != nil { - return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.seccompProfilePath) + return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.SeccompProfilePath) } configSpec.Linux.Seccomp = &seccompConfig } @@ -347,10 +347,10 @@ func createConfigToOCISpec(config *createConfig) (*spec.Spec, error) { func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) { bio := spec.LinuxBlockIO{} - bio.Weight = &c.resources.blkioWeight - if len(c.resources.blkioWeightDevice) > 0 { + bio.Weight = &c.Resources.BlkioWeight + if len(c.Resources.BlkioWeightDevice) > 0 { var lwds []spec.LinuxWeightDevice - for _, i := range c.resources.blkioWeightDevice { + for _, i := range c.Resources.BlkioWeightDevice { wd, err := validateweightDevice(i) if err != nil { return bio, errors.Wrapf(err, "invalid values for blkio-weight-device") @@ -364,29 +364,29 @@ func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) { lwds = append(lwds, lwd) } } - if len(c.resources.deviceReadBps) > 0 { - readBps, err := makeThrottleArray(c.resources.deviceReadBps) + 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 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 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 len(c.Resources.DeviceWriteIOps) > 0 { + writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps) if err != nil { return bio, err } @@ -401,7 +401,7 @@ func (c *createConfig) GetAnnotations() map[string]string { a := getDefaultAnnotations() // TODO - Which annotations do we want added by default // TODO - This should be added to the DB long term - if c.tty { + if c.Tty { a["io.kubernetes.cri-o.TTY"] = "true" } return a @@ -445,7 +445,7 @@ func getDefaultAnnotations() map[string]string { func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { var m []spec.Mount var options []string - for _, i := range c.volumes { + for _, i := range c.Volumes { // We need to handle SELinux options better here, specifically :Z spliti := strings.Split(i, ":") if len(spliti) > 2 { @@ -472,12 +472,12 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { options = append(options, "rw") } if foundz { - if err := label.Relabel(spliti[0], c.mountLabel, true); err != nil { + if err := label.Relabel(spliti[0], c.MountLabel, true); err != nil { return nil, errors.Wrapf(err, "relabel failed %q", spliti[0]) } } if foundZ { - if err := label.Relabel(spliti[0], c.mountLabel, false); err != nil { + if err := label.Relabel(spliti[0], c.MountLabel, false); err != nil { return nil, errors.Wrapf(err, "relabel failed %q", spliti[0]) } } @@ -495,10 +495,10 @@ func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) { return m, nil } -//GetTmpfsMounts takes user provided input for tmpfs mounts and creates Mount structs +//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 { + for _, i := range c.Tmpfs { // Default options if nothing passed options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"} spliti := strings.Split(i, ":") @@ -522,12 +522,12 @@ func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, er // Uncomment after talking to mheon about unimplemented funcs // options = append(options, libpod.WithLabels(c.labels)) - if c.interactive { + if c.Interactive { options = append(options, libpod.WithStdin()) } - if c.name != "" { - logrus.Debugf("appending name %s", c.name) - options = append(options, libpod.WithName(c.name)) + if c.Name != "" { + logrus.Debugf("appending name %s", c.Name) + options = append(options, libpod.WithName(c.Name)) } return options, nil diff --git a/cmd/kpod/spec_test.go b/cmd/kpod/spec_test.go index 799d6b23585f..01e1a4ad3716 100644 --- a/cmd/kpod/spec_test.go +++ b/cmd/kpod/spec_test.go @@ -16,7 +16,7 @@ func TestCreateConfig_GetVolumeMounts(t *testing.T) { Options: []string{"ro", "rbind", "rprivate"}, } config := createConfig{ - volumes: []string{"foobar:/foobar:ro"}, + Volumes: []string{"foobar:/foobar:ro"}, } specMount, err := config.GetVolumeMounts() assert.NoError(t, err) @@ -31,7 +31,7 @@ func TestCreateConfig_GetTmpfsMounts(t *testing.T) { Options: []string{"rw", "size=787448k", "mode=1777"}, } config := createConfig{ - tmpfs: []string{"/homer:rw,size=787448k,mode=1777"}, + Tmpfs: []string{"/homer:rw,size=787448k,mode=1777"}, } tmpfsMount := config.GetTmpfsMounts() assert.True(t, reflect.DeepEqual(data, tmpfsMount[0])) diff --git a/docs/kpod-inspect.1.md b/docs/kpod-inspect.1.md index 1baa46f13844..d3927cd37100 100644 --- a/docs/kpod-inspect.1.md +++ b/docs/kpod-inspect.1.md @@ -28,144 +28,55 @@ Display the total file size if the type is a container ## EXAMPLE -kpod inspect redis:alpine - -{ - "ArgsEscaped": true, - "AttachStderr": false, - "AttachStdin": false, - "AttachStdout": false, - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"redis-server\"]" - ], - "Domainname": "", - "Entrypoint": [ - "entrypoint.sh" - ], - "Env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "REDIS_VERSION=3.2.9", - "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz", - "REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26" - ], - "ExposedPorts": { - "6379/tcp": {} - }, - "Hostname": "e1ede117fb1e", - "Image": "sha256:75e877aa15b534396de82d385386cc4dda7819d5cbb018b9f97b77aeb8f4b55a", - "Labels": {}, - "OnBuild": [], - "OpenStdin": false, - "StdinOnce": false, - "Tty": false, - "User": "", - "Volumes": { - "/data": {} - }, - "WorkingDir": "/data" -} +``` +# kpod inspect fedora { - "ID": "b3f2436bdb978c1d33b1387afb5d7ba7e3243ed2ce908db431ac0069da86cb45", - "Names": [ - "docker.io/library/redis:alpine" + "Id": "422dc563ca3260ad9ef5c47a1c246f5065d7f177ce51f4dd208efd82967ff182", + "Digest": "sha256:1b9bfb4e634dc1e5c19d0fa1eb2e5a28a5c2b498e3d3e4ac742bd7f5dae08611", + "RepoTags": [ + "docker.io/library/fedora:latest" ], - "Digests": [ - "sha256:88286f41530e93dffd4b964e1db22ce4939fffa4a4c665dab8591fbab03d4926", - "sha256:07b1ac6c7a5068201d8b63a09bb15358ec1616b813ef3942eb8cc12ae191227f", - "sha256:91e2e140ea27b3e89f359cd9fab4ec45647dda2a8e5fb0c78633217d9dca87b5", - "sha256:08957ceaa2b3be874cde8d7fa15c274300f47185acd62bca812a2ffb6228482d", - "sha256:acd3d12a6a79f772961a771f678c1a39e1f370e7baeb9e606ad8f1b92572f4ab", - "sha256:4ad88df090801e8faa8cf0be1f403b77613d13e11dad73f561461d482f79256c", - "sha256:159ac12c79e1a8d85dfe61afff8c64b96881719139730012a9697f432d6b739a" + "RepoDigests": [ + "docker.io/library/fedora@sha256:1b9bfb4e634dc1e5c19d0fa1eb2e5a28a5c2b498e3d3e4ac742bd7f5dae08611" ], "Parent": "", "Comment": "", - "Created": "2017-06-28T22:14:36.35280993Z", - "Container": "ba8d6c6b0d7fdd201fce404236136b44f3bfdda883466531a3d1a1f87906770b", - "ContainerConfig": { - "Hostname": "e1ede117fb1e", - "Domainname": "", - "User": "", - "AttachStdin": false, - "AttachStdout": false, - "AttachStderr": false, - "Tty": false, - "OpenStdin": false, - "StdinOnce": false, - "Env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "REDIS_VERSION=3.2.9", - "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz", - "REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26" - ], - "Cmd": [ - "/bin/sh", - "-c", - "#(nop) ", - "CMD [\"redis-server\"]" - ], - "ArgsEscaped": true, - "Image": "sha256:75e877aa15b534396de82d385386cc4dda7819d5cbb018b9f97b77aeb8f4b55a", - "Volumes": { - "/data": {} - }, - "WorkingDir": "/data", - "Entrypoint": [ - "entrypoint.sh" - ], - "Labels": {}, - "OnBuild": [] - }, - "Author": "", + "Created": "2017-11-14T21:07:08.475840838Z", "Config": { - "ExposedPorts": { - "6379/tcp": {} - }, - "Env": [ - "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", - "REDIS_VERSION=3.2.9", - "REDIS_DOWNLOAD_URL=http://download.redis.io/releases/redis-3.2.9.tar.gz", - "REDIS_DOWNLOAD_SHA=6eaacfa983b287e440d0839ead20c2231749d5d6b78bbe0e0ffa3a890c59ff26" - ], - "Entrypoint": [ - "entrypoint.sh" - ], - "Cmd": [ - "redis-server" - ], - "Volumes": { - "/data": {} - }, - "WorkingDir": "/data" + "Env": [ + "PATH=/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin", + "DISTTAG=f27container", + "FGC=f27", + "FBR=f27" + ] }, + "Version": "17.06.2-ce", + "Author": "[Adam Miller \u003cmaxamillion@fedoraproject.org\u003e] [Patrick Uiterwijk \u003cpatrick@puiterwijk.org\u003e]", "Architecture": "amd64", - "OS": "linux", - "Size": 3965955, - "VirtualSize": 19808086, + "Os": "linux", + "Size": 251722732, + "VirtualSize": 514895140, "GraphDriver": { - "Name": "overlay", - "Data": { - "MergedDir": "/var/lib/containers/storage/overlay/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/merged", - "UpperDir": "/var/lib/containers/storage/overlay/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/diff", - "WorkDir": "/var/lib/containers/storage/overlay/2059d805c90e034cb773d9722232ef018a72143dd31113b470fb876baeccd700/work" - } + "Name": "overlay", + "Data": { + "MergedDir": "/var/lib/containers/storage/overlay/d32459d9ce237564fb93573b85cbc707600d43fbe5e46e8eeef22cad914bb516/merged", + "UpperDir": "/var/lib/containers/storage/overlay/d32459d9ce237564fb93573b85cbc707600d43fbe5e46e8eeef22cad914bb516/diff", + "WorkDir": "/var/lib/containers/storage/overlay/d32459d9ce237564fb93573b85cbc707600d43fbe5e46e8eeef22cad914bb516/work" + } }, "RootFS": { - "type": "layers", - "diff_ids": [ - "sha256:5bef08742407efd622d243692b79ba0055383bbce12900324f75e56f589aedb0", - "sha256:c92a8fc997217611d0bfc9ff14d7ec00350ca564aef0ecbf726624561d7872d7", - "sha256:d4c406dea37a107b0cccb845611266a146725598be3e82ba31c55c08d1583b5a", - "sha256:8b4fa064e2b6c03a6c37089b0203f167375a8b49259c0ad7cb47c8c1e58b3fa0", - "sha256:c393e3d0b00ddf6b4166f1e2ad68245e08e9e3be0a0567a36d0a43854f03bfd6", - "sha256:38047b4117cb8bb3bba82991daf9a4e14ba01f9f66c1434d4895a7e96f67d8ba" - ] - } + "Type": "layers", + "Layers": [ + "sha256:d32459d9ce237564fb93573b85cbc707600d43fbe5e46e8eeef22cad914bb516" + ] + }, + "Labels": null, + "Annotations": {} } - +``` ## SEE ALSO kpod(1) + +## HISTORY +July 2017, Originally compiled by Dan Walsh diff --git a/libkpod/container_data.go b/libkpod/container_data.go deleted file mode 100644 index eb84aa42d30e..000000000000 --- a/libkpod/container_data.go +++ /dev/null @@ -1,210 +0,0 @@ -package libkpod - -import ( - "encoding/json" - "os" - "time" - - "k8s.io/apimachinery/pkg/fields" - pb "k8s.io/kubernetes/pkg/kubelet/apis/cri/v1alpha1/runtime" - - "github.com/opencontainers/image-spec/specs-go/v1" - specs "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod/driver" - "github.com/projectatomic/libpod/libpod/images" - "github.com/projectatomic/libpod/oci" -) - -// ContainerData handles the data used when inspecting a container -type ContainerData struct { - ID string - Name string - LogPath string - Labels fields.Set - Annotations fields.Set - State *ContainerState - Metadata *pb.ContainerMetadata - BundlePath string - StopSignal string - FromImage string `json:"Image,omitempty"` - FromImageID string `json:"ImageID"` - MountPoint string `json:"Mountpoint,omitempty"` - MountLabel string - Mounts []specs.Mount - AppArmorProfile string - ImageAnnotations map[string]string `json:"Annotations,omitempty"` - ImageCreatedBy string `json:"CreatedBy,omitempty"` - Config v1.ImageConfig `json:"Config,omitempty"` - SizeRw uint `json:"SizeRw,omitempty"` - SizeRootFs uint `json:"SizeRootFs,omitempty"` - Args []string - ResolvConfPath string - HostnamePath string - HostsPath string - GraphDriver driverData -} - -type driverData struct { - Name string - Data map[string]string -} - -// ContainerState represents the status of a container. -type ContainerState struct { - specs.State - Created time.Time `json:"created"` - Started time.Time `json:"started,omitempty"` - Finished time.Time `json:"finished,omitempty"` - ExitCode int32 `json:"exitCode"` - OOMKilled bool `json:"oomKilled,omitempty"` - Error string `json:"error,omitempty"` -} - -// GetContainerData gets the ContainerData for a container with the given name in the given store. -// If size is set to true, it will also determine the size of the container -func (c *ContainerServer) GetContainerData(name string, size bool) (*ContainerData, error) { - ctr, err := c.inspectContainer(name) - if err != nil { - return nil, errors.Wrapf(err, "error reading build container %q", name) - } - container, err := c.store.Container(name) - if err != nil { - return nil, errors.Wrapf(err, "error reading container data") - } - - // The runtime configuration won't exist if the container has never been started by cri-o or kpod, - // so treat a not-exist error as non-fatal. - m := getBlankSpec() - config, err := c.store.FromContainerDirectory(ctr.ID(), "config.json") - if err != nil && !os.IsNotExist(errors.Cause(err)) { - return nil, err - } - if len(config) > 0 { - if err = json.Unmarshal(config, &m); err != nil { - return nil, err - } - } - - if container.ImageID == "" { - return nil, errors.Errorf("error reading container image data: container is not based on an image") - } - imageData, err := images.GetData(c.store, container.ImageID) - if err != nil { - return nil, errors.Wrapf(err, "error reading container image data") - } - - driverName, err := driver.GetDriverName(c.store) - if err != nil { - return nil, err - } - topLayer, err := c.GetContainerTopLayerID(ctr.ID()) - if err != nil { - return nil, err - } - layer, err := c.store.Layer(topLayer) - if err != nil { - return nil, err - } - driverMetadata, err := driver.GetDriverMetadata(c.store, topLayer) - if err != nil { - return nil, err - } - imageName := "" - if len(imageData.Tags) > 0 { - imageName = imageData.Tags[0] - } else if len(imageData.Digests) > 0 { - imageName = imageData.Digests[0] - } - data := &ContainerData{ - ID: ctr.ID(), - Name: ctr.Name(), - LogPath: ctr.LogPath(), - Labels: ctr.Labels(), - Annotations: ctr.Annotations(), - State: c.State(ctr), - Metadata: ctr.Metadata(), - BundlePath: ctr.BundlePath(), - StopSignal: ctr.GetStopSignal(), - Args: m.Process.Args, - FromImage: imageName, - FromImageID: container.ImageID, - MountPoint: layer.MountPoint, - ImageAnnotations: imageData.Annotations, - ImageCreatedBy: imageData.CreatedBy, - Config: imageData.Config, - GraphDriver: driverData{ - Name: driverName, - Data: driverMetadata, - }, - MountLabel: m.Linux.MountLabel, - Mounts: m.Mounts, - AppArmorProfile: m.Process.ApparmorProfile, - ResolvConfPath: "", - HostnamePath: "", - HostsPath: "", - } - - if size { - sizeRootFs, err := c.GetContainerRootFsSize(data.ID) - if err != nil { - - return nil, errors.Wrapf(err, "error reading size for container %q", name) - } - data.SizeRootFs = uint(sizeRootFs) - sizeRw, err := c.GetContainerRwSize(data.ID) - if err != nil { - return nil, errors.Wrapf(err, "error reading RWSize for container %q", name) - } - data.SizeRw = uint(sizeRw) - } - - return data, nil -} - -// Get an oci.Container and update its status -func (c *ContainerServer) inspectContainer(container string) (*oci.Container, error) { - ociCtr, err := c.LookupContainer(container) - if err != nil { - return nil, err - } - // call runtime.UpdateStatus() - err = c.Runtime().UpdateStatus(ociCtr) - if err != nil { - return nil, err - } - return ociCtr, nil -} - -func getBlankSpec() specs.Spec { - return specs.Spec{ - Process: &specs.Process{}, - Root: &specs.Root{}, - Mounts: []specs.Mount{}, - Hooks: &specs.Hooks{}, - Annotations: make(map[string]string), - Linux: &specs.Linux{}, - Solaris: &specs.Solaris{}, - Windows: &specs.Windows{}, - } -} - -// State copies the crio container state to ContainerState type for kpod -func (c *ContainerServer) State(ctr *oci.Container) *ContainerState { - crioState := ctr.State() - specState := specs.State{ - Version: crioState.Version, - ID: crioState.ID, - Status: crioState.Status, - Pid: crioState.Pid, - Bundle: crioState.Bundle, - Annotations: crioState.Annotations, - } - cState := &ContainerState{ - Started: crioState.Started, - Created: crioState.Created, - Finished: crioState.Finished, - } - cState.State = specState - return cState -} diff --git a/libpod/container.go b/libpod/container.go index fce64b0dd62c..d53a863c03c3 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -22,6 +22,7 @@ import ( "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod/driver" crioAnnotations "github.com/projectatomic/libpod/pkg/annotations" "github.com/sirupsen/logrus" "github.com/ulule/deepcopier" @@ -132,6 +133,7 @@ type ContainerConfig struct { SharedNamespaceMap map[string]string `json:"sharedNamespaces"` // Time container was created CreatedTime time.Time `json:"createdTime"` + // TODO save log location here and pass into OCI code // TODO allow overriding of log path } @@ -192,7 +194,6 @@ func (c *Container) Labels() map[string]string { for key, value := range c.config.Labels { labels[key] = value } - return labels } @@ -204,6 +205,68 @@ func (c *Container) Config() *ContainerConfig { return returnConfig } +// RuntimeName returns the name of the runtime +func (c *Container) RuntimeName() string { + return c.runtime.ociRuntime.name +} + +// rootFsSize gets the size of the container's root filesystem +// A container FS is split into two parts. The first is the top layer, a +// mutable layer, and the rest is the RootFS: the set of immutable layers +// that make up the image on which the container is based. +func (c *Container) rootFsSize() (int64, error) { + container, err := c.runtime.store.Container(c.ID()) + if err != nil { + return 0, err + } + + // Ignore the size of the top layer. The top layer is a mutable RW layer + // and is not considered a part of the rootfs + rwLayer, err := c.runtime.store.Layer(container.LayerID) + if err != nil { + return 0, err + } + layer, err := c.runtime.store.Layer(rwLayer.Parent) + if err != nil { + return 0, err + } + + size := int64(0) + for layer.Parent != "" { + layerSize, err := c.runtime.store.DiffSize(layer.Parent, layer.ID) + if err != nil { + return size, errors.Wrapf(err, "getting diffsize of layer %q and its parent %q", layer.ID, layer.Parent) + } + size += layerSize + layer, err = c.runtime.store.Layer(layer.Parent) + if err != nil { + return 0, err + } + } + // Get the size of the last layer. Has to be outside of the loop + // because the parent of the last layer is "", andlstore.Get("") + // will return an error. + layerSize, err := c.runtime.store.DiffSize(layer.Parent, layer.ID) + return size + layerSize, err +} + +// rwSize Gets the size of the mutable top layer of the container. +func (c *Container) rwSize() (int64, error) { + container, err := c.runtime.store.Container(c.ID()) + if err != nil { + return 0, err + } + + // Get the size of the top layer by calculating the size of the diff + // between the layer and its parent. The top layer of a container is + // the only RW layer, all others are immutable + layer, err := c.runtime.store.Layer(container.LayerID) + if err != nil { + return 0, err + } + return c.runtime.store.DiffSize(layer.Parent, layer.ID) +} + // LogPath returns the path to the container's log file // This file will only be present after Init() is called to create the container // in runc @@ -829,6 +892,31 @@ func (c *Container) getArtifactPath(name string) string { return filepath.Join(c.config.StaticDir, artifactsDir, name) } +// Inspect a container for low-level information +func (c *Container) Inspect(size bool) (*ContainerInspectData, error) { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + + storeCtr, err := c.runtime.store.Container(c.ID()) + if err != nil { + return nil, errors.Wrapf(err, "error getting container from store %q", c.ID()) + } + layer, err := c.runtime.store.Layer(storeCtr.LayerID) + if err != nil { + return nil, errors.Wrapf(err, "error reading information about layer %q", storeCtr.LayerID) + } + driverData, err := driver.GetDriverData(c.runtime.store, layer.ID) + if err != nil { + return nil, errors.Wrapf(err, "error getting graph driver info %q", c.ID()) + } + + return c.getContainerInspectData(size, driverData) +} + // Commit commits the changes between a container and its image, creating a new // image func (c *Container) Commit() (*storage.Image, error) { diff --git a/libpod/container_inspect.go b/libpod/container_inspect.go new file mode 100644 index 000000000000..5f29a231e295 --- /dev/null +++ b/libpod/container_inspect.go @@ -0,0 +1,70 @@ +package libpod + +import ( + "github.com/projectatomic/libpod/libpod/driver" + "github.com/sirupsen/logrus" +) + +func (c *Container) getContainerInspectData(size bool, driverData *driver.Data) (*ContainerInspectData, error) { + config := c.config + runtimeInfo := c.state + spec := c.config.Spec + + args := config.Spec.Process.Args + var path string + if len(args) > 0 { + path = args[0] + } + if len(args) > 1 { + args = args[1:] + } + + data := &ContainerInspectData{ + ID: config.ID, + Created: config.CreatedTime, + Path: path, + Args: args, + State: &ContainerInspectState{ + OciVersion: spec.Version, + Status: runtimeInfo.State.String(), + Running: runtimeInfo.State == ContainerStateRunning, + Paused: runtimeInfo.State == ContainerStatePaused, + OOMKilled: runtimeInfo.OOMKilled, + Dead: runtimeInfo.State.String() == "bad state", + Pid: runtimeInfo.PID, + ExitCode: runtimeInfo.ExitCode, + Error: "", // can't get yet + StartedAt: runtimeInfo.StartedTime, + FinishedAt: runtimeInfo.FinishedTime, + }, + ImageID: config.RootfsImageID, + ImageName: config.RootfsImageName, + ResolvConfPath: "", // TODO get from networking path + HostnamePath: spec.Annotations["io.kubernetes.cri-o.HostnamePath"], // not sure + HostsPath: "", // can't get yet + StaticDir: config.StaticDir, + LogPath: c.LogPath(), + Name: config.Name, + Driver: driverData.Name, + MountLabel: config.MountLabel, + ProcessLabel: spec.Process.SelinuxLabel, + AppArmorProfile: spec.Process.ApparmorProfile, + ExecIDs: []string{}, //TODO + GraphDriver: driverData, + Mounts: spec.Mounts, + NetworkSettings: &NetworkSettings{}, // TODO from networking patch + } + if size { + rootFsSize, err := c.rootFsSize() + if err != nil { + logrus.Errorf("error getting rootfs size %q: %v", config.ID, err) + } + rwSize, err := c.rwSize() + if err != nil { + logrus.Errorf("error getting rw size %q: %v", config.ID, err) + } + data.SizeRootFs = rootFsSize + data.SizeRw = rwSize + } + return data, nil +} diff --git a/libpod/driver/driver.go b/libpod/driver/driver.go index 4db55852c1e3..8475810a8277 100644 --- a/libpod/driver/driver.go +++ b/libpod/driver/driver.go @@ -4,8 +4,8 @@ import cstorage "github.com/containers/storage" // Data handles the data for a storage driver type Data struct { - Name string - Data map[string]string + Name string `json:"Name"` + Data map[string]string `json:"Data"` } // GetDriverName returns the name of the driver for the given store @@ -25,3 +25,19 @@ func GetDriverMetadata(store cstorage.Store, layerID string) (map[string]string, } return driver.Metadata(layerID) } + +// GetDriverData returns the Data struct with information of the driver used by the store +func GetDriverData(store cstorage.Store, layerID string) (*Data, error) { + name, err := GetDriverName(store) + if err != nil { + return nil, err + } + metaData, err := GetDriverMetadata(store, layerID) + if err != nil { + return nil, err + } + return &Data{ + Name: name, + Data: metaData, + }, nil +} diff --git a/libpod/image_inspect.go b/libpod/image_inspect.go new file mode 100644 index 000000000000..a086654349d3 --- /dev/null +++ b/libpod/image_inspect.go @@ -0,0 +1,81 @@ +package libpod + +import ( + "encoding/json" + "strings" + + "github.com/containers/image/types" + "github.com/containers/storage" + digest "github.com/opencontainers/go-digest" + ociv1 "github.com/opencontainers/image-spec/specs-go/v1" + "github.com/pkg/errors" + "github.com/projectatomic/libpod/libpod/driver" +) + +func getImageData(img storage.Image, imgRef types.Image, size int64, driver *driver.Data) (*ImageData, error) { + imgSize, err := imgRef.Size() + if err != nil { + return nil, errors.Wrapf(err, "error reading size of image %q", img.ID) + } + manifest, manifestType, err := imgRef.Manifest() + if err != nil { + return nil, errors.Wrapf(err, "error reading manifest for image %q", img.ID) + } + imgDigest := digest.Digest("") + if len(manifest) > 0 { + imgDigest = digest.Canonical.FromBytes(manifest) + } + annotations := annotations(manifest, manifestType) + + ociv1Img, err := imgRef.OCIConfig() + if err != nil { + return nil, errors.Wrapf(err, "error getting oci image info %q", img.ID) + } + info, err := imgRef.Inspect() + if err != nil { + return nil, errors.Wrapf(err, "error getting image info %q", img.ID) + } + + var repoDigests []string + for _, name := range img.Names { + repoDigests = append(repoDigests, strings.SplitN(name, ":", 2)[0]+"@"+imgDigest.String()) + } + + data := &ImageData{ + ID: img.ID, + RepoTags: img.Names, + RepoDigests: repoDigests, + Comment: ociv1Img.History[0].Comment, + Created: ociv1Img.Created, + Author: ociv1Img.History[0].Author, + Architecture: ociv1Img.Architecture, + Os: ociv1Img.OS, + Config: &ociv1Img.Config, + Version: info.DockerVersion, + Size: size, + VirtualSize: size + imgSize, + Annotations: annotations, + Digest: imgDigest, + Labels: info.Labels, + RootFS: &RootFS{ + Type: ociv1Img.RootFS.Type, + Layers: ociv1Img.RootFS.DiffIDs, + }, + GraphDriver: driver, + } + return data, nil +} + +func annotations(manifest []byte, manifestType string) map[string]string { + annotations := make(map[string]string) + switch manifestType { + case ociv1.MediaTypeImageManifest: + var m ociv1.Manifest + if err := json.Unmarshal(manifest, &m); err == nil { + for k, v := range m.Annotations { + annotations[k] = v + } + } + } + return annotations +} diff --git a/libpod/images/image_data.go b/libpod/images/image_data.go deleted file mode 100644 index 1fa63f6cb830..000000000000 --- a/libpod/images/image_data.go +++ /dev/null @@ -1,202 +0,0 @@ -package images - -import ( - "encoding/json" - "time" - - "github.com/containers/image/docker/reference" - is "github.com/containers/image/storage" - "github.com/containers/image/transports" - "github.com/containers/image/types" - "github.com/containers/storage" - digest "github.com/opencontainers/go-digest" - ociv1 "github.com/opencontainers/image-spec/specs-go/v1" - "github.com/pkg/errors" - "github.com/projectatomic/libpod/libpod/driver" -) - -// Data handles the data used when inspecting a container -// nolint -type Data struct { - ID string - Tags []string - Digests []string - Digest digest.Digest - Comment string - Created *time.Time - Container string - Author string - Config ociv1.ImageConfig - Architecture string - OS string - Annotations map[string]string - CreatedBy string - Size uint - VirtualSize uint - GraphDriver driver.Data - RootFS ociv1.RootFS -} - -// ParseImageNames parses the names we've stored with an image into a list of -// tagged references and a list of references which contain digests. -func ParseImageNames(names []string) (tags, digests []string, err error) { - for _, name := range names { - if named, err := reference.ParseNamed(name); err == nil { - if digested, ok := named.(reference.Digested); ok { - canonical, err := reference.WithDigest(named, digested.Digest()) - if err == nil { - digests = append(digests, canonical.String()) - } - } else { - if reference.IsNameOnly(named) { - named = reference.TagNameOnly(named) - } - if tagged, ok := named.(reference.Tagged); ok { - namedTagged, err := reference.WithTag(named, tagged.Tag()) - if err == nil { - tags = append(tags, namedTagged.String()) - } - } - } - } - } - return tags, digests, nil -} - -func annotations(manifest []byte, manifestType string) map[string]string { - annotations := make(map[string]string) - switch manifestType { - case ociv1.MediaTypeImageManifest: - var m ociv1.Manifest - if err := json.Unmarshal(manifest, &m); err == nil { - for k, v := range m.Annotations { - annotations[k] = v - } - } - } - return annotations -} - -// GetData gets the Data for a container with the given name in the given store. -func GetData(store storage.Store, name string) (*Data, error) { - img, err := FindImage(store, name) - if err != nil { - return nil, errors.Wrapf(err, "error reading image %q", name) - } - - imgRef, err := FindImageRef(store, "@"+img.ID) - if err != nil { - return nil, errors.Wrapf(err, "error reading image %q", img.ID) - } - - tags, digests, err := ParseImageNames(img.Names) - if err != nil { - return nil, errors.Wrapf(err, "error parsing image names for %q", name) - } - - driverName, err := driver.GetDriverName(store) - if err != nil { - return nil, errors.Wrapf(err, "error reading name of storage driver") - } - - topLayerID := img.TopLayer - - driverMetadata, err := driver.GetDriverMetadata(store, topLayerID) - if err != nil { - return nil, errors.Wrapf(err, "error asking storage driver %q for metadata", driverName) - } - - layer, err := store.Layer(topLayerID) - if err != nil { - return nil, errors.Wrapf(err, "error reading information about layer %q", topLayerID) - } - size, err := store.DiffSize(layer.Parent, layer.ID) - if err != nil { - return nil, errors.Wrapf(err, "error determining size of layer %q", layer.ID) - } - - imgSize, err := imgRef.Size() - if err != nil { - return nil, errors.Wrapf(err, "error determining size of image %q", transports.ImageName(imgRef.Reference())) - } - - manifest, manifestType, err := imgRef.Manifest() - if err != nil { - return nil, errors.Wrapf(err, "error reading manifest for image %q", img.ID) - } - manifestDigest := digest.Digest("") - if len(manifest) > 0 { - manifestDigest = digest.Canonical.FromBytes(manifest) - } - annotations := annotations(manifest, manifestType) - - config, err := imgRef.OCIConfig() - if err != nil { - return nil, errors.Wrapf(err, "error reading image configuration for %q", img.ID) - } - historyComment := "" - historyCreatedBy := "" - if len(config.History) > 0 { - historyComment = config.History[len(config.History)-1].Comment - historyCreatedBy = config.History[len(config.History)-1].CreatedBy - } - - return &Data{ - ID: img.ID, - Tags: tags, - Digests: digests, - Digest: manifestDigest, - Comment: historyComment, - Created: config.Created, - Author: config.Author, - Config: config.Config, - Architecture: config.Architecture, - OS: config.OS, - Annotations: annotations, - CreatedBy: historyCreatedBy, - Size: uint(size), - VirtualSize: uint(size + imgSize), - GraphDriver: driver.Data{ - Name: driverName, - Data: driverMetadata, - }, - RootFS: config.RootFS, - }, nil -} - -// FindImage searches for a *storage.Image with a matching the given name or ID in the given store. -func FindImage(store storage.Store, image string) (*storage.Image, error) { - var img *storage.Image - ref, err := is.Transport.ParseStoreReference(store, image) - if err == nil { - img, err = is.Transport.GetStoreImage(store, ref) - } - if err != nil { - img2, err2 := store.Image(image) - if err2 != nil { - if ref == nil { - return nil, errors.Wrapf(err, "error parsing reference to image %q", image) - } - return nil, errors.Wrapf(err, "unable to locate image %q", image) - } - img = img2 - } - return img, nil -} - -// FindImageRef searches for and returns a new types.Image matching the given name or ID in the given store. -func FindImageRef(store storage.Store, image string) (types.Image, error) { - img, err := FindImage(store, image) - if err != nil { - return nil, errors.Wrapf(err, "unable to locate image %q", image) - } - ref, err := is.Transport.ParseStoreReference(store, "@"+img.ID) - if err != nil { - return nil, errors.Wrapf(err, "error parsing reference to image %q", img.ID) - } - imgRef, err := ref.NewImage(nil) - if err != nil { - return nil, errors.Wrapf(err, "error reading image %q", img.ID) - } - return imgRef, nil -} diff --git a/libpod/inspect_data.go b/libpod/inspect_data.go new file mode 100644 index 000000000000..072b94ab2b2d --- /dev/null +++ b/libpod/inspect_data.go @@ -0,0 +1,103 @@ +package libpod + +import ( + "time" + + digest "github.com/opencontainers/go-digest" + "github.com/opencontainers/image-spec/specs-go/v1" + specs "github.com/opencontainers/runtime-spec/specs-go" + "github.com/projectatomic/libpod/libpod/driver" +) + +// ContainerInspectData handles the data used when inspecting a container +type ContainerInspectData struct { + ID string `json:"ID"` + Created time.Time `json:"Created"` + Path string `json:"Path"` + Args []string `json:"Args"` + State *ContainerInspectState `json:"State"` + ImageID string `json:"Image"` + ImageName string `json:"ImageName"` + ResolvConfPath string `json:"ResolvConfPath"` + HostnamePath string `json:"HostnamePath"` //TODO + HostsPath string `json:"HostsPath"` //TODO + StaticDir string `json:"StaticDir"` + LogPath string `json:"LogPath"` + Name string `json:"Name"` + RestartCount int32 `json:"RestartCount"` //TODO + Driver string `json:"Driver"` + MountLabel string `json:"MountLabel"` + ProcessLabel string `json:"ProcessLabel"` + AppArmorProfile string `json:"AppArmorProfile"` + ExecIDs []string `json:"ExecIDs"` //TODO + GraphDriver *driver.Data `json:"GraphDriver"` + SizeRw int64 `json:"SizeRw,omitempty"` + SizeRootFs int64 `json:"SizeRootFs,omitempty"` + Mounts []specs.Mount `json:"Mounts"` + NetworkSettings *NetworkSettings `json:"NetworkSettings"` //TODO +} + +// ContainerInspectState represents the state of a container. +type ContainerInspectState struct { + OciVersion string `json:"OciVersion"` + Status string `json:"Status"` + Running bool `json:"Running"` + Paused bool `json:"Paused"` + Restarting bool `json:"Restarting"` // TODO + OOMKilled bool `json:"OOMKilled"` + Dead bool `json:"Dead"` + Pid int `json:"Pid"` + ExitCode int32 `json:"ExitCode"` + Error string `json:"Error"` // TODO + StartedAt time.Time `json:"StartedAt"` + FinishedAt time.Time `json:"FinishedAt"` +} + +// NetworkSettings holds information about the newtwork settings of the container +type NetworkSettings struct { + Bridge string `json:"Bridge"` + SandboxID string `json:"SandboxID"` + HairpinMode bool `json:"HairpinMode"` + LinkLocalIPv6Address string `json:"LinkLocalIPv6Address"` + LinkLocalIPv6PrefixLen int `json:"LinkLocalIPv6PrefixLen"` + Ports map[string]struct{} `json:"Ports"` + SandboxKey string `json:"SandboxKey"` + SecondaryIPAddresses string `json:"SecondaryIPAddresses"` //idk type + SecondaryIPv6Addresses string `json:"SecondaryIPv6Addresses"` //idk type + EndpointID string `json:"EndpointID"` + Gateway string `json:"Gateway"` + GlobalIPv6Addresses string `json:"GlobalIPv6Addresses"` + GlobalIPv6PrefixLen int `json:"GlobalIPv6PrefixLen"` + IPAddress string `json:"IPAddress"` + IPPrefixLen int `json:"IPPrefixLen"` + IPv6Gateway string `json:"IPv6Gateway"` + MacAddress string `json:"MacAddress"` +} + +// ImageData holds the inspect information of an image +type ImageData struct { + ID string `json:"ID"` + Digest digest.Digest `json:"Digest"` + RepoTags []string `json:"RepoTags"` + RepoDigests []string `json:"RepoDigests"` + Parent string `json:"Parent"` + Comment string `json:"Comment"` + Created *time.Time `json:"Created"` + Config *v1.ImageConfig `json:"Config"` + Version string `json:"Version"` + Author string `json:"Author"` + Architecture string `json:"Architecture"` + Os string `json:"Os"` + Size int64 `json:"Size"` + VirtualSize int64 `json:"VirtualSize"` + GraphDriver *driver.Data `json:"GraphDriver"` + RootFS *RootFS `json:"RootFS"` + Labels map[string]string `json:"Labels"` + Annotations map[string]string `json:"Annotations"` +} + +// RootFS holds the root fs information of an image +type RootFS struct { + Type string `json:"Type"` + Layers []digest.Digest `json:"Layers"` +} diff --git a/libpod/runtime_img.go b/libpod/runtime_img.go index 26f85b0370cc..d5da35c420af 100644 --- a/libpod/runtime_img.go +++ b/libpod/runtime_img.go @@ -20,15 +20,14 @@ import ( "github.com/containers/image/signature" is "github.com/containers/image/storage" "github.com/containers/image/tarball" - "github.com/containers/image/transports" "github.com/containers/image/transports/alltransports" "github.com/containers/image/types" "github.com/containers/storage" "github.com/containers/storage/pkg/archive" - digest "github.com/opencontainers/go-digest" ociv1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/projectatomic/libpod/libpod/common" + "github.com/projectatomic/libpod/libpod/driver" ) // Runtime API @@ -452,7 +451,7 @@ func getRegistries() ([]string, error) { // ImageFilter is a function to determine whether an image is included in // command output. Images to be outputted are tested using the function. A true // return will include the image, a false return will exclude it. -type ImageFilter func(*storage.Image, *types.ImageInspectInfo) bool +type ImageFilter func(*storage.Image, *ImageData) bool func (ips imageDecomposeStruct) returnFQName() string { return fmt.Sprintf("%s%s/%s:%s", ips.transport, ips.registry, ips.imageName, ips.tag) @@ -1032,7 +1031,7 @@ func (r *Runtime) ImportImage(path string, options CopyOptions) error { } // GetImageInspectInfo returns the inspect information of an image -func (r *Runtime) GetImageInspectInfo(image storage.Image) (*types.ImageInspectInfo, error) { +func (r *Runtime) GetImageInspectInfo(image storage.Image) (*ImageData, error) { r.lock.RLock() defer r.lock.RUnlock() @@ -1042,12 +1041,25 @@ func (r *Runtime) GetImageInspectInfo(image storage.Image) (*types.ImageInspectI return r.getImageInspectInfo(image) } -func (r *Runtime) getImageInspectInfo(image storage.Image) (*types.ImageInspectInfo, error) { - img, err := r.getImageRef(image.ID) +func (r *Runtime) getImageInspectInfo(image storage.Image) (*ImageData, error) { + imgRef, err := r.getImageRef("@" + image.ID) if err != nil { - return nil, err + return nil, errors.Wrapf(err, "error reading image %q", image.ID) + } + + layer, err := r.store.Layer(image.TopLayer) + if err != nil { + return nil, errors.Wrapf(err, "error reading information about layer %q", image.TopLayer) + } + size, err := r.store.DiffSize(layer.Parent, layer.ID) + if err != nil { + return nil, errors.Wrapf(err, "error determining size of layer %q", layer.ID) + } + driverData, err := driver.GetDriverData(r.store, layer.ID) + if err != nil { + return nil, errors.Wrapf(err, "error getting graph driver info %q", image.ID) } - return img.Inspect() + return getImageData(image, imgRef, size, driverData) } // ParseImageFilter takes a set of images and a filter string as input, and returns the libpod.ImageFilterParams struct @@ -1093,7 +1105,7 @@ func (r *Runtime) ParseImageFilter(imageInput, filter string) (*ImageFilterParam if err != nil { return nil, err } - params.BeforeImage = info.Created + params.BeforeImage = *info.Created } else { return nil, fmt.Errorf("no such id: %s", pair[0]) } @@ -1103,7 +1115,7 @@ func (r *Runtime) ParseImageFilter(imageInput, filter string) (*ImageFilterParam if err != nil { return nil, err } - params.SinceImage = info.Created + params.SinceImage = *info.Created } else { return nil, fmt.Errorf("no such id: %s``", pair[0]) } @@ -1116,43 +1128,6 @@ func (r *Runtime) ParseImageFilter(imageInput, filter string) (*ImageFilterParam return ¶ms, nil } -// InfoAndDigestAndSize returns the inspection info and size of the image in the given -// store and the digest of its manifest, if it has one, or "" if it doesn't. -func (r *Runtime) InfoAndDigestAndSize(img storage.Image) (*types.ImageInspectInfo, digest.Digest, int64, error) { - r.lock.RLock() - defer r.lock.RUnlock() - - if !r.valid { - return nil, "", -1, ErrRuntimeStopped - } - - imgRef, err := r.getImageRef("@" + img.ID) - if err != nil { - return nil, "", -1, errors.Wrapf(err, "error reading image %q", img.ID) - } - return infoAndDigestAndSize(imgRef) -} - -func infoAndDigestAndSize(imgRef types.Image) (*types.ImageInspectInfo, digest.Digest, int64, error) { - imgSize, err := imgRef.Size() - if err != nil { - return nil, "", -1, errors.Wrapf(err, "error reading size of image %q", transports.ImageName(imgRef.Reference())) - } - manifest, _, err := imgRef.Manifest() - if err != nil { - return nil, "", -1, errors.Wrapf(err, "error reading manifest for image %q", transports.ImageName(imgRef.Reference())) - } - manifestDigest := digest.Digest("") - if len(manifest) > 0 { - manifestDigest = digest.Canonical.FromBytes(manifest) - } - info, err := imgRef.Inspect() - if err != nil { - return nil, "", -1, errors.Wrapf(err, "error inspecting image %q", transports.ImageName(imgRef.Reference())) - } - return info, manifestDigest, imgSize, nil -} - // MatchesID returns true if argID is a full or partial match for id func MatchesID(id, argID string) bool { return strings.HasPrefix(argID, id) diff --git a/test/kpod_inspect.bats b/test/kpod_inspect.bats index ca7e16aad35a..86a4e7698fef 100644 --- a/test/kpod_inspect.bats +++ b/test/kpod_inspect.bats @@ -40,3 +40,13 @@ function setup() { echo "$output" [ "$status" -eq 0 ] } + +@test "kpod inspect container with size" { + run bash -c "${KPOD_BINARY} ${KPOD_OPTIONS} create ${BB} ls" + echo "$output" + [ "$status" -eq 0 ] + ctr_id="$output" + run bash -c "${KPOD_BINARY} $KPOD_OPTIONS inspect --size $ctr_id | python -m json.tool | grep SizeRootFs" + echo "$output" + [ "$status" -eq 0 ] +}