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/adapter/runtime.go | 8 +- pkg/api/handlers/libpod/pods.go | 74 +------------ pkg/api/handlers/libpod/volumes.go | 9 +- pkg/api/handlers/types.go | 13 --- pkg/api/server/register_pods.go | 2 +- pkg/bindings/pods/pods.go | 24 +++- pkg/bindings/test/pods_test.go | 12 ++ pkg/domain/entities/engine_container.go | 2 +- pkg/domain/entities/pods.go | 51 ++++++++- pkg/domain/entities/types.go | 27 +++++ pkg/domain/infra/abi/pods.go | 11 ++ pkg/domain/infra/abi/volumes.go | 15 ++- pkg/domain/infra/tunnel/pods.go | 7 ++ 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 ---------------------------- pkg/varlinkapi/volumes.go | 10 +- 23 files changed, 864 insertions(+), 599 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') diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 7817a1f98..76e221fae 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -356,11 +356,11 @@ func (r *LocalRuntime) PruneVolumes(ctx context.Context) ([]string, []error) { errs = append(errs, err) return vids, errs } - for _, r := range reports { - if r.Err == nil { - vids = append(vids, r.Id) + for k, v := range reports { + if v == nil { + vids = append(vids, k) } else { - errs = append(errs, r.Err) + errs = append(errs, v) } } return vids, errs diff --git a/pkg/api/handlers/libpod/pods.go b/pkg/api/handlers/libpod/pods.go index 5baf61ac9..7e9c2e2c0 100644 --- a/pkg/api/handlers/libpod/pods.go +++ b/pkg/api/handlers/libpod/pods.go @@ -4,15 +4,13 @@ import ( "encoding/json" "fmt" "net/http" - "strings" - "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/shared/parse" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/api/handlers" "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/util" "github.com/gorilla/schema" "github.com/pkg/errors" @@ -21,76 +19,14 @@ import ( func PodCreate(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) - options []libpod.PodCreateOption err error ) - labels := make(map[string]string) - input := handlers.PodCreateConfig{} - if err := json.NewDecoder(r.Body).Decode(&input); err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()")) + var psg specgen.PodSpecGenerator + if err := json.NewDecoder(r.Body).Decode(&psg); err != nil { + utils.Error(w, "Failed to decode specgen", http.StatusInternalServerError, errors.Wrap(err, "failed to decode specgen")) return } - if len(input.InfraCommand) > 0 || len(input.InfraImage) > 0 { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, - errors.New("infra-command and infra-image are not implemented yet")) - return - } - // TODO long term we should break the following out of adapter and into libpod proper - // so that the cli and api can share the creation of a pod with the same options - if len(input.CGroupParent) > 0 { - options = append(options, libpod.WithPodCgroupParent(input.CGroupParent)) - } - - if len(input.Labels) > 0 { - labels, err = parse.GetAllLabels([]string{}, input.Labels) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - } - - if len(labels) != 0 { - options = append(options, libpod.WithPodLabels(labels)) - } - - if len(input.Name) > 0 { - options = append(options, libpod.WithPodName(input.Name)) - } - - if len(input.Hostname) > 0 { - options = append(options, libpod.WithPodHostname(input.Hostname)) - } - - if input.Infra { - // TODO infra-image and infra-command are not supported in the libpod API yet. Will fix - // when implemented in libpod - options = append(options, libpod.WithInfraContainer()) - sharedNamespaces := shared.DefaultKernelNamespaces - if len(input.Share) > 0 { - sharedNamespaces = input.Share - } - nsOptions, err := shared.GetNamespaceOptions(strings.Split(sharedNamespaces, ",")) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, nsOptions...) - } - - if len(input.Publish) > 0 { - portBindings, err := shared.CreatePortBindings(input.Publish) - if err != nil { - utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err) - return - } - options = append(options, libpod.WithInfraContainerPorts(portBindings)) - - } - // always have containers use pod cgroups - // User Opt out is not yet supported - options = append(options, libpod.WithPodCgroups()) - - pod, err := runtime.NewPod(r.Context(), options...) + pod, err := psg.MakePod(runtime) if err != nil { http_code := http.StatusInternalServerError if errors.Cause(err) == define.ErrPodExists { diff --git a/pkg/api/handlers/libpod/volumes.go b/pkg/api/handlers/libpod/volumes.go index e61d272f4..5a6fc021e 100644 --- a/pkg/api/handlers/libpod/volumes.go +++ b/pkg/api/handlers/libpod/volumes.go @@ -149,13 +149,20 @@ func ListVolumes(w http.ResponseWriter, r *http.Request) { func PruneVolumes(w http.ResponseWriter, r *http.Request) { var ( runtime = r.Context().Value("runtime").(*libpod.Runtime) + reports []*entities.VolumePruneReport ) pruned, err := runtime.PruneVolumes(r.Context()) if err != nil { utils.InternalServerError(w, err) return } - utils.WriteResponse(w, http.StatusOK, pruned) + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + utils.WriteResponse(w, http.StatusOK, reports) } func RemoveVolume(w http.ResponseWriter, r *http.Request) { diff --git a/pkg/api/handlers/types.go b/pkg/api/handlers/types.go index fe4198c37..1ca5db3f9 100644 --- a/pkg/api/handlers/types.go +++ b/pkg/api/handlers/types.go @@ -133,19 +133,6 @@ type ContainerTopOKBody struct { dockerContainer.ContainerTopOKBody } -// swagger:model PodCreateConfig -type PodCreateConfig struct { - Name string `json:"name"` - CGroupParent string `json:"cgroup-parent"` - Hostname string `json:"hostname"` - Infra bool `json:"infra"` - InfraCommand string `json:"infra-command"` - InfraImage string `json:"infra-image"` - Labels []string `json:"labels"` - Publish []string `json:"publish"` - Share string `json:"share"` -} - type ErrorModel struct { Message string `json:"message"` } diff --git a/pkg/api/server/register_pods.go b/pkg/api/server/register_pods.go index 87194fdd7..5ba2263e8 100644 --- a/pkg/api/server/register_pods.go +++ b/pkg/api/server/register_pods.go @@ -37,7 +37,7 @@ func (s *APIServer) registerPodsHandlers(r *mux.Router) error { // description: attributes for creating a pod // schema: // type: object - // $ref: "#/definitions/PodCreateConfig" + // $ref: "#/definitions/PodSpecGenerator" // responses: // 200: // $ref: "#/definitions/IdResponse" diff --git a/pkg/bindings/pods/pods.go b/pkg/bindings/pods/pods.go index 49cce6e2b..bb0abebc4 100644 --- a/pkg/bindings/pods/pods.go +++ b/pkg/bindings/pods/pods.go @@ -5,15 +5,33 @@ import ( "net/http" "net/url" "strconv" + "strings" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" + jsoniter "github.com/json-iterator/go" ) -func CreatePod() error { - // TODO - return bindings.ErrNotImplemented +func CreatePodFromSpec(ctx context.Context, s *specgen.PodSpecGenerator) (*entities.PodCreateReport, error) { + var ( + pcr entities.PodCreateReport + ) + conn, err := bindings.GetClient(ctx) + if err != nil { + return nil, err + } + specgenString, err := jsoniter.MarshalToString(s) + if err != nil { + return nil, err + } + stringReader := strings.NewReader(specgenString) + response, err := conn.DoRequest(stringReader, http.MethodPost, "/pods/create", nil) + if err != nil { + return nil, err + } + return &pcr, response.Process(&pcr) } // Exists is a lightweight method to determine if a pod exists in local storage diff --git a/pkg/bindings/test/pods_test.go b/pkg/bindings/test/pods_test.go index c54170648..0f786e341 100644 --- a/pkg/bindings/test/pods_test.go +++ b/pkg/bindings/test/pods_test.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/bindings" "github.com/containers/libpod/pkg/bindings/pods" + "github.com/containers/libpod/pkg/specgen" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" "github.com/onsi/gomega/gexec" @@ -307,4 +308,15 @@ var _ = Describe("Podman pods", func() { Expect(err).To(BeNil()) Expect(len(podSummary)).To(Equal(0)) }) + + It("simple create pod", func() { + ps := specgen.PodSpecGenerator{} + ps.Name = "foobar" + _, err := pods.CreatePodFromSpec(bt.conn, &ps) + Expect(err).To(BeNil()) + + exists, err := pods.Exists(bt.conn, "foobar") + Expect(err).To(BeNil()) + Expect(exists).To(BeTrue()) + }) }) diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 21a674c43..fceed1003 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -15,7 +15,7 @@ type ContainerEngine interface { ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error) ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error) ContainerTop(ctx context.Context, options TopOptions) (*StringSliceReport, error) - + PodCreate(ctx context.Context, opts PodCreateOptions) (*PodCreateReport, error) PodExists(ctx context.Context, nameOrId string) (*BoolReport, error) PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error) PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error) diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go index 8d594620f..efda17d65 100644 --- a/pkg/domain/entities/pods.go +++ b/pkg/domain/entities/pods.go @@ -1,6 +1,10 @@ package entities -import "time" +import ( + "time" + + "github.com/containers/libpod/pkg/specgen" +) type PodKillOptions struct { All bool @@ -92,3 +96,48 @@ type PodRmReport struct { Err error Id string } + +type PodCreateOptions struct { + CGroupParent string + Hostname string + Infra bool + InfraImage string + InfraCommand string + Labels map[string]string + Name string + Net *NetOptions + Share []string +} + +type PodCreateReport struct { + Id string +} + +func (p PodCreateOptions) ToPodSpecGen(s *specgen.PodSpecGenerator) { + // Basic Config + s.Name = p.Name + s.Hostname = p.Hostname + s.Labels = p.Labels + s.NoInfra = !p.Infra + s.InfraCommand = []string{p.InfraCommand} + s.InfraImage = p.InfraImage + s.SharedNamespaces = p.Share + + // Networking config + s.NetNS = p.Net.Network + s.StaticIP = p.Net.StaticIP + s.StaticMAC = p.Net.StaticMAC + s.PortMappings = p.Net.PublishPorts + s.CNINetworks = p.Net.CNINetworks + if p.Net.DNSHost { + s.NoManageResolvConf = true + } + s.DNSServer = p.Net.DNSServers + s.DNSSearch = p.Net.DNSSearch + s.DNSOption = p.Net.DNSOptions + s.NoManageHosts = p.Net.NoHosts + s.HostAdd = p.Net.AddHosts + + // Cgroup + s.CgroupParent = p.CGroupParent +} diff --git a/pkg/domain/entities/types.go b/pkg/domain/entities/types.go index e7757a74b..a1a729584 100644 --- a/pkg/domain/entities/types.go +++ b/pkg/domain/entities/types.go @@ -1,5 +1,12 @@ package entities +import ( + "net" + + "github.com/containers/libpod/pkg/specgen" + "github.com/cri-o/ocicni/pkg/ocicni" +) + type Container struct { IdOrNamed } @@ -15,3 +22,23 @@ type Report struct { type PodDeleteReport struct{ Report } type PodPruneOptions struct{} + +type PodPruneReport struct{ Report } +type VolumeDeleteOptions struct{} +type VolumeDeleteReport struct{ Report } + +// NetOptions reflect the shared network options between +// pods and containers +type NetOptions struct { + AddHosts []string + CNINetworks []string + DNSHost bool + DNSOptions []string + DNSSearch []string + DNSServers []net.IP + Network specgen.Namespace + NoHosts bool + PublishPorts []ocicni.PortMapping + StaticIP *net.IP + StaticMAC *net.HardwareAddr +} diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go index 4f68ad5f9..619e973cf 100644 --- a/pkg/domain/infra/abi/pods.go +++ b/pkg/domain/infra/abi/pods.go @@ -9,6 +9,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/signal" + "github.com/containers/libpod/pkg/specgen" "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -239,3 +240,13 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio } return reports, nil } + +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) + if err != nil { + return nil, err + } + return &entities.PodCreateReport{Id: pod.ID()}, nil +} diff --git a/pkg/domain/infra/abi/volumes.go b/pkg/domain/infra/abi/volumes.go index 5527bb82e..bdae4359d 100644 --- a/pkg/domain/infra/abi/volumes.go +++ b/pkg/domain/infra/abi/volumes.go @@ -113,7 +113,20 @@ func (ic *ContainerEngine) VolumeInspect(ctx context.Context, namesOrIds []strin } func (ic *ContainerEngine) VolumePrune(ctx context.Context, opts entities.VolumePruneOptions) ([]*entities.VolumePruneReport, error) { - return ic.Libpod.PruneVolumes(ctx) + var ( + reports []*entities.VolumePruneReport + ) + pruned, err := ic.Libpod.PruneVolumes(ctx) + if err != nil { + return nil, err + } + for k, v := range pruned { + reports = append(reports, &entities.VolumePruneReport{ + Err: v, + Id: k, + }) + } + return reports, nil } func (ic *ContainerEngine) VolumeList(ctx context.Context, opts entities.VolumeListOptions) ([]*entities.VolumeListReport, error) { diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go index 53003da42..4894874e5 100644 --- a/pkg/domain/infra/tunnel/pods.go +++ b/pkg/domain/infra/tunnel/pods.go @@ -5,6 +5,7 @@ import ( "github.com/containers/libpod/pkg/bindings/pods" "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/specgen" ) func (ic *ContainerEngine) PodExists(ctx context.Context, nameOrId string) (*entities.BoolReport, error) { @@ -170,3 +171,9 @@ func (ic *ContainerEngine) PodRm(ctx context.Context, namesOrIds []string, optio } return reports, nil } + +func (ic *ContainerEngine) PodCreate(ctx context.Context, opts entities.PodCreateOptions) (*entities.PodCreateReport, error) { + podSpec := specgen.NewPodSpecGenerator() + opts.ToPodSpecGen(podSpec) + return pods.CreatePodFromSpec(ic.ClientCxt, podSpec) +} 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 -} diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index cbb4a70cc..e497cb537 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -6,7 +6,7 @@ import ( "encoding/json" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" ) @@ -113,11 +113,11 @@ func (i *LibpodAPI) VolumesPrune(call iopodman.VarlinkCall) error { if err != nil { return call.ReplyVolumesPrune([]string{}, []string{err.Error()}) } - for _, i := range responses { - if i.Err == nil { - prunedNames = append(prunedNames, i.Id) + for k, v := range responses { + if v == nil { + prunedNames = append(prunedNames, k) } else { - prunedErrors = append(prunedErrors, i.Err.Error()) + prunedErrors = append(prunedErrors, v.Error()) } } return call.ReplyVolumesPrune(prunedNames, prunedErrors) -- cgit v1.2.3-54-g00ecf