diff options
Diffstat (limited to 'pkg/spec')
-rw-r--r-- | pkg/spec/config_linux.go | 371 | ||||
-rw-r--r-- | pkg/spec/config_linux_cgo.go | 47 | ||||
-rw-r--r-- | pkg/spec/config_linux_nocgo.go | 11 | ||||
-rw-r--r-- | pkg/spec/config_unsupported.go | 36 | ||||
-rw-r--r-- | pkg/spec/containerconfig.go | 41 | ||||
-rw-r--r-- | pkg/spec/createconfig.go | 426 | ||||
-rw-r--r-- | pkg/spec/namespaces.go | 463 | ||||
-rw-r--r-- | pkg/spec/parse.go | 225 | ||||
-rw-r--r-- | pkg/spec/ports.go | 107 | ||||
-rw-r--r-- | pkg/spec/security.go | 204 | ||||
-rw-r--r-- | pkg/spec/spec.go | 593 | ||||
-rw-r--r-- | pkg/spec/spec_test.go | 108 | ||||
-rw-r--r-- | pkg/spec/storage.go | 875 | ||||
-rw-r--r-- | pkg/spec/storage_test.go | 38 |
14 files changed, 0 insertions, 3545 deletions
diff --git a/pkg/spec/config_linux.go b/pkg/spec/config_linux.go deleted file mode 100644 index 319cce61f..000000000 --- a/pkg/spec/config_linux.go +++ /dev/null @@ -1,371 +0,0 @@ -// +build linux - -package createconfig - -import ( - "fmt" - "io/ioutil" - "os" - "path/filepath" - "strconv" - "strings" - - "github.com/containers/podman/v2/pkg/rootless" - "github.com/opencontainers/runc/libcontainer/configs" - "github.com/opencontainers/runc/libcontainer/devices" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/pkg/errors" - "golang.org/x/sys/unix" -) - -// Device transforms a libcontainer configs.Device to a specs.LinuxDevice object. -func Device(d *configs.Device) spec.LinuxDevice { - return spec.LinuxDevice{ - Type: string(d.Type), - Path: d.Path, - Major: d.Major, - Minor: d.Minor, - FileMode: fmPtr(int64(d.FileMode)), - UID: u32Ptr(int64(d.Uid)), - GID: u32Ptr(int64(d.Gid)), - } -} - -// DevicesFromPath computes a list of devices -func DevicesFromPath(g *generate.Generator, devicePath string) error { - devs := strings.Split(devicePath, ":") - resolvedDevicePath := devs[0] - // check if it is a symbolic link - if src, err := os.Lstat(resolvedDevicePath); err == nil && src.Mode()&os.ModeSymlink == os.ModeSymlink { - if linkedPathOnHost, err := filepath.EvalSymlinks(resolvedDevicePath); err == nil { - resolvedDevicePath = linkedPathOnHost - } - } - st, err := os.Stat(resolvedDevicePath) - if err != nil { - return errors.Wrapf(err, "cannot stat %s", devicePath) - } - if st.IsDir() { - found := false - src := resolvedDevicePath - dest := src - var devmode string - if len(devs) > 1 { - if len(devs[1]) > 0 && devs[1][0] == '/' { - dest = devs[1] - } else { - devmode = devs[1] - } - } - if len(devs) > 2 { - if devmode != "" { - return errors.Wrapf(unix.EINVAL, "invalid device specification %s", devicePath) - } - devmode = devs[2] - } - - // mount the internal devices recursively - if err := filepath.Walk(resolvedDevicePath, func(dpath string, f os.FileInfo, e error) error { - - if f.Mode()&os.ModeDevice == os.ModeDevice { - found = true - device := fmt.Sprintf("%s:%s", dpath, filepath.Join(dest, strings.TrimPrefix(dpath, src))) - if devmode != "" { - device = fmt.Sprintf("%s:%s", device, devmode) - } - if err := addDevice(g, device); err != nil { - return errors.Wrapf(err, "failed to add %s device", dpath) - } - } - return nil - }); err != nil { - return err - } - if !found { - return errors.Wrapf(unix.EINVAL, "no devices found in %s", devicePath) - } - return nil - } - - return addDevice(g, strings.Join(append([]string{resolvedDevicePath}, devs[1:]...), ":")) -} - -func deviceCgroupRules(g *generate.Generator, deviceCgroupRules []string) error { - for _, deviceCgroupRule := range deviceCgroupRules { - if err := validateDeviceCgroupRule(deviceCgroupRule); err != nil { - return err - } - ss := parseDeviceCgroupRule(deviceCgroupRule) - if len(ss[0]) != 5 { - return errors.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule) - } - matches := ss[0] - var major, minor *int64 - if matches[2] == "*" { - majorDev := int64(-1) - major = &majorDev - } else { - majorDev, err := strconv.ParseInt(matches[2], 10, 64) - if err != nil { - return errors.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule) - } - major = &majorDev - } - if matches[3] == "*" { - minorDev := int64(-1) - minor = &minorDev - } else { - minorDev, err := strconv.ParseInt(matches[2], 10, 64) - if err != nil { - return errors.Errorf("invalid major value in device cgroup rule format: '%s'", deviceCgroupRule) - } - minor = &minorDev - } - g.AddLinuxResourcesDevice(true, matches[1], major, minor, matches[4]) - } - return nil -} - -func addDevice(g *generate.Generator, device string) error { - src, dst, permissions, err := ParseDevice(device) - if err != nil { - return err - } - dev, err := devices.DeviceFromPath(src, permissions) - if err != nil { - return errors.Wrapf(err, "%s is not a valid device", src) - } - if rootless.IsRootless() { - if _, err := os.Stat(src); err != nil { - if os.IsNotExist(err) { - return errors.Wrapf(err, "the specified device %s doesn't exist", src) - } - return errors.Wrapf(err, "stat device %s exist", src) - } - perm := "ro" - if strings.Contains(permissions, "w") { - perm = "rw" - } - devMnt := spec.Mount{ - Destination: dst, - Type: TypeBind, - Source: src, - Options: []string{"slave", "nosuid", "noexec", perm, "rbind"}, - } - g.Config.Mounts = append(g.Config.Mounts, devMnt) - return nil - } - dev.Path = dst - linuxdev := spec.LinuxDevice{ - Path: dev.Path, - Type: string(dev.Type), - Major: dev.Major, - Minor: dev.Minor, - FileMode: &dev.FileMode, - UID: &dev.Uid, - GID: &dev.Gid, - } - g.AddDevice(linuxdev) - g.AddLinuxResourcesDevice(true, string(dev.Type), &dev.Major, &dev.Minor, string(dev.Permissions)) - return nil -} - -// based on getDevices from runc (libcontainer/devices/devices.go) -func getDevices(path string) ([]*configs.Device, error) { - files, err := ioutil.ReadDir(path) - if err != nil { - if rootless.IsRootless() && os.IsPermission(err) { - return nil, nil - } - return nil, err - } - out := []*configs.Device{} - for _, f := range files { - switch { - case f.IsDir(): - switch f.Name() { - // ".lxc" & ".lxd-mounts" added to address https://github.com/lxc/lxd/issues/2825 - case "pts", "shm", "fd", "mqueue", ".lxc", ".lxd-mounts": - continue - default: - sub, err := getDevices(filepath.Join(path, f.Name())) - if err != nil { - return nil, err - } - if sub != nil { - out = append(out, sub...) - } - continue - } - case f.Name() == "console": - continue - case f.Mode()&os.ModeSymlink != 0: - // do not add symlink'd devices to privileged devices - continue - } - device, err := devices.DeviceFromPath(filepath.Join(path, f.Name()), "rwm") - if err != nil { - if err == devices.ErrNotADevice { - continue - } - if os.IsNotExist(err) { - continue - } - return nil, err - } - out = append(out, device) - } - return out, nil -} - -func addPrivilegedDevices(g *generate.Generator) error { - hostDevices, err := getDevices("/dev") - if err != nil { - return err - } - g.ClearLinuxDevices() - - if rootless.IsRootless() { - mounts := make(map[string]interface{}) - for _, m := range g.Mounts() { - mounts[m.Destination] = true - } - newMounts := []spec.Mount{} - for _, d := range hostDevices { - devMnt := spec.Mount{ - Destination: d.Path, - Type: TypeBind, - Source: d.Path, - Options: []string{"slave", "nosuid", "noexec", "rw", "rbind"}, - } - if d.Path == "/dev/ptmx" || strings.HasPrefix(d.Path, "/dev/tty") { - continue - } - if _, found := mounts[d.Path]; found { - continue - } - st, err := os.Stat(d.Path) - if err != nil { - if err == unix.EPERM { - continue - } - return errors.Wrapf(err, "stat %s", d.Path) - } - // Skip devices that the user has not access to. - if st.Mode()&0007 == 0 { - continue - } - newMounts = append(newMounts, devMnt) - } - g.Config.Mounts = append(newMounts, g.Config.Mounts...) - g.Config.Linux.Resources.Devices = nil - } else { - for _, d := range hostDevices { - g.AddDevice(Device(d)) - } - // Add resources device - need to clear the existing one first. - g.Config.Linux.Resources.Devices = nil - g.AddLinuxResourcesDevice(true, "", nil, nil, "rwm") - } - - return nil -} - -func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) { - var ret *spec.LinuxBlockIO - bio := &spec.LinuxBlockIO{} - if c.Resources.BlkioWeight > 0 { - ret = bio - bio.Weight = &c.Resources.BlkioWeight - } - if len(c.Resources.BlkioWeightDevice) > 0 { - var lwds []spec.LinuxWeightDevice - ret = bio - for _, i := range c.Resources.BlkioWeightDevice { - wd, err := ValidateweightDevice(i) - if err != nil { - return ret, errors.Wrapf(err, "invalid values for blkio-weight-device") - } - wdStat, err := GetStatFromPath(wd.Path) - if err != nil { - return ret, errors.Wrapf(err, "error getting stat from path %q", 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) - } - bio.WeightDevice = lwds - } - if len(c.Resources.DeviceReadBps) > 0 { - ret = bio - readBps, err := makeThrottleArray(c.Resources.DeviceReadBps, bps) - if err != nil { - return ret, err - } - bio.ThrottleReadBpsDevice = readBps - } - if len(c.Resources.DeviceWriteBps) > 0 { - ret = bio - writeBpds, err := makeThrottleArray(c.Resources.DeviceWriteBps, bps) - if err != nil { - return ret, err - } - bio.ThrottleWriteBpsDevice = writeBpds - } - if len(c.Resources.DeviceReadIOps) > 0 { - ret = bio - readIOps, err := makeThrottleArray(c.Resources.DeviceReadIOps, iops) - if err != nil { - return ret, err - } - bio.ThrottleReadIOPSDevice = readIOps - } - if len(c.Resources.DeviceWriteIOps) > 0 { - ret = bio - writeIOps, err := makeThrottleArray(c.Resources.DeviceWriteIOps, iops) - if err != nil { - return ret, err - } - bio.ThrottleWriteIOPSDevice = writeIOps - } - return ret, nil -} - -func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) { - var ( - ltds []spec.LinuxThrottleDevice - t *throttleDevice - err error - ) - for _, i := range throttleInput { - if rateType == bps { - t, err = validateBpsDevice(i) - } else { - t, err = validateIOpsDevice(i) - } - if err != nil { - return []spec.LinuxThrottleDevice{}, err - } - ltdStat, err := GetStatFromPath(t.path) - if err != nil { - return ltds, errors.Wrapf(err, "error getting stat from path %q", t.path) - } - ltd := spec.LinuxThrottleDevice{ - Rate: t.rate, - } - ltd.Major = int64(unix.Major(ltdStat.Rdev)) - ltd.Minor = int64(unix.Minor(ltdStat.Rdev)) - ltds = append(ltds, ltd) - } - return ltds, nil -} - -func GetStatFromPath(path string) (unix.Stat_t, error) { - s := unix.Stat_t{} - err := unix.Stat(path, &s) - return s, err -} diff --git a/pkg/spec/config_linux_cgo.go b/pkg/spec/config_linux_cgo.go deleted file mode 100644 index d0891b574..000000000 --- a/pkg/spec/config_linux_cgo.go +++ /dev/null @@ -1,47 +0,0 @@ -// +build linux,cgo - -package createconfig - -import ( - "io/ioutil" - - goSeccomp "github.com/containers/common/pkg/seccomp" - "github.com/containers/podman/v2/pkg/seccomp" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - var seccompConfig *spec.LinuxSeccomp - var err error - - if config.SeccompPolicy == seccomp.PolicyImage && config.SeccompProfileFromImage != "" { - logrus.Debug("Loading seccomp profile from the security config") - seccompConfig, err = goSeccomp.LoadProfile(config.SeccompProfileFromImage, configSpec) - if err != nil { - return nil, errors.Wrap(err, "loading seccomp profile failed") - } - return seccompConfig, nil - } - - if config.SeccompProfilePath != "" { - logrus.Debugf("Loading seccomp profile from %q", config.SeccompProfilePath) - seccompProfile, err := ioutil.ReadFile(config.SeccompProfilePath) - if err != nil { - return nil, errors.Wrap(err, "opening seccomp profile failed") - } - seccompConfig, err = goSeccomp.LoadProfile(string(seccompProfile), configSpec) - if err != nil { - return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", config.SeccompProfilePath) - } - } else { - logrus.Debug("Loading default seccomp profile") - seccompConfig, err = goSeccomp.GetDefaultProfile(configSpec) - if err != nil { - return nil, errors.Wrapf(err, "loading default seccomp profile failed") - } - } - - return seccompConfig, nil -} diff --git a/pkg/spec/config_linux_nocgo.go b/pkg/spec/config_linux_nocgo.go deleted file mode 100644 index 8d720b6d4..000000000 --- a/pkg/spec/config_linux_nocgo.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build linux,!cgo - -package createconfig - -import ( - spec "github.com/opencontainers/runtime-spec/specs-go" -) - -func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - return nil, nil -} diff --git a/pkg/spec/config_unsupported.go b/pkg/spec/config_unsupported.go deleted file mode 100644 index 568afde55..000000000 --- a/pkg/spec/config_unsupported.go +++ /dev/null @@ -1,36 +0,0 @@ -// +build !linux - -package createconfig - -import ( - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/pkg/errors" -) - -func getSeccompConfig(config *SecurityConfig, configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - return nil, errors.New("function not supported on non-linux OS's") -} -func addDevice(g *generate.Generator, device string) error { - return errors.New("function not implemented") -} - -func addPrivilegedDevices(g *generate.Generator) error { - return errors.New("function not implemented") -} - -func (c *CreateConfig) createBlockIO() (*spec.LinuxBlockIO, error) { - return nil, errors.New("function not implemented") -} - -func makeThrottleArray(throttleInput []string, rateType int) ([]spec.LinuxThrottleDevice, error) { - return nil, errors.New("function not implemented") -} - -func DevicesFromPath(g *generate.Generator, devicePath string) error { - return errors.New("function not implemented") -} - -func deviceCgroupRules(g *generate.Generator, deviceCgroupRules []string) error { - return errors.New("function not implemented") -} diff --git a/pkg/spec/containerconfig.go b/pkg/spec/containerconfig.go deleted file mode 100644 index f11d85b7e..000000000 --- a/pkg/spec/containerconfig.go +++ /dev/null @@ -1,41 +0,0 @@ -package createconfig - -import ( - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// MakeContainerConfig generates all configuration necessary to start a -// container with libpod from a completed CreateConfig struct. -func (config *CreateConfig) MakeContainerConfig(runtime *libpod.Runtime, pod *libpod.Pod) (*spec.Spec, []libpod.CtrCreateOption, error) { - if config.Pod != "" && pod == nil { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "pod was specified but no pod passed") - } else if config.Pod == "" && pod != nil { - return nil, nil, errors.Wrapf(define.ErrInvalidArg, "pod was given but no pod is specified") - } - - // Parse volumes flag into OCI spec mounts and libpod Named Volumes. - // If there is an identical mount in the OCI spec, we will replace it - // with a mount generated here. - mounts, namedVolumes, err := config.parseVolumes(runtime) - if err != nil { - return nil, nil, err - } - - runtimeSpec, err := config.createConfigToOCISpec(runtime, mounts) - if err != nil { - return nil, nil, err - } - - options, err := config.getContainerCreateOptions(runtime, pod, mounts, namedVolumes) - if err != nil { - return nil, nil, err - } - - logrus.Debugf("created OCI spec and options for new container") - - return runtimeSpec, options, nil -} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go deleted file mode 100644 index 4887e9262..000000000 --- a/pkg/spec/createconfig.go +++ /dev/null @@ -1,426 +0,0 @@ -package createconfig - -import ( - "context" - "os" - "strconv" - "strings" - "syscall" - - "github.com/containers/image/v5/manifest" - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/namespaces" - "github.com/containers/podman/v2/pkg/seccomp" - "github.com/containers/storage" - "github.com/docker/go-connections/nat" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// Type constants -const ( - bps = iota - iops -) - -// CreateResourceConfig represents resource elements in CreateConfig -// structures -type CreateResourceConfig struct { - BlkioWeight uint16 // blkio-weight - BlkioWeightDevice []string // blkio-weight-device - CgroupConf map[string]string - CPUPeriod uint64 // cpu-period - CPUQuota int64 // cpu-quota - CPURtPeriod uint64 // cpu-rt-period - CPURtRuntime int64 // cpu-rt-runtime - CPUShares uint64 // cpu-shares - CPUs float64 // cpus - CPUsetCPUs string - CPUsetMems string // cpuset-mems - DeviceCgroupRules []string //device-cgroup-rule - 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 int64 - Ulimit []string //ulimit -} - -// PidConfig configures the pid namespace for the container -type PidConfig struct { - PidMode namespaces.PidMode //pid -} - -// IpcConfig configures the ipc namespace for the container -type IpcConfig struct { - IpcMode namespaces.IpcMode //ipc -} - -// CgroupConfig configures the cgroup namespace for the container -type CgroupConfig struct { - Cgroups string - Cgroupns string - CgroupParent string // cgroup-parent - CgroupMode namespaces.CgroupMode //cgroup -} - -// UserConfig configures the user namespace for the container -type UserConfig struct { - GroupAdd []string // group-add - IDMappings *storage.IDMappingOptions - UsernsMode namespaces.UsernsMode //userns - User string //user -} - -// UtsConfig configures the uts namespace for the container -type UtsConfig struct { - UtsMode namespaces.UTSMode //uts - NoHosts bool - HostAdd []string //add-host - Hostname string -} - -// NetworkConfig configures the network namespace for the container -type NetworkConfig struct { - DNSOpt []string //dns-opt - DNSSearch []string //dns-search - DNSServers []string //dns - ExposedPorts map[nat.Port]struct{} - HTTPProxy bool - IP6Address string //ipv6 - IPAddress string //ip - LinkLocalIP []string // link-local-ip - MacAddress string //mac-address - NetMode namespaces.NetworkMode //net - Network string //network - NetworkAlias []string //network-alias - PortBindings nat.PortMap - Publish []string //publish - PublishAll bool //publish-all -} - -// SecurityConfig configures the security features for the container -type SecurityConfig struct { - CapAdd []string // cap-add - CapDrop []string // cap-drop - CapRequired []string // cap-required - LabelOpts []string //SecurityOpts - NoNewPrivs bool //SecurityOpts - ApparmorProfile string //SecurityOpts - SeccompProfilePath string //SecurityOpts - SeccompProfileFromImage string // seccomp profile from the container image - SeccompPolicy seccomp.Policy - SecurityOpts []string - Privileged bool //privileged - ReadOnlyRootfs bool //read-only - ReadOnlyTmpfs bool //read-only-tmpfs - Sysctl map[string]string //sysctl - ProcOpts []string -} - -// CreateConfig is a pre OCI spec structure. It represents user input from varlink or the CLI -// swagger:model CreateConfig -type CreateConfig struct { - Annotations map[string]string - Args []string - CidFile string - ConmonPidFile string - Command []string // Full command that will be used - UserCommand []string // User-entered command (or image CMD) - Detach bool // detach - Devices []string // device - Entrypoint []string //entrypoint - Env map[string]string //env - HealthCheck *manifest.Schema2HealthConfig - Init bool // init - InitPath string //init-path - Image string - ImageID string - RawImageName string - BuiltinImgVolumes map[string]struct{} // volumes defined in the image config - ImageVolumeType string // how to handle the image volume, either bind, tmpfs, or ignore - Interactive bool //interactive - Labels map[string]string //label - LogDriver string // log-driver - LogDriverOpt []string // log-opt - Name string //name - PodmanPath string - Pod string //pod - Quiet bool //quiet - Resources CreateResourceConfig - RestartPolicy string - Rm bool //rm - Rmi bool //rmi - StopSignal syscall.Signal // stop-signal - StopTimeout uint // stop-timeout - Systemd bool - Tmpfs []string // tmpfs - Tty bool //tty - Mounts []spec.Mount - MountsFlag []string // mounts - NamedVolumes []*libpod.ContainerNamedVolume - Volumes []string //volume - VolumesFrom []string - WorkDir string //workdir - Rootfs string - Security SecurityConfig - Syslog bool // Whether to enable syslog on exit commands - - // Namespaces - Pid PidConfig - Ipc IpcConfig - Cgroup CgroupConfig - User UserConfig - Uts UtsConfig - Network NetworkConfig -} - -func u32Ptr(i int64) *uint32 { u := uint32(i); return &u } -func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm } - -// CreateBlockIO returns a LinuxBlockIO struct from a CreateConfig -func (c *CreateConfig) CreateBlockIO() (*spec.LinuxBlockIO, error) { - return c.createBlockIO() -} - -func (c *CreateConfig) createExitCommand(runtime *libpod.Runtime) ([]string, error) { - config, err := runtime.GetConfig() - if err != nil { - return nil, err - } - storageConfig := runtime.StorageConfig() - - // We need a cleanup process for containers in the current model. - // But we can't assume that the caller is Podman - it could be another - // user of the API. - // As such, provide a way to specify a path to Podman, so we can - // still invoke a cleanup process. - cmd := c.PodmanPath - if cmd == "" { - cmd, _ = os.Executable() - } - - command := []string{cmd, - "--root", storageConfig.GraphRoot, - "--runroot", storageConfig.RunRoot, - "--log-level", logrus.GetLevel().String(), - "--cgroup-manager", config.Engine.CgroupManager, - "--tmpdir", config.Engine.TmpDir, - } - if config.Engine.OCIRuntime != "" { - command = append(command, []string{"--runtime", config.Engine.OCIRuntime}...) - } - if storageConfig.GraphDriverName != "" { - command = append(command, []string{"--storage-driver", storageConfig.GraphDriverName}...) - } - for _, opt := range storageConfig.GraphDriverOptions { - command = append(command, []string{"--storage-opt", opt}...) - } - if config.Engine.EventsLogger != "" { - command = append(command, []string{"--events-backend", config.Engine.EventsLogger}...) - } - - if c.Syslog { - command = append(command, "--syslog", "true") - } - command = append(command, []string{"container", "cleanup"}...) - - if c.Rm { - command = append(command, "--rm") - } - - if c.Rmi { - command = append(command, "--rmi") - } - - return command, nil -} - -// GetContainerCreateOptions takes a CreateConfig and returns a slice of CtrCreateOptions -func (c *CreateConfig) getContainerCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod, mounts []spec.Mount, namedVolumes []*libpod.ContainerNamedVolume) ([]libpod.CtrCreateOption, error) { - var options []libpod.CtrCreateOption - var err error - - if c.Interactive { - options = append(options, libpod.WithStdin()) - } - if c.Systemd { - options = append(options, libpod.WithSystemd()) - } - if c.Name != "" { - logrus.Debugf("setting container name %s", c.Name) - options = append(options, libpod.WithName(c.Name)) - } - if c.Pod != "" { - logrus.Debugf("adding container to pod %s", c.Pod) - options = append(options, runtime.WithPod(pod)) - } - - // handle some spec from the InfraContainer when it's a pod - if pod != nil && pod.HasInfraContainer() { - InfraCtr, err := pod.InfraContainer() - if err != nil { - return nil, err - } - // handle the pod.spec.hostAliases - options = append(options, libpod.WithHosts(InfraCtr.HostsAdd())) - } - - if len(mounts) != 0 || len(namedVolumes) != 0 { - destinations := []string{} - - // Take all mount and named volume destinations. - for _, mount := range mounts { - destinations = append(destinations, mount.Destination) - } - for _, volume := range namedVolumes { - destinations = append(destinations, volume.Dest) - } - - options = append(options, libpod.WithUserVolumes(destinations)) - } - - if len(namedVolumes) != 0 { - options = append(options, libpod.WithNamedVolumes(namedVolumes)) - } - - if len(c.UserCommand) != 0 { - options = append(options, libpod.WithCommand(c.UserCommand)) - } - - // Add entrypoint if it was set - // If it's empty it's because it was explicitly set to "" - if c.Entrypoint != nil { - options = append(options, libpod.WithEntrypoint(c.Entrypoint)) - } - - // TODO: MNT, USER, CGROUP - options = append(options, libpod.WithStopSignal(c.StopSignal)) - options = append(options, libpod.WithStopTimeout(c.StopTimeout)) - - logPath, logTag := getLoggingOpts(c.LogDriverOpt) - if logPath != "" { - options = append(options, libpod.WithLogPath(logPath)) - } - if logTag != "" { - options = append(options, libpod.WithLogTag(logTag)) - } - - if c.LogDriver != "" { - options = append(options, libpod.WithLogDriver(c.LogDriver)) - } - - secOpts, err := c.Security.ToCreateOptions() - if err != nil { - return nil, err - } - options = append(options, secOpts...) - - nsOpts, err := c.Cgroup.ToCreateOptions(runtime) - if err != nil { - return nil, err - } - options = append(options, nsOpts...) - - nsOpts, err = c.Ipc.ToCreateOptions(runtime) - if err != nil { - return nil, err - } - options = append(options, nsOpts...) - - nsOpts, err = c.Pid.ToCreateOptions(runtime) - if err != nil { - return nil, err - } - options = append(options, nsOpts...) - - nsOpts, err = c.Network.ToCreateOptions(runtime, &c.User) - if err != nil { - return nil, err - } - options = append(options, nsOpts...) - - nsOpts, err = c.Uts.ToCreateOptions(runtime, pod) - if err != nil { - return nil, err - } - options = append(options, nsOpts...) - - nsOpts, err = c.User.ToCreateOptions(runtime) - if err != nil { - return nil, err - } - options = append(options, nsOpts...) - - // Gather up the options for NewContainer which consist of With... funcs - options = append(options, libpod.WithRootFSFromImage(c.ImageID, c.Image, c.RawImageName)) - options = append(options, libpod.WithConmonPidFile(c.ConmonPidFile)) - options = append(options, libpod.WithLabels(c.Labels)) - options = append(options, libpod.WithShmSize(c.Resources.ShmSize)) - if c.Rootfs != "" { - options = append(options, libpod.WithRootFS(c.Rootfs)) - } - // Default used if not overridden on command line - - if c.RestartPolicy != "" { - if c.RestartPolicy == "unless-stopped" { - return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") - } - - split := strings.Split(c.RestartPolicy, ":") - if len(split) > 1 { - numTries, err := strconv.Atoi(split[1]) - if err != nil { - return nil, errors.Wrapf(err, "%s is not a valid number of retries for restart policy", split[1]) - } - if numTries < 0 { - return nil, errors.Wrapf(define.ErrInvalidArg, "restart policy requires a positive number of retries") - } - options = append(options, libpod.WithRestartRetries(uint(numTries))) - } - options = append(options, libpod.WithRestartPolicy(split[0])) - } - - // Always use a cleanup process to clean up Podman after termination - exitCmd, err := c.createExitCommand(runtime) - if err != nil { - return nil, err - } - options = append(options, libpod.WithExitCommand(exitCmd)) - - if c.HealthCheck != nil { - options = append(options, libpod.WithHealthCheck(c.HealthCheck)) - logrus.Debugf("New container has a health check") - } - return options, nil -} - -// AddPrivilegedDevices iterates through host devices and adds all -// host devices to the spec -func AddPrivilegedDevices(g *generate.Generator) error { - return addPrivilegedDevices(g) -} - -func CreateContainerFromCreateConfig(ctx context.Context, r *libpod.Runtime, createConfig *CreateConfig, pod *libpod.Pod) (*libpod.Container, error) { - runtimeSpec, options, err := createConfig.MakeContainerConfig(r, pod) - if err != nil { - return nil, err - } - - ctr, err := r.NewContainer(ctx, runtimeSpec, options...) - if err != nil { - return nil, err - } - return ctr, nil -} diff --git a/pkg/spec/namespaces.go b/pkg/spec/namespaces.go deleted file mode 100644 index 8f98a9786..000000000 --- a/pkg/spec/namespaces.go +++ /dev/null @@ -1,463 +0,0 @@ -package createconfig - -import ( - "net" - "os" - "strconv" - "strings" - - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/cgroups" - "github.com/cri-o/ocicni/pkg/ocicni" - "github.com/docker/go-connections/nat" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// DefaultKernelNamespaces is a comma-separated list of default kernel -// namespaces. -const DefaultKernelNamespaces = "cgroup,ipc,net,uts" - -// ToCreateOptions converts the input to a slice of container create options. -func (c *NetworkConfig) ToCreateOptions(runtime *libpod.Runtime, userns *UserConfig) ([]libpod.CtrCreateOption, error) { - var portBindings []ocicni.PortMapping - var err error - if len(c.PortBindings) > 0 { - portBindings, err = NatToOCIPortBindings(c.PortBindings) - if err != nil { - return nil, errors.Wrapf(err, "unable to create port bindings") - } - } - - options := make([]libpod.CtrCreateOption, 0) - userNetworks := c.NetMode.UserDefined() - networks := make([]string, 0) - - if IsPod(userNetworks) { - userNetworks = "" - } - if userNetworks != "" { - for _, netName := range strings.Split(userNetworks, ",") { - if netName == "" { - return nil, errors.Errorf("container networks %q invalid", userNetworks) - } - networks = append(networks, netName) - } - } - - switch { - case c.NetMode.IsNS(): - ns := c.NetMode.NS() - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - case c.NetMode.IsContainer(): - connectedCtr, err := runtime.LookupContainer(c.NetMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.NetMode.Container()) - } - options = append(options, libpod.WithNetNSFrom(connectedCtr)) - case !c.NetMode.IsHost() && !c.NetMode.IsNone(): - postConfigureNetNS := userns.getPostConfigureNetNS() - options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(c.NetMode), networks)) - } - - if len(c.DNSSearch) > 0 { - options = append(options, libpod.WithDNSSearch(c.DNSSearch)) - } - if len(c.DNSServers) > 0 { - if len(c.DNSServers) == 1 && strings.ToLower(c.DNSServers[0]) == "none" { - options = append(options, libpod.WithUseImageResolvConf()) - } else { - options = append(options, libpod.WithDNS(c.DNSServers)) - } - } - if len(c.DNSOpt) > 0 { - options = append(options, libpod.WithDNSOption(c.DNSOpt)) - } - if c.IPAddress != "" { - ip := net.ParseIP(c.IPAddress) - if ip == nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as IP address", c.IPAddress) - } else if ip.To4() == nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "%s is not an IPv4 address", c.IPAddress) - } - options = append(options, libpod.WithStaticIP(ip)) - } - - if c.MacAddress != "" { - mac, err := net.ParseMAC(c.MacAddress) - if err != nil { - return nil, errors.Wrapf(define.ErrInvalidArg, "cannot parse %s as MAC address: %v", c.MacAddress, err) - } - options = append(options, libpod.WithStaticMAC(mac)) - } - - return options, nil -} - -// ConfigureGenerator configures the generator based according to the current -// state of the NetworkConfig. -func (c *NetworkConfig) ConfigureGenerator(g *generate.Generator) error { - netMode := c.NetMode - netCtr := netMode.Container() - switch { - case netMode.IsHost(): - logrus.Debug("Using host netmode") - if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil { - return err - } - case netMode.IsNone(): - logrus.Debug("Using none netmode") - case netMode.IsBridge(): - logrus.Debug("Using bridge netmode") - case netCtr != "": - logrus.Debugf("using container %s netmode", netCtr) - case IsNS(string(netMode)): - logrus.Debug("Using ns netmode") - if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), NS(string(netMode))); err != nil { - return err - } - case IsPod(string(netMode)): - logrus.Debug("Using pod netmode, unless pod is not sharing") - case netMode.IsSlirp4netns(): - logrus.Debug("Using slirp4netns netmode") - case netMode.IsUserDefined(): - logrus.Debug("Using user defined netmode") - default: - return errors.Errorf("unknown network mode") - } - - if c.HTTPProxy { - for _, envSpec := range []string{ - "http_proxy", - "HTTP_PROXY", - "https_proxy", - "HTTPS_PROXY", - "ftp_proxy", - "FTP_PROXY", - "no_proxy", - "NO_PROXY", - } { - envVal := os.Getenv(envSpec) - if envVal != "" { - g.AddProcessEnv(envSpec, envVal) - } - } - } - - if g.Config.Annotations == nil { - g.Config.Annotations = make(map[string]string) - } - - if c.PublishAll { - g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseTrue - } else { - g.Config.Annotations[define.InspectAnnotationPublishAll] = define.InspectResponseFalse - } - - return nil -} - -// NatToOCIPortBindings iterates a nat.portmap slice and creates []ocicni portmapping slice -func NatToOCIPortBindings(ports nat.PortMap) ([]ocicni.PortMapping, error) { - var portBindings []ocicni.PortMapping - for containerPb, hostPb := range ports { - var pm ocicni.PortMapping - pm.ContainerPort = int32(containerPb.Int()) - for _, i := range hostPb { - var hostPort int - var err error - pm.HostIP = i.HostIP - if i.HostPort == "" { - hostPort = containerPb.Int() - } else { - hostPort, err = strconv.Atoi(i.HostPort) - if err != nil { - return nil, errors.Wrapf(err, "unable to convert host port to integer") - } - } - - pm.HostPort = int32(hostPort) - pm.Protocol = containerPb.Proto() - portBindings = append(portBindings, pm) - } - } - return portBindings, nil -} - -// ToCreateOptions converts the input to container create options. -func (c *CgroupConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - if c.CgroupMode.IsNS() { - ns := c.CgroupMode.NS() - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined network namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - } else if c.CgroupMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.CgroupMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.CgroupMode.Container()) - } - options = append(options, libpod.WithCgroupNSFrom(connectedCtr)) - } - - if c.CgroupParent != "" { - options = append(options, libpod.WithCgroupParent(c.CgroupParent)) - } - - if c.Cgroups != "" { - options = append(options, libpod.WithCgroupsMode(c.Cgroups)) - } - - return options, nil -} - -// ToCreateOptions converts the input to container create options. -func (c *UserConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - switch { - case c.UsernsMode.IsNS(): - ns := c.UsernsMode.NS() - if ns == "" { - return nil, errors.Errorf("invalid empty user-defined user namespace") - } - _, err := os.Stat(ns) - if err != nil { - return nil, err - } - options = append(options, libpod.WithIDMappings(*c.IDMappings)) - case c.UsernsMode.IsContainer(): - connectedCtr, err := runtime.LookupContainer(c.UsernsMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.UsernsMode.Container()) - } - options = append(options, libpod.WithUserNSFrom(connectedCtr)) - default: - options = append(options, libpod.WithIDMappings(*c.IDMappings)) - } - - options = append(options, libpod.WithUser(c.User)) - options = append(options, libpod.WithGroups(c.GroupAdd)) - - return options, nil -} - -// ConfigureGenerator configures the generator according to the current state -// of the UserConfig. -func (c *UserConfig) ConfigureGenerator(g *generate.Generator) error { - if IsNS(string(c.UsernsMode)) { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), NS(string(c.UsernsMode))); err != nil { - return err - } - // runc complains if no mapping is specified, even if we join another ns. So provide a dummy mapping - g.AddLinuxUIDMapping(uint32(0), uint32(0), uint32(1)) - g.AddLinuxGIDMapping(uint32(0), uint32(0), uint32(1)) - } - - if (len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0) && !c.UsernsMode.IsHost() { - if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil { - return err - } - } - for _, uidmap := range c.IDMappings.UIDMap { - g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size)) - } - for _, gidmap := range c.IDMappings.GIDMap { - g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size)) - } - return nil -} - -func (c *UserConfig) getPostConfigureNetNS() bool { - hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || c.UsernsMode.IsAuto() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 - postConfigureNetNS := hasUserns && !c.UsernsMode.IsHost() - return postConfigureNetNS -} - -// InNS returns true if the UserConfig indicates to be in a dedicated user -// namespace. -func (c *UserConfig) InNS(isRootless bool) bool { - hasUserns := c.UsernsMode.IsContainer() || c.UsernsMode.IsNS() || c.UsernsMode.IsAuto() || len(c.IDMappings.UIDMap) > 0 || len(c.IDMappings.GIDMap) > 0 - return isRootless || (hasUserns && !c.UsernsMode.IsHost()) -} - -// ToCreateOptions converts the input to container create options. -func (c *IpcConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - if c.IpcMode.IsHost() { - options = append(options, libpod.WithShmDir("/dev/shm")) - } else if c.IpcMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.IpcMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.IpcMode.Container()) - } - - options = append(options, libpod.WithIPCNSFrom(connectedCtr)) - options = append(options, libpod.WithShmDir(connectedCtr.ShmDir())) - } - - return options, nil -} - -// ConfigureGenerator configures the generator according to the current state -// of the IpcConfig. -func (c *IpcConfig) ConfigureGenerator(g *generate.Generator) error { - ipcMode := c.IpcMode - if IsNS(string(ipcMode)) { - return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), NS(string(ipcMode))) - } - if ipcMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.IPCNamespace)) - } - if ipcCtr := ipcMode.Container(); ipcCtr != "" { - logrus.Debugf("Using container %s ipcmode", ipcCtr) - } - - return nil -} - -// ConfigureGenerator configures the generator according to the current state -// of the CgroupConfig. -func (c *CgroupConfig) ConfigureGenerator(g *generate.Generator) error { - cgroupMode := c.CgroupMode - if cgroupMode.IsDefaultValue() { - // If the value is not specified, default to "private" on cgroups v2 and "host" on cgroups v1. - unified, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return err - } - if unified { - cgroupMode = "private" - } else { - cgroupMode = "host" - } - } - if cgroupMode.IsNS() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), NS(string(cgroupMode))) - } - if cgroupMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.CgroupNamespace)) - } - if cgroupMode.IsPrivate() { - return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "") - } - if cgCtr := cgroupMode.Container(); cgCtr != "" { - logrus.Debugf("Using container %s cgroup mode", cgCtr) - } - return nil -} - -// ToCreateOptions converts the input to container create options. -func (c *PidConfig) ToCreateOptions(runtime *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - if c.PidMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.PidMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.PidMode.Container()) - } - - options = append(options, libpod.WithPIDNSFrom(connectedCtr)) - } - - return options, nil -} - -// ConfigureGenerator configures the generator according to the current state -// of the PidConfig. -func (c *PidConfig) ConfigureGenerator(g *generate.Generator) error { - pidMode := c.PidMode - if IsNS(string(pidMode)) { - return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), NS(string(pidMode))) - } - if pidMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.PIDNamespace)) - } - if pidCtr := pidMode.Container(); pidCtr != "" { - logrus.Debugf("using container %s pidmode", pidCtr) - } - if IsPod(string(pidMode)) { - logrus.Debug("using pod pidmode") - } - return nil -} - -// ToCreateOptions converts the input to container create options. -func (c *UtsConfig) ToCreateOptions(runtime *libpod.Runtime, pod *libpod.Pod) ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - if IsPod(string(c.UtsMode)) { - options = append(options, libpod.WithUTSNSFromPod(pod)) - } - if c.UtsMode.IsContainer() { - connectedCtr, err := runtime.LookupContainer(c.UtsMode.Container()) - if err != nil { - return nil, errors.Wrapf(err, "container %q not found", c.UtsMode.Container()) - } - - options = append(options, libpod.WithUTSNSFrom(connectedCtr)) - } - if c.NoHosts { - options = append(options, libpod.WithUseImageHosts()) - } - if len(c.HostAdd) > 0 && !c.NoHosts { - options = append(options, libpod.WithHosts(c.HostAdd)) - } - - return options, nil -} - -// ConfigureGenerator configures the generator according to the current state -// of the UtsConfig. -func (c *UtsConfig) ConfigureGenerator(g *generate.Generator, net *NetworkConfig, runtime *libpod.Runtime) error { - hostname := c.Hostname - utsCtrID := c.UtsMode.Container() - var err error - if hostname == "" { - switch { - case utsCtrID != "": - utsCtr, err := runtime.LookupContainer(utsCtrID) - if err != nil { - return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", utsCtrID) - } - hostname = utsCtr.Hostname() - case net.NetMode.IsHost() || c.UtsMode.IsHost(): - hostname, err = os.Hostname() - if err != nil { - return errors.Wrap(err, "unable to retrieve hostname of the host") - } - default: - logrus.Debug("No hostname set; container's hostname will default to runtime default") - } - } - g.RemoveHostname() - if c.Hostname != "" || !c.UtsMode.IsHost() { - // Set the hostname in the OCI configuration only - // if specified by the user or if we are creating - // a new UTS namespace. - g.SetHostname(hostname) - } - g.AddProcessEnv("HOSTNAME", hostname) - - utsMode := c.UtsMode - if IsNS(string(utsMode)) { - return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), NS(string(utsMode))) - } - if utsMode.IsHost() { - return g.RemoveLinuxNamespace(string(spec.UTSNamespace)) - } - if utsCtr := utsMode.Container(); utsCtr != "" { - logrus.Debugf("using container %s utsmode", utsCtr) - } - return nil -} diff --git a/pkg/spec/parse.go b/pkg/spec/parse.go deleted file mode 100644 index 9ebcf8d29..000000000 --- a/pkg/spec/parse.go +++ /dev/null @@ -1,225 +0,0 @@ -package createconfig - -import ( - "fmt" - "regexp" - "strconv" - "strings" - - "github.com/docker/go-units" - "github.com/pkg/errors" -) - -// deviceCgroupRulegex defines the valid format of device-cgroup-rule -var deviceCgroupRuleRegex = regexp.MustCompile(`^([acb]) ([0-9]+|\*):([0-9]+|\*) ([rwm]{1,3})$`) - -// Pod signifies a kernel namespace is being shared -// by a container with the pod it is associated with -const Pod = "pod" - -// weightDevice is a structure that holds device:weight pair -type weightDevice struct { - Path string - Weight uint16 -} - -func (w *weightDevice) String() string { - return fmt.Sprintf("%s:%d", w.Path, w.Weight) -} - -// LinuxNS is a struct that contains namespace information -// It implemented Valid to show it is a valid namespace -type LinuxNS interface { - Valid() bool -} - -// IsNS returns if the specified string has a ns: prefix -func IsNS(s string) bool { - parts := strings.SplitN(s, ":", 2) - return len(parts) > 1 && parts[0] == "ns" -} - -// IsPod returns if the specified string is pod -func IsPod(s string) bool { - return s == Pod -} - -// Valid checks the validity of a linux namespace -// s should be the string representation of ns -func Valid(s string, ns LinuxNS) bool { - return IsPod(s) || IsNS(s) || ns.Valid() -} - -// NS is the path to the namespace to join. -func NS(s string) string { - parts := strings.SplitN(s, ":", 2) - if len(parts) > 1 { - return parts[1] - } - return "" -} - -// ValidateweightDevice validates that the specified string has a valid device-weight format -// for blkio-weight-device flag -func ValidateweightDevice(val string) (*weightDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - weight, err := strconv.ParseUint(split[1], 10, 0) - if err != nil { - return nil, fmt.Errorf("invalid weight for device: %s", val) - } - if weight > 0 && (weight < 10 || weight > 1000) { - return nil, fmt.Errorf("invalid weight for device: %s", val) - } - - return &weightDevice{ - Path: split[0], - Weight: uint16(weight), - }, nil -} - -// throttleDevice is a structure that holds device:rate_per_second pair -type throttleDevice struct { - path string - rate uint64 -} - -func (t *throttleDevice) String() string { - return fmt.Sprintf("%s:%d", t.path, t.rate) -} - -// validateBpsDevice validates that the specified string has a valid device-rate format -// for device-read-bps and device-write-bps flags -func validateBpsDevice(val string) (*throttleDevice, error) { - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := units.RAMInBytes(split[1]) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - if rate < 0 { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>[<unit>]. Number must be a positive integer. Unit is optional and can be kb, mb, or gb", val) - } - - return &throttleDevice{ - path: split[0], - rate: uint64(rate), - }, nil -} - -// validateIOpsDevice validates that the specified string has a valid device-rate format -// for device-write-iops and device-read-iops flags -func validateIOpsDevice(val string) (*throttleDevice, error) { //nolint - split := strings.SplitN(val, ":", 2) - if len(split) != 2 { - return nil, fmt.Errorf("bad format: %s", val) - } - if !strings.HasPrefix(split[0], "/dev/") { - return nil, fmt.Errorf("bad format for device path: %s", val) - } - rate, err := strconv.ParseUint(split[1], 10, 64) - if err != nil { - return nil, fmt.Errorf("invalid rate for device: %s. The correct format is <device-path>:<number>. Number must be a positive integer", val) - } - return &throttleDevice{ - path: split[0], - rate: rate, - }, nil -} - -// getLoggingOpts splits the path= and tag= options provided to --log-opt. -func getLoggingOpts(opts []string) (string, string) { - var path, tag string - for _, opt := range opts { - arr := strings.SplitN(opt, "=", 2) - if len(arr) == 2 { - if strings.TrimSpace(arr[0]) == "path" { - path = strings.TrimSpace(arr[1]) - } else if strings.TrimSpace(arr[0]) == "tag" { - tag = strings.TrimSpace(arr[1]) - } - } - if path != "" && tag != "" { - break - } - } - return path, tag -} - -// ParseDevice parses device mapping string to a src, dest & permissions string -func ParseDevice(device string) (string, string, string, error) { //nolint - src := "" - dst := "" - permissions := "rwm" - arr := strings.Split(device, ":") - switch len(arr) { - case 3: - if !IsValidDeviceMode(arr[2]) { - return "", "", "", fmt.Errorf("invalid device mode: %s", arr[2]) - } - permissions = arr[2] - fallthrough - case 2: - if IsValidDeviceMode(arr[1]) { - permissions = arr[1] - } else { - if len(arr[1]) == 0 || arr[1][0] != '/' { - return "", "", "", fmt.Errorf("invalid device mode: %s", arr[1]) - } - dst = arr[1] - } - fallthrough - case 1: - src = arr[0] - default: - return "", "", "", fmt.Errorf("invalid device specification: %s", device) - } - - if dst == "" { - dst = src - } - return src, dst, permissions, nil -} - -// IsValidDeviceMode checks if the mode for device is valid or not. -// IsValid mode is a composition of r (read), w (write), and m (mknod). -func IsValidDeviceMode(mode string) bool { - var legalDeviceMode = map[rune]bool{ - 'r': true, - 'w': true, - 'm': true, - } - if mode == "" { - return false - } - for _, c := range mode { - if !legalDeviceMode[c] { - return false - } - legalDeviceMode[c] = false - } - return true -} - -// validateDeviceCgroupRule validates the format of deviceCgroupRule -func validateDeviceCgroupRule(deviceCgroupRule string) error { - if !deviceCgroupRuleRegex.MatchString(deviceCgroupRule) { - return errors.Errorf("invalid device cgroup rule format: '%s'", deviceCgroupRule) - } - return nil -} - -// parseDeviceCgroupRule matches and parses the deviceCgroupRule into slice -func parseDeviceCgroupRule(deviceCgroupRule string) [][]string { - return deviceCgroupRuleRegex.FindAllStringSubmatch(deviceCgroupRule, -1) -} diff --git a/pkg/spec/ports.go b/pkg/spec/ports.go deleted file mode 100644 index bdd26bd83..000000000 --- a/pkg/spec/ports.go +++ /dev/null @@ -1,107 +0,0 @@ -package createconfig - -import ( - "fmt" - "net" - "strconv" - - "github.com/docker/go-connections/nat" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// ExposedPorts parses user and image ports and returns binding information -func ExposedPorts(expose, publish []string, publishAll bool, imageExposedPorts map[string]struct{}) (map[nat.Port][]nat.PortBinding, error) { - containerPorts := make(map[string]string) - - // add expose ports from the image itself - for expose := range imageExposedPorts { - _, port := nat.SplitProtoPort(expose) - containerPorts[port] = "" - } - - // add the expose ports from the user (--expose) - // can be single or a range - for _, expose := range expose { - //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] - _, port := nat.SplitProtoPort(expose) - //parse the start and end port and create a sequence of ports to expose - //if expose a port, the start and end port are the same - start, end, err := nat.ParsePortRange(port) - if err != nil { - return nil, fmt.Errorf("invalid range format for --expose: %s, error: %s", expose, err) - } - for i := start; i <= end; i++ { - containerPorts[strconv.Itoa(int(i))] = "" - } - } - - // parse user inputted port bindings - pbPorts, portBindings, err := nat.ParsePortSpecs(publish) - if err != nil { - return nil, err - } - - // delete exposed container ports if being used by -p - for i := range pbPorts { - delete(containerPorts, i.Port()) - } - - // iterate container ports and make port bindings from them - if publishAll { - for e := range containerPorts { - //support two formats for expose, original format <portnum>/[<proto>] or <startport-endport>/[<proto>] - //proto, port := nat.SplitProtoPort(e) - p, err := nat.NewPort("tcp", e) - if err != nil { - return nil, err - } - rp, err := getRandomPort() - if err != nil { - return nil, err - } - logrus.Debug(fmt.Sprintf("Using random host port %d with container port %d", rp, p.Int())) - portBindings[p] = CreatePortBinding(rp, "") - } - } - - // We need to see if any host ports are not populated and if so, we need to assign a - // random port to them. - for k, pb := range portBindings { - if pb[0].HostPort == "" { - hostPort, err := getRandomPort() - if err != nil { - return nil, err - } - logrus.Debug(fmt.Sprintf("Using random host port %d with container port %s", hostPort, k.Port())) - pb[0].HostPort = strconv.Itoa(hostPort) - } - } - return portBindings, nil -} - -func getRandomPort() (int, error) { - l, err := net.Listen("tcp", ":0") - if err != nil { - return 0, errors.Wrapf(err, "unable to get free port") - } - defer l.Close() - _, randomPort, err := net.SplitHostPort(l.Addr().String()) - if err != nil { - return 0, errors.Wrapf(err, "unable to determine free port") - } - rp, err := strconv.Atoi(randomPort) - if err != nil { - return 0, errors.Wrapf(err, "unable to convert random port to int") - } - return rp, nil -} - -//CreatePortBinding takes port (int) and IP (string) and creates an array of portbinding structs -func CreatePortBinding(hostPort int, hostIP string) []nat.PortBinding { - pb := nat.PortBinding{ - HostPort: strconv.Itoa(hostPort), - } - pb.HostIP = hostIP - return []nat.PortBinding{pb} -} diff --git a/pkg/spec/security.go b/pkg/spec/security.go deleted file mode 100644 index 5f7db7edb..000000000 --- a/pkg/spec/security.go +++ /dev/null @@ -1,204 +0,0 @@ -package createconfig - -import ( - "fmt" - "strings" - - "github.com/containers/common/pkg/capabilities" - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/util" - "github.com/opencontainers/runtime-tools/generate" - "github.com/opencontainers/selinux/go-selinux/label" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// ToCreateOptions convert the SecurityConfig to a slice of container create -// options. -func (c *SecurityConfig) ToCreateOptions() ([]libpod.CtrCreateOption, error) { - options := make([]libpod.CtrCreateOption, 0) - options = append(options, libpod.WithSecLabels(c.LabelOpts)) - options = append(options, libpod.WithPrivileged(c.Privileged)) - return options, nil -} - -// SetLabelOpts sets the label options of the SecurityConfig according to the -// input. -func (c *SecurityConfig) SetLabelOpts(runtime *libpod.Runtime, pidConfig *PidConfig, ipcConfig *IpcConfig) error { - if c.Privileged { - c.LabelOpts = label.DisableSecOpt() - return nil - } - - var labelOpts []string - if pidConfig.PidMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if pidConfig.PidMode.IsContainer() { - ctr, err := runtime.LookupContainer(pidConfig.PidMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", pidConfig.PidMode.Container()) - } - secopts, err := label.DupSecOpt(ctr.ProcessLabel()) - if err != nil { - return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) - } - labelOpts = append(labelOpts, secopts...) - } - - if ipcConfig.IpcMode.IsHost() { - labelOpts = append(labelOpts, label.DisableSecOpt()...) - } else if ipcConfig.IpcMode.IsContainer() { - ctr, err := runtime.LookupContainer(ipcConfig.IpcMode.Container()) - if err != nil { - return errors.Wrapf(err, "container %q not found", ipcConfig.IpcMode.Container()) - } - secopts, err := label.DupSecOpt(ctr.ProcessLabel()) - if err != nil { - return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel()) - } - labelOpts = append(labelOpts, secopts...) - } - - c.LabelOpts = append(c.LabelOpts, labelOpts...) - return nil -} - -// SetSecurityOpts the the security options (labels, apparmor, seccomp, etc.). -func (c *SecurityConfig) SetSecurityOpts(runtime *libpod.Runtime, securityOpts []string) error { - for _, opt := range securityOpts { - if opt == "no-new-privileges" { - c.NoNewPrivs = true - } else { - con := strings.SplitN(opt, "=", 2) - if len(con) != 2 { - return fmt.Errorf("invalid --security-opt 1: %q", opt) - } - - switch con[0] { - case "proc-opts": - c.ProcOpts = strings.Split(con[1], ",") - case "label": - c.LabelOpts = append(c.LabelOpts, con[1]) - case "apparmor": - c.ApparmorProfile = con[1] - case "seccomp": - c.SeccompProfilePath = con[1] - default: - return fmt.Errorf("invalid --security-opt 2: %q", opt) - } - } - } - - if c.SeccompProfilePath == "" { - var err error - c.SeccompProfilePath, err = libpod.DefaultSeccompPath() - if err != nil { - return err - } - } - c.SecurityOpts = securityOpts - return nil -} - -// ConfigureGenerator configures the generator according to the input. -func (c *SecurityConfig) ConfigureGenerator(g *generate.Generator, user *UserConfig) error { - // HANDLE CAPABILITIES - // NOTE: Must happen before SECCOMP - if c.Privileged { - g.SetupPrivileged(true) - } - - useNotRoot := func(user string) bool { - if user == "" || user == "root" || user == "0" { - return false - } - return true - } - - configSpec := g.Config - var err error - var defaultCaplist []string - bounding := configSpec.Process.Capabilities.Bounding - if useNotRoot(user.User) { - configSpec.Process.Capabilities.Bounding = defaultCaplist - } - defaultCaplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, c.CapAdd, c.CapDrop) - if err != nil { - return err - } - - privCapRequired := []string{} - - if !c.Privileged && len(c.CapRequired) > 0 { - // Pass CapRequired in CapAdd field to normalize capabilities names - capRequired, err := capabilities.MergeCapabilities(nil, c.CapRequired, nil) - if err != nil { - logrus.Errorf("capabilities requested by user or image are not valid: %q", strings.Join(c.CapRequired, ",")) - } else { - // Verify all capRequiered are in the defaultCapList - for _, cap := range capRequired { - if !util.StringInSlice(cap, defaultCaplist) { - privCapRequired = append(privCapRequired, cap) - } - } - } - if len(privCapRequired) == 0 { - defaultCaplist = capRequired - } else { - logrus.Errorf("capabilities requested by user or image are not allowed by default: %q", strings.Join(privCapRequired, ",")) - } - } - configSpec.Process.Capabilities.Bounding = defaultCaplist - configSpec.Process.Capabilities.Permitted = defaultCaplist - configSpec.Process.Capabilities.Inheritable = defaultCaplist - configSpec.Process.Capabilities.Effective = defaultCaplist - configSpec.Process.Capabilities.Ambient = defaultCaplist - if useNotRoot(user.User) { - defaultCaplist, err = capabilities.MergeCapabilities(bounding, c.CapAdd, c.CapDrop) - if err != nil { - return err - } - } - configSpec.Process.Capabilities.Bounding = defaultCaplist - - // HANDLE SECCOMP - if c.SeccompProfilePath != "unconfined" { - seccompConfig, err := getSeccompConfig(c, configSpec) - if err != nil { - return err - } - configSpec.Linux.Seccomp = seccompConfig - } - - // Clear default Seccomp profile from Generator for privileged containers - if c.SeccompProfilePath == "unconfined" || c.Privileged { - configSpec.Linux.Seccomp = nil - } - - for _, opt := range c.SecurityOpts { - // Split on both : and = - splitOpt := strings.SplitN(opt, "=", 2) - if len(splitOpt) == 1 { - splitOpt = strings.Split(opt, ":") - } - if len(splitOpt) < 2 { - continue - } - switch splitOpt[0] { - case "label": - configSpec.Annotations[define.InspectAnnotationLabel] = splitOpt[1] - case "seccomp": - configSpec.Annotations[define.InspectAnnotationSeccomp] = splitOpt[1] - case "apparmor": - configSpec.Annotations[define.InspectAnnotationApparmor] = splitOpt[1] - } - } - - g.SetRootReadonly(c.ReadOnlyRootfs) - for sysctlKey, sysctlVal := range c.Sysctl { - g.AddLinuxSysctl(sysctlKey, sysctlVal) - } - - return nil -} diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go deleted file mode 100644 index 81620997f..000000000 --- a/pkg/spec/spec.go +++ /dev/null @@ -1,593 +0,0 @@ -package createconfig - -import ( - "strings" - - "github.com/containers/common/pkg/capabilities" - cconfig "github.com/containers/common/pkg/config" - "github.com/containers/common/pkg/sysinfo" - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/libpod/define" - "github.com/containers/podman/v2/pkg/cgroups" - "github.com/containers/podman/v2/pkg/env" - "github.com/containers/podman/v2/pkg/rootless" - "github.com/containers/podman/v2/pkg/util" - "github.com/docker/go-units" - "github.com/opencontainers/runc/libcontainer/user" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" - "golang.org/x/sys/unix" -) - -const CpuPeriod = 100000 - -func GetAvailableGids() (int64, error) { - idMap, err := user.ParseIDMapFile("/proc/self/gid_map") - if err != nil { - return 0, err - } - count := int64(0) - for _, r := range idMap { - count += r.Count - } - return count, nil -} - -// CreateConfigToOCISpec parses information needed to create a container into an OCI runtime spec -func (config *CreateConfig) createConfigToOCISpec(runtime *libpod.Runtime, userMounts []spec.Mount) (*spec.Spec, error) { - cgroupPerm := "ro" - g, err := generate.New("linux") - if err != nil { - return nil, err - } - // Remove the default /dev/shm mount to ensure we overwrite it - g.RemoveMount("/dev/shm") - g.HostSpecific = true - addCgroup := true - canMountSys := true - - isRootless := rootless.IsRootless() - inUserNS := config.User.InNS(isRootless) - - if inUserNS && config.Network.NetMode.IsHost() { - canMountSys = false - } - - if config.Security.Privileged && canMountSys { - cgroupPerm = "rw" - g.RemoveMount("/sys") - sysMnt := spec.Mount{ - Destination: "/sys", - Type: "sysfs", - Source: "sysfs", - Options: []string{"rprivate", "nosuid", "noexec", "nodev", "rw"}, - } - g.AddMount(sysMnt) - } else if !canMountSys { - addCgroup = false - g.RemoveMount("/sys") - r := "ro" - if config.Security.Privileged { - r = "rw" - } - sysMnt := spec.Mount{ - Destination: "/sys", - Type: TypeBind, - Source: "/sys", - Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"}, - } - g.AddMount(sysMnt) - if !config.Security.Privileged && isRootless { - g.AddLinuxMaskedPaths("/sys/kernel") - } - } - var runtimeConfig *cconfig.Config - - if runtime != nil { - runtimeConfig, err = runtime.GetConfig() - if err != nil { - return nil, err - } - g.Config.Process.Capabilities.Bounding = runtimeConfig.Containers.DefaultCapabilities - sysctls, err := util.ValidateSysctls(runtimeConfig.Containers.DefaultSysctls) - if err != nil { - return nil, err - } - - for name, val := range config.Security.Sysctl { - sysctls[name] = val - } - config.Security.Sysctl = sysctls - if !util.StringInSlice("host", config.Resources.Ulimit) { - config.Resources.Ulimit = append(runtimeConfig.Containers.DefaultUlimits, config.Resources.Ulimit...) - } - if config.Resources.PidsLimit < 0 && !config.cgroupDisabled() { - config.Resources.PidsLimit = runtimeConfig.Containers.PidsLimit - } - - } else { - g.Config.Process.Capabilities.Bounding = cconfig.DefaultCapabilities - if config.Resources.PidsLimit < 0 && !config.cgroupDisabled() { - config.Resources.PidsLimit = cconfig.DefaultPidsLimit - } - } - - gid5Available := true - if isRootless { - nGids, err := GetAvailableGids() - if err != nil { - return nil, err - } - gid5Available = nGids >= 5 - } - // When using a different user namespace, check that the GID 5 is mapped inside - // the container. - if gid5Available && len(config.User.IDMappings.GIDMap) > 0 { - mappingFound := false - for _, r := range config.User.IDMappings.GIDMap { - if r.ContainerID <= 5 && 5 < r.ContainerID+r.Size { - mappingFound = true - break - } - } - if !mappingFound { - gid5Available = false - } - - } - if !gid5Available { - // If we have no GID mappings, the gid=5 default option would fail, so drop it. - g.RemoveMount("/dev/pts") - devPts := spec.Mount{ - Destination: "/dev/pts", - Type: "devpts", - Source: "devpts", - Options: []string{"rprivate", "nosuid", "noexec", "newinstance", "ptmxmode=0666", "mode=0620"}, - } - g.AddMount(devPts) - } - - if inUserNS && config.Ipc.IpcMode.IsHost() { - g.RemoveMount("/dev/mqueue") - devMqueue := spec.Mount{ - Destination: "/dev/mqueue", - Type: TypeBind, - Source: "/dev/mqueue", - Options: []string{"bind", "nosuid", "noexec", "nodev"}, - } - g.AddMount(devMqueue) - } - if inUserNS && config.Pid.PidMode.IsHost() { - g.RemoveMount("/proc") - procMount := spec.Mount{ - Destination: "/proc", - Type: TypeBind, - Source: "/proc", - Options: []string{"rbind", "nosuid", "noexec", "nodev"}, - } - g.AddMount(procMount) - } - - if addCgroup { - cgroupMnt := spec.Mount{ - Destination: "/sys/fs/cgroup", - Type: "cgroup", - Source: "cgroup", - Options: []string{"rprivate", "nosuid", "noexec", "nodev", "relatime", cgroupPerm}, - } - g.AddMount(cgroupMnt) - } - g.SetProcessCwd(config.WorkDir) - - ProcessArgs := make([]string, 0) - // We need to iterate the input for entrypoint because it is a []string - // but "" is a legit json input, which translates into a []string with an - // empty position. This messes up the eventual command being executed - // in the container - for _, a := range config.Entrypoint { - if len(a) > 0 { - ProcessArgs = append(ProcessArgs, a) - } - } - // Same issue as explained above for config.Entrypoint. - for _, a := range config.Command { - if len(a) > 0 { - ProcessArgs = append(ProcessArgs, a) - } - } - - g.SetProcessArgs(ProcessArgs) - g.SetProcessTerminal(config.Tty) - - for key, val := range config.Annotations { - g.AddAnnotation(key, val) - } - - addedResources := false - - // RESOURCES - MEMORY - if config.Resources.Memory != 0 { - g.SetLinuxResourcesMemoryLimit(config.Resources.Memory) - // If a swap limit is not explicitly set, also set a swap limit - // Default to double the memory limit - if config.Resources.MemorySwap == 0 { - g.SetLinuxResourcesMemorySwap(2 * config.Resources.Memory) - } - addedResources = true - } - if config.Resources.MemoryReservation != 0 { - g.SetLinuxResourcesMemoryReservation(config.Resources.MemoryReservation) - addedResources = true - } - if config.Resources.MemorySwap != 0 { - g.SetLinuxResourcesMemorySwap(config.Resources.MemorySwap) - addedResources = true - } - if config.Resources.KernelMemory != 0 { - g.SetLinuxResourcesMemoryKernel(config.Resources.KernelMemory) - addedResources = true - } - if config.Resources.MemorySwappiness != -1 { - g.SetLinuxResourcesMemorySwappiness(uint64(config.Resources.MemorySwappiness)) - addedResources = true - } - g.SetLinuxResourcesMemoryDisableOOMKiller(config.Resources.DisableOomKiller) - g.SetProcessOOMScoreAdj(config.Resources.OomScoreAdj) - - // RESOURCES - CPU - if config.Resources.CPUShares != 0 { - g.SetLinuxResourcesCPUShares(config.Resources.CPUShares) - addedResources = true - } - if config.Resources.CPUQuota != 0 { - g.SetLinuxResourcesCPUQuota(config.Resources.CPUQuota) - addedResources = true - } - if config.Resources.CPUPeriod != 0 { - g.SetLinuxResourcesCPUPeriod(config.Resources.CPUPeriod) - addedResources = true - } - if config.Resources.CPUs != 0 { - g.SetLinuxResourcesCPUPeriod(CpuPeriod) - g.SetLinuxResourcesCPUQuota(int64(config.Resources.CPUs * CpuPeriod)) - addedResources = true - } - if config.Resources.CPURtRuntime != 0 { - g.SetLinuxResourcesCPURealtimeRuntime(config.Resources.CPURtRuntime) - addedResources = true - } - if config.Resources.CPURtPeriod != 0 { - g.SetLinuxResourcesCPURealtimePeriod(config.Resources.CPURtPeriod) - addedResources = true - } - if config.Resources.CPUsetCPUs != "" { - g.SetLinuxResourcesCPUCpus(config.Resources.CPUsetCPUs) - addedResources = true - } - if config.Resources.CPUsetMems != "" { - g.SetLinuxResourcesCPUMems(config.Resources.CPUsetMems) - addedResources = true - } - - // Devices - if config.Security.Privileged { - // If privileged, we need to add all the host devices to the - // spec. We do not add the user provided ones because we are - // already adding them all. - if err := AddPrivilegedDevices(&g); err != nil { - return nil, err - } - } else { - for _, devicePath := range config.Devices { - if err := DevicesFromPath(&g, devicePath); err != nil { - return nil, err - } - } - if len(config.Resources.DeviceCgroupRules) != 0 { - if err := deviceCgroupRules(&g, config.Resources.DeviceCgroupRules); err != nil { - return nil, err - } - addedResources = true - } - } - - g.SetProcessNoNewPrivileges(config.Security.NoNewPrivs) - - if !config.Security.Privileged { - g.SetProcessApparmorProfile(config.Security.ApparmorProfile) - } - - // Unless already set via the CLI, check if we need to disable process - // labels or set the defaults. - if len(config.Security.LabelOpts) == 0 && runtimeConfig != nil { - if !runtimeConfig.Containers.EnableLabeling { - // Disabled in the config. - config.Security.LabelOpts = append(config.Security.LabelOpts, "disable") - } else if err := config.Security.SetLabelOpts(runtime, &config.Pid, &config.Ipc); err != nil { - // Defaults! - return nil, err - } - } - - BlockAccessToKernelFilesystems(config.Security.Privileged, config.Pid.PidMode.IsHost(), &g) - - // RESOURCES - PIDS - if config.Resources.PidsLimit > 0 { - // if running on rootless on a cgroupv1 machine or using the cgroupfs manager, pids - // limit is not supported. If the value is still the default - // then ignore the settings. If the caller asked for a - // non-default, then try to use it. - setPidLimit := true - if rootless.IsRootless() { - cgroup2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, err - } - if (!cgroup2 || (runtimeConfig != nil && runtimeConfig.Engine.CgroupManager != cconfig.SystemdCgroupsManager)) && config.Resources.PidsLimit == sysinfo.GetDefaultPidsLimit() { - setPidLimit = false - } - } - if setPidLimit { - g.SetLinuxResourcesPidsLimit(config.Resources.PidsLimit) - addedResources = true - } - } - - // Make sure to always set the default variables unless overridden in the - // config. - var defaultEnv map[string]string - if runtimeConfig == nil { - defaultEnv = env.DefaultEnvVariables() - } else { - defaultEnv, err = env.ParseSlice(runtimeConfig.Containers.Env) - if err != nil { - return nil, errors.Wrap(err, "Env fields in containers.conf failed to parse") - } - defaultEnv = env.Join(env.DefaultEnvVariables(), defaultEnv) - } - - if err := addRlimits(config, &g); err != nil { - return nil, err - } - - // NAMESPACES - - if err := config.Pid.ConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := config.User.ConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := config.Network.ConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := config.Uts.ConfigureGenerator(&g, &config.Network, runtime); err != nil { - return nil, err - } - - if err := config.Ipc.ConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := config.Cgroup.ConfigureGenerator(&g); err != nil { - return nil, err - } - - config.Env = env.Join(defaultEnv, config.Env) - for name, val := range config.Env { - g.AddProcessEnv(name, val) - } - configSpec := g.Config - - // If the container image specifies an label with a - // capabilities.ContainerImageLabel then split the comma separated list - // of capabilities and record them. This list indicates the only - // capabilities, required to run the container. - var capRequired []string - for key, val := range config.Labels { - if util.StringInSlice(key, capabilities.ContainerImageLabels) { - capRequired = strings.Split(val, ",") - } - } - config.Security.CapRequired = capRequired - - if err := config.Security.ConfigureGenerator(&g, &config.User); err != nil { - return nil, err - } - - // BIND MOUNTS - configSpec.Mounts = SupercedeUserMounts(userMounts, configSpec.Mounts) - // Process mounts to ensure correct options - if err := InitFSMounts(configSpec.Mounts); err != nil { - return nil, err - } - - // BLOCK IO - blkio, err := config.CreateBlockIO() - if err != nil { - return nil, errors.Wrapf(err, "error creating block io") - } - if blkio != nil { - configSpec.Linux.Resources.BlockIO = blkio - addedResources = true - } - - if rootless.IsRootless() { - cgroup2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - return nil, err - } - if !addedResources { - configSpec.Linux.Resources = &spec.LinuxResources{} - } - - canUseResources := cgroup2 && runtimeConfig != nil && (runtimeConfig.Engine.CgroupManager == cconfig.SystemdCgroupsManager) - - if addedResources && !canUseResources { - return nil, errors.New("invalid configuration, cannot specify resource limits without cgroups v2 and --cgroup-manager=systemd") - } - if !canUseResources { - // Force the resources block to be empty instead of having default values. - configSpec.Linux.Resources = &spec.LinuxResources{} - } - } - - switch config.Cgroup.Cgroups { - case "disabled": - if addedResources { - return nil, errors.New("cannot specify resource limits when cgroups are disabled is specified") - } - configSpec.Linux.Resources = &spec.LinuxResources{} - case "enabled", "no-conmon", "": - // Do nothing - default: - return nil, errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'") - } - - // Add annotations - if configSpec.Annotations == nil { - configSpec.Annotations = make(map[string]string) - } - - if config.CidFile != "" { - configSpec.Annotations[define.InspectAnnotationCIDFile] = config.CidFile - } - - if config.Rm { - configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseTrue - } else { - configSpec.Annotations[define.InspectAnnotationAutoremove] = define.InspectResponseFalse - } - - if len(config.VolumesFrom) > 0 { - configSpec.Annotations[define.InspectAnnotationVolumesFrom] = strings.Join(config.VolumesFrom, ",") - } - - if config.Security.Privileged { - configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseTrue - } else { - configSpec.Annotations[define.InspectAnnotationPrivileged] = define.InspectResponseFalse - } - - if config.Init { - configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseTrue - } else { - configSpec.Annotations[define.InspectAnnotationInit] = define.InspectResponseFalse - } - - return configSpec, nil -} - -func (config *CreateConfig) cgroupDisabled() bool { - return config.Cgroup.Cgroups == "disabled" -} - -func BlockAccessToKernelFilesystems(privileged, pidModeIsHost bool, g *generate.Generator) { - if !privileged { - for _, mp := range []string{ - "/proc/acpi", - "/proc/kcore", - "/proc/keys", - "/proc/latency_stats", - "/proc/timer_list", - "/proc/timer_stats", - "/proc/sched_debug", - "/proc/scsi", - "/sys/firmware", - "/sys/fs/selinux", - } { - g.AddLinuxMaskedPaths(mp) - } - - if pidModeIsHost && rootless.IsRootless() { - return - } - - for _, rp := range []string{ - "/proc/asound", - "/proc/bus", - "/proc/fs", - "/proc/irq", - "/proc/sys", - "/proc/sysrq-trigger", - } { - g.AddLinuxReadonlyPaths(rp) - } - } -} - -func addRlimits(config *CreateConfig, g *generate.Generator) error { - var ( - isRootless = rootless.IsRootless() - nofileSet = false - nprocSet = false - ) - - for _, u := range config.Resources.Ulimit { - if u == "host" { - if len(config.Resources.Ulimit) != 1 { - return errors.New("ulimit can use host only once") - } - g.Config.Process.Rlimits = nil - break - } - - ul, err := units.ParseUlimit(u) - if err != nil { - return errors.Wrapf(err, "ulimit option %q requires name=SOFT:HARD, failed to be parsed", u) - } - - if ul.Name == "nofile" { - nofileSet = true - } else if ul.Name == "nproc" { - nprocSet = true - } - - g.AddProcessRlimits("RLIMIT_"+strings.ToUpper(ul.Name), uint64(ul.Hard), uint64(ul.Soft)) - } - - // If not explicitly overridden by the user, default number of open - // files and number of processes to the maximum they can be set to - // (without overriding a sysctl) - if !nofileSet { - max := define.RLimitDefaultValue - current := define.RLimitDefaultValue - if isRootless { - var rlimit unix.Rlimit - if err := unix.Getrlimit(unix.RLIMIT_NOFILE, &rlimit); err != nil { - logrus.Warnf("failed to return RLIMIT_NOFILE ulimit %q", err) - } - if rlimit.Cur < current { - current = rlimit.Cur - } - if rlimit.Max < max { - max = rlimit.Max - } - } - g.AddProcessRlimits("RLIMIT_NOFILE", max, current) - } - if !nprocSet { - max := define.RLimitDefaultValue - current := define.RLimitDefaultValue - if isRootless { - var rlimit unix.Rlimit - if err := unix.Getrlimit(unix.RLIMIT_NPROC, &rlimit); err != nil { - logrus.Warnf("failed to return RLIMIT_NPROC ulimit %q", err) - } - if rlimit.Cur < current { - current = rlimit.Cur - } - if rlimit.Max < max { - max = rlimit.Max - } - } - g.AddProcessRlimits("RLIMIT_NPROC", max, current) - } - - return nil -} diff --git a/pkg/spec/spec_test.go b/pkg/spec/spec_test.go deleted file mode 100644 index d01102fa2..000000000 --- a/pkg/spec/spec_test.go +++ /dev/null @@ -1,108 +0,0 @@ -package createconfig - -import ( - "runtime" - "testing" - - "github.com/containers/common/pkg/sysinfo" - "github.com/containers/podman/v2/pkg/cgroups" - "github.com/containers/podman/v2/pkg/rootless" - "github.com/containers/storage" - "github.com/containers/storage/pkg/idtools" - "github.com/docker/go-units" - "github.com/stretchr/testify/assert" -) - -var ( - sysInfo = sysinfo.New(true) -) - -// Make createconfig to test with -func makeTestCreateConfig() *CreateConfig { - cc := new(CreateConfig) - cc.Resources = CreateResourceConfig{} - cc.User.IDMappings = new(storage.IDMappingOptions) - cc.User.IDMappings.UIDMap = []idtools.IDMap{} - cc.User.IDMappings.GIDMap = []idtools.IDMap{} - - return cc -} - -func doCommonSkipChecks(t *testing.T) { - // The default configuration of podman enables seccomp, which is not available on non-Linux systems. - // Thus, any tests that use the default seccomp setting would fail. - // Skip the tests on non-Linux platforms rather than explicitly disable seccomp in the test and possibly affect the test result. - if runtime.GOOS != "linux" { - t.Skip("seccomp, which is enabled by default, is only supported on Linux") - } - - if rootless.IsRootless() { - isCgroupV2, err := cgroups.IsCgroup2UnifiedMode() - if err != nil { - t.Errorf("unexpected error: %v", err) - } - - if !isCgroupV2 { - t.Skip("cgroups v1 cannot be used when rootless") - } - } -} - -// TestPIDsLimit verifies the given pid-limit is correctly defined in the spec -func TestPIDsLimit(t *testing.T) { - doCommonSkipChecks(t) - - if !sysInfo.PidsLimit { - t.Skip("running test not supported by the host system") - } - - cc := makeTestCreateConfig() - cc.Resources.PidsLimit = 22 - - spec, err := cc.createConfigToOCISpec(nil, nil) - assert.NoError(t, err) - - assert.Equal(t, spec.Linux.Resources.Pids.Limit, int64(22)) -} - -// TestBLKIOWeightDevice verifies the given blkio weight is correctly set in the -// spec. -func TestBLKIOWeightDevice(t *testing.T) { - doCommonSkipChecks(t) - - if !sysInfo.BlkioWeightDevice { - t.Skip("running test not supported by the host system") - } - - cc := makeTestCreateConfig() - cc.Resources.BlkioWeightDevice = []string{"/dev/zero:100"} - - spec, err := cc.createConfigToOCISpec(nil, nil) - assert.NoError(t, err) - - // /dev/zero is guaranteed 1,5 by the Linux kernel - assert.Equal(t, spec.Linux.Resources.BlockIO.WeightDevice[0].Major, int64(1)) - assert.Equal(t, spec.Linux.Resources.BlockIO.WeightDevice[0].Minor, int64(5)) - assert.Equal(t, *(spec.Linux.Resources.BlockIO.WeightDevice[0].Weight), uint16(100)) -} - -// TestMemorySwap verifies that the given swap memory limit is correctly set in -// the spec. -func TestMemorySwap(t *testing.T) { - doCommonSkipChecks(t) - - if !sysInfo.SwapLimit { - t.Skip("running test not supported by the host system") - } - - swapLimit, err := units.RAMInBytes("45m") - assert.NoError(t, err) - - cc := makeTestCreateConfig() - cc.Resources.MemorySwap = swapLimit - - spec, err := cc.createConfigToOCISpec(nil, nil) - assert.NoError(t, err) - - assert.Equal(t, *(spec.Linux.Resources.Memory.Swap), swapLimit) -} diff --git a/pkg/spec/storage.go b/pkg/spec/storage.go deleted file mode 100644 index b441daf08..000000000 --- a/pkg/spec/storage.go +++ /dev/null @@ -1,875 +0,0 @@ -package createconfig - -import ( - "fmt" - "os" - "path" - "path/filepath" - "strings" - - "github.com/containers/buildah/pkg/parse" - "github.com/containers/podman/v2/libpod" - "github.com/containers/podman/v2/pkg/util" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -const ( - // TypeBind is the type for mounting host dir - TypeBind = "bind" - // TypeVolume is the type for named volumes - TypeVolume = "volume" - // TypeTmpfs is the type for mounting tmpfs - TypeTmpfs = "tmpfs" -) - -var ( - errDuplicateDest = errors.Errorf("duplicate mount destination") - optionArgError = errors.Errorf("must provide an argument for option") - noDestError = errors.Errorf("must set volume destination") -) - -// Parse all volume-related options in the create config into a set of mounts -// and named volumes to add to the container. -// Handles --volumes-from, --volumes, --tmpfs, --init, and --init-path flags. -// TODO: Named volume options - should we default to rprivate? It bakes into a -// bind mount under the hood... -// TODO: handle options parsing/processing via containers/storage/pkg/mount -func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount, []*libpod.ContainerNamedVolume, error) { - // Add image volumes. - baseMounts, baseVolumes, err := config.getImageVolumes() - if err != nil { - return nil, nil, err - } - - // Add --volumes-from. - // Overrides image volumes unconditionally. - vFromMounts, vFromVolumes, err := config.getVolumesFrom(runtime) - if err != nil { - return nil, nil, err - } - for dest, mount := range vFromMounts { - baseMounts[dest] = mount - } - for dest, volume := range vFromVolumes { - baseVolumes[dest] = volume - } - - // Next mounts from the --mounts flag. - // Do not override yet. - unifiedMounts, unifiedVolumes, err := config.getMounts() - if err != nil { - return nil, nil, err - } - - // Next --volumes flag. - // Do not override yet. - volumeMounts, volumeVolumes, err := config.getVolumeMounts() - if err != nil { - return nil, nil, err - } - - // Next --tmpfs flag. - // Do not override yet. - tmpfsMounts, err := config.getTmpfsMounts() - if err != nil { - return nil, nil, err - } - - // Unify mounts from --mount, --volume, --tmpfs. - // Also add mounts + volumes directly from createconfig. - // Start with --volume. - for dest, mount := range volumeMounts { - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, dest) - } - unifiedMounts[dest] = mount - } - for dest, volume := range volumeVolumes { - if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, dest) - } - unifiedVolumes[dest] = volume - } - // Now --tmpfs - for dest, tmpfs := range tmpfsMounts { - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, dest) - } - unifiedMounts[dest] = tmpfs - } - // Now spec mounts and volumes - for _, mount := range config.Mounts { - dest := mount.Destination - if _, ok := unifiedMounts[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, dest) - } - unifiedMounts[dest] = mount - } - for _, volume := range config.NamedVolumes { - dest := volume.Dest - if _, ok := unifiedVolumes[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, dest) - } - unifiedVolumes[dest] = volume - } - - // If requested, add container init binary - if config.Init { - initPath := config.InitPath - if initPath == "" { - rtc, err := runtime.GetConfig() - if err != nil { - return nil, nil, err - } - initPath = rtc.Engine.InitPath - } - initMount, err := config.addContainerInitBinary(initPath) - if err != nil { - return nil, nil, err - } - if _, ok := unifiedMounts[initMount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict with mount added by --init to %q", initMount.Destination) - } - unifiedMounts[initMount.Destination] = initMount - } - - // Before superseding, we need to find volume mounts which conflict with - // named volumes, and vice versa. - // We'll delete the conflicts here as we supersede. - for dest := range unifiedMounts { - if _, ok := baseVolumes[dest]; ok { - delete(baseVolumes, dest) - } - } - for dest := range unifiedVolumes { - if _, ok := baseMounts[dest]; ok { - delete(baseMounts, dest) - } - } - - // Supersede volumes-from/image volumes with unified volumes from above. - // This is an unconditional replacement. - for dest, mount := range unifiedMounts { - baseMounts[dest] = mount - } - for dest, volume := range unifiedVolumes { - baseVolumes[dest] = volume - } - - // If requested, add tmpfs filesystems for read-only containers. - if config.Security.ReadOnlyRootfs && config.Security.ReadOnlyTmpfs { - readonlyTmpfs := []string{"/tmp", "/var/tmp", "/run"} - options := []string{"rw", "rprivate", "nosuid", "nodev", "tmpcopyup"} - for _, dest := range readonlyTmpfs { - if _, ok := baseMounts[dest]; ok { - continue - } - if _, ok := baseVolumes[dest]; ok { - continue - } - localOpts := options - if dest == "/run" { - localOpts = append(localOpts, "noexec", "size=65536k") - } else { - localOpts = append(localOpts, "exec") - } - baseMounts[dest] = spec.Mount{ - Destination: dest, - Type: "tmpfs", - Source: "tmpfs", - Options: localOpts, - } - } - } - - // Check for conflicts between named volumes and mounts - for dest := range baseMounts { - if _, ok := baseVolumes[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - } - } - for dest := range baseVolumes { - if _, ok := baseMounts[dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, "conflict at mount destination %v", dest) - } - } - - // Final step: maps to arrays - finalMounts := make([]spec.Mount, 0, len(baseMounts)) - for _, mount := range baseMounts { - if mount.Type == TypeBind { - absSrc, err := filepath.Abs(mount.Source) - if err != nil { - return nil, nil, errors.Wrapf(err, "error getting absolute path of %s", mount.Source) - } - mount.Source = absSrc - } - finalMounts = append(finalMounts, mount) - } - finalVolumes := make([]*libpod.ContainerNamedVolume, 0, len(baseVolumes)) - for _, volume := range baseVolumes { - finalVolumes = append(finalVolumes, volume) - } - - return finalMounts, finalVolumes, nil -} - -// Parse volumes from - a set of containers whose volumes we will mount in. -// Grab the containers, retrieve any user-created spec mounts and all named -// volumes, and return a list of them. -// Conflicts are resolved simply - the last container specified wins. -// Container names may be suffixed by mount options after a colon. -// TODO: We should clean these paths if possible -func (config *CreateConfig) getVolumesFrom(runtime *libpod.Runtime) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { - // Both of these are maps of mount destination to mount type. - // We ensure that each destination is only mounted to once in this way. - finalMounts := make(map[string]spec.Mount) - finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) - - for _, vol := range config.VolumesFrom { - var ( - options = []string{} - err error - splitVol = strings.SplitN(vol, ":", 2) - ) - if len(splitVol) == 2 { - splitOpts := strings.Split(splitVol[1], ",") - for _, checkOpt := range splitOpts { - switch checkOpt { - case "z", "ro", "rw": - // Do nothing, these are valid options - default: - return nil, nil, errors.Errorf("invalid options %q, can only specify 'ro', 'rw', and 'z'", splitVol[1]) - } - } - - if options, err = parse.ValidateVolumeOpts(splitOpts); err != nil { - return nil, nil, err - } - } - ctr, err := runtime.LookupContainer(splitVol[0]) - if err != nil { - return nil, nil, errors.Wrapf(err, "error looking up container %q for volumes-from", splitVol[0]) - } - - logrus.Debugf("Adding volumes from container %s", ctr.ID()) - - // Look up the container's user volumes. This gets us the - // destinations of all mounts the user added to the container. - userVolumesArr := ctr.UserVolumes() - - // We're going to need to access them a lot, so convert to a map - // to reduce looping. - // We'll also use the map to indicate if we missed any volumes along the way. - userVolumes := make(map[string]bool) - for _, dest := range userVolumesArr { - userVolumes[dest] = false - } - - // Now we get the container's spec and loop through its volumes - // and append them in if we can find them. - spec := ctr.Spec() - if spec == nil { - return nil, nil, errors.Errorf("error retrieving container %s spec for volumes-from", ctr.ID()) - } - for _, mnt := range spec.Mounts { - if mnt.Type != TypeBind { - continue - } - if _, exists := userVolumes[mnt.Destination]; exists { - userVolumes[mnt.Destination] = true - - if len(options) != 0 { - mnt.Options = options - } - - if _, ok := finalMounts[mnt.Destination]; ok { - logrus.Debugf("Overriding mount to %s with new mount from container %s", mnt.Destination, ctr.ID()) - } - finalMounts[mnt.Destination] = mnt - } - } - - // We're done with the spec mounts. Add named volumes. - // Add these unconditionally - none of them are automatically - // part of the container, as some spec mounts are. - namedVolumes := ctr.NamedVolumes() - for _, namedVol := range namedVolumes { - if _, exists := userVolumes[namedVol.Dest]; exists { - userVolumes[namedVol.Dest] = true - } - - if len(options) != 0 { - namedVol.Options = options - } - - if _, ok := finalMounts[namedVol.Dest]; ok { - logrus.Debugf("Overriding named volume mount to %s with new named volume from container %s", namedVol.Dest, ctr.ID()) - } - finalNamedVolumes[namedVol.Dest] = namedVol - } - - // Check if we missed any volumes - for volDest, found := range userVolumes { - if !found { - logrus.Warnf("Unable to match volume %s from container %s for volumes-from", volDest, ctr.ID()) - } - } - } - - return finalMounts, finalNamedVolumes, nil -} - -// getMounts takes user-provided input from the --mount flag and creates OCI -// spec mounts and Libpod named volumes. -// podman run --mount type=bind,src=/etc/resolv.conf,target=/etc/resolv.conf ... -// podman run --mount type=tmpfs,target=/dev/shm ... -// podman run --mount type=volume,source=test-volume, ... -func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { - finalMounts := make(map[string]spec.Mount) - finalNamedVolumes := make(map[string]*libpod.ContainerNamedVolume) - - errInvalidSyntax := errors.Errorf("incorrect mount format: should be --mount type=<bind|tmpfs|volume>,[src=<host-dir|volume-name>,]target=<ctr-dir>[,options]") - - // TODO(vrothberg): the manual parsing can be replaced with a regular expression - // to allow a more robust parsing of the mount format and to give - // precise errors regarding supported format versus supported options. - for _, mount := range config.MountsFlag { - arr := strings.SplitN(mount, ",", 2) - if len(arr) < 2 { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) - } - kv := strings.Split(arr[0], "=") - // TODO: type is not explicitly required in Docker. - // If not specified, it defaults to "volume". - if len(kv) != 2 || kv[0] != "type" { - return nil, nil, errors.Wrapf(errInvalidSyntax, "%q", mount) - } - - tokens := strings.Split(arr[1], ",") - switch kv[1] { - case TypeBind: - mount, err := getBindMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case TypeTmpfs: - mount, err := getTmpfsMount(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalMounts[mount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, mount.Destination) - } - finalMounts[mount.Destination] = mount - case "volume": - volume, err := getNamedVolume(tokens) - if err != nil { - return nil, nil, err - } - if _, ok := finalNamedVolumes[volume.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, volume.Dest) - } - finalNamedVolumes[volume.Dest] = volume - default: - return nil, nil, errors.Errorf("invalid filesystem type %q", kv[1]) - } - } - - return finalMounts, finalNamedVolumes, nil -} - -// Parse a single bind mount entry from the --mount flag. -func getBindMount(args []string) (spec.Mount, error) { - newMount := spec.Mount{ - Type: TypeBind, - } - - var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool - - for _, val := range args { - kv := strings.SplitN(val, "=", 2) - switch kv[0] { - case "bind-nonrecursive": - newMount.Options = append(newMount.Options, "bind") - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' or 'rw' options more than once") - } - setRORW = true - // Can be formatted as one of: - // ro - // ro=[true|false] - // rw - // rw=[true|false] - switch len(kv) { - case 1: - newMount.Options = append(newMount.Options, kv[0]) - case 2: - switch strings.ToLower(kv[1]) { - case "true": - newMount.Options = append(newMount.Options, kv[0]) - case "false": - // Set the opposite only for rw - // ro's opposite is the default - if kv[0] == "rw" { - newMount.Options = append(newMount.Options, "ro") - } - default: - return newMount, errors.Wrapf(optionArgError, "%s must be set to true or false, instead received %q", kv[0], kv[1]) - } - default: - return newMount, errors.Wrapf(optionArgError, "badly formatted option %q", val) - } - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "shared", "rshared", "private", "rprivate", "slave", "rslave", "unbindable", "runbindable", "Z", "z": - newMount.Options = append(newMount.Options, kv[0]) - case "bind-propagation": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, kv[1]) - case "src", "source": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeHostDir(kv[1]); err != nil { - return newMount, err - } - newMount.Source = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err - } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - case "relabel": - if setRelabel { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'relabel' option more than once") - } - setRelabel = true - if len(kv) != 2 { - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - switch kv[1] { - case "private": - newMount.Options = append(newMount.Options, "z") - case "shared": - newMount.Options = append(newMount.Options, "Z") - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, "%s mount option must be 'private' or 'shared'", kv[0]) - } - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } - } - - if !setDest { - return newMount, noDestError - } - - if !setSource { - newMount.Source = newMount.Destination - } - - options, err := parse.ValidateVolumeOpts(newMount.Options) - if err != nil { - return newMount, err - } - newMount.Options = options - return newMount, nil -} - -// Parse a single tmpfs mount entry from the --mount flag -func getTmpfsMount(args []string) (spec.Mount, error) { - newMount := spec.Mount{ - Type: TypeTmpfs, - Source: TypeTmpfs, - } - - var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool - - for _, val := range args { - kv := strings.SplitN(val, "=", 2) - switch kv[0] { - case "tmpcopyup", "notmpcopyup": - if setTmpcopyup { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'tmpcopyup' and 'notmpcopyup' options more than once") - } - setTmpcopyup = true - newMount.Options = append(newMount.Options, kv[0]) - case "ro", "rw": - if setRORW { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newMount.Options = append(newMount.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newMount.Options = append(newMount.Options, kv[0]) - case "nodev", "dev": - if setDev { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newMount.Options = append(newMount.Options, kv[0]) - case "noexec", "exec": - if setExec { - return newMount, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newMount.Options = append(newMount.Options, kv[0]) - case "tmpfs-mode": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("mode=%s", kv[1])) - case "tmpfs-size": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - newMount.Options = append(newMount.Options, fmt.Sprintf("size=%s", kv[1])) - case "src", "source": - return newMount, errors.Errorf("source is not supported with tmpfs mounts") - case "target", "dst", "destination": - if len(kv) == 1 { - return newMount, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return newMount, err - } - newMount.Destination = filepath.Clean(kv[1]) - setDest = true - default: - return newMount, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } - } - - if !setDest { - return newMount, noDestError - } - - return newMount, nil -} - -// Parse a single volume mount entry from the --mount flag. -// Note that the volume-label option for named volumes is currently NOT supported. -// TODO: add support for --volume-label -func getNamedVolume(args []string) (*libpod.ContainerNamedVolume, error) { - newVolume := new(libpod.ContainerNamedVolume) - - var setSource, setDest, setRORW, setSuid, setDev, setExec bool - - for _, val := range args { - kv := strings.SplitN(val, "=", 2) - switch kv[0] { - case "ro", "rw": - if setRORW { - return nil, errors.Wrapf(optionArgError, "cannot pass 'ro' and 'rw' options more than once") - } - setRORW = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nosuid", "suid": - if setSuid { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nosuid' and 'suid' options more than once") - } - setSuid = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "nodev", "dev": - if setDev { - return nil, errors.Wrapf(optionArgError, "cannot pass 'nodev' and 'dev' options more than once") - } - setDev = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "noexec", "exec": - if setExec { - return nil, errors.Wrapf(optionArgError, "cannot pass 'noexec' and 'exec' options more than once") - } - setExec = true - newVolume.Options = append(newVolume.Options, kv[0]) - case "volume-label": - return nil, errors.Errorf("the --volume-label option is not presently implemented") - case "src", "source": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - newVolume.Name = kv[1] - setSource = true - case "target", "dst", "destination": - if len(kv) == 1 { - return nil, errors.Wrapf(optionArgError, kv[0]) - } - if err := parse.ValidateVolumeCtrDir(kv[1]); err != nil { - return nil, err - } - newVolume.Dest = filepath.Clean(kv[1]) - setDest = true - default: - return nil, errors.Wrapf(util.ErrBadMntOption, kv[0]) - } - } - - if !setSource { - return nil, errors.Errorf("must set source volume") - } - if !setDest { - return nil, noDestError - } - - return newVolume, nil -} - -func (config *CreateConfig) getVolumeMounts() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*libpod.ContainerNamedVolume) - - volumeFormatErr := errors.Errorf("incorrect volume format, should be [host-dir:]ctr-dir[:option]") - - for _, vol := range config.Volumes { - var ( - options []string - src string - dest string - err error - ) - - splitVol := strings.Split(vol, ":") - if len(splitVol) > 3 { - return nil, nil, errors.Wrapf(volumeFormatErr, vol) - } - - src = splitVol[0] - if len(splitVol) == 1 { - // This is an anonymous named volume. Only thing given - // is destination. - // Name/source will be blank, and populated by libpod. - src = "" - dest = splitVol[0] - } else if len(splitVol) > 1 { - dest = splitVol[1] - } - if len(splitVol) > 2 { - if options, err = parse.ValidateVolumeOpts(strings.Split(splitVol[2], ",")); err != nil { - return nil, nil, err - } - } - - // Do not check source dir for anonymous volumes - if len(splitVol) > 1 { - if err := parse.ValidateVolumeHostDir(src); err != nil { - return nil, nil, err - } - } - if err := parse.ValidateVolumeCtrDir(dest); err != nil { - return nil, nil, err - } - - cleanDest := filepath.Clean(dest) - - if strings.HasPrefix(src, "/") || strings.HasPrefix(src, ".") { - // This is not a named volume - newMount := spec.Mount{ - Destination: cleanDest, - Type: string(TypeBind), - Source: src, - Options: options, - } - if _, ok := mounts[newMount.Destination]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newMount.Destination) - } - mounts[newMount.Destination] = newMount - } else { - // This is a named volume - newNamedVol := new(libpod.ContainerNamedVolume) - newNamedVol.Name = src - newNamedVol.Dest = cleanDest - newNamedVol.Options = options - - if _, ok := volumes[newNamedVol.Dest]; ok { - return nil, nil, errors.Wrapf(errDuplicateDest, newNamedVol.Dest) - } - volumes[newNamedVol.Dest] = newNamedVol - } - - logrus.Debugf("User mount %s:%s options %v", src, dest, options) - } - - return mounts, volumes, nil -} - -// Get mounts for container's image volumes -func (config *CreateConfig) getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { - mounts := make(map[string]spec.Mount) - volumes := make(map[string]*libpod.ContainerNamedVolume) - - if config.ImageVolumeType == "ignore" { - return mounts, volumes, nil - } - - for vol := range config.BuiltinImgVolumes { - cleanDest := filepath.Clean(vol) - logrus.Debugf("Adding image volume at %s", cleanDest) - if config.ImageVolumeType == "tmpfs" { - // Tmpfs image volumes are handled as mounts - mount := spec.Mount{ - Destination: cleanDest, - Source: TypeTmpfs, - Type: TypeTmpfs, - Options: []string{"rprivate", "rw", "nodev", "exec"}, - } - mounts[cleanDest] = mount - } else { - // Anonymous volumes have no name. - namedVolume := new(libpod.ContainerNamedVolume) - namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"} - namedVolume.Dest = cleanDest - volumes[cleanDest] = namedVolume - } - } - - return mounts, volumes, nil -} - -// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts -func (config *CreateConfig) getTmpfsMounts() (map[string]spec.Mount, error) { - m := make(map[string]spec.Mount) - for _, i := range config.Tmpfs { - // Default options if nothing passed - var options []string - spliti := strings.Split(i, ":") - destPath := spliti[0] - if err := parse.ValidateVolumeCtrDir(spliti[0]); err != nil { - return nil, err - } - if len(spliti) > 1 { - options = strings.Split(spliti[1], ",") - } - - if _, ok := m[destPath]; ok { - return nil, errors.Wrapf(errDuplicateDest, destPath) - } - - mount := spec.Mount{ - Destination: filepath.Clean(destPath), - Type: string(TypeTmpfs), - Options: options, - Source: string(TypeTmpfs), - } - m[destPath] = mount - } - return m, nil -} - -// AddContainerInitBinary adds the init binary specified by path iff the -// container will run in a private PID namespace that is not shared with the -// host or another pre-existing container, where an init-like process is -// already running. -// -// Note that AddContainerInitBinary prepends "/dev/init" "--" to the command -// to execute the bind-mounted binary as PID 1. -func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, error) { - mount := spec.Mount{ - Destination: "/dev/init", - Type: TypeBind, - Source: path, - Options: []string{TypeBind, "ro"}, - } - - if path == "" { - return mount, fmt.Errorf("please specify a path to the container-init binary") - } - if !config.Pid.PidMode.IsPrivate() { - return mount, fmt.Errorf("cannot add init binary as PID 1 (PID namespace isn't private)") - } - if config.Systemd { - return mount, fmt.Errorf("cannot use container-init binary with systemd") - } - if _, err := os.Stat(path); os.IsNotExist(err) { - return mount, errors.Wrap(err, "container-init binary not found on the host") - } - config.Command = append([]string{"/dev/init", "--"}, config.Command...) - return mount, nil -} - -// Supersede existing mounts in the spec with new, user-specified mounts. -// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by -// one mount, and we already have /tmp/a and /tmp/b, should we remove -// the /tmp/a and /tmp/b mounts in favor of the more general /tmp? -func SupercedeUserMounts(mounts []spec.Mount, configMount []spec.Mount) []spec.Mount { - if len(mounts) > 0 { - // If we have overlappings mounts, remove them from the spec in favor of - // the user-added volume mounts - destinations := make(map[string]bool) - for _, mount := range mounts { - destinations[path.Clean(mount.Destination)] = true - } - // Copy all mounts from spec to defaultMounts, except for - // - mounts overridden by a user supplied mount; - // - all mounts under /dev if a user supplied /dev is present; - mountDev := destinations["/dev"] - for _, mount := range configMount { - if _, ok := destinations[path.Clean(mount.Destination)]; !ok { - if mountDev && strings.HasPrefix(mount.Destination, "/dev/") { - // filter out everything under /dev if /dev is user-mounted - continue - } - - logrus.Debugf("Adding mount %s", mount.Destination) - mounts = append(mounts, mount) - } - } - return mounts - } - return configMount -} - -// Ensure mount options on all mounts are correct -func InitFSMounts(mounts []spec.Mount) error { - for i, m := range mounts { - switch { - case m.Type == TypeBind: - opts, err := util.ProcessOptions(m.Options, false, m.Source) - if err != nil { - return err - } - mounts[i].Options = opts - case m.Type == TypeTmpfs && filepath.Clean(m.Destination) != "/dev": - opts, err := util.ProcessOptions(m.Options, true, "") - if err != nil { - return err - } - mounts[i].Options = opts - } - } - return nil -} diff --git a/pkg/spec/storage_test.go b/pkg/spec/storage_test.go deleted file mode 100644 index 04a9d6976..000000000 --- a/pkg/spec/storage_test.go +++ /dev/null @@ -1,38 +0,0 @@ -package createconfig - -import ( - "testing" - - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/stretchr/testify/assert" -) - -func TestGetVolumeMountsOneVolume(t *testing.T) { - data := spec.Mount{ - Destination: "/foobar", - Type: "bind", - Source: "/tmp", - Options: []string{"ro"}, - } - config := CreateConfig{ - Volumes: []string{"/tmp:/foobar:ro"}, - } - specMount, _, err := config.getVolumeMounts() - assert.NoError(t, err) - assert.EqualValues(t, data, specMount[data.Destination]) -} - -func TestGetTmpfsMounts(t *testing.T) { - data := spec.Mount{ - Destination: "/homer", - Type: "tmpfs", - Source: "tmpfs", - Options: []string{"rw", "size=787448k", "mode=1777"}, - } - config := CreateConfig{ - Tmpfs: []string{"/homer:rw,size=787448k,mode=1777"}, - } - tmpfsMount, err := config.getTmpfsMounts() - assert.NoError(t, err) - assert.EqualValues(t, data, tmpfsMount[data.Destination]) -} |