aboutsummaryrefslogtreecommitdiff
path: root/pkg/specgen/generate
diff options
context:
space:
mode:
Diffstat (limited to 'pkg/specgen/generate')
-rw-r--r--pkg/specgen/generate/config_linux.go323
-rw-r--r--pkg/specgen/generate/config_linux_cgo.go62
-rw-r--r--pkg/specgen/generate/config_linux_nocgo.go14
-rw-r--r--pkg/specgen/generate/container.go176
-rw-r--r--pkg/specgen/generate/container_create.go198
-rw-r--r--pkg/specgen/generate/namespaces.go417
-rw-r--r--pkg/specgen/generate/oci.go271
-rw-r--r--pkg/specgen/generate/pod_create.go83
-rw-r--r--pkg/specgen/generate/security.go154
-rw-r--r--pkg/specgen/generate/storage.go885
10 files changed, 2583 insertions, 0 deletions
diff --git a/pkg/specgen/generate/config_linux.go b/pkg/specgen/generate/config_linux.go
new file mode 100644
index 000000000..1b2a2ac32
--- /dev/null
+++ b/pkg/specgen/generate/config_linux.go
@@ -0,0 +1,323 @@
+package generate
+
+import (
+ "fmt"
+ "io/ioutil"
+ "os"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/libpod/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"
+)
+
+func u32Ptr(i int64) *uint32 { u := uint32(i); return &u }
+func fmPtr(i int64) *os.FileMode { fm := os.FileMode(i); return &fm }
+
+// 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)),
+ }
+}
+
+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
+}
+
+// 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 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)
+ }
+ }
+}
+
+// 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
+ }
+ 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 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, dev.Permissions)
+ return nil
+}
+
+// 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 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
+}
diff --git a/pkg/specgen/generate/config_linux_cgo.go b/pkg/specgen/generate/config_linux_cgo.go
new file mode 100644
index 000000000..b06ef5c9a
--- /dev/null
+++ b/pkg/specgen/generate/config_linux_cgo.go
@@ -0,0 +1,62 @@
+// +build linux,cgo
+
+package generate
+
+import (
+ "context"
+ "io/ioutil"
+
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/seccomp"
+ "github.com/containers/libpod/pkg/specgen"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/pkg/errors"
+ goSeccomp "github.com/seccomp/containers-golang"
+ "github.com/sirupsen/logrus"
+)
+
+func getSeccompConfig(s *specgen.SpecGenerator, configSpec *spec.Spec, img *image.Image) (*spec.LinuxSeccomp, error) {
+ var seccompConfig *spec.LinuxSeccomp
+ var err error
+ scp, err := seccomp.LookupPolicy(s.SeccompPolicy)
+ if err != nil {
+ return nil, err
+ }
+
+ if scp == seccomp.PolicyImage {
+ labels, err := img.Labels(context.Background())
+ if err != nil {
+ return nil, err
+ }
+ imagePolicy := labels[seccomp.ContainerImageLabel]
+ if len(imagePolicy) < 1 {
+ return nil, errors.New("no seccomp policy defined by image")
+ }
+ logrus.Debug("Loading seccomp profile from the security config")
+ seccompConfig, err = goSeccomp.LoadProfile(imagePolicy, configSpec)
+ if err != nil {
+ return nil, errors.Wrap(err, "loading seccomp profile failed")
+ }
+ return seccompConfig, nil
+ }
+
+ if s.SeccompProfilePath != "" {
+ logrus.Debugf("Loading seccomp profile from %q", s.SeccompProfilePath)
+ seccompProfile, err := ioutil.ReadFile(s.SeccompProfilePath)
+ if err != nil {
+ return nil, errors.Wrapf(err, "opening seccomp profile (%s) failed", s.SeccompProfilePath)
+ }
+ seccompConfig, err = goSeccomp.LoadProfile(string(seccompProfile), configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath)
+ }
+ } else {
+ logrus.Debug("Loading default seccomp profile")
+ seccompConfig, err = goSeccomp.GetDefaultProfile(configSpec)
+ if err != nil {
+ return nil, errors.Wrapf(err, "loading seccomp profile (%s) failed", s.SeccompProfilePath)
+ }
+ }
+
+ return seccompConfig, nil
+}
diff --git a/pkg/specgen/generate/config_linux_nocgo.go b/pkg/specgen/generate/config_linux_nocgo.go
new file mode 100644
index 000000000..fc8ed206d
--- /dev/null
+++ b/pkg/specgen/generate/config_linux_nocgo.go
@@ -0,0 +1,14 @@
+// +build linux,!cgo
+
+package generate
+
+import (
+ "errors"
+
+ "github.com/containers/libpod/pkg/specgen"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+)
+
+func (s *specgen.SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) {
+ return nil, errors.New("not implemented")
+}
diff --git a/pkg/specgen/generate/container.go b/pkg/specgen/generate/container.go
new file mode 100644
index 000000000..7233acb8a
--- /dev/null
+++ b/pkg/specgen/generate/container.go
@@ -0,0 +1,176 @@
+package generate
+
+import (
+ "context"
+
+ "github.com/containers/libpod/libpod"
+ ann "github.com/containers/libpod/pkg/annotations"
+ envLib "github.com/containers/libpod/pkg/env"
+ "github.com/containers/libpod/pkg/signal"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/pkg/errors"
+ "golang.org/x/sys/unix"
+)
+
+func CompleteSpec(ctx context.Context, r *libpod.Runtime, s *specgen.SpecGenerator) error {
+
+ newImage, err := r.ImageRuntime().NewFromLocal(s.Image)
+ if err != nil {
+ return err
+ }
+
+ // Image stop signal
+ if s.StopSignal == nil && newImage.Config != nil {
+ sig, err := signal.ParseSignalNameOrNumber(newImage.Config.StopSignal)
+ if err != nil {
+ return err
+ }
+ s.StopSignal = &sig
+ }
+ // Image envs from the image if they don't exist
+ // already
+ if newImage.Config != nil && len(newImage.Config.Env) > 0 {
+ envs, err := envLib.ParseSlice(newImage.Config.Env)
+ if err != nil {
+ return err
+ }
+ for k, v := range envs {
+ if _, exists := s.Env[k]; !exists {
+ s.Env[v] = k
+ }
+ }
+ }
+
+ // labels from the image that dont exist already
+ if config := newImage.Config; config != nil {
+ for k, v := range config.Labels {
+ if _, exists := s.Labels[k]; !exists {
+ s.Labels[k] = v
+ }
+ }
+ }
+
+ // annotations
+ // in the event this container is in a pod, and the pod has an infra container
+ // we will want to configure it as a type "container" instead defaulting to
+ // the behavior of a "sandbox" container
+ // In Kata containers:
+ // - "sandbox" is the annotation that denotes the container should use its own
+ // VM, which is the default behavior
+ // - "container" denotes the container should join the VM of the SandboxID
+ // (the infra container)
+ s.Annotations = make(map[string]string)
+ if len(s.Pod) > 0 {
+ s.Annotations[ann.SandboxID] = s.Pod
+ s.Annotations[ann.ContainerType] = ann.ContainerTypeContainer
+ }
+ //
+ // Next, add annotations from the image
+ annotations, err := newImage.Annotations(ctx)
+ if err != nil {
+ return err
+ }
+ for k, v := range annotations {
+ annotations[k] = v
+ }
+
+ // entrypoint
+ if config := newImage.Config; config != nil {
+ if len(s.Entrypoint) < 1 && len(config.Entrypoint) > 0 {
+ s.Entrypoint = config.Entrypoint
+ }
+ if len(s.Command) < 1 && len(config.Cmd) > 0 {
+ s.Command = config.Cmd
+ }
+ if len(s.Command) < 1 && len(s.Entrypoint) < 1 {
+ return errors.Errorf("No command provided or as CMD or ENTRYPOINT in this image")
+ }
+ // workdir
+ if len(s.WorkDir) < 1 && len(config.WorkingDir) > 1 {
+ s.WorkDir = config.WorkingDir
+ }
+ }
+
+ if len(s.SeccompProfilePath) < 1 {
+ p, err := libpod.DefaultSeccompPath()
+ if err != nil {
+ return err
+ }
+ s.SeccompProfilePath = p
+ }
+
+ if user := s.User; len(user) == 0 {
+ switch {
+ // TODO This should be enabled when namespaces actually work
+ //case usernsMode.IsKeepID():
+ // user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID())
+ case newImage.Config == nil || (newImage.Config != nil && len(newImage.Config.User) == 0):
+ s.User = "0"
+ default:
+ s.User = newImage.Config.User
+ }
+ }
+ if err := finishThrottleDevices(s); err != nil {
+ return err
+ }
+ // Unless already set via the CLI, check if we need to disable process
+ // labels or set the defaults.
+ if len(s.SelinuxOpts) == 0 {
+ if err := SetLabelOpts(s, r, s.PidNS, s.IpcNS); err != nil {
+ return err
+ }
+ }
+
+ return nil
+}
+
+// finishThrottleDevices takes the temporary representation of the throttle
+// devices in the specgen and looks up the major and major minors. it then
+// sets the throttle devices proper in the specgen
+func finishThrottleDevices(s *specgen.SpecGenerator) error {
+ if bps := s.ThrottleReadBpsDevice; len(bps) > 0 {
+ for k, v := range bps {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleReadBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleReadBpsDevice, v)
+ }
+ }
+ if bps := s.ThrottleWriteBpsDevice; len(bps) > 0 {
+ for k, v := range bps {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteBpsDevice, v)
+ }
+ }
+ if iops := s.ThrottleReadIOPSDevice; len(iops) > 0 {
+ for k, v := range iops {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleReadIOPSDevice, v)
+ }
+ }
+ if iops := s.ThrottleWriteBpsDevice; len(iops) > 0 {
+ for k, v := range iops {
+ statT := unix.Stat_t{}
+ if err := unix.Stat(k, &statT); err != nil {
+ return err
+ }
+ v.Major = (int64(unix.Major(statT.Rdev)))
+ v.Minor = (int64(unix.Minor(statT.Rdev)))
+ s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice = append(s.ResourceLimits.BlockIO.ThrottleWriteIOPSDevice, v)
+ }
+ }
+ return nil
+}
diff --git a/pkg/specgen/generate/container_create.go b/pkg/specgen/generate/container_create.go
new file mode 100644
index 000000000..264e0ff8e
--- /dev/null
+++ b/pkg/specgen/generate/container_create.go
@@ -0,0 +1,198 @@
+package generate
+
+import (
+ "context"
+ "os"
+
+ "github.com/containers/common/pkg/config"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/storage"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+// MakeContainer creates a container based on the SpecGenerator
+func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Container, error) {
+ if err := s.Validate(); err != nil {
+ return nil, errors.Wrap(err, "invalid config provided")
+ }
+ rtc, err := rt.GetConfig()
+ if err != nil {
+ return nil, err
+ }
+
+ options, err := createContainerOptions(rt, s)
+ if err != nil {
+ return nil, err
+ }
+
+ podmanPath, err := os.Executable()
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, createExitCommandOption(s, rt.StorageConfig(), rtc, podmanPath))
+ newImage, err := rt.ImageRuntime().NewFromLocal(s.Image)
+ if err != nil {
+ return nil, err
+ }
+
+ options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName))
+
+ runtimeSpec, err := SpecGenToOCI(s, rt, newImage)
+ if err != nil {
+ return nil, err
+ }
+ return rt.NewContainer(context.Background(), runtimeSpec, options...)
+}
+
+func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]libpod.CtrCreateOption, error) {
+ var options []libpod.CtrCreateOption
+ var err error
+
+ if s.Stdin {
+ options = append(options, libpod.WithStdin())
+ }
+ if len(s.Systemd) > 0 {
+ options = append(options, libpod.WithSystemd())
+ }
+ if len(s.Name) > 0 {
+ logrus.Debugf("setting container name %s", s.Name)
+ options = append(options, libpod.WithName(s.Name))
+ }
+ if s.Pod != "" {
+ pod, err := rt.LookupPod(s.Pod)
+ if err != nil {
+ return nil, err
+ }
+ logrus.Debugf("adding container to pod %s", s.Pod)
+ options = append(options, rt.WithPod(pod))
+ }
+ destinations := []string{}
+ // // Take all mount and named volume destinations.
+ for _, mount := range s.Mounts {
+ destinations = append(destinations, mount.Destination)
+ }
+ for _, volume := range s.Volumes {
+ destinations = append(destinations, volume.Dest)
+ }
+ options = append(options, libpod.WithUserVolumes(destinations))
+
+ if len(s.Volumes) != 0 {
+ var volumes []*libpod.ContainerNamedVolume
+ for _, v := range s.Volumes {
+ volumes = append(volumes, &libpod.ContainerNamedVolume{
+ Name: v.Name,
+ Dest: v.Dest,
+ Options: v.Options,
+ })
+ }
+ options = append(options, libpod.WithNamedVolumes(volumes))
+ }
+
+ if len(s.Command) != 0 {
+ options = append(options, libpod.WithCommand(s.Command))
+ }
+
+ options = append(options, libpod.WithEntrypoint(s.Entrypoint))
+ if s.StopSignal != nil {
+ options = append(options, libpod.WithStopSignal(*s.StopSignal))
+ }
+ if s.StopTimeout != nil {
+ options = append(options, libpod.WithStopTimeout(*s.StopTimeout))
+ }
+ if s.LogConfiguration != nil {
+ if len(s.LogConfiguration.Path) > 0 {
+ options = append(options, libpod.WithLogPath(s.LogConfiguration.Path))
+ }
+ if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" {
+ // Note: I'm really guessing here.
+ options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"]))
+ }
+
+ if len(s.LogConfiguration.Driver) > 0 {
+ options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver))
+ }
+ }
+
+ // Security options
+ if len(s.SelinuxOpts) > 0 {
+ options = append(options, libpod.WithSecLabels(s.SelinuxOpts))
+ }
+ options = append(options, libpod.WithPrivileged(s.Privileged))
+
+ // Get namespace related options
+ namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, namespaceOptions...)
+
+ if len(s.ConmonPidFile) > 0 {
+ options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile))
+ }
+ options = append(options, libpod.WithLabels(s.Labels))
+ if s.ShmSize != nil {
+ options = append(options, libpod.WithShmSize(*s.ShmSize))
+ }
+ if s.Rootfs != "" {
+ options = append(options, libpod.WithRootFS(s.Rootfs))
+ }
+ // Default used if not overridden on command line
+
+ if s.RestartPolicy != "" {
+ if s.RestartPolicy == "unless-stopped" {
+ return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported")
+ }
+ if s.RestartRetries != nil {
+ options = append(options, libpod.WithRestartRetries(*s.RestartRetries))
+ }
+ options = append(options, libpod.WithRestartPolicy(s.RestartPolicy))
+ }
+
+ if s.ContainerHealthCheckConfig.HealthConfig != nil {
+ options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig))
+ logrus.Debugf("New container has a health check")
+ }
+ return options, nil
+}
+
+func createExitCommandOption(s *specgen.SpecGenerator, storageConfig storage.StoreOptions, config *config.Config, podmanPath string) libpod.CtrCreateOption {
+ // 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.
+
+ command := []string{podmanPath,
+ "--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}...)
+ }
+
+ // TODO Mheon wants to leave this for now
+ //if s.sys {
+ // command = append(command, "--syslog", "true")
+ //}
+ command = append(command, []string{"container", "cleanup"}...)
+
+ if s.Remove {
+ command = append(command, "--rm")
+ }
+ return libpod.WithExitCommand(command)
+}
diff --git a/pkg/specgen/generate/namespaces.go b/pkg/specgen/generate/namespaces.go
new file mode 100644
index 000000000..cdd7d86da
--- /dev/null
+++ b/pkg/specgen/generate/namespaces.go
@@ -0,0 +1,417 @@
+package generate
+
+import (
+ "os"
+
+ "github.com/containers/common/pkg/capabilities"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/cri-o/ocicni/pkg/ocicni"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/runtime-tools/generate"
+ "github.com/pkg/errors"
+ "github.com/sirupsen/logrus"
+)
+
+func GenerateNamespaceContainerOpts(s *specgen.SpecGenerator, rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) {
+ var portBindings []ocicni.PortMapping
+ options := make([]libpod.CtrCreateOption, 0)
+
+ // Cgroups
+ switch {
+ case s.CgroupNS.IsPrivate():
+ ns := s.CgroupNS.Value
+ if _, err := os.Stat(ns); err != nil {
+ return nil, err
+ }
+ case s.CgroupNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.CgroupNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.CgroupNS.Value)
+ }
+ options = append(options, libpod.WithCgroupNSFrom(connectedCtr))
+ // TODO
+ //default:
+ // return nil, errors.New("cgroup name only supports private and container")
+ }
+
+ if s.CgroupParent != "" {
+ options = append(options, libpod.WithCgroupParent(s.CgroupParent))
+ }
+
+ if s.CgroupsMode != "" {
+ options = append(options, libpod.WithCgroupsMode(s.CgroupsMode))
+ }
+
+ // ipc
+ switch {
+ case s.IpcNS.IsHost():
+ options = append(options, libpod.WithShmDir("/dev/shm"))
+ case s.IpcNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.IpcNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.IpcNS.Value)
+ }
+ options = append(options, libpod.WithIPCNSFrom(connectedCtr))
+ options = append(options, libpod.WithShmDir(connectedCtr.ShmDir()))
+ }
+
+ // pid
+ if s.PidNS.IsContainer() {
+ connectedCtr, err := rt.LookupContainer(s.PidNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.PidNS.Value)
+ }
+ options = append(options, libpod.WithPIDNSFrom(connectedCtr))
+ }
+
+ // uts
+ switch {
+ case s.UtsNS.IsPod():
+ connectedPod, err := rt.LookupPod(s.UtsNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "pod %q not found", s.UtsNS.Value)
+ }
+ options = append(options, libpod.WithUTSNSFromPod(connectedPod))
+ case s.UtsNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.UtsNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.UtsNS.Value)
+ }
+
+ options = append(options, libpod.WithUTSNSFrom(connectedCtr))
+ }
+
+ if s.UseImageHosts {
+ options = append(options, libpod.WithUseImageHosts())
+ } else if len(s.HostAdd) > 0 {
+ options = append(options, libpod.WithHosts(s.HostAdd))
+ }
+
+ // User
+
+ switch {
+ case s.UserNS.IsPath():
+ ns := s.UserNS.Value
+ if ns == "" {
+ return nil, errors.Errorf("invalid empty user-defined user namespace")
+ }
+ _, err := os.Stat(ns)
+ if err != nil {
+ return nil, err
+ }
+ if s.IDMappings != nil {
+ options = append(options, libpod.WithIDMappings(*s.IDMappings))
+ }
+ case s.UserNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.UserNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.UserNS.Value)
+ }
+ options = append(options, libpod.WithUserNSFrom(connectedCtr))
+ default:
+ if s.IDMappings != nil {
+ options = append(options, libpod.WithIDMappings(*s.IDMappings))
+ }
+ }
+
+ options = append(options, libpod.WithUser(s.User))
+ options = append(options, libpod.WithGroups(s.Groups))
+
+ if len(s.PortMappings) > 0 {
+ portBindings = s.PortMappings
+ }
+
+ switch {
+ case s.NetNS.IsPath():
+ ns := s.NetNS.Value
+ if ns == "" {
+ return nil, errors.Errorf("invalid empty user-defined network namespace")
+ }
+ _, err := os.Stat(ns)
+ if err != nil {
+ return nil, err
+ }
+ case s.NetNS.IsContainer():
+ connectedCtr, err := rt.LookupContainer(s.NetNS.Value)
+ if err != nil {
+ return nil, errors.Wrapf(err, "container %q not found", s.NetNS.Value)
+ }
+ options = append(options, libpod.WithNetNSFrom(connectedCtr))
+ case !s.NetNS.IsHost() && s.NetNS.NSMode != specgen.NoNetwork:
+ postConfigureNetNS := !s.UserNS.IsHost()
+ options = append(options, libpod.WithNetNS(portBindings, postConfigureNetNS, string(s.NetNS.NSMode), s.CNINetworks))
+ }
+
+ if len(s.DNSSearch) > 0 {
+ options = append(options, libpod.WithDNSSearch(s.DNSSearch))
+ }
+ if len(s.DNSServer) > 0 {
+ // TODO I'm not sure how we are going to handle this given the input
+ if len(s.DNSServer) == 1 { //&& strings.ToLower(s.DNSServer[0].) == "none" {
+ options = append(options, libpod.WithUseImageResolvConf())
+ } else {
+ var dnsServers []string
+ for _, d := range s.DNSServer {
+ dnsServers = append(dnsServers, d.String())
+ }
+ options = append(options, libpod.WithDNS(dnsServers))
+ }
+ }
+ if len(s.DNSOption) > 0 {
+ options = append(options, libpod.WithDNSOption(s.DNSOption))
+ }
+ if s.StaticIP != nil {
+ options = append(options, libpod.WithStaticIP(*s.StaticIP))
+ }
+
+ if s.StaticMAC != nil {
+ options = append(options, libpod.WithStaticMAC(*s.StaticMAC))
+ }
+ return options, nil
+}
+
+func pidConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
+ if s.PidNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.PIDNamespace), s.PidNS.Value)
+ }
+ if s.PidNS.IsHost() {
+ return g.RemoveLinuxNamespace(string(spec.PIDNamespace))
+ }
+ if s.PidNS.IsContainer() {
+ logrus.Debugf("using container %s pidmode", s.PidNS.Value)
+ }
+ if s.PidNS.IsPod() {
+ logrus.Debug("using pod pidmode")
+ }
+ return nil
+}
+
+func utsConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, runtime *libpod.Runtime) error {
+ hostname := s.Hostname
+ var err error
+ if hostname == "" {
+ switch {
+ case s.UtsNS.IsContainer():
+ utsCtr, err := runtime.LookupContainer(s.UtsNS.Value)
+ if err != nil {
+ return errors.Wrapf(err, "unable to retrieve hostname from dependency container %s", s.UtsNS.Value)
+ }
+ hostname = utsCtr.Hostname()
+ case s.NetNS.IsHost() || s.UtsNS.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 s.Hostname != "" || !s.UtsNS.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)
+
+ if s.UtsNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.UTSNamespace), s.UtsNS.Value)
+ }
+ if s.UtsNS.IsHost() {
+ return g.RemoveLinuxNamespace(string(spec.UTSNamespace))
+ }
+ if s.UtsNS.IsContainer() {
+ logrus.Debugf("using container %s utsmode", s.UtsNS.Value)
+ }
+ return nil
+}
+
+func ipcConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
+ if s.IpcNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.IPCNamespace), s.IpcNS.Value)
+ }
+ if s.IpcNS.IsHost() {
+ return g.RemoveLinuxNamespace(s.IpcNS.Value)
+ }
+ if s.IpcNS.IsContainer() {
+ logrus.Debugf("Using container %s ipcmode", s.IpcNS.Value)
+ }
+ return nil
+}
+
+func cgroupConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
+ if s.CgroupNS.IsPath() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), s.CgroupNS.Value)
+ }
+ if s.CgroupNS.IsHost() {
+ return g.RemoveLinuxNamespace(s.CgroupNS.Value)
+ }
+ if s.CgroupNS.IsPrivate() {
+ return g.AddOrReplaceLinuxNamespace(string(spec.CgroupNamespace), "")
+ }
+ if s.CgroupNS.IsContainer() {
+ logrus.Debugf("Using container %s cgroup mode", s.CgroupNS.Value)
+ }
+ return nil
+}
+
+func networkConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
+ switch {
+ case s.NetNS.IsHost():
+ logrus.Debug("Using host netmode")
+ if err := g.RemoveLinuxNamespace(string(spec.NetworkNamespace)); err != nil {
+ return err
+ }
+
+ case s.NetNS.NSMode == specgen.NoNetwork:
+ logrus.Debug("Using none netmode")
+ case s.NetNS.NSMode == specgen.Bridge:
+ logrus.Debug("Using bridge netmode")
+ case s.NetNS.IsContainer():
+ logrus.Debugf("using container %s netmode", s.NetNS.Value)
+ case s.NetNS.IsPath():
+ logrus.Debug("Using ns netmode")
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.NetworkNamespace), s.NetNS.Value); err != nil {
+ return err
+ }
+ case s.NetNS.IsPod():
+ logrus.Debug("Using pod netmode, unless pod is not sharing")
+ case s.NetNS.NSMode == specgen.Slirp:
+ logrus.Debug("Using slirp4netns netmode")
+ default:
+ return errors.Errorf("unknown network mode")
+ }
+
+ if g.Config.Annotations == nil {
+ g.Config.Annotations = make(map[string]string)
+ }
+
+ if s.PublishImagePorts {
+ g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseTrue
+ } else {
+ g.Config.Annotations[libpod.InspectAnnotationPublishAll] = libpod.InspectResponseFalse
+ }
+
+ return nil
+}
+
+func userConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator) error {
+ if s.UserNS.IsPath() {
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), s.UserNS.Value); 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 s.IDMappings != nil {
+ if (len(s.IDMappings.UIDMap) > 0 || len(s.IDMappings.GIDMap) > 0) && !s.UserNS.IsHost() {
+ if err := g.AddOrReplaceLinuxNamespace(string(spec.UserNamespace), ""); err != nil {
+ return err
+ }
+ }
+ for _, uidmap := range s.IDMappings.UIDMap {
+ g.AddLinuxUIDMapping(uint32(uidmap.HostID), uint32(uidmap.ContainerID), uint32(uidmap.Size))
+ }
+ for _, gidmap := range s.IDMappings.GIDMap {
+ g.AddLinuxGIDMapping(uint32(gidmap.HostID), uint32(gidmap.ContainerID), uint32(gidmap.Size))
+ }
+ }
+ return nil
+}
+
+func securityConfigureGenerator(s *specgen.SpecGenerator, g *generate.Generator, newImage *image.Image) error {
+ // HANDLE CAPABILITIES
+ // NOTE: Must happen before SECCOMP
+ if s.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 caplist []string
+ bounding := configSpec.Process.Capabilities.Bounding
+ if useNotRoot(s.User) {
+ configSpec.Process.Capabilities.Bounding = caplist
+ }
+ caplist, err = capabilities.MergeCapabilities(configSpec.Process.Capabilities.Bounding, s.CapAdd, s.CapDrop)
+ if err != nil {
+ return err
+ }
+
+ configSpec.Process.Capabilities.Bounding = caplist
+ configSpec.Process.Capabilities.Permitted = caplist
+ configSpec.Process.Capabilities.Inheritable = caplist
+ configSpec.Process.Capabilities.Effective = caplist
+ configSpec.Process.Capabilities.Ambient = caplist
+ if useNotRoot(s.User) {
+ caplist, err = capabilities.MergeCapabilities(bounding, s.CapAdd, s.CapDrop)
+ if err != nil {
+ return err
+ }
+ }
+ configSpec.Process.Capabilities.Bounding = caplist
+
+ // HANDLE SECCOMP
+ if s.SeccompProfilePath != "unconfined" {
+ seccompConfig, err := getSeccompConfig(s, configSpec, newImage)
+ if err != nil {
+ return err
+ }
+ configSpec.Linux.Seccomp = seccompConfig
+ }
+
+ // Clear default Seccomp profile from Generator for privileged containers
+ if s.SeccompProfilePath == "unconfined" || s.Privileged {
+ configSpec.Linux.Seccomp = nil
+ }
+
+ g.SetRootReadonly(s.ReadOnlyFilesystem)
+ for sysctlKey, sysctlVal := range s.Sysctl {
+ g.AddLinuxSysctl(sysctlKey, sysctlVal)
+ }
+
+ return nil
+}
+
+// GetNamespaceOptions transforms a slice of kernel namespaces
+// into a slice of pod create options. Currently, not all
+// kernel namespaces are supported, and they will be returned in an error
+func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) {
+ var options []libpod.PodCreateOption
+ var erroredOptions []libpod.PodCreateOption
+ for _, toShare := range ns {
+ switch toShare {
+ case "cgroup":
+ options = append(options, libpod.WithPodCgroups())
+ case "net":
+ options = append(options, libpod.WithPodNet())
+ case "mnt":
+ return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level")
+ case "pid":
+ options = append(options, libpod.WithPodPID())
+ case "user":
+ return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level")
+ case "ipc":
+ options = append(options, libpod.WithPodIPC())
+ case "uts":
+ options = append(options, libpod.WithPodUTS())
+ case "":
+ case "none":
+ return erroredOptions, nil
+ default:
+ return erroredOptions, errors.Errorf("Invalid kernel namespace to share: %s. Options are: net, pid, ipc, uts or none", toShare)
+ }
+ }
+ return options, nil
+}
diff --git a/pkg/specgen/generate/oci.go b/pkg/specgen/generate/oci.go
new file mode 100644
index 000000000..0ed091f9a
--- /dev/null
+++ b/pkg/specgen/generate/oci.go
@@ -0,0 +1,271 @@
+package generate
+
+import (
+ "strings"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/libpod/image"
+ "github.com/containers/libpod/pkg/rootless"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/opencontainers/runc/libcontainer/user"
+ spec "github.com/opencontainers/runtime-spec/specs-go"
+ "github.com/opencontainers/runtime-tools/generate"
+)
+
+func SpecGenToOCI(s *specgen.SpecGenerator, rt *libpod.Runtime, newImage *image.Image) (*spec.Spec, error) {
+ var (
+ inUserNS bool
+ )
+ 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()
+ if isRootless {
+ inUserNS = true
+ }
+ if !s.UserNS.IsHost() {
+ if s.UserNS.IsContainer() || s.UserNS.IsPath() {
+ inUserNS = true
+ }
+ if s.UserNS.IsPrivate() {
+ inUserNS = true
+ }
+ }
+ if inUserNS && s.NetNS.IsHost() {
+ canMountSys = false
+ }
+
+ if s.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 s.Privileged {
+ r = "rw"
+ }
+ sysMnt := spec.Mount{
+ Destination: "/sys",
+ Type: "bind", // should we use a constant for this, like createconfig?
+ Source: "/sys",
+ Options: []string{"rprivate", "nosuid", "noexec", "nodev", r, "rbind"},
+ }
+ g.AddMount(sysMnt)
+ if !s.Privileged && isRootless {
+ g.AddLinuxMaskedPaths("/sys/kernel")
+ }
+ }
+ 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 && (s.IDMappings != nil && len(s.IDMappings.GIDMap) > 0) {
+ mappingFound := false
+ for _, r := range s.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 && s.IpcNS.IsHost() {
+ g.RemoveMount("/dev/mqueue")
+ devMqueue := spec.Mount{
+ Destination: "/dev/mqueue",
+ Type: "bind", // constant ?
+ Source: "/dev/mqueue",
+ Options: []string{"bind", "nosuid", "noexec", "nodev"},
+ }
+ g.AddMount(devMqueue)
+ }
+ if inUserNS && s.PidNS.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(s.WorkDir)
+ g.SetProcessArgs(s.Command)
+ g.SetProcessTerminal(s.Terminal)
+
+ for key, val := range s.Annotations {
+ g.AddAnnotation(key, val)
+ }
+ g.AddProcessEnv("container", "podman")
+
+ g.Config.Linux.Resources = s.ResourceLimits
+
+ // Devices
+ if s.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 _, device := range s.Devices {
+ if err := DevicesFromPath(&g, device.Path); err != nil {
+ return nil, err
+ }
+ }
+ }
+
+ // SECURITY OPTS
+ g.SetProcessNoNewPrivileges(s.NoNewPrivileges)
+
+ if !s.Privileged {
+ g.SetProcessApparmorProfile(s.ApparmorProfile)
+ }
+
+ BlockAccessToKernelFilesystems(s.Privileged, s.PidNS.IsHost(), &g)
+
+ for name, val := range s.Env {
+ g.AddProcessEnv(name, val)
+ }
+
+ // TODO rlimits and ulimits needs further refinement by someone more
+ // familiar with the code.
+ //if err := addRlimits(config, &g); err != nil {
+ // return nil, err
+ //}
+
+ // NAMESPACES
+
+ if err := pidConfigureGenerator(s, &g); err != nil {
+ return nil, err
+ }
+
+ if err := userConfigureGenerator(s, &g); err != nil {
+ return nil, err
+ }
+
+ if err := networkConfigureGenerator(s, &g); err != nil {
+ return nil, err
+ }
+
+ if err := utsConfigureGenerator(s, &g, rt); err != nil {
+ return nil, err
+ }
+
+ if err := ipcConfigureGenerator(s, &g); err != nil {
+ return nil, err
+ }
+
+ if err := cgroupConfigureGenerator(s, &g); err != nil {
+ return nil, err
+ }
+ configSpec := g.Config
+
+ if err := securityConfigureGenerator(s, &g, newImage); err != nil {
+ return nil, err
+ }
+
+ // BIND MOUNTS
+ configSpec.Mounts = SupercedeUserMounts(s.Mounts, configSpec.Mounts)
+ // Process mounts to ensure correct options
+ if err := InitFSMounts(configSpec.Mounts); err != nil {
+ return nil, err
+ }
+
+ // Add annotations
+ if configSpec.Annotations == nil {
+ configSpec.Annotations = make(map[string]string)
+ }
+
+ // TODO cidfile is not in specgen; when wiring up cli, we will need to move this out of here
+ // leaving as a reminder
+ //if config.CidFile != "" {
+ // configSpec.Annotations[libpod.InspectAnnotationCIDFile] = config.CidFile
+ //}
+
+ if s.Remove {
+ configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationAutoremove] = libpod.InspectResponseFalse
+ }
+
+ if len(s.VolumesFrom) > 0 {
+ configSpec.Annotations[libpod.InspectAnnotationVolumesFrom] = strings.Join(s.VolumesFrom, ",")
+ }
+
+ if s.Privileged {
+ configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseTrue
+ } else {
+ configSpec.Annotations[libpod.InspectAnnotationPrivileged] = libpod.InspectResponseFalse
+ }
+
+ // TODO Init might not make it into the specgen and therefore is not available here. We should deal
+ // with this when we wire up the CLI; leaving as a reminder
+ //if s.Init {
+ // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseTrue
+ //} else {
+ // configSpec.Annotations[libpod.InspectAnnotationInit] = libpod.InspectResponseFalse
+ //}
+
+ return configSpec, nil
+}
+
+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
+}
diff --git a/pkg/specgen/generate/pod_create.go b/pkg/specgen/generate/pod_create.go
new file mode 100644
index 000000000..292f9b155
--- /dev/null
+++ b/pkg/specgen/generate/pod_create.go
@@ -0,0 +1,83 @@
+package generate
+
+import (
+ "context"
+
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/sirupsen/logrus"
+)
+
+func MakePod(p *specgen.PodSpecGenerator, rt *libpod.Runtime) (*libpod.Pod, error) {
+ if err := p.Validate(); err != nil {
+ return nil, err
+ }
+ options, err := createPodOptions(p)
+ if err != nil {
+ return nil, err
+ }
+ return rt.NewPod(context.Background(), options...)
+}
+
+func createPodOptions(p *specgen.PodSpecGenerator) ([]libpod.PodCreateOption, error) {
+ var (
+ options []libpod.PodCreateOption
+ )
+ if !p.NoInfra {
+ options = append(options, libpod.WithInfraContainer())
+ nsOptions, err := GetNamespaceOptions(p.SharedNamespaces)
+ if err != nil {
+ return nil, err
+ }
+ options = append(options, nsOptions...)
+ }
+ if len(p.CgroupParent) > 0 {
+ options = append(options, libpod.WithPodCgroupParent(p.CgroupParent))
+ }
+ if len(p.Labels) > 0 {
+ options = append(options, libpod.WithPodLabels(p.Labels))
+ }
+ if len(p.Name) > 0 {
+ options = append(options, libpod.WithPodName(p.Name))
+ }
+ if len(p.Hostname) > 0 {
+ options = append(options, libpod.WithPodHostname(p.Hostname))
+ }
+ if len(p.HostAdd) > 0 {
+ options = append(options, libpod.WithPodHosts(p.HostAdd))
+ }
+ if len(p.DNSOption) > 0 {
+ options = append(options, libpod.WithPodDNSOption(p.DNSOption))
+ }
+ if len(p.DNSSearch) > 0 {
+ options = append(options, libpod.WithPodDNSSearch(p.DNSSearch))
+ }
+ if p.StaticIP != nil {
+ options = append(options, libpod.WithPodStaticIP(*p.StaticIP))
+ }
+ if p.StaticMAC != nil {
+ options = append(options, libpod.WithPodStaticMAC(*p.StaticMAC))
+ }
+ if p.NoManageResolvConf {
+ options = append(options, libpod.WithPodUseImageResolvConf())
+ }
+ switch p.NetNS.NSMode {
+ case specgen.Bridge:
+ logrus.Debugf("Pod using default network mode")
+ case specgen.Host:
+ logrus.Debugf("Pod will use host networking")
+ options = append(options, libpod.WithPodHostNetwork())
+ default:
+ logrus.Debugf("Pod joining CNI networks: %v", p.CNINetworks)
+ options = append(options, libpod.WithPodNetworks(p.CNINetworks))
+ }
+
+ if p.NoManageHosts {
+ options = append(options, libpod.WithPodUseImageHosts())
+ }
+ if len(p.PortMappings) > 0 {
+ options = append(options, libpod.WithInfraContainerPorts(p.PortMappings))
+ }
+ options = append(options, libpod.WithPodCgroups())
+ return options, nil
+}
diff --git a/pkg/specgen/generate/security.go b/pkg/specgen/generate/security.go
new file mode 100644
index 000000000..ef4b3b47a
--- /dev/null
+++ b/pkg/specgen/generate/security.go
@@ -0,0 +1,154 @@
+package generate
+
+import (
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/opencontainers/selinux/go-selinux/label"
+ "github.com/pkg/errors"
+)
+
+// SetLabelOpts sets the label options of the SecurityConfig according to the
+// input.
+func SetLabelOpts(s *specgen.SpecGenerator, runtime *libpod.Runtime, pidConfig specgen.Namespace, ipcConfig specgen.Namespace) error {
+ if !runtime.EnableLabeling() || s.Privileged {
+ s.SelinuxOpts = label.DisableSecOpt()
+ return nil
+ }
+
+ var labelOpts []string
+ if pidConfig.IsHost() {
+ labelOpts = append(labelOpts, label.DisableSecOpt()...)
+ } else if pidConfig.IsContainer() {
+ ctr, err := runtime.LookupContainer(pidConfig.Value)
+ if err != nil {
+ return errors.Wrapf(err, "container %q not found", pidConfig.Value)
+ }
+ 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.IsHost() {
+ labelOpts = append(labelOpts, label.DisableSecOpt()...)
+ } else if ipcConfig.IsContainer() {
+ ctr, err := runtime.LookupContainer(ipcConfig.Value)
+ if err != nil {
+ return errors.Wrapf(err, "container %q not found", ipcConfig.Value)
+ }
+ secopts, err := label.DupSecOpt(ctr.ProcessLabel())
+ if err != nil {
+ return errors.Wrapf(err, "failed to duplicate label %q ", ctr.ProcessLabel())
+ }
+ labelOpts = append(labelOpts, secopts...)
+ }
+
+ s.SelinuxOpts = append(s.SelinuxOpts, labelOpts...)
+ 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.Split(opt, "=")
+ if len(splitOpt) == 1 {
+ splitOpt = strings.Split(opt, ":")
+ }
+ if len(splitOpt) < 2 {
+ continue
+ }
+ switch splitOpt[0] {
+ case "label":
+ configSpec.Annotations[libpod.InspectAnnotationLabel] = splitOpt[1]
+ case "seccomp":
+ configSpec.Annotations[libpod.InspectAnnotationSeccomp] = splitOpt[1]
+ case "apparmor":
+ configSpec.Annotations[libpod.InspectAnnotationApparmor] = splitOpt[1]
+ }
+ }
+
+ g.SetRootReadonly(c.ReadOnlyRootfs)
+ for sysctlKey, sysctlVal := range c.Sysctl {
+ g.AddLinuxSysctl(sysctlKey, sysctlVal)
+ }
+
+ return nil
+}
+
+*/
diff --git a/pkg/specgen/generate/storage.go b/pkg/specgen/generate/storage.go
new file mode 100644
index 000000000..c9a36ed46
--- /dev/null
+++ b/pkg/specgen/generate/storage.go
@@ -0,0 +1,885 @@
+package generate
+
+//nolint
+
+import (
+ "fmt"
+ "path"
+ "path/filepath"
+ "strings"
+
+ "github.com/containers/buildah/pkg/parse"
+ "github.com/containers/libpod/libpod"
+ "github.com/containers/libpod/pkg/specgen"
+ "github.com/containers/libpod/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") //nolint
+ optionArgError = errors.Errorf("must provide an argument for option") //nolint
+ noDestError = errors.Errorf("must set volume destination") //nolint
+)
+
+// 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 parseVolumes(s *specgen.SpecGenerator, mounts, volMounts, tmpMounts []string) error { //nolint
+
+ // TODO this needs to come from the image and erquires a runtime
+
+ // 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, _, err := getMounts(mounts)
+ //if err != nil {
+ // return err
+ //}
+ //
+ //// Next --volumes flag.
+ //// Do not override yet.
+ //volumeMounts, _ , err := getVolumeMounts(volMounts)
+ //if err != nil {
+ // return err
+ //}
+ //
+ //// Next --tmpfs flag.
+ //// Do not override yet.
+ //tmpfsMounts, err := getTmpfsMounts(tmpMounts)
+ //if err != nil {
+ // return 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([]*define.ContainerNamedVolume, 0, len(baseVolumes))
+ //for _, volume := range baseVolumes {
+ // finalVolumes = append(finalVolumes, volume)
+ //}
+
+ //return finalMounts, finalVolumes, nil
+ return 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
+// TODO deferred baude
+func getVolumesFrom() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ // 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]*define.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
+ return nil, nil, 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 getMounts(mounts []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ 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 mounts {
+ 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) { //nolint
+ newMount := spec.Mount{
+ Type: TypeBind,
+ }
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec, setRelabel bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ 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", "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) { //nolint
+ newMount := spec.Mount{
+ Type: TypeTmpfs,
+ Source: TypeTmpfs,
+ }
+
+ var setDest, setRORW, setSuid, setDev, setExec, setTmpcopyup bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ 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) { //nolint
+ newVolume := new(libpod.ContainerNamedVolume)
+
+ var setSource, setDest, setRORW, setSuid, setDev, setExec bool
+
+ for _, val := range args {
+ kv := strings.Split(val, "=")
+ 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 getVolumeMounts(vols []string) (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ 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 vols {
+ 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
+// TODO deferred baude
+func getImageVolumes() (map[string]spec.Mount, map[string]*libpod.ContainerNamedVolume, error) { //nolint
+ //mounts := make(map[string]spec.Mount)
+ //volumes := make(map[string]*define.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(define.ContainerNamedVolume)
+ // namedVolume.Options = []string{"rprivate", "rw", "nodev", "exec"}
+ // namedVolume.Dest = cleanDest
+ // volumes[cleanDest] = namedVolume
+ // }
+ //}
+ //
+ //return mounts, volumes, nil
+ return nil, nil, nil
+}
+
+// GetTmpfsMounts creates spec.Mount structs for user-requested tmpfs mounts
+func getTmpfsMounts(mounts []string) (map[string]spec.Mount, error) { //nolint
+ m := make(map[string]spec.Mount)
+ for _, i := range mounts {
+ // 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.
+// TODO this needs to be worked on to work in new env
+func addContainerInitBinary(path string) (spec.Mount, error) { //nolint
+ 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
+}
+
+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
+}