From e56d5295614b745115abf0198f7b67ae157aae1e Mon Sep 17 00:00:00 2001 From: Brent Baude Date: Tue, 24 Mar 2020 07:28:36 -0500 Subject: podmanv2 pod create using podspecgen using the factory approach similar to container, we now create pods based on a pod spec generator. wired up the podmanv2 pod create command, podcreatewithspec binding, simple binding test, and apiv2 endpoint. also included some code refactoring as it introduced as easy circular import. Signed-off-by: Brent Baude --- pkg/specgen/container_create.go | 188 ++++++++++++++++++++++++++++++++++++++ pkg/specgen/container_validate.go | 167 +++++++++++++++++++++++++++++++++ pkg/specgen/create.go | 188 -------------------------------------- pkg/specgen/pod.go | 140 ---------------------------- pkg/specgen/pod_create.go | 83 +++++++++++++++++ pkg/specgen/pod_validate.go | 104 +++++++++++++++++++++ pkg/specgen/podspecgen.go | 153 +++++++++++++++++++++++++++++++ pkg/specgen/specgen.go | 6 +- pkg/specgen/validate.go | 169 ---------------------------------- 9 files changed, 698 insertions(+), 500 deletions(-) create mode 100644 pkg/specgen/container_create.go create mode 100644 pkg/specgen/container_validate.go delete mode 100644 pkg/specgen/create.go delete mode 100644 pkg/specgen/pod.go create mode 100644 pkg/specgen/pod_create.go create mode 100644 pkg/specgen/pod_validate.go create mode 100644 pkg/specgen/podspecgen.go delete mode 100644 pkg/specgen/validate.go (limited to 'pkg/specgen') diff --git a/pkg/specgen/container_create.go b/pkg/specgen/container_create.go new file mode 100644 index 000000000..cf082441d --- /dev/null +++ b/pkg/specgen/container_create.go @@ -0,0 +1,188 @@ +package specgen + +import ( + "context" + "os" + + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/config" + "github.com/containers/libpod/libpod/define" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +// MakeContainer creates a container based on the SpecGenerator +func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) { + if err := s.validate(); err != nil { + return nil, errors.Wrap(err, "invalid config provided") + } + rtc, err := rt.GetConfig() + if err != nil { + return nil, err + } + + options, err := s.createContainerOptions(rt) + if err != nil { + return nil, err + } + + podmanPath, err := os.Executable() + if err != nil { + return nil, err + } + options = append(options, s.createExitCommandOption(rtc, podmanPath)) + newImage, err := rt.ImageRuntime().NewFromLocal(s.Image) + if err != nil { + return nil, err + } + + options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) + + runtimeSpec, err := s.toOCISpec(rt, newImage) + if err != nil { + return nil, err + } + return rt.NewContainer(context.Background(), runtimeSpec, options...) +} + +func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { + var options []libpod.CtrCreateOption + var err error + + if s.Stdin { + options = append(options, libpod.WithStdin()) + } + if len(s.Systemd) > 0 { + options = append(options, libpod.WithSystemd()) + } + if len(s.Name) > 0 { + logrus.Debugf("setting container name %s", s.Name) + options = append(options, libpod.WithName(s.Name)) + } + if s.Pod != "" { + pod, err := rt.LookupPod(s.Pod) + if err != nil { + return nil, err + } + logrus.Debugf("adding container to pod %s", s.Pod) + options = append(options, rt.WithPod(pod)) + } + destinations := []string{} + // // Take all mount and named volume destinations. + for _, mount := range s.Mounts { + destinations = append(destinations, mount.Destination) + } + for _, volume := range s.Volumes { + destinations = append(destinations, volume.Dest) + } + options = append(options, libpod.WithUserVolumes(destinations)) + + if len(s.Volumes) != 0 { + options = append(options, libpod.WithNamedVolumes(s.Volumes)) + } + + if len(s.Command) != 0 { + options = append(options, libpod.WithCommand(s.Command)) + } + + options = append(options, libpod.WithEntrypoint(s.Entrypoint)) + if s.StopSignal != nil { + options = append(options, libpod.WithStopSignal(*s.StopSignal)) + } + if s.StopTimeout != nil { + options = append(options, libpod.WithStopTimeout(*s.StopTimeout)) + } + if s.LogConfiguration != nil { + if len(s.LogConfiguration.Path) > 0 { + options = append(options, libpod.WithLogPath(s.LogConfiguration.Path)) + } + if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" { + // Note: I'm really guessing here. + options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"])) + } + + if len(s.LogConfiguration.Driver) > 0 { + options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver)) + } + } + + // Security options + if len(s.SelinuxOpts) > 0 { + options = append(options, libpod.WithSecLabels(s.SelinuxOpts)) + } + options = append(options, libpod.WithPrivileged(s.Privileged)) + + // Get namespace related options + namespaceOptions, err := s.generateNamespaceContainerOpts(rt) + if err != nil { + return nil, err + } + options = append(options, namespaceOptions...) + + if len(s.ConmonPidFile) > 0 { + options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile)) + } + options = append(options, libpod.WithLabels(s.Labels)) + if s.ShmSize != nil { + options = append(options, libpod.WithShmSize(*s.ShmSize)) + } + if s.Rootfs != "" { + options = append(options, libpod.WithRootFS(s.Rootfs)) + } + // Default used if not overridden on command line + + if s.RestartPolicy != "" { + if s.RestartPolicy == "unless-stopped" { + return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") + } + if s.RestartRetries != nil { + options = append(options, libpod.WithRestartRetries(*s.RestartRetries)) + } + options = append(options, libpod.WithRestartPolicy(s.RestartPolicy)) + } + + if s.ContainerHealthCheckConfig.HealthConfig != nil { + options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig)) + logrus.Debugf("New container has a health check") + } + return options, nil +} + +func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPath string) libpod.CtrCreateOption { + // We need a cleanup process for containers in the current model. + // But we can't assume that the caller is Podman - it could be another + // user of the API. + // As such, provide a way to specify a path to Podman, so we can + // still invoke a cleanup process. + + command := []string{podmanPath, + "--root", config.StorageConfig.GraphRoot, + "--runroot", config.StorageConfig.RunRoot, + "--log-level", logrus.GetLevel().String(), + "--cgroup-manager", config.CgroupManager, + "--tmpdir", config.TmpDir, + } + if config.OCIRuntime != "" { + command = append(command, []string{"--runtime", config.OCIRuntime}...) + } + if config.StorageConfig.GraphDriverName != "" { + command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) + } + for _, opt := range config.StorageConfig.GraphDriverOptions { + command = append(command, []string{"--storage-opt", opt}...) + } + if config.EventsLogger != "" { + command = append(command, []string{"--events-backend", config.EventsLogger}...) + } + + // TODO Mheon wants to leave this for now + //if s.sys { + // command = append(command, "--syslog", "true") + //} + command = append(command, []string{"container", "cleanup"}...) + + if s.Remove { + command = append(command, "--rm") + } + return libpod.WithExitCommand(command) +} diff --git a/pkg/specgen/container_validate.go b/pkg/specgen/container_validate.go new file mode 100644 index 000000000..b27659f5f --- /dev/null +++ b/pkg/specgen/container_validate.go @@ -0,0 +1,167 @@ +package specgen + +import ( + "strings" + + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" +) + +var ( + // ErrInvalidSpecConfig describes an error that the given SpecGenerator is invalid + ErrInvalidSpecConfig error = errors.New("invalid configuration") + // SystemDValues describes the only values that SystemD can be + SystemDValues = []string{"true", "false", "always"} + // ImageVolumeModeValues describes the only values that ImageVolumeMode can be + ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"} +) + +func exclusiveOptions(opt1, opt2 string) error { + return errors.Errorf("%s and %s are mutually exclusive options", opt1, opt2) +} + +// Validate verifies that the given SpecGenerator is valid and satisfies required +// input for creating a container. +func (s *SpecGenerator) validate() error { + + // + // ContainerBasicConfig + // + // Rootfs and Image cannot both populated + if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { + return errors.Wrap(ErrInvalidSpecConfig, "both image and rootfs cannot be simultaneously") + } + // Cannot set hostname and utsns + if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { + return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace") + } + // systemd values must be true, false, or always + if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) { + return errors.Wrapf(ErrInvalidSpecConfig, "SystemD values must be one of %s", strings.Join(SystemDValues, ",")) + } + + // + // ContainerStorageConfig + // + // rootfs and image cannot both be set + if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { + return exclusiveOptions("rootfs", "image") + } + // imagevolumemode must be one of ignore, tmpfs, or anonymous if given + if len(s.ContainerStorageConfig.ImageVolumeMode) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerStorageConfig.ImageVolumeMode), ImageVolumeModeValues) { + return errors.Errorf("ImageVolumeMode values must be one of %s", strings.Join(ImageVolumeModeValues, ",")) + } + // shmsize conflicts with IPC namespace + if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() { + return errors.New("cannot set shmsize when creating an IPC namespace") + } + + // + // ContainerSecurityConfig + // + // groups and privileged are exclusive + if len(s.Groups) > 0 && s.Privileged { + return exclusiveOptions("Groups", "privileged") + } + // capadd and privileged are exclusive + if len(s.CapAdd) > 0 && s.Privileged { + return exclusiveOptions("CapAdd", "privileged") + } + // selinuxprocesslabel and privileged are exclusive + if len(s.SelinuxProcessLabel) > 0 && s.Privileged { + return exclusiveOptions("SelinuxProcessLabel", "privileged") + } + // selinuxmounmtlabel and privileged are exclusive + if len(s.SelinuxMountLabel) > 0 && s.Privileged { + return exclusiveOptions("SelinuxMountLabel", "privileged") + } + // selinuxopts and privileged are exclusive + if len(s.SelinuxOpts) > 0 && s.Privileged { + return exclusiveOptions("SelinuxOpts", "privileged") + } + // apparmor and privileged are exclusive + if len(s.ApparmorProfile) > 0 && s.Privileged { + return exclusiveOptions("AppArmorProfile", "privileged") + } + // userns and idmappings conflict + if s.UserNS.IsPrivate() && s.IDMappings == nil { + return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace") + } + + // + // ContainerCgroupConfig + // + // + // None for now + + // + // ContainerNetworkConfig + // + if !s.NetNS.IsPrivate() && s.ConfigureNetNS { + return errors.New("can only configure network namespace when creating a network a network namespace") + } + // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption + if s.UseImageResolvConf { + if len(s.DNSServer) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSServer") + } + if len(s.DNSSearch) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSSearch") + } + if len(s.DNSOption) > 0 { + return exclusiveOptions("UseImageResolvConf", "DNSOption") + } + } + // UseImageHosts and HostAdd are exclusive + if s.UseImageHosts && len(s.HostAdd) > 0 { + return exclusiveOptions("UseImageHosts", "HostAdd") + } + + // TODO the specgen does not appear to handle this? Should it + //switch config.Cgroup.Cgroups { + //case "disabled": + // if addedResources { + // return errors.New("cannot specify resource limits when cgroups are disabled is specified") + // } + // configSpec.Linux.Resources = &spec.LinuxResources{} + //case "enabled", "no-conmon", "": + // // Do nothing + //default: + // return errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'") + //} + + // Namespaces + if err := s.UtsNS.validate(); err != nil { + return err + } + if err := s.IpcNS.validate(); err != nil { + return err + } + if err := s.PidNS.validate(); err != nil { + return err + } + if err := s.CgroupNS.validate(); err != nil { + return err + } + if err := s.UserNS.validate(); err != nil { + return err + } + + // The following are defaults as needed by container creation + if len(s.WorkDir) < 1 { + s.WorkDir = "/" + } + + // Set defaults if network info is not provided + if s.NetNS.NSMode == "" { + s.NetNS.NSMode = Bridge + if rootless.IsRootless() { + s.NetNS.NSMode = Slirp + } + } + if err := validateNetNS(&s.NetNS); err != nil { + return err + } + return nil +} diff --git a/pkg/specgen/create.go b/pkg/specgen/create.go deleted file mode 100644 index aefbe7405..000000000 --- a/pkg/specgen/create.go +++ /dev/null @@ -1,188 +0,0 @@ -package specgen - -import ( - "context" - "os" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/libpod/config" - "github.com/containers/libpod/libpod/define" - "github.com/pkg/errors" - "github.com/sirupsen/logrus" -) - -// MakeContainer creates a container based on the SpecGenerator -func (s *SpecGenerator) MakeContainer(rt *libpod.Runtime) (*libpod.Container, error) { - if err := s.validate(rt); err != nil { - return nil, errors.Wrap(err, "invalid config provided") - } - rtc, err := rt.GetConfig() - if err != nil { - return nil, err - } - - options, err := s.createContainerOptions(rt) - if err != nil { - return nil, err - } - - podmanPath, err := os.Executable() - if err != nil { - return nil, err - } - options = append(options, s.createExitCommandOption(rtc, podmanPath)) - newImage, err := rt.ImageRuntime().NewFromLocal(s.Image) - if err != nil { - return nil, err - } - - options = append(options, libpod.WithRootFSFromImage(newImage.ID(), s.Image, s.RawImageName)) - - runtimeSpec, err := s.toOCISpec(rt, newImage) - if err != nil { - return nil, err - } - return rt.NewContainer(context.Background(), runtimeSpec, options...) -} - -func (s *SpecGenerator) createContainerOptions(rt *libpod.Runtime) ([]libpod.CtrCreateOption, error) { - var options []libpod.CtrCreateOption - var err error - - if s.Stdin { - options = append(options, libpod.WithStdin()) - } - if len(s.Systemd) > 0 { - options = append(options, libpod.WithSystemd()) - } - if len(s.Name) > 0 { - logrus.Debugf("setting container name %s", s.Name) - options = append(options, libpod.WithName(s.Name)) - } - if s.Pod != "" { - pod, err := rt.LookupPod(s.Pod) - if err != nil { - return nil, err - } - logrus.Debugf("adding container to pod %s", s.Pod) - options = append(options, rt.WithPod(pod)) - } - destinations := []string{} - // // Take all mount and named volume destinations. - for _, mount := range s.Mounts { - destinations = append(destinations, mount.Destination) - } - for _, volume := range s.Volumes { - destinations = append(destinations, volume.Dest) - } - options = append(options, libpod.WithUserVolumes(destinations)) - - if len(s.Volumes) != 0 { - options = append(options, libpod.WithNamedVolumes(s.Volumes)) - } - - if len(s.Command) != 0 { - options = append(options, libpod.WithCommand(s.Command)) - } - - options = append(options, libpod.WithEntrypoint(s.Entrypoint)) - if s.StopSignal != nil { - options = append(options, libpod.WithStopSignal(*s.StopSignal)) - } - if s.StopTimeout != nil { - options = append(options, libpod.WithStopTimeout(*s.StopTimeout)) - } - if s.LogConfiguration != nil { - if len(s.LogConfiguration.Path) > 0 { - options = append(options, libpod.WithLogPath(s.LogConfiguration.Path)) - } - if len(s.LogConfiguration.Options) > 0 && s.LogConfiguration.Options["tag"] != "" { - // Note: I'm really guessing here. - options = append(options, libpod.WithLogTag(s.LogConfiguration.Options["tag"])) - } - - if len(s.LogConfiguration.Driver) > 0 { - options = append(options, libpod.WithLogDriver(s.LogConfiguration.Driver)) - } - } - - // Security options - if len(s.SelinuxOpts) > 0 { - options = append(options, libpod.WithSecLabels(s.SelinuxOpts)) - } - options = append(options, libpod.WithPrivileged(s.Privileged)) - - // Get namespace related options - namespaceOptions, err := s.generateNamespaceContainerOpts(rt) - if err != nil { - return nil, err - } - options = append(options, namespaceOptions...) - - if len(s.ConmonPidFile) > 0 { - options = append(options, libpod.WithConmonPidFile(s.ConmonPidFile)) - } - options = append(options, libpod.WithLabels(s.Labels)) - if s.ShmSize != nil { - options = append(options, libpod.WithShmSize(*s.ShmSize)) - } - if s.Rootfs != "" { - options = append(options, libpod.WithRootFS(s.Rootfs)) - } - // Default used if not overridden on command line - - if s.RestartPolicy != "" { - if s.RestartPolicy == "unless-stopped" { - return nil, errors.Wrapf(define.ErrInvalidArg, "the unless-stopped restart policy is not supported") - } - if s.RestartRetries != nil { - options = append(options, libpod.WithRestartRetries(*s.RestartRetries)) - } - options = append(options, libpod.WithRestartPolicy(s.RestartPolicy)) - } - - if s.ContainerHealthCheckConfig.HealthConfig != nil { - options = append(options, libpod.WithHealthCheck(s.ContainerHealthCheckConfig.HealthConfig)) - logrus.Debugf("New container has a health check") - } - return options, nil -} - -func (s *SpecGenerator) createExitCommandOption(config *config.Config, podmanPath string) libpod.CtrCreateOption { - // We need a cleanup process for containers in the current model. - // But we can't assume that the caller is Podman - it could be another - // user of the API. - // As such, provide a way to specify a path to Podman, so we can - // still invoke a cleanup process. - - command := []string{podmanPath, - "--root", config.StorageConfig.GraphRoot, - "--runroot", config.StorageConfig.RunRoot, - "--log-level", logrus.GetLevel().String(), - "--cgroup-manager", config.CgroupManager, - "--tmpdir", config.TmpDir, - } - if config.OCIRuntime != "" { - command = append(command, []string{"--runtime", config.OCIRuntime}...) - } - if config.StorageConfig.GraphDriverName != "" { - command = append(command, []string{"--storage-driver", config.StorageConfig.GraphDriverName}...) - } - for _, opt := range config.StorageConfig.GraphDriverOptions { - command = append(command, []string{"--storage-opt", opt}...) - } - if config.EventsLogger != "" { - command = append(command, []string{"--events-backend", config.EventsLogger}...) - } - - // TODO Mheon wants to leave this for now - //if s.sys { - // command = append(command, "--syslog", "true") - //} - command = append(command, []string{"container", "cleanup"}...) - - if s.Remove { - command = append(command, "--rm") - } - return libpod.WithExitCommand(command) -} diff --git a/pkg/specgen/pod.go b/pkg/specgen/pod.go deleted file mode 100644 index 1aada83c4..000000000 --- a/pkg/specgen/pod.go +++ /dev/null @@ -1,140 +0,0 @@ -package specgen - -import ( - "net" - - "github.com/cri-o/ocicni/pkg/ocicni" -) - -// PodBasicConfig contains basic configuration options for pods. -type PodBasicConfig struct { - // Name is the name of the pod. - // If not provided, a name will be generated when the pod is created. - // Optional. - Name string `json:"name,omitempty"` - // Hostname is the pod's hostname. If not set, the name of the pod will - // be used (if a name was not provided here, the name auto-generated for - // the pod will be used). This will be used by the infra container and - // all containers in the pod as long as the UTS namespace is shared. - // Optional. - Hostname string `json:"hostname,omitempty"` - // Labels are key-value pairs that are used to add metadata to pods. - // Optional. - Labels map[string]string `json:"labels,omitempty"` - // NoInfra tells the pod not to create an infra container. If this is - // done, many networking-related options will become unavailable. - // Conflicts with setting any options in PodNetworkConfig, and the - // InfraCommand and InfraImages in this struct. - // Optional. - NoInfra bool `json:"no_infra,omitempty"` - // InfraCommand sets the command that will be used to start the infra - // container. - // If not set, the default set in the Libpod configuration file will be - // used. - // Conflicts with NoInfra=true. - // Optional. - InfraCommand []string `json:"infra_command,omitempty"` - // InfraImage is the image that will be used for the infra container. - // If not set, the default set in the Libpod configuration file will be - // used. - // Conflicts with NoInfra=true. - // Optional. - InfraImage string `json:"infra_image,omitempty"` - // SharedNamespaces instructs the pod to share a set of namespaces. - // Shared namespaces will be joined (by default) by every container - // which joins the pod. - // If not set and NoInfra is false, the pod will set a default set of - // namespaces to share. - // Conflicts with NoInfra=true. - // Optional. - SharedNamespaces []string `json:"shared_namespaces,omitempty"` -} - -// PodNetworkConfig contains networking configuration for a pod. -type PodNetworkConfig struct { - // NetNS is the configuration to use for the infra container's network - // namespace. This network will, by default, be shared with all - // containers in the pod. - // Cannot be set to FromContainer and FromPod. - // Setting this to anything except "" conflicts with NoInfra=true. - // Defaults to Bridge as root and Slirp as rootless. - // Mandatory. - NetNS Namespace `json:"netns,omitempty"` - // StaticIP sets a static IP for the infra container. As the infra - // container's network is used for the entire pod by default, this will - // thus be a static IP for the whole pod. - // Only available if NetNS is set to Bridge (the default for root). - // As such, conflicts with NoInfra=true by proxy. - // Optional. - StaticIP *net.IP `json:"static_ip,omitempty"` - // StaticMAC sets a static MAC for the infra container. As the infra - // container's network is used for the entire pod by default, this will - // thus be a static MAC for the entire pod. - // Only available if NetNS is set to Bridge (the default for root). - // As such, conflicts with NoInfra=true by proxy. - // Optional. - StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"` - // PortMappings is a set of ports to map into the infra container. - // As, by default, containers share their network with the infra - // container, this will forward the ports to the entire pod. - // Only available if NetNS is set to Bridge or Slirp. - // Optional. - PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` - // CNINetworks is a list of CNI networks that the infra container will - // join. As, by default, containers share their network with the infra - // container, these networks will effectively be joined by the - // entire pod. - // Only available when NetNS is set to Bridge, the default for root. - // Optional. - CNINetworks []string `json:"cni_networks,omitempty"` - // NoManageResolvConf indicates that /etc/resolv.conf should not be - // managed by the pod. Instead, each container will create and manage a - // separate resolv.conf as if they had not joined a pod. - // Conflicts with NoInfra=true and DNSServer, DNSSearch, DNSOption. - // Optional. - NoManageResolvConf bool `json:"no_manage_resolv_conf,omitempty"` - // DNSServer is a set of DNS servers that will be used in the infra - // container's resolv.conf, which will, by default, be shared with all - // containers in the pod. - // If not provided, the host's DNS servers will be used, unless the only - // server set is a localhost address. As the container cannot connect to - // the host's localhost, a default server will instead be set. - // Conflicts with NoInfra=true. - // Optional. - DNSServer []net.IP `json:"dns_server,omitempty"` - // DNSSearch is a set of DNS search domains that will be used in the - // infra container's resolv.conf, which will, by default, be shared with - // all containers in the pod. - // If not provided, DNS search domains from the host's resolv.conf will - // be used. - // Conflicts with NoInfra=true. - // Optional. - DNSSearch []string `json:"dns_search,omitempty"` - // DNSOption is a set of DNS options that will be used in the infra - // container's resolv.conf, which will, by default, be shared with all - // containers in the pod. - // Conflicts with NoInfra=true. - // Optional. - DNSOption []string `json:"dns_option,omitempty"` - // NoManageHosts indicates that /etc/hosts should not be managed by the - // pod. Instead, each container will create a separate /etc/hosts as - // they would if not in a pod. - // Conflicts with HostAdd. - NoManageHosts bool `json:"no_manage_hosts,omitempty"` - // HostAdd is a set of hosts that will be added to the infra container's - // /etc/hosts that will, by default, be shared with all containers in - // the pod. - // Conflicts with NoInfra=true and NoManageHosts. - // Optional. - HostAdd []string `json:"hostadd,omitempty"` -} - -// PodCgroupConfig contains configuration options about a pod's cgroups. -// This will be expanded in future updates to pods. -type PodCgroupConfig struct { - // CgroupParent is the parent for the CGroup that the pod will create. - // This pod cgroup will, in turn, be the default cgroup parent for all - // containers in the pod. - // Optional. - CgroupParent string `json:"cgroup_parent,omitempty"` -} diff --git a/pkg/specgen/pod_create.go b/pkg/specgen/pod_create.go new file mode 100644 index 000000000..06aa24e22 --- /dev/null +++ b/pkg/specgen/pod_create.go @@ -0,0 +1,83 @@ +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 new file mode 100644 index 000000000..50309f096 --- /dev/null +++ b/pkg/specgen/pod_validate.go @@ -0,0 +1,104 @@ +package specgen + +import ( + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/pkg/rootless" + "github.com/pkg/errors" +) + +var ( + // ErrInvalidPodSpecConfig describes an error given when the podspecgenerator is invalid + ErrInvalidPodSpecConfig error = errors.New("invalid pod spec") +) + +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 { + // PodBasicConfig + if p.NoInfra { + if len(p.InfraCommand) > 0 { + return exclusivePodOptions("NoInfra", "InfraCommand") + } + if len(p.InfraImage) > 0 { + return exclusivePodOptions("NoInfra", "InfraImage") + } + if len(p.SharedNamespaces) > 0 { + return exclusivePodOptions("NoInfo", "SharedNamespaces") + } + } + + // PodNetworkConfig + if err := p.NetNS.validate(); err != nil { + return err + } + if p.NoInfra { + if p.NetNS.NSMode == NoNetwork { + return errors.New("NoInfra and a none network cannot be used toegther") + } + if p.StaticIP != nil { + return exclusivePodOptions("NoInfra", "StaticIP") + } + if p.StaticMAC != nil { + return exclusivePodOptions("NoInfra", "StaticMAC") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoInfra", "DNSOption") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoInfo", "DNSSearch") + } + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoInfra", "DNSServer") + } + if len(p.HostAdd) > 0 { + return exclusivePodOptions("NoInfra", "HostAdd") + } + if p.NoManageResolvConf { + return exclusivePodOptions("NoInfra", "NoManageResolvConf") + } + } + if p.NetNS.NSMode != Bridge { + if len(p.PortMappings) > 0 { + return errors.New("PortMappings can only be used with Bridge mode networking") + } + if len(p.CNINetworks) > 0 { + return errors.New("CNINetworks can only be used with Bridge mode networking") + } + } + if p.NoManageResolvConf { + if len(p.DNSServer) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSServer") + } + if len(p.DNSSearch) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSSearch") + } + if len(p.DNSOption) > 0 { + return exclusivePodOptions("NoManageResolvConf", "DNSOption") + } + } + if p.NoManageHosts && len(p.HostAdd) > 0 { + return exclusivePodOptions("NoManageHosts", "HostAdd") + } + + if err := p.NetNS.validate(); err != nil { + return err + } + + // Set Defaults + if p.NetNS.Value == "" { + if rootless.IsRootless() { + p.NetNS.NSMode = Slirp + } else { + p.NetNS.NSMode = Bridge + } + } + if len(p.InfraImage) < 1 { + p.InfraImage = define.DefaultInfraImage + } + if len(p.InfraCommand) < 1 { + p.InfraCommand = []string{define.DefaultInfraCommand} + } + return nil +} diff --git a/pkg/specgen/podspecgen.go b/pkg/specgen/podspecgen.go new file mode 100644 index 000000000..3f830014d --- /dev/null +++ b/pkg/specgen/podspecgen.go @@ -0,0 +1,153 @@ +package specgen + +import ( + "net" + + "github.com/cri-o/ocicni/pkg/ocicni" +) + +// PodBasicConfig contains basic configuration options for pods. +type PodBasicConfig struct { + // Name is the name of the pod. + // If not provided, a name will be generated when the pod is created. + // Optional. + Name string `json:"name,omitempty"` + // Hostname is the pod's hostname. If not set, the name of the pod will + // be used (if a name was not provided here, the name auto-generated for + // the pod will be used). This will be used by the infra container and + // all containers in the pod as long as the UTS namespace is shared. + // Optional. + Hostname string `json:"hostname,omitempty"` + // Labels are key-value pairs that are used to add metadata to pods. + // Optional. + Labels map[string]string `json:"labels,omitempty"` + // NoInfra tells the pod not to create an infra container. If this is + // done, many networking-related options will become unavailable. + // Conflicts with setting any options in PodNetworkConfig, and the + // InfraCommand and InfraImages in this struct. + // Optional. + NoInfra bool `json:"no_infra,omitempty"` + // InfraCommand sets the command that will be used to start the infra + // container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraCommand []string `json:"infra_command,omitempty"` + // InfraImage is the image that will be used for the infra container. + // If not set, the default set in the Libpod configuration file will be + // used. + // Conflicts with NoInfra=true. + // Optional. + InfraImage string `json:"infra_image,omitempty"` + // SharedNamespaces instructs the pod to share a set of namespaces. + // Shared namespaces will be joined (by default) by every container + // which joins the pod. + // If not set and NoInfra is false, the pod will set a default set of + // namespaces to share. + // Conflicts with NoInfra=true. + // Optional. + SharedNamespaces []string `json:"shared_namespaces,omitempty"` +} + +// PodNetworkConfig contains networking configuration for a pod. +type PodNetworkConfig struct { + // NetNS is the configuration to use for the infra container's network + // namespace. This network will, by default, be shared with all + // containers in the pod. + // Cannot be set to FromContainer and FromPod. + // Setting this to anything except "" conflicts with NoInfra=true. + // Defaults to Bridge as root and Slirp as rootless. + // Mandatory. + NetNS Namespace `json:"netns,omitempty"` + // StaticIP sets a static IP for the infra container. As the infra + // container's network is used for the entire pod by default, this will + // thus be a static IP for the whole pod. + // Only available if NetNS is set to Bridge (the default for root). + // As such, conflicts with NoInfra=true by proxy. + // Optional. + StaticIP *net.IP `json:"static_ip,omitempty"` + // StaticMAC sets a static MAC for the infra container. As the infra + // container's network is used for the entire pod by default, this will + // thus be a static MAC for the entire pod. + // Only available if NetNS is set to Bridge (the default for root). + // As such, conflicts with NoInfra=true by proxy. + // Optional. + StaticMAC *net.HardwareAddr `json:"static_mac,omitempty"` + // PortMappings is a set of ports to map into the infra container. + // As, by default, containers share their network with the infra + // container, this will forward the ports to the entire pod. + // Only available if NetNS is set to Bridge or Slirp. + // Optional. + PortMappings []ocicni.PortMapping `json:"portmappings,omitempty"` + // CNINetworks is a list of CNI networks that the infra container will + // join. As, by default, containers share their network with the infra + // container, these networks will effectively be joined by the + // entire pod. + // Only available when NetNS is set to Bridge, the default for root. + // Optional. + CNINetworks []string `json:"cni_networks,omitempty"` + // NoManageResolvConf indicates that /etc/resolv.conf should not be + // managed by the pod. Instead, each container will create and manage a + // separate resolv.conf as if they had not joined a pod. + // Conflicts with NoInfra=true and DNSServer, DNSSearch, DNSOption. + // Optional. + NoManageResolvConf bool `json:"no_manage_resolv_conf,omitempty"` + // DNSServer is a set of DNS servers that will be used in the infra + // container's resolv.conf, which will, by default, be shared with all + // containers in the pod. + // If not provided, the host's DNS servers will be used, unless the only + // server set is a localhost address. As the container cannot connect to + // the host's localhost, a default server will instead be set. + // Conflicts with NoInfra=true. + // Optional. + DNSServer []net.IP `json:"dns_server,omitempty"` + // DNSSearch is a set of DNS search domains that will be used in the + // infra container's resolv.conf, which will, by default, be shared with + // all containers in the pod. + // If not provided, DNS search domains from the host's resolv.conf will + // be used. + // Conflicts with NoInfra=true. + // Optional. + DNSSearch []string `json:"dns_search,omitempty"` + // DNSOption is a set of DNS options that will be used in the infra + // container's resolv.conf, which will, by default, be shared with all + // containers in the pod. + // Conflicts with NoInfra=true. + // Optional. + DNSOption []string `json:"dns_option,omitempty"` + // NoManageHosts indicates that /etc/hosts should not be managed by the + // pod. Instead, each container will create a separate /etc/hosts as + // they would if not in a pod. + // Conflicts with HostAdd. + NoManageHosts bool `json:"no_manage_hosts,omitempty"` + // HostAdd is a set of hosts that will be added to the infra container's + // /etc/hosts that will, by default, be shared with all containers in + // the pod. + // Conflicts with NoInfra=true and NoManageHosts. + // Optional. + HostAdd []string `json:"hostadd,omitempty"` +} + +// PodCgroupConfig contains configuration options about a pod's cgroups. +// This will be expanded in future updates to pods. +type PodCgroupConfig struct { + // CgroupParent is the parent for the CGroup that the pod will create. + // This pod cgroup will, in turn, be the default cgroup parent for all + // containers in the pod. + // Optional. + CgroupParent string `json:"cgroup_parent,omitempty"` +} + +// PodSpecGenerator describes options to create a pod +// swagger:model PodSpecGenerator +type PodSpecGenerator struct { + PodBasicConfig + PodNetworkConfig + PodCgroupConfig +} + +// NewPodSpecGenerator creates a new pod spec +func NewPodSpecGenerator() *PodSpecGenerator { + return &PodSpecGenerator{} +} diff --git a/pkg/specgen/specgen.go b/pkg/specgen/specgen.go index b123c1da5..89c76c273 100644 --- a/pkg/specgen/specgen.go +++ b/pkg/specgen/specgen.go @@ -394,18 +394,18 @@ type SpecGenerator struct { // NewSpecGenerator returns a SpecGenerator struct given one of two mandatory inputs func NewSpecGenerator(image string) *SpecGenerator { - net := ContainerNetworkConfig{ + networkConfig := ContainerNetworkConfig{ NetNS: Namespace{ NSMode: Bridge, }, } csc := ContainerStorageConfig{Image: image} if rootless.IsRootless() { - net.NetNS.NSMode = Slirp + networkConfig.NetNS.NSMode = Slirp } return &SpecGenerator{ ContainerStorageConfig: csc, - ContainerNetworkConfig: net, + ContainerNetworkConfig: networkConfig, } } diff --git a/pkg/specgen/validate.go b/pkg/specgen/validate.go deleted file mode 100644 index 5f567f725..000000000 --- a/pkg/specgen/validate.go +++ /dev/null @@ -1,169 +0,0 @@ -package specgen - -import ( - "strings" - - "github.com/containers/libpod/pkg/rootless" - - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/util" - "github.com/pkg/errors" -) - -var ( - // ErrInvalidSpecConfig describes an error that the given SpecGenerator is invalid - ErrInvalidSpecConfig error = errors.New("invalid configuration") - // SystemDValues describes the only values that SystemD can be - SystemDValues = []string{"true", "false", "always"} - // ImageVolumeModeValues describes the only values that ImageVolumeMode can be - ImageVolumeModeValues = []string{"ignore", "tmpfs", "anonymous"} -) - -func exclusiveOptions(opt1, opt2 string) error { - return errors.Errorf("%s and %s are mutually exclusive options", opt1, opt2) -} - -// Validate verifies that the given SpecGenerator is valid and satisfies required -// input for creating a container. -func (s *SpecGenerator) validate(rt *libpod.Runtime) error { - - // - // ContainerBasicConfig - // - // Rootfs and Image cannot both populated - if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { - return errors.Wrap(ErrInvalidSpecConfig, "both image and rootfs cannot be simultaneously") - } - // Cannot set hostname and utsns - if len(s.ContainerBasicConfig.Hostname) > 0 && !s.ContainerBasicConfig.UtsNS.IsPrivate() { - return errors.Wrap(ErrInvalidSpecConfig, "cannot set hostname when creating an UTS namespace") - } - // systemd values must be true, false, or always - if len(s.ContainerBasicConfig.Systemd) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerBasicConfig.Systemd), SystemDValues) { - return errors.Wrapf(ErrInvalidSpecConfig, "SystemD values must be one of %s", strings.Join(SystemDValues, ",")) - } - - // - // ContainerStorageConfig - // - // rootfs and image cannot both be set - if len(s.ContainerStorageConfig.Image) > 0 && len(s.ContainerStorageConfig.Rootfs) > 0 { - return exclusiveOptions("rootfs", "image") - } - // imagevolumemode must be one of ignore, tmpfs, or anonymous if given - if len(s.ContainerStorageConfig.ImageVolumeMode) > 0 && !util.StringInSlice(strings.ToLower(s.ContainerStorageConfig.ImageVolumeMode), ImageVolumeModeValues) { - return errors.Errorf("ImageVolumeMode values must be one of %s", strings.Join(ImageVolumeModeValues, ",")) - } - // shmsize conflicts with IPC namespace - if s.ContainerStorageConfig.ShmSize != nil && !s.ContainerStorageConfig.IpcNS.IsPrivate() { - return errors.New("cannot set shmsize when creating an IPC namespace") - } - - // - // ContainerSecurityConfig - // - // groups and privileged are exclusive - if len(s.Groups) > 0 && s.Privileged { - return exclusiveOptions("Groups", "privileged") - } - // capadd and privileged are exclusive - if len(s.CapAdd) > 0 && s.Privileged { - return exclusiveOptions("CapAdd", "privileged") - } - // selinuxprocesslabel and privileged are exclusive - if len(s.SelinuxProcessLabel) > 0 && s.Privileged { - return exclusiveOptions("SelinuxProcessLabel", "privileged") - } - // selinuxmounmtlabel and privileged are exclusive - if len(s.SelinuxMountLabel) > 0 && s.Privileged { - return exclusiveOptions("SelinuxMountLabel", "privileged") - } - // selinuxopts and privileged are exclusive - if len(s.SelinuxOpts) > 0 && s.Privileged { - return exclusiveOptions("SelinuxOpts", "privileged") - } - // apparmor and privileged are exclusive - if len(s.ApparmorProfile) > 0 && s.Privileged { - return exclusiveOptions("AppArmorProfile", "privileged") - } - // userns and idmappings conflict - if s.UserNS.IsPrivate() && s.IDMappings == nil { - return errors.Wrap(ErrInvalidSpecConfig, "IDMappings are required when not creating a User namespace") - } - - // - // ContainerCgroupConfig - // - // - // None for now - - // - // ContainerNetworkConfig - // - if !s.NetNS.IsPrivate() && s.ConfigureNetNS { - return errors.New("can only configure network namespace when creating a network a network namespace") - } - // useimageresolveconf conflicts with dnsserver, dnssearch, dnsoption - if s.UseImageResolvConf { - if len(s.DNSServer) > 0 { - return exclusiveOptions("UseImageResolvConf", "DNSServer") - } - if len(s.DNSSearch) > 0 { - return exclusiveOptions("UseImageResolvConf", "DNSSearch") - } - if len(s.DNSOption) > 0 { - return exclusiveOptions("UseImageResolvConf", "DNSOption") - } - } - // UseImageHosts and HostAdd are exclusive - if s.UseImageHosts && len(s.HostAdd) > 0 { - return exclusiveOptions("UseImageHosts", "HostAdd") - } - - // TODO the specgen does not appear to handle this? Should it - //switch config.Cgroup.Cgroups { - //case "disabled": - // if addedResources { - // return errors.New("cannot specify resource limits when cgroups are disabled is specified") - // } - // configSpec.Linux.Resources = &spec.LinuxResources{} - //case "enabled", "no-conmon", "": - // // Do nothing - //default: - // return errors.New("unrecognized option for cgroups; supported are 'default', 'disabled', 'no-conmon'") - //} - - // Namespaces - if err := s.UtsNS.validate(); err != nil { - return err - } - if err := s.IpcNS.validate(); err != nil { - return err - } - if err := s.PidNS.validate(); err != nil { - return err - } - if err := s.CgroupNS.validate(); err != nil { - return err - } - if err := s.UserNS.validate(); err != nil { - return err - } - - // The following are defaults as needed by container creation - if len(s.WorkDir) < 1 { - s.WorkDir = "/" - } - - // Set defaults if network info is not provided - if s.NetNS.NSMode == "" { - s.NetNS.NSMode = Bridge - if rootless.IsRootless() { - s.NetNS.NSMode = Slirp - } - } - if err := validateNetNS(&s.NetNS); err != nil { - return err - } - return nil -} -- cgit v1.2.3-54-g00ecf