From 714718794236245e81d4552f30731157d731aa9d Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 14 Apr 2020 14:13:06 -0500 Subject: v2specgen prune libpod use libpod only in the specgen/generate package so that the remote clients do not inherit libpod bloat. Signed-off-by: Brent Baude --- pkg/api/handlers/libpod/pods.go | 3 +- pkg/domain/infra/abi/pods.go | 6 +- pkg/specgen/config_linux_cgo.go | 61 -- pkg/specgen/config_linux_nocgo.go | 11 - pkg/specgen/generate/config_linux_cgo.go | 62 ++ pkg/specgen/generate/config_linux_nocgo.go | 14 + pkg/specgen/generate/container_create.go | 14 +- pkg/specgen/generate/namespaces.go | 417 ++++++++++++++ pkg/specgen/generate/oci.go | 259 +++++++++ pkg/specgen/generate/pod_create.go | 83 +++ pkg/specgen/generate/storage.go | 885 +++++++++++++++++++++++++++++ pkg/specgen/namespaces.go | 379 ------------ pkg/specgen/oci.go | 258 --------- pkg/specgen/pod_create.go | 83 --- pkg/specgen/pod_validate.go | 3 +- pkg/specgen/specgen.go | 11 +- pkg/specgen/storage.go | 885 ----------------------------- 17 files changed, 1746 insertions(+), 1688 deletions(-) delete mode 100644 pkg/specgen/config_linux_cgo.go delete mode 100644 pkg/specgen/config_linux_nocgo.go create mode 100644 pkg/specgen/generate/config_linux_cgo.go create mode 100644 pkg/specgen/generate/config_linux_nocgo.go create mode 100644 pkg/specgen/generate/namespaces.go create mode 100644 pkg/specgen/generate/oci.go create mode 100644 pkg/specgen/generate/pod_create.go create mode 100644 pkg/specgen/generate/storage.go delete mode 100644 pkg/specgen/oci.go delete mode 100644 pkg/specgen/pod_create.go delete mode 100644 pkg/specgen/storage.go diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index a890169a1..81cab1ede 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -12,6 +12,7 @@ import ( "github.com/containers/libpod/pkg/api/handlers/utils" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/containers/libpod/pkg/util" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -27,7 +28,7 @@ func PodCreate(w http.ResponseWriter, r *http.Request) { utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } - pod, err := psg.MakePod(runtime) + pod, err := generate.MakePod(&psg, runtime) if err != nil { http_code := http.StatusInternalServerError if errors.Cause(err) == define.ErrPodExists { diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index c3e5d59bc..bb637de3e 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -5,13 +5,13 @@ package abi import ( "context" - lpfilters "github.com/containers/libpod/libpod/filters" - "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" + lpfilters "github.com/containers/libpod/libpod/filters" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" "github.com/containers/libpod/pkg/specgen" + "github.com/containers/libpod/pkg/specgen/generate" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -246,7 +246,7 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { podSpec := specgen.NewPodSpecGenerator() opts.ToPodSpecGen(podSpec) - pod, err := podSpec.MakePod(ic.Libpod) + pod, err := generate.MakePod(podSpec, ic.Libpod) if err != nil { return nil, err } diff --git a/pkg/specgen/config_linux_cgo.go b/pkg/specgen/config_linux_cgo.go deleted file mode 100644 index ef6c6e951..000000000 --- a/pkg/specgen/config_linux_cgo.go +++ /dev/null @@ -1,61 +0,0 @@ -// +build linux,cgo - -package specgen - -import ( - "context" - "io/ioutil" - - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/seccomp" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/pkg/errors" - goSeccomp "github.com/seccomp/containers-golang" - "github.com/sirupsen/logrus" -) - -func (s *SpecGenerator) getSeccompConfig(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/config_linux_nocgo.go b/pkg/specgen/config_linux_nocgo.go deleted file mode 100644 index fc0c58c37..000000000 --- a/pkg/specgen/config_linux_nocgo.go +++ /dev/null @@ -1,11 +0,0 @@ -// +build linux,!cgo - -package specgen - -import ( - spec "github.com/opencontainers/runtime-spec/specs-go" -) - -func (s *SpecGenerator) getSeccompConfig(configSpec *spec.Spec) (*spec.LinuxSeccomp, error) { - return nil, nil -} 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_create.go b/pkg/specgen/generate/container_create.go index aad59a861..264e0ff8e 100644 --- a/pkg/specgen/generate/container_create.go +++ b/pkg/specgen/generate/container_create.go @@ -40,7 +40,7 @@ func MakeContainer(rt *libpod.Runtime, s *specgen.SpecGenerator) (*libpod.Contai options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - runtimeSpec, err := s.ToOCISpec(rt, newImage) + runtimeSpec, err := SpecGenToOCI(s, rt, newImage) if err != nil { return nil, err } @@ -80,7 +80,15 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithUserVolumes(destinations)) if len(s.Volumes) != 0 { - options = append(options, libpod.WithNamedVolumes(s.Volumes)) + 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 { @@ -115,7 +123,7 @@ func createContainerOptions(rt *libpod.Runtime, s *specgen.SpecGenerator) ([]lib options = append(options, libpod.WithPrivileged(s.Privileged)) // Get namespace related options - namespaceOptions, err := s.GenerateNamespaceContainerOpts(rt) + namespaceOptions, err := GenerateNamespaceContainerOpts(s, rt) if err != nil { return nil, err } 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..4bc4d2327 --- /dev/null +++ b/pkg/specgen/generate/oci.go @@ -0,0 +1,259 @@ +package generate + +import ( + "strings" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/rootless" + createconfig "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/specgen" + 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 := createconfig.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: createconfig.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 := createconfig.AddPrivilegedDevices(&g); err != nil { + return nil, err + } + } else { + for _, device := range s.Devices { + if err := createconfig.DevicesFromPath(&g, device.Path); err != nil { + return nil, err + } + } + } + + // SECURITY OPTS + g.SetProcessNoNewPrivileges(s.NoNewPrivileges) + + if !s.Privileged { + g.SetProcessApparmorProfile(s.ApparmorProfile) + } + + createconfig.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 = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts) + // Process mounts to ensure correct options + if err := createconfig.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 +} 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/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=,[src=,]target=[,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 +} diff --git a/pkg/specgen/namespaces.go b/pkg/specgen/namespaces.go index 2a7bb3495..2ef5bc229 100644 --- a/pkg/specgen/namespaces.go +++ b/pkg/specgen/namespaces.go @@ -1,16 +1,7 @@ package specgen import ( - "os" - - "github.com/containers/common/pkg/capabilities" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "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" ) type NamespaceMode string @@ -105,373 +96,3 @@ func (n *Namespace) validate() error { } return nil } - -func (s *SpecGenerator) GenerateNamespaceContainerOpts(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 != 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 (s *SpecGenerator) pidConfigureGenerator(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 (s *SpecGenerator) utsConfigureGenerator(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 (s *SpecGenerator) ipcConfigureGenerator(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 (s *SpecGenerator) cgroupConfigureGenerator(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 (s *SpecGenerator) networkConfigureGenerator(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 == NoNetwork: - logrus.Debug("Using none netmode") - case s.NetNS.NSMode == 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 == 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 (s *SpecGenerator) userConfigureGenerator(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 (s *SpecGenerator) securityConfigureGenerator(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 := s.getSeccompConfig(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 -} diff --git a/pkg/specgen/oci.go b/pkg/specgen/oci.go deleted file mode 100644 index 0756782b4..000000000 --- a/pkg/specgen/oci.go +++ /dev/null @@ -1,258 +0,0 @@ -package specgen - -import ( - "strings" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/image" - "github.com/containers/libpod/pkg/rootless" - createconfig "github.com/containers/libpod/pkg/spec" - spec "github.com/opencontainers/runtime-spec/specs-go" - "github.com/opencontainers/runtime-tools/generate" -) - -func (s *SpecGenerator) ToOCISpec(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 := createconfig.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: createconfig.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 := createconfig.AddPrivilegedDevices(&g); err != nil { - return nil, err - } - } else { - for _, device := range s.Devices { - if err := createconfig.DevicesFromPath(&g, device.Path); err != nil { - return nil, err - } - } - } - - // SECURITY OPTS - g.SetProcessNoNewPrivileges(s.NoNewPrivileges) - - if !s.Privileged { - g.SetProcessApparmorProfile(s.ApparmorProfile) - } - - createconfig.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 := s.pidConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := s.userConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := s.networkConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := s.utsConfigureGenerator(&g, rt); err != nil { - return nil, err - } - - if err := s.ipcConfigureGenerator(&g); err != nil { - return nil, err - } - - if err := s.cgroupConfigureGenerator(&g); err != nil { - return nil, err - } - configSpec := g.Config - - if err := s.securityConfigureGenerator(&g, newImage); err != nil { - return nil, err - } - - // BIND MOUNTS - configSpec.Mounts = createconfig.SupercedeUserMounts(s.Mounts, configSpec.Mounts) - // Process mounts to ensure correct options - if err := createconfig.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 -} diff --git a/pkg/specgen/pod_create.go b/pkg/specgen/pod_create.go deleted file mode 100644 index 06aa24e22..000000000 --- a/pkg/specgen/pod_create.go +++ /dev/null @@ -1,83 +0,0 @@ -package specgen - -import ( - "context" - - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/libpod" - "github.com/sirupsen/logrus" -) - -func (p *PodSpecGenerator) MakePod(rt *libpod.Runtime) (*libpod.Pod, error) { - if err := p.validate(); err != nil { - return nil, err - } - options, err := p.createPodOptions() - if err != nil { - return nil, err - } - return rt.NewPod(context.Background(), options...) -} - -func (p *PodSpecGenerator) createPodOptions() ([]libpod.PodCreateOption, error) { - var ( - options []libpod.PodCreateOption - ) - if !p.NoInfra { - options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.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 Bridge: - logrus.Debugf("Pod using default network mode") - case 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/pod_validate.go b/pkg/specgen/pod_validate.go index 92026309f..9e9659fa9 100644 --- a/pkg/specgen/pod_validate.go +++ b/pkg/specgen/pod_validate.go @@ -15,7 +15,8 @@ func exclusivePodOptions(opt1, opt2 string) error { return errors.Wrapf(ErrInvalidPodSpecConfig, "%s and %s are mutually exclusive pod options", opt1, opt2) } -func (p *PodSpecGenerator) validate() error { +// Validate verifies the input is valid +func (p *PodSpecGenerator) Validate() error { // PodBasicConfig if p.NoInfra { if len(p.InfraCommand) > 0 { diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index 2e6dd9c1d..8482ef2c9 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -4,8 +4,6 @@ import ( "net" "syscall" - "github.com/containers/libpod/libpod" - "github.com/containers/image/v5/manifest" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" @@ -174,7 +172,7 @@ type ContainerStorageConfig struct { // These will supersede Image Volumes and VolumesFrom volumes where // there are conflicts. // Optional. - Volumes []*libpod.ContainerNamedVolume `json:"volumes,omitempty"` + Volumes []*Volumes `json:"volumes,omitempty"` // Devices are devices that will be added to the container. // Optional. Devices []spec.LinuxDevice `json:"devices,omitempty"` @@ -403,6 +401,13 @@ type SpecGenerator struct { ContainerHealthCheckConfig } +// Volumes is a temporary struct to hold input from the User +type Volumes struct { + Name string + Dest string + Options []string +} + // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(image string) *SpecGenerator { networkConfig := ContainerNetworkConfig{ diff --git a/pkg/specgen/storage.go b/pkg/specgen/storage.go deleted file mode 100644 index 1b903f608..000000000 --- a/pkg/specgen/storage.go +++ /dev/null @@ -1,885 +0,0 @@ -package specgen - -//nolint - -import ( - "fmt" - "path" - "path/filepath" - "strings" - - "github.com/containers/libpod/libpod" - - "github.com/containers/buildah/pkg/parse" - "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 (s *SpecGenerator) parseVolumes(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=,[src=,]target=[,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 -} -- cgit v1.2.3-54-g00ecf