Files
podman/cmd/kpod/spec.go
umohnani8 74ee579375 Update kpod inspect to use the new container state
kpod inspect now uses the new libpod container state
and closely matches the output of docker inspect
some aspects of it are still WIP as the libpod container state
is still being worked on

Signed-off-by: umohnani8 <umohnani@redhat.com>
2017-12-12 09:46:23 -05:00

558 lines
15 KiB
Go

package main
import (
"encoding/json"
"fmt"
"io/ioutil"
"strings"
"github.com/docker/docker/daemon/caps"
"github.com/docker/docker/pkg/mount"
"github.com/docker/go-units"
spec "github.com/opencontainers/runtime-spec/specs-go"
"github.com/opencontainers/runtime-tools/generate"
"github.com/opencontainers/selinux/go-selinux/label"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/libpod"
ann "github.com/projectatomic/libpod/pkg/annotations"
"github.com/sirupsen/logrus"
"golang.org/x/sys/unix"
)
func blockAccessToKernelFilesystems(config *createConfig, g *generate.Generator) {
if !config.Privileged {
for _, mp := range []string{
"/proc/kcore",
"/proc/latency_stats",
"/proc/timer_list",
"/proc/timer_stats",
"/proc/sched_debug",
"/proc/scsi",
"/sys/firmware",
} {
g.AddLinuxMaskedPaths(mp)
}
for _, rp := range []string{
"/proc/asound",
"/proc/bus",
"/proc/fs",
"/proc/irq",
"/proc/sys",
"/proc/sysrq-trigger",
} {
g.AddLinuxReadonlyPaths(rp)
}
}
}
func addPidNS(config *createConfig, g *generate.Generator) error {
pidMode := config.PidMode
if pidMode.IsHost() {
return g.RemoveLinuxNamespace(libpod.PIDNamespace)
}
if pidMode.IsContainer() {
ctr, err := config.Runtime.LookupContainer(pidMode.Container())
if err != nil {
return errors.Wrapf(err, "container %q not found", pidMode.Container())
}
pid, err := ctr.PID()
if err != nil {
return errors.Wrapf(err, "Failed to get pid of container %q", pidMode.Container())
}
pidNsPath := fmt.Sprintf("/proc/%d/ns/pid", pid)
if err := g.AddOrReplaceLinuxNamespace(libpod.PIDNamespace, pidNsPath); err != nil {
return err
}
}
return nil
}
func addNetNS(config *createConfig, g *generate.Generator) error {
netMode := config.NetMode
if netMode.IsHost() {
return g.RemoveLinuxNamespace(libpod.NetNamespace)
}
if netMode.IsNone() {
return libpod.ErrNotImplemented
}
if netMode.IsBridge() {
return libpod.ErrNotImplemented
}
if netMode.IsContainer() {
ctr, err := config.Runtime.LookupContainer(netMode.ConnectedContainer())
if err != nil {
return errors.Wrapf(err, "container %q not found", netMode.ConnectedContainer())
}
pid, err := ctr.PID()
if err != nil {
return errors.Wrapf(err, "Failed to get pid of container %q", netMode.ConnectedContainer())
}
nsPath := fmt.Sprintf("/proc/%d/ns/net", pid)
if err := g.AddOrReplaceLinuxNamespace(libpod.NetNamespace, nsPath); err != nil {
return err
}
}
return nil
}
func addUTSNS(config *createConfig, g *generate.Generator) error {
utsMode := config.UtsMode
if utsMode.IsHost() {
return g.RemoveLinuxNamespace(libpod.UTSNamespace)
}
return nil
}
func addIpcNS(config *createConfig, g *generate.Generator) error {
ipcMode := config.IpcMode
if ipcMode.IsHost() {
return g.RemoveLinuxNamespace(libpod.IPCNamespace)
}
if ipcMode.IsContainer() {
ctr, err := config.Runtime.LookupContainer(ipcMode.Container())
if err != nil {
return errors.Wrapf(err, "container %q not found", ipcMode.Container())
}
pid, err := ctr.PID()
if err != nil {
return errors.Wrapf(err, "Failed to get pid of container %q", ipcMode.Container())
}
nsPath := fmt.Sprintf("/proc/%d/ns/ipc", pid)
if err := g.AddOrReplaceLinuxNamespace(libpod.IPCNamespace, nsPath); err != nil {
return err
}
}
return nil
}
func addRlimits(config *createConfig, g *generate.Generator) error {
var (
ul *units.Ulimit
err error
)
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)
}
g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Soft), uint64(ul.Hard))
}
return nil
}
func setupCapabilities(config *createConfig, configSpec *spec.Spec) error {
var err error
var caplist []string
if config.Privileged {
caplist = caps.GetAllCapabilities()
} else {
caplist, err = caps.TweakCapabilities(configSpec.Process.Capabilities.Bounding, config.CapAdd, config.CapDrop)
if err != nil {
return err
}
}
configSpec.Process.Capabilities.Bounding = caplist
configSpec.Process.Capabilities.Permitted = caplist
configSpec.Process.Capabilities.Inheritable = caplist
configSpec.Process.Capabilities.Effective = caplist
return nil
}
// Parses information needed to create a container into an OCI runtime spec
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)
// User and Group must go together
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)
}
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.MemoryReservation != 0 {
g.SetLinuxResourcesMemoryReservation(config.Resources.MemoryReservation)
}
if config.Resources.MemorySwap != 0 {
g.SetLinuxResourcesMemorySwap(config.Resources.MemorySwap)
}
if config.Resources.KernelMemory != 0 {
g.SetLinuxResourcesMemoryKernel(config.Resources.KernelMemory)
}
if config.Resources.MemorySwappiness != -1 {
g.SetLinuxResourcesMemorySwappiness(uint64(config.Resources.MemorySwappiness))
}
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.CpuQuota != 0 {
g.SetLinuxResourcesCPUQuota(config.Resources.CpuQuota)
}
if config.Resources.CpuPeriod != 0 {
g.SetLinuxResourcesCPUPeriod(config.Resources.CpuPeriod)
}
if config.Resources.CpuRtRuntime != 0 {
g.SetLinuxResourcesCPURealtimeRuntime(config.Resources.CpuRtRuntime)
}
if config.Resources.CpuRtPeriod != 0 {
g.SetLinuxResourcesCPURealtimePeriod(config.Resources.CpuRtPeriod)
}
if config.Resources.Cpus != "" {
g.SetLinuxResourcesCPUCpus(config.Resources.Cpus)
}
if config.Resources.CpusetMems != "" {
g.SetLinuxResourcesCPUMems(config.Resources.CpusetMems)
}
// SECURITY OPTS
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)
}
for _, i := range config.Tmpfs {
options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"}
spliti := strings.SplitN(i, ":", 2)
if len(spliti) > 1 {
if _, _, err := mount.ParseTmpfsOptions(spliti[1]); err != nil {
return nil, err
}
options = strings.Split(spliti[1], ",")
}
// Default options if nothing passed
g.AddTmpfsMount(spliti[0], options)
}
for name, val := range config.Env {
g.AddProcessEnv(name, val)
}
if err := addRlimits(config, &g); err != nil {
return nil, err
}
if err := addPidNS(config, &g); err != nil {
return nil, err
}
if err := addNetNS(config, &g); err != nil {
return nil, err
}
if err := addUTSNS(config, &g); err != nil {
return nil, err
}
if err := addIpcNS(config, &g); err != nil {
return nil, err
}
configSpec := g.Spec()
if config.SeccompProfilePath != "" && config.SeccompProfilePath != "unconfined" {
seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath)
if err != nil {
return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", config.SeccompProfilePath)
}
var seccompConfig spec.LinuxSeccomp
if err := json.Unmarshal(seccompProfile, &seccompConfig); err != nil {
return nil, errors.Wrapf(err, "decoding seccomp profile (%s) failed", config.SeccompProfilePath)
}
configSpec.Linux.Seccomp = &seccompConfig
}
// BIND MOUNTS
mounts, err := config.GetVolumeMounts()
if err != nil {
return nil, errors.Wrapf(err, "error getting volume mounts")
}
configSpec.Mounts = append(configSpec.Mounts, mounts...)
for _, mount := range configSpec.Mounts {
for _, opt := range mount.Options {
switch opt {
case "private", "rprivate", "slave", "rslave", "shared", "rshared":
if err := g.SetLinuxRootPropagation(opt); err != nil {
return nil, errors.Wrapf(err, "error setting root propagation for %q", mount.Destination)
}
}
}
}
// HANDLE CAPABILITIES
if err := setupCapabilities(config, configSpec); err != nil {
return nil, err
}
/*
Hooks: &configSpec.Hooks{},
//Annotations
Resources: &configSpec.LinuxResources{
Devices: config.GetDefaultDevices(),
BlockIO: &blkio,
//HugepageLimits:
Network: &configSpec.LinuxNetwork{
// ClassID *uint32
// Priorites []LinuxInterfacePriority
},
},
//CgroupsPath:
//Namespaces: []LinuxNamespace
//Devices
// DefaultAction:
// Architectures
// Syscalls:
},
// RootfsPropagation
// MaskedPaths
// ReadonlyPaths:
// IntelRdt
},
}
*/
return configSpec, nil
}
func (c *createConfig) CreateBlockIO() (spec.LinuxBlockIO, error) {
bio := spec.LinuxBlockIO{}
bio.Weight = &c.Resources.BlkioWeight
if len(c.Resources.BlkioWeightDevice) > 0 {
var lwds []spec.LinuxWeightDevice
for _, i := range c.Resources.BlkioWeightDevice {
wd, err := validateweightDevice(i)
if err != nil {
return bio, errors.Wrapf(err, "invalid values for blkio-weight-device")
}
wdStat := getStatFromPath(wd.path)
lwd := spec.LinuxWeightDevice{
Weight: &wd.weight,
}
lwd.Major = int64(unix.Major(wdStat.Rdev))
lwd.Minor = int64(unix.Minor(wdStat.Rdev))
lwds = append(lwds, lwd)
}
}
if len(c.Resources.DeviceReadBps) > 0 {
readBps, err := makeThrottleArray(c.Resources.DeviceReadBps)
if err != nil {
return bio, err
}
bio.ThrottleReadBpsDevice = readBps
}
if len(c.Resources.DeviceWriteBps) > 0 {
writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps)
if err != nil {
return bio, err
}
bio.ThrottleWriteBpsDevice = writeBpds
}
if len(c.Resources.DeviceReadIOps) > 0 {
readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps)
if err != nil {
return bio, err
}
bio.ThrottleReadIOPSDevice = readIOps
}
if len(c.Resources.DeviceWriteIOps) > 0 {
writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps)
if err != nil {
return bio, err
}
bio.ThrottleWriteIOPSDevice = writeIOps
}
return bio, nil
}
// GetAnnotations returns the all the annotations for the container
func (c *createConfig) GetAnnotations() map[string]string {
a := getDefaultAnnotations()
// TODO - Which annotations do we want added by default
// TODO - This should be added to the DB long term
if c.Tty {
a["io.kubernetes.cri-o.TTY"] = "true"
}
return a
}
func getDefaultAnnotations() map[string]string {
var annotations map[string]string
annotations = make(map[string]string)
annotations[ann.Annotations] = ""
annotations[ann.ContainerID] = ""
annotations[ann.ContainerName] = ""
annotations[ann.ContainerType] = ""
annotations[ann.Created] = ""
annotations[ann.HostName] = ""
annotations[ann.IP] = ""
annotations[ann.Image] = ""
annotations[ann.ImageName] = ""
annotations[ann.ImageRef] = ""
annotations[ann.KubeName] = ""
annotations[ann.Labels] = ""
annotations[ann.LogPath] = ""
annotations[ann.Metadata] = ""
annotations[ann.Name] = ""
annotations[ann.PrivilegedRuntime] = ""
annotations[ann.ResolvPath] = ""
annotations[ann.HostnamePath] = ""
annotations[ann.SandboxID] = ""
annotations[ann.SandboxName] = ""
annotations[ann.ShmPath] = ""
annotations[ann.MountPoint] = ""
annotations[ann.TrustedSandbox] = ""
annotations[ann.TTY] = "false"
annotations[ann.Stdin] = ""
annotations[ann.StdinOnce] = ""
annotations[ann.Volumes] = ""
return annotations
}
//GetVolumeMounts takes user provided input for bind mounts and creates Mount structs
func (c *createConfig) GetVolumeMounts() ([]spec.Mount, error) {
var m []spec.Mount
var options []string
for _, i := range c.Volumes {
// We need to handle SELinux options better here, specifically :Z
spliti := strings.Split(i, ":")
if len(spliti) > 2 {
options = strings.Split(spliti[2], ",")
}
options = append(options, "rbind")
var foundrw, foundro, foundz, foundZ bool
var rootProp string
for _, opt := range options {
switch opt {
case "rw":
foundrw = true
case "ro":
foundro = true
case "z":
foundz = true
case "Z":
foundZ = true
case "private", "rprivate", "slave", "rslave", "shared", "rshared":
rootProp = opt
}
}
if !foundrw && !foundro {
options = append(options, "rw")
}
if foundz {
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 {
return nil, errors.Wrapf(err, "relabel failed %q", spliti[0])
}
}
if rootProp == "" {
options = append(options, "rprivate")
}
m = append(m, spec.Mount{
Destination: spliti[1],
Type: string(TypeBind),
Source: spliti[0],
Options: options,
})
}
return m, nil
}
//GetTmpfsMounts takes user provided input for Tmpfs mounts and creates Mount structs
func (c *createConfig) GetTmpfsMounts() []spec.Mount {
var m []spec.Mount
for _, i := range c.Tmpfs {
// Default options if nothing passed
options := []string{"rw", "noexec", "nosuid", "nodev", "size=65536k"}
spliti := strings.Split(i, ":")
destPath := spliti[0]
if len(spliti) > 1 {
options = strings.Split(spliti[1], ",")
}
m = append(m, spec.Mount{
Destination: destPath,
Type: string(TypeTmpfs),
Options: options,
Source: string(TypeTmpfs),
})
}
return m
}
func (c *createConfig) GetContainerCreateOptions() ([]libpod.CtrCreateOption, error) {
var options []libpod.CtrCreateOption
// Uncomment after talking to mheon about unimplemented funcs
// options = append(options, libpod.WithLabels(c.labels))
if c.Interactive {
options = append(options, libpod.WithStdin())
}
if c.Name != "" {
logrus.Debugf("appending name %s", c.Name)
options = append(options, libpod.WithName(c.Name))
}
return options, nil
}
func getStatFromPath(path string) unix.Stat_t {
s := unix.Stat_t{}
_ = unix.Stat(path, &s)
return s
}
func makeThrottleArray(throttleInput []string) ([]spec.LinuxThrottleDevice, error) {
var ltds []spec.LinuxThrottleDevice
for _, i := range throttleInput {
t, err := validateBpsDevice(i)
if err != nil {
return []spec.LinuxThrottleDevice{}, err
}
ltd := spec.LinuxThrottleDevice{}
ltd.Rate = t.rate
ltdStat := getStatFromPath(t.path)
ltd.Major = int64(unix.Major(ltdStat.Rdev))
ltd.Minor = int64(unix.Major(ltdStat.Rdev))
ltds = append(ltds, ltd)
}
return ltds, nil
}