diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2020-04-16 14:04:58 -0700 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-04-16 14:04:58 -0700 |
commit | 0d2b5532c417c58bd24e71a56c5c55b43e423a59 (patch) | |
tree | 4001e8e47a022bb1b9bfbf2332c42e1aeb802f9e /pkg/varlinkapi | |
parent | 88c6fd06cd54fb9a8826306dfdf1a77e400de5de (diff) | |
parent | 241326a9a8c20ad7f2bcf651416b836e7778e090 (diff) | |
download | podman-0d2b5532c417c58bd24e71a56c5c55b43e423a59.tar.gz podman-0d2b5532c417c58bd24e71a56c5c55b43e423a59.tar.bz2 podman-0d2b5532c417c58bd24e71a56c5c55b43e423a59.zip |
Merge pull request #5852 from baude/v1prune
Podman V2 birth
Diffstat (limited to 'pkg/varlinkapi')
-rw-r--r-- | pkg/varlinkapi/container.go | 928 | ||||
-rw-r--r-- | pkg/varlinkapi/containers.go | 24 | ||||
-rw-r--r-- | pkg/varlinkapi/containers_create.go | 5 | ||||
-rw-r--r-- | pkg/varlinkapi/create.go | 1154 | ||||
-rw-r--r-- | pkg/varlinkapi/funcs.go | 121 | ||||
-rw-r--r-- | pkg/varlinkapi/generate.go | 3 | ||||
-rw-r--r-- | pkg/varlinkapi/images.go | 5 | ||||
-rw-r--r-- | pkg/varlinkapi/intermediate.go | 289 | ||||
-rw-r--r-- | pkg/varlinkapi/intermediate_varlink.go | 457 | ||||
-rw-r--r-- | pkg/varlinkapi/pods.go | 56 | ||||
-rw-r--r-- | pkg/varlinkapi/shortcuts.go | 66 | ||||
-rw-r--r-- | pkg/varlinkapi/util.go | 17 | ||||
-rw-r--r-- | pkg/varlinkapi/volumes.go | 47 |
13 files changed, 3129 insertions, 43 deletions
diff --git a/pkg/varlinkapi/container.go b/pkg/varlinkapi/container.go new file mode 100644 index 000000000..eae54dfeb --- /dev/null +++ b/pkg/varlinkapi/container.go @@ -0,0 +1,928 @@ +package varlinkapi + +import ( + "context" + "fmt" + "io" + "os" + "path/filepath" + "regexp" + "runtime" + "sort" + "strconv" + "strings" + "sync" + "time" + + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/pkg/timetype" + "github.com/containers/libpod/pkg/util" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-units" + "github.com/google/shlex" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + v1 "k8s.io/api/core/v1" +) + +const ( + cidTruncLength = 12 + podTruncLength = 12 + iidTruncLength = 12 + cmdTruncLength = 17 +) + +// PsOptions describes the struct being formed for ps. +type PsOptions struct { + All bool + Format string + Last int + Latest bool + NoTrunc bool + Pod bool + Quiet bool + Size bool + Sort string + Namespace bool + Sync bool +} + +// BatchContainerStruct is the return object from BatchContainer and contains +// container related information. +type BatchContainerStruct struct { + ConConfig *libpod.ContainerConfig + ConState define.ContainerStatus + ExitCode int32 + Exited bool + Pid int + StartedTime time.Time + ExitedTime time.Time + Size *ContainerSize +} + +// PsContainerOutput is the struct being returned from a parallel +// batch operation. +type PsContainerOutput struct { + ID string + Image string + ImageID string + Command string + Created string + Ports string + Names string + IsInfra bool + Status string + State define.ContainerStatus + Pid int + Size *ContainerSize + Pod string + PodName string + CreatedAt time.Time + ExitedAt time.Time + StartedAt time.Time + Labels map[string]string + PID string + Cgroup string + IPC string + MNT string + NET string + PIDNS string + User string + UTS string + Mounts string +} + +// Namespace describes output for ps namespace. +type Namespace struct { + PID string `json:"pid,omitempty"` + Cgroup string `json:"cgroup,omitempty"` + IPC string `json:"ipc,omitempty"` + MNT string `json:"mnt,omitempty"` + NET string `json:"net,omitempty"` + PIDNS string `json:"pidns,omitempty"` + User string `json:"user,omitempty"` + UTS string `json:"uts,omitempty"` +} + +// ContainerSize holds the size of the container's root filesystem and top +// read-write layer. +type ContainerSize struct { + RootFsSize int64 `json:"rootFsSize"` + RwSize int64 `json:"rwSize"` +} + +// NewBatchContainer runs a batch process under one lock to get container information and only +// be called in PBatch. +func NewBatchContainer(r *libpod.Runtime, ctr *libpod.Container, opts PsOptions) (PsContainerOutput, error) { + var ( + conState define.ContainerStatus + command string + created string + status string + exitedAt time.Time + startedAt time.Time + exitCode int32 + err error + pid int + size *ContainerSize + ns *Namespace + pso PsContainerOutput + ) + batchErr := ctr.Batch(func(c *libpod.Container) error { + if opts.Sync { + if err := c.Sync(); err != nil { + return err + } + } + + conState, err = c.State() + if err != nil { + return errors.Wrapf(err, "unable to obtain container state") + } + command = strings.Join(c.Command(), " ") + created = units.HumanDuration(time.Since(c.CreatedTime())) + " ago" + + exitCode, _, err = c.ExitCode() + if err != nil { + return errors.Wrapf(err, "unable to obtain container exit code") + } + startedAt, err = c.StartedTime() + if err != nil { + logrus.Errorf("error getting started time for %q: %v", c.ID(), err) + } + exitedAt, err = c.FinishedTime() + if err != nil { + logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) + } + if opts.Namespace { + pid, err = c.PID() + if err != nil { + return errors.Wrapf(err, "unable to obtain container pid") + } + ns = GetNamespaces(pid) + } + if opts.Size { + size = new(ContainerSize) + + rootFsSize, err := c.RootFsSize() + if err != nil { + logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) + } + + rwSize, err := c.RWSize() + if err != nil { + logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) + } + + size.RootFsSize = rootFsSize + size.RwSize = rwSize + } + + return nil + }) + + if batchErr != nil { + return pso, batchErr + } + + switch conState.String() { + case define.ContainerStateExited.String(): + fallthrough + case define.ContainerStateStopped.String(): + exitedSince := units.HumanDuration(time.Since(exitedAt)) + status = fmt.Sprintf("Exited (%d) %s ago", exitCode, exitedSince) + case define.ContainerStateRunning.String(): + status = "Up " + units.HumanDuration(time.Since(startedAt)) + " ago" + case define.ContainerStatePaused.String(): + status = "Paused" + case define.ContainerStateCreated.String(), define.ContainerStateConfigured.String(): + status = "Created" + case define.ContainerStateRemoving.String(): + status = "Removing" + default: + status = "Error" + } + + imageID, imageName := ctr.Image() + cid := ctr.ID() + podID := ctr.PodID() + if !opts.NoTrunc { + cid = cid[0:cidTruncLength] + if len(podID) > podTruncLength { + podID = podID[0:podTruncLength] + } + if len(command) > cmdTruncLength { + command = command[0:cmdTruncLength] + "..." + } + if len(imageID) > iidTruncLength { + imageID = imageID[0:iidTruncLength] + } + } + + ports, err := ctr.PortMappings() + if err != nil { + logrus.Errorf("unable to lookup namespace container for %s", ctr.ID()) + } + + pso.ID = cid + pso.Image = imageName + pso.ImageID = imageID + pso.Command = command + pso.Created = created + pso.Ports = portsToString(ports) + pso.Names = ctr.Name() + pso.IsInfra = ctr.IsInfra() + pso.Status = status + pso.State = conState + pso.Pid = pid + pso.Size = size + pso.ExitedAt = exitedAt + pso.CreatedAt = ctr.CreatedTime() + pso.StartedAt = startedAt + pso.Labels = ctr.Labels() + pso.Mounts = strings.Join(ctr.UserVolumes(), " ") + + // Add pod name and pod ID if requested by user. + // No need to look up the pod if its ID is empty. + if opts.Pod && len(podID) > 0 { + // The pod name is not in the container definition + // so we need to retrieve it using the pod ID. + var podName string + pod, err := r.LookupPod(podID) + if err != nil { + logrus.Errorf("unable to lookup pod for container %s", ctr.ID()) + } else { + podName = pod.Name() + } + + pso.Pod = podID + pso.PodName = podName + } + + if opts.Namespace { + pso.Cgroup = ns.Cgroup + pso.IPC = ns.IPC + pso.MNT = ns.MNT + pso.NET = ns.NET + pso.User = ns.User + pso.UTS = ns.UTS + pso.PIDNS = ns.PIDNS + } + + return pso, nil +} + +type batchFunc func() (PsContainerOutput, error) + +type workerInput struct { + parallelFunc batchFunc + opts PsOptions + cid string + job int +} + +// worker is a "threaded" worker that takes jobs from the channel "queue". +func worker(wg *sync.WaitGroup, jobs <-chan workerInput, results chan<- PsContainerOutput, errors chan<- error) { + for j := range jobs { + r, err := j.parallelFunc() + // If we find an error, we return just the error. + if err != nil { + errors <- err + } else { + // Return the result. + results <- r + } + wg.Done() + } +} + +// GenerateContainerFilterFuncs return ContainerFilter functions based of filter. +func GenerateContainerFilterFuncs(filter, filterValue string, r *libpod.Runtime) (func(container *libpod.Container) bool, error) { + switch filter { + case "id": + return func(c *libpod.Container) bool { + return strings.Contains(c.ID(), filterValue) + }, nil + case "label": + var filterArray = strings.SplitN(filterValue, "=", 2) + var filterKey = filterArray[0] + if len(filterArray) > 1 { + filterValue = filterArray[1] + } else { + filterValue = "" + } + return func(c *libpod.Container) bool { + for labelKey, labelValue := range c.Labels() { + if labelKey == filterKey && ("" == filterValue || labelValue == filterValue) { + return true + } + } + return false + }, nil + case "name": + return func(c *libpod.Container) bool { + match, err := regexp.MatchString(filterValue, c.Name()) + if err != nil { + return false + } + return match + }, nil + case "exited": + exitCode, err := strconv.ParseInt(filterValue, 10, 32) + if err != nil { + return nil, errors.Wrapf(err, "exited code out of range %q", filterValue) + } + return func(c *libpod.Container) bool { + ec, exited, err := c.ExitCode() + if ec == int32(exitCode) && err == nil && exited { + return true + } + return false + }, nil + case "status": + if !util.StringInSlice(filterValue, []string{"created", "running", "paused", "stopped", "exited", "unknown"}) { + return nil, errors.Errorf("%s is not a valid status", filterValue) + } + return func(c *libpod.Container) bool { + status, err := c.State() + if err != nil { + return false + } + if filterValue == "stopped" { + filterValue = "exited" + } + state := status.String() + if status == define.ContainerStateConfigured { + state = "created" + } else if status == define.ContainerStateStopped { + state = "exited" + } + return state == filterValue + }, nil + case "ancestor": + // This needs to refine to match docker + // - ancestor=(<image-name>[:tag]|<image-id>| ⟨image@digest⟩) - containers created from an image or a descendant. + return func(c *libpod.Container) bool { + containerConfig := c.Config() + if strings.Contains(containerConfig.RootfsImageID, filterValue) || strings.Contains(containerConfig.RootfsImageName, filterValue) { + return true + } + return false + }, nil + case "before": + ctr, err := r.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.After(cc.CreatedTime) + }, nil + case "since": + ctr, err := r.LookupContainer(filterValue) + if err != nil { + return nil, errors.Errorf("unable to find container by name or id of %s", filterValue) + } + containerConfig := ctr.Config() + createTime := containerConfig.CreatedTime + return func(c *libpod.Container) bool { + cc := c.Config() + return createTime.Before(cc.CreatedTime) + }, nil + case "volume": + //- volume=(<volume-name>|<mount-point-destination>) + return func(c *libpod.Container) bool { + containerConfig := c.Config() + var dest string + arr := strings.Split(filterValue, ":") + source := arr[0] + if len(arr) == 2 { + dest = arr[1] + } + for _, mount := range containerConfig.Spec.Mounts { + if dest != "" && (mount.Source == source && mount.Destination == dest) { + return true + } + if dest == "" && mount.Source == source { + return true + } + } + return false + }, nil + case "health": + return func(c *libpod.Container) bool { + hcStatus, err := c.HealthCheckStatus() + if err != nil { + return false + } + return hcStatus == filterValue + }, nil + case "until": + ts, err := timetype.GetTimestamp(filterValue, time.Now()) + if err != nil { + return nil, err + } + seconds, nanoseconds, err := timetype.ParseTimestamps(ts, 0) + if err != nil { + return nil, err + } + until := time.Unix(seconds, nanoseconds) + return func(c *libpod.Container) bool { + if !until.IsZero() && c.CreatedTime().After((until)) { + return true + } + return false + }, nil + } + return nil, errors.Errorf("%s is an invalid filter", filter) +} + +// GetPsContainerOutput returns a slice of containers specifically for ps output. +func GetPsContainerOutput(r *libpod.Runtime, opts PsOptions, filters []string, maxWorkers int) ([]PsContainerOutput, error) { + var ( + filterFuncs []libpod.ContainerFilter + outputContainers []*libpod.Container + ) + + if len(filters) > 0 { + for _, f := range filters { + filterSplit := strings.SplitN(f, "=", 2) + if len(filterSplit) < 2 { + return nil, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) + } + generatedFunc, err := GenerateContainerFilterFuncs(filterSplit[0], filterSplit[1], r) + if err != nil { + return nil, errors.Wrapf(err, "invalid filter") + } + filterFuncs = append(filterFuncs, generatedFunc) + } + } + if !opts.Latest { + // Get all containers. + containers, err := r.GetContainers(filterFuncs...) + if err != nil { + return nil, err + } + + // We only want the last few containers. + if opts.Last > 0 && opts.Last <= len(containers) { + return nil, errors.Errorf("--last not yet supported") + } else { + outputContainers = containers + } + } else { + // Get just the latest container. + // Ignore filters. + latestCtr, err := r.GetLatestContainer() + if err != nil { + return nil, err + } + + outputContainers = []*libpod.Container{latestCtr} + } + + pss := PBatch(r, outputContainers, maxWorkers, opts) + return pss, nil +} + +// PBatch performs batch operations on a container in parallel. It spawns the +// number of workers relative to the number of parallel operations desired. +func PBatch(r *libpod.Runtime, containers []*libpod.Container, workers int, opts PsOptions) []PsContainerOutput { + var wg sync.WaitGroup + psResults := []PsContainerOutput{} + + // If the number of containers in question is less than the number of + // proposed parallel operations, we shouldn't spawn so many workers. + if workers > len(containers) { + workers = len(containers) + } + + jobs := make(chan workerInput, len(containers)) + results := make(chan PsContainerOutput, len(containers)) + batchErrors := make(chan error, len(containers)) + + // Create the workers. + for w := 1; w <= workers; w++ { + go worker(&wg, jobs, results, batchErrors) + } + + // Add jobs to the workers. + for i, j := range containers { + j := j + wg.Add(1) + f := func() (PsContainerOutput, error) { + return NewBatchContainer(r, j, opts) + } + jobs <- workerInput{ + parallelFunc: f, + opts: opts, + cid: j.ID(), + job: i, + } + } + close(jobs) + wg.Wait() + close(results) + close(batchErrors) + for err := range batchErrors { + logrus.Errorf("unable to get container info: %q", err) + } + for res := range results { + // We sort out running vs non-running here to save lots of copying + // later. + if !opts.All && !opts.Latest && opts.Last < 1 { + if !res.IsInfra && res.State == define.ContainerStateRunning { + psResults = append(psResults, res) + } + } else { + psResults = append(psResults, res) + } + } + return psResults +} + +// BatchContainerOp is used in ps to reduce performance hits by "batching" +// locks. +func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) { + var ( + conConfig *libpod.ContainerConfig + conState define.ContainerStatus + err error + exitCode int32 + exited bool + pid int + size *ContainerSize + startedTime time.Time + exitedTime time.Time + ) + + batchErr := ctr.Batch(func(c *libpod.Container) error { + conConfig = c.Config() + conState, err = c.State() + if err != nil { + return errors.Wrapf(err, "unable to obtain container state") + } + + exitCode, exited, err = c.ExitCode() + if err != nil { + return errors.Wrapf(err, "unable to obtain container exit code") + } + startedTime, err = c.StartedTime() + if err != nil { + logrus.Errorf("error getting started time for %q: %v", c.ID(), err) + } + exitedTime, err = c.FinishedTime() + if err != nil { + logrus.Errorf("error getting exited time for %q: %v", c.ID(), err) + } + + if !opts.Size && !opts.Namespace { + return nil + } + + if opts.Namespace { + pid, err = c.PID() + if err != nil { + return errors.Wrapf(err, "unable to obtain container pid") + } + } + if opts.Size { + size = new(ContainerSize) + + rootFsSize, err := c.RootFsSize() + if err != nil { + logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err) + } + + rwSize, err := c.RWSize() + if err != nil { + logrus.Errorf("error getting rw size for %q: %v", c.ID(), err) + } + + size.RootFsSize = rootFsSize + size.RwSize = rwSize + } + return nil + }) + if batchErr != nil { + return BatchContainerStruct{}, batchErr + } + return BatchContainerStruct{ + ConConfig: conConfig, + ConState: conState, + ExitCode: exitCode, + Exited: exited, + Pid: pid, + StartedTime: startedTime, + ExitedTime: exitedTime, + Size: size, + }, nil +} + +// GetNamespaces returns a populated namespace struct. +func GetNamespaces(pid int) *Namespace { + ctrPID := strconv.Itoa(pid) + cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup")) + ipc, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc")) + mnt, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt")) + net, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net")) + pidns, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid")) + user, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user")) + uts, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts")) + + return &Namespace{ + PID: ctrPID, + Cgroup: cgroup, + IPC: ipc, + MNT: mnt, + NET: net, + PIDNS: pidns, + User: user, + UTS: uts, + } +} + +// GetNamespaceInfo is an exported wrapper for getNamespaceInfo +func GetNamespaceInfo(path string) (string, error) { + return getNamespaceInfo(path) +} + +func getNamespaceInfo(path string) (string, error) { + val, err := os.Readlink(path) + if err != nil { + return "", errors.Wrapf(err, "error getting info from %q", path) + } + return getStrFromSquareBrackets(val), nil +} + +// getStrFromSquareBrackets gets the string inside [] from a string. +func getStrFromSquareBrackets(cmd string) string { + reg := regexp.MustCompile(`.*\[|\].*`) + arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",") + return strings.Join(arr, ",") +} + +func comparePorts(i, j ocicni.PortMapping) bool { + if i.ContainerPort != j.ContainerPort { + return i.ContainerPort < j.ContainerPort + } + + if i.HostIP != j.HostIP { + return i.HostIP < j.HostIP + } + + if i.HostPort != j.HostPort { + return i.HostPort < j.HostPort + } + + return i.Protocol < j.Protocol +} + +// formatGroup returns the group as <IP:startPort:lastPort->startPort:lastPort/Proto> +// e.g 0.0.0.0:1000-1006->1000-1006/tcp. +func formatGroup(key string, start, last int32) string { + parts := strings.Split(key, "/") + groupType := parts[0] + var ip string + if len(parts) > 1 { + ip = parts[0] + groupType = parts[1] + } + group := strconv.Itoa(int(start)) + if start != last { + group = fmt.Sprintf("%s-%d", group, last) + } + if ip != "" { + group = fmt.Sprintf("%s:%s->%s", ip, group, group) + } + return fmt.Sprintf("%s/%s", group, groupType) +} + +// portsToString converts the ports used to a string of the from "port1, port2" +// and also groups a continuous list of ports into a readable format. +func portsToString(ports []ocicni.PortMapping) string { + type portGroup struct { + first int32 + last int32 + } + var portDisplay []string + if len(ports) == 0 { + return "" + } + //Sort the ports, so grouping continuous ports become easy. + sort.Slice(ports, func(i, j int) bool { + return comparePorts(ports[i], ports[j]) + }) + + // portGroupMap is used for grouping continuous ports. + portGroupMap := make(map[string]*portGroup) + var groupKeyList []string + + for _, v := range ports { + + hostIP := v.HostIP + if hostIP == "" { + hostIP = "0.0.0.0" + } + // If hostPort and containerPort are not same, consider as individual port. + if v.ContainerPort != v.HostPort { + portDisplay = append(portDisplay, fmt.Sprintf("%s:%d->%d/%s", hostIP, v.HostPort, v.ContainerPort, v.Protocol)) + continue + } + + portMapKey := fmt.Sprintf("%s/%s", hostIP, v.Protocol) + + portgroup, ok := portGroupMap[portMapKey] + if !ok { + portGroupMap[portMapKey] = &portGroup{first: v.ContainerPort, last: v.ContainerPort} + // This list is required to traverse portGroupMap. + groupKeyList = append(groupKeyList, portMapKey) + continue + } + + if portgroup.last == (v.ContainerPort - 1) { + portgroup.last = v.ContainerPort + continue + } + } + // For each portMapKey, format group list and appned to output string. + for _, portKey := range groupKeyList { + group := portGroupMap[portKey] + portDisplay = append(portDisplay, formatGroup(portKey, group.first, group.last)) + } + return strings.Join(portDisplay, ", ") +} + +// GetRunlabel is a helper function for runlabel; it gets the image if needed and begins the +// construction of the runlabel output and environment variables. +func GetRunlabel(label string, runlabelImage string, ctx context.Context, runtime *libpod.Runtime, pull bool, inputCreds string, dockerRegistryOptions image.DockerRegistryOptions, authfile string, signaturePolicyPath string, output io.Writer) (string, string, error) { + var ( + newImage *image.Image + err error + imageName string + ) + if pull { + var registryCreds *types.DockerAuthConfig + if inputCreds != "" { + creds, err := util.ParseRegistryCreds(inputCreds) + if err != nil { + return "", "", err + } + registryCreds = creds + } + dockerRegistryOptions.DockerRegistryCreds = registryCreds + newImage, err = runtime.ImageRuntime().New(ctx, runlabelImage, signaturePolicyPath, authfile, output, &dockerRegistryOptions, image.SigningOptions{}, &label, util.PullImageMissing) + } else { + newImage, err = runtime.ImageRuntime().NewFromLocal(runlabelImage) + } + if err != nil { + return "", "", errors.Wrapf(err, "unable to find image") + } + + if len(newImage.Names()) < 1 { + imageName = newImage.ID() + } else { + imageName = newImage.Names()[0] + } + + runLabel, err := newImage.GetLabel(ctx, label) + return runLabel, imageName, err +} + +// GenerateRunlabelCommand generates the command that will eventually be execucted by Podman. +func GenerateRunlabelCommand(runLabel, imageName, name string, opts map[string]string, extraArgs []string, globalOpts string) ([]string, []string, error) { + // If no name is provided, we use the image's basename instead. + if name == "" { + baseName, err := image.GetImageBaseName(imageName) + if err != nil { + return nil, nil, err + } + name = baseName + } + // The user provided extra arguments that need to be tacked onto the label's command. + if len(extraArgs) > 0 { + runLabel = fmt.Sprintf("%s %s", runLabel, strings.Join(extraArgs, " ")) + } + cmd, err := GenerateCommand(runLabel, imageName, name, globalOpts) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to generate command") + } + env := GenerateRunEnvironment(name, imageName, opts) + env = append(env, "PODMAN_RUNLABEL_NESTED=1") + + envmap := envSliceToMap(env) + + envmapper := func(k string) string { + switch k { + case "OPT1": + return envmap["OPT1"] + case "OPT2": + return envmap["OPT2"] + case "OPT3": + return envmap["OPT3"] + case "PWD": + // I would prefer to use os.getenv but it appears PWD is not in the os env list. + d, err := os.Getwd() + if err != nil { + logrus.Error("unable to determine current working directory") + return "" + } + return d + } + return "" + } + newS := os.Expand(strings.Join(cmd, " "), envmapper) + cmd, err = shlex.Split(newS) + if err != nil { + return nil, nil, err + } + return cmd, env, nil +} + +func envSliceToMap(env []string) map[string]string { + m := make(map[string]string) + for _, i := range env { + split := strings.Split(i, "=") + m[split[0]] = strings.Join(split[1:], " ") + } + return m +} + +// GenerateKube generates kubernetes yaml based on a pod or container. +func GenerateKube(name string, service bool, r *libpod.Runtime) (*v1.Pod, *v1.Service, error) { + var ( + pod *libpod.Pod + podYAML *v1.Pod + err error + container *libpod.Container + servicePorts []v1.ServicePort + serviceYAML v1.Service + ) + // Get the container in question. + container, err = r.LookupContainer(name) + if err != nil { + pod, err = r.LookupPod(name) + if err != nil { + return nil, nil, err + } + podYAML, servicePorts, err = pod.GenerateForKube() + } else { + if len(container.Dependencies()) > 0 { + return nil, nil, errors.Wrapf(define.ErrNotImplemented, "containers with dependencies") + } + podYAML, err = container.GenerateForKube() + } + if err != nil { + return nil, nil, err + } + + if service { + serviceYAML = libpod.GenerateKubeServiceFromV1Pod(podYAML, servicePorts) + } + return podYAML, &serviceYAML, nil +} + +// Parallelize provides the maximum number of parallel workers (int) as calculated by a basic +// heuristic. This can be overridden by the --max-workers primary switch to podman. +func Parallelize(job string) int { + numCpus := runtime.NumCPU() + switch job { + case "kill": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "pause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + case "ps": + return 8 + case "restart": + return numCpus * 2 + case "rm": + if numCpus <= 3 { + return numCpus * 3 + } else { + return numCpus * 4 + } + case "stop": + if numCpus <= 2 { + return 4 + } else { + return numCpus * 3 + } + case "unpause": + if numCpus <= 3 { + return numCpus * 3 + } + return numCpus * 4 + } + return 3 +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 66b3e4095..8fba07c18 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -14,11 +14,9 @@ import ( "syscall" "time" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/logs" - "github.com/containers/libpod/pkg/adapter/shortcuts" "github.com/containers/libpod/pkg/cgroups" "github.com/containers/libpod/pkg/rootless" iopodman "github.com/containers/libpod/pkg/varlink" @@ -39,12 +37,12 @@ func (i *VarlinkAPI) ListContainers(call iopodman.VarlinkCall) error { if err != nil { return call.ReplyErrorOccurred(err.Error()) } - opts := shared.PsOptions{ + opts := PsOptions{ Namespace: true, Size: true, } for _, ctr := range containers { - batchInfo, err := shared.BatchContainerOp(ctr, opts) + batchInfo, err := BatchContainerOp(ctr, opts) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -58,13 +56,13 @@ func (i *VarlinkAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error { var ( containers []iopodman.PsContainer ) - maxWorkers := shared.Parallelize("ps") + maxWorkers := Parallelize("ps") psOpts := makePsOpts(opts) filters := []string{} if opts.Filters != nil { filters = *opts.Filters } - psContainerOutputs, err := shared.GetPsContainerOutput(i.Runtime, psOpts, filters, maxWorkers) + psContainerOutputs, err := GetPsContainerOutput(i.Runtime, psOpts, filters, maxWorkers) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -111,22 +109,22 @@ func (i *VarlinkAPI) GetContainer(call iopodman.VarlinkCall, id string) error { if err != nil { return call.ReplyContainerNotFound(id, err.Error()) } - opts := shared.PsOptions{ + opts := PsOptions{ Namespace: true, Size: true, } - batchInfo, err := shared.BatchContainerOp(ctr, opts) + batchInfo, err := BatchContainerOp(ctr, opts) if err != nil { return call.ReplyErrorOccurred(err.Error()) } return call.ReplyGetContainer(makeListContainer(ctr.ID(), batchInfo)) } -// GetContainersByContext returns a slice of container ids based on all, latest, or a list +// getContainersByContext returns a slice of container ids based on all, latest, or a list func (i *VarlinkAPI) GetContainersByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { var ids []string - ctrs, err := shortcuts.GetContainersByContext(all, latest, input, i.Runtime) + ctrs, err := getContainersByContext(all, latest, input, i.Runtime) if err != nil { if errors.Cause(err) == define.ErrNoSuchCtr { return call.ReplyContainerNotFound("", err.Error()) @@ -160,9 +158,9 @@ func (i *VarlinkAPI) GetContainersByStatus(call iopodman.VarlinkCall, statuses [ if err != nil { return call.ReplyErrorOccurred(err.Error()) } - opts := shared.PsOptions{Size: true, Namespace: true} + opts := PsOptions{Size: true, Namespace: true} for _, ctr := range filteredContainers { - batchInfo, err := shared.BatchContainerOp(ctr, opts) + batchInfo, err := BatchContainerOp(ctr, opts) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -752,7 +750,7 @@ func (i *VarlinkAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string tailLen = 0 } logChannel := make(chan *logs.LogLine, tailLen*len(names)+1) - containers, err := shortcuts.GetContainersByContext(false, latest, names, i.Runtime) + containers, err := getContainersByContext(false, latest, names, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/containers_create.go b/pkg/varlinkapi/containers_create.go index c1c1f6674..f0a87491a 100644 --- a/pkg/varlinkapi/containers_create.go +++ b/pkg/varlinkapi/containers_create.go @@ -3,14 +3,13 @@ package varlinkapi import ( - "github.com/containers/libpod/cmd/podman/shared" iopodman "github.com/containers/libpod/pkg/varlink" ) // CreateContainer ... func (i *VarlinkAPI) CreateContainer(call iopodman.VarlinkCall, config iopodman.Create) error { - generic := shared.VarlinkCreateToGeneric(config) - ctr, _, err := shared.CreateContainer(getContext(), &generic, i.Runtime) + generic := VarlinkCreateToGeneric(config) + ctr, _, err := CreateContainer(getContext(), &generic, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/create.go b/pkg/varlinkapi/create.go new file mode 100644 index 000000000..63d5072c6 --- /dev/null +++ b/pkg/varlinkapi/create.go @@ -0,0 +1,1154 @@ +package varlinkapi + +import ( + "context" + "encoding/json" + "fmt" + "io" + "os" + "path/filepath" + goruntime "runtime" + "strconv" + "strings" + "syscall" + "time" + + "github.com/containers/image/v5/manifest" + "github.com/containers/libpod/cmd/podman/parse" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" + "github.com/containers/libpod/libpod/image" + ann "github.com/containers/libpod/pkg/annotations" + "github.com/containers/libpod/pkg/autoupdate" + "github.com/containers/libpod/pkg/cgroups" + envLib "github.com/containers/libpod/pkg/env" + "github.com/containers/libpod/pkg/errorhandling" + "github.com/containers/libpod/pkg/inspect" + ns "github.com/containers/libpod/pkg/namespaces" + "github.com/containers/libpod/pkg/rootless" + "github.com/containers/libpod/pkg/seccomp" + cc "github.com/containers/libpod/pkg/spec" + "github.com/containers/libpod/pkg/sysinfo" + systemdGen "github.com/containers/libpod/pkg/systemd/generate" + "github.com/containers/libpod/pkg/util" + "github.com/docker/go-connections/nat" + "github.com/docker/go-units" + "github.com/opentracing/opentracing-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var DefaultKernelNamespaces = "cgroup,ipc,net,uts" + +func CreateContainer(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime) (*libpod.Container, *cc.CreateConfig, error) { + var ( + healthCheck *manifest.Schema2HealthConfig + err error + cidFile *os.File + ) + if c.Bool("trace") { + span, _ := opentracing.StartSpanFromContext(ctx, "createContainer") + defer span.Finish() + } + if c.Bool("rm") && c.String("restart") != "" && c.String("restart") != "no" { + return nil, nil, errors.Errorf("the --rm option conflicts with --restart") + } + + rtc, err := runtime.GetConfig() + if err != nil { + return nil, nil, err + } + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.InputArgs[0] + } + + if c.IsSet("cidfile") { + cidFile, err = util.OpenExclusiveFile(c.String("cidfile")) + if err != nil && os.IsExist(err) { + return nil, nil, errors.Errorf("container id file exists. Ensure another container is not using it or delete %s", c.String("cidfile")) + } + if err != nil { + return nil, nil, errors.Errorf("error opening cidfile %s", c.String("cidfile")) + } + defer errorhandling.CloseQuiet(cidFile) + defer errorhandling.SyncQuiet(cidFile) + } + + imageName := "" + rawImageName := "" + var imageData *inspect.ImageData = nil + + // Set the storage if there is no rootfs specified + if rootfs == "" { + var writer io.Writer + if !c.Bool("quiet") { + writer = os.Stderr + } + + if len(c.InputArgs) != 0 { + rawImageName = c.InputArgs[0] + } else { + return nil, nil, errors.Errorf("error, image name not provided") + } + + pullType, err := util.ValidatePullType(c.String("pull")) + if err != nil { + return nil, nil, err + } + + overrideOS := c.String("override-os") + overrideArch := c.String("override-arch") + dockerRegistryOptions := image.DockerRegistryOptions{ + OSChoice: overrideOS, + ArchitectureChoice: overrideArch, + } + + newImage, err := runtime.ImageRuntime().New(ctx, rawImageName, rtc.Engine.SignaturePolicyPath, c.String("authfile"), writer, &dockerRegistryOptions, image.SigningOptions{}, nil, pullType) + if err != nil { + return nil, nil, err + } + imageData, err = newImage.InspectNoSize(ctx) + if err != nil { + return nil, nil, err + } + + if overrideOS == "" && imageData.Os != goruntime.GOOS { + logrus.Infof("Using %q (OS) image on %q host", imageData.Os, goruntime.GOOS) + } + + if overrideArch == "" && imageData.Architecture != goruntime.GOARCH { + logrus.Infof("Using %q (architecture) on %q host", imageData.Architecture, goruntime.GOARCH) + } + + names := newImage.Names() + if len(names) > 0 { + imageName = names[0] + } else { + imageName = newImage.ID() + } + + // if the user disabled the healthcheck with "none" or the no-healthcheck + // options is provided, we skip adding it + healthCheckCommandInput := c.String("healthcheck-command") + + // the user didn't disable the healthcheck but did pass in a healthcheck command + // now we need to make a healthcheck from the commandline input + if healthCheckCommandInput != "none" && !c.Bool("no-healthcheck") { + if len(healthCheckCommandInput) > 0 { + healthCheck, err = makeHealthCheckFromCli(c) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to create healthcheck") + } + } else { + // the user did not disable the health check and did not pass in a healthcheck + // command as input. so now we add healthcheck if it exists AND is correct mediatype + _, mediaType, err := newImage.Manifest(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to determine mediatype of image %s", newImage.ID()) + } + if mediaType == manifest.DockerV2Schema2MediaType { + healthCheck, err = newImage.GetHealthCheck(ctx) + if err != nil { + return nil, nil, errors.Wrapf(err, "unable to get healthcheck for %s", c.InputArgs[0]) + } + + if healthCheck != nil { + hcCommand := healthCheck.Test + if len(hcCommand) < 1 || hcCommand[0] == "" || hcCommand[0] == "NONE" { + // disable health check + healthCheck = nil + } else { + // apply defaults if image doesn't override them + if healthCheck.Interval == 0 { + healthCheck.Interval = 30 * time.Second + } + if healthCheck.Timeout == 0 { + healthCheck.Timeout = 30 * time.Second + } + /* Docker default is 0s, so the following would be a no-op + if healthCheck.StartPeriod == 0 { + healthCheck.StartPeriod = 0 * time.Second + } + */ + if healthCheck.Retries == 0 { + healthCheck.Retries = 3 + } + } + } + } + } + } + } + + createConfig, err := ParseCreateOpts(ctx, c, runtime, imageName, rawImageName, imageData) + if err != nil { + return nil, nil, err + } + + // (VR): Ideally we perform the checks _before_ pulling the image but that + // would require some bigger code refactoring of `ParseCreateOpts` and the + // logic here. But as the creation code will be consolidated in the future + // and given auto updates are experimental, we can live with that for now. + // In the end, the user may only need to correct the policy or the raw image + // name. + autoUpdatePolicy, autoUpdatePolicySpecified := createConfig.Labels[autoupdate.Label] + if autoUpdatePolicySpecified { + if _, err := autoupdate.LookupPolicy(autoUpdatePolicy); err != nil { + return nil, nil, err + } + // Now we need to make sure we're having a fully-qualified image reference. + if rootfs != "" { + return nil, nil, errors.Errorf("auto updates do not work with --rootfs") + } + // Make sure the input image is a docker. + if err := autoupdate.ValidateImageReference(rawImageName); err != nil { + return nil, nil, err + } + } + + // Because parseCreateOpts does derive anything from the image, we add health check + // at this point. The rest is done by WithOptions. + createConfig.HealthCheck = healthCheck + + // TODO: Should be able to return this from ParseCreateOpts + var pod *libpod.Pod + if createConfig.Pod != "" { + pod, err = runtime.LookupPod(createConfig.Pod) + if err != nil { + return nil, nil, errors.Wrapf(err, "error looking up pod to join") + } + } + + ctr, err := CreateContainerFromCreateConfig(runtime, createConfig, ctx, pod) + if err != nil { + return nil, nil, err + } + if cidFile != nil { + _, err = cidFile.WriteString(ctr.ID()) + if err != nil { + logrus.Error(err) + } + + } + + logrus.Debugf("New container created %q", ctr.ID()) + return ctr, createConfig, nil +} + +func configureEntrypoint(c *GenericCLIResults, data *inspect.ImageData) []string { + entrypoint := []string{} + if c.IsSet("entrypoint") { + // Force entrypoint to "" + if c.String("entrypoint") == "" { + return entrypoint + } + // Check if entrypoint specified is json + if err := json.Unmarshal([]byte(c.String("entrypoint")), &entrypoint); err == nil { + return entrypoint + } + // Return entrypoint as a single command + return []string{c.String("entrypoint")} + } + if data != nil { + return data.Config.Entrypoint + } + return entrypoint +} + +func configurePod(c *GenericCLIResults, runtime *libpod.Runtime, namespaces map[string]string, podName string) (map[string]string, string, error) { + pod, err := runtime.LookupPod(podName) + if err != nil { + return namespaces, "", err + } + podInfraID, err := pod.InfraContainerID() + if err != nil { + return namespaces, "", err + } + hasUserns := false + if podInfraID != "" { + podCtr, err := runtime.GetContainer(podInfraID) + if err != nil { + return namespaces, "", err + } + mappings, err := podCtr.IDMappings() + if err != nil { + return namespaces, "", err + } + hasUserns = len(mappings.UIDMap) > 0 + } + + if (namespaces["pid"] == cc.Pod) || (!c.IsSet("pid") && pod.SharesPID()) { + namespaces["pid"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["net"] == cc.Pod) || (!c.IsSet("net") && !c.IsSet("network") && pod.SharesNet()) { + namespaces["net"] = fmt.Sprintf("container:%s", podInfraID) + } + if hasUserns && (namespaces["user"] == cc.Pod) || (!c.IsSet("user") && pod.SharesUser()) { + namespaces["user"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["ipc"] == cc.Pod) || (!c.IsSet("ipc") && pod.SharesIPC()) { + namespaces["ipc"] = fmt.Sprintf("container:%s", podInfraID) + } + if (namespaces["uts"] == cc.Pod) || (!c.IsSet("uts") && pod.SharesUTS()) { + namespaces["uts"] = fmt.Sprintf("container:%s", podInfraID) + } + return namespaces, podInfraID, nil +} + +// Parses CLI options related to container creation into a config which can be +// parsed into an OCI runtime spec +func ParseCreateOpts(ctx context.Context, c *GenericCLIResults, runtime *libpod.Runtime, imageName string, rawImageName string, data *inspect.ImageData) (*cc.CreateConfig, error) { + var ( + inputCommand, command []string + memoryLimit, memoryReservation, memorySwap, memoryKernel int64 + blkioWeight uint16 + namespaces map[string]string + ) + + idmappings, err := util.ParseIDMapping(ns.UsernsMode(c.String("userns")), c.StringSlice("uidmap"), c.StringSlice("gidmap"), c.String("subuidname"), c.String("subgidname")) + if err != nil { + return nil, err + } + + imageID := "" + + inputCommand = c.InputArgs[1:] + if data != nil { + imageID = data.ID + } + + rootfs := "" + if c.Bool("rootfs") { + rootfs = c.InputArgs[0] + } + + if c.String("memory") != "" { + memoryLimit, err = units.RAMInBytes(c.String("memory")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for memory") + } + } + if c.String("memory-reservation") != "" { + memoryReservation, err = units.RAMInBytes(c.String("memory-reservation")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for memory-reservation") + } + } + if c.String("memory-swap") != "" { + if c.String("memory-swap") == "-1" { + memorySwap = -1 + } else { + memorySwap, err = units.RAMInBytes(c.String("memory-swap")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for memory-swap") + } + } + } + if c.String("kernel-memory") != "" { + memoryKernel, err = units.RAMInBytes(c.String("kernel-memory")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for kernel-memory") + } + } + if c.String("blkio-weight") != "" { + u, err := strconv.ParseUint(c.String("blkio-weight"), 10, 16) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for blkio-weight") + } + blkioWeight = uint16(u) + } + + tty := c.Bool("tty") + + if c.Changed("cpu-period") && c.Changed("cpus") { + return nil, errors.Errorf("--cpu-period and --cpus cannot be set together") + } + if c.Changed("cpu-quota") && c.Changed("cpus") { + return nil, errors.Errorf("--cpu-quota and --cpus cannot be set together") + } + + if c.Bool("no-hosts") && c.Changed("add-host") { + return nil, errors.Errorf("--no-hosts and --add-host cannot be set together") + } + + // EXPOSED PORTS + var portBindings map[nat.Port][]nat.PortBinding + if data != nil { + portBindings, err = cc.ExposedPorts(c.StringSlice("expose"), c.StringSlice("publish"), c.Bool("publish-all"), data.Config.ExposedPorts) + if err != nil { + return nil, err + } + } + + // Kernel Namespaces + // TODO Fix handling of namespace from pod + // Instead of integrating here, should be done in libpod + // However, that also involves setting up security opts + // when the pod's namespace is integrated + namespaces = map[string]string{ + "cgroup": c.String("cgroupns"), + "pid": c.String("pid"), + "net": c.String("network"), + "ipc": c.String("ipc"), + "user": c.String("userns"), + "uts": c.String("uts"), + } + + originalPodName := c.String("pod") + podName := strings.Replace(originalPodName, "new:", "", 1) + // after we strip out :new, make sure there is something left for a pod name + if len(podName) < 1 && c.IsSet("pod") { + return nil, errors.Errorf("new pod name must be at least one character") + } + + // If we are adding a container to a pod, we would like to add an annotation for the infra ID + // so kata containers can share VMs inside the pod + var podInfraID string + if c.IsSet("pod") { + if strings.HasPrefix(originalPodName, "new:") { + // pod does not exist; lets make it + var podOptions []libpod.PodCreateOption + podOptions = append(podOptions, libpod.WithPodName(podName), libpod.WithInfraContainer(), libpod.WithPodCgroups()) + if len(portBindings) > 0 { + ociPortBindings, err := cc.NatToOCIPortBindings(portBindings) + if err != nil { + return nil, err + } + podOptions = append(podOptions, libpod.WithInfraContainerPorts(ociPortBindings)) + } + + podNsOptions, err := GetNamespaceOptions(strings.Split(DefaultKernelNamespaces, ",")) + if err != nil { + return nil, err + } + podOptions = append(podOptions, podNsOptions...) + // make pod + pod, err := runtime.NewPod(ctx, podOptions...) + if err != nil { + return nil, err + } + logrus.Debugf("pod %s created by new container request", pod.ID()) + + // The container now cannot have port bindings; so we reset the map + portBindings = make(map[nat.Port][]nat.PortBinding) + } + namespaces, podInfraID, err = configurePod(c, runtime, namespaces, podName) + if err != nil { + return nil, err + } + } + + pidMode := ns.PidMode(namespaces["pid"]) + if !cc.Valid(string(pidMode), pidMode) { + return nil, errors.Errorf("--pid %q is not valid", c.String("pid")) + } + + usernsMode := ns.UsernsMode(namespaces["user"]) + if !cc.Valid(string(usernsMode), usernsMode) { + return nil, errors.Errorf("--userns %q is not valid", namespaces["user"]) + } + + utsMode := ns.UTSMode(namespaces["uts"]) + if !cc.Valid(string(utsMode), utsMode) { + return nil, errors.Errorf("--uts %q is not valid", namespaces["uts"]) + } + + cgroupMode := ns.CgroupMode(namespaces["cgroup"]) + if !cgroupMode.Valid() { + return nil, errors.Errorf("--cgroup %q is not valid", namespaces["cgroup"]) + } + + ipcMode := ns.IpcMode(namespaces["ipc"]) + if !cc.Valid(string(ipcMode), ipcMode) { + return nil, errors.Errorf("--ipc %q is not valid", ipcMode) + } + + // Make sure if network is set to container namespace, port binding is not also being asked for + netMode := ns.NetworkMode(namespaces["net"]) + if netMode.IsContainer() { + if len(portBindings) > 0 { + return nil, errors.Errorf("cannot set port bindings on an existing container network namespace") + } + } + + // USER + user := c.String("user") + if user == "" { + switch { + case usernsMode.IsKeepID(): + user = fmt.Sprintf("%d:%d", rootless.GetRootlessUID(), rootless.GetRootlessGID()) + case data == nil: + user = "0" + default: + user = data.Config.User + } + } + + // STOP SIGNAL + stopSignal := syscall.SIGTERM + signalString := "" + if data != nil { + signalString = data.Config.StopSignal + } + if c.IsSet("stop-signal") { + signalString = c.String("stop-signal") + } + if signalString != "" { + stopSignal, err = util.ParseSignal(signalString) + if err != nil { + return nil, err + } + } + + // ENVIRONMENT VARIABLES + // + // Precedence order (higher index wins): + // 1) env-host, 2) image data, 3) env-file, 4) env + env := map[string]string{ + "container": "podman", + } + + // First transform the os env into a map. We need it for the labels later in + // any case. + osEnv, err := envLib.ParseSlice(os.Environ()) + if err != nil { + return nil, errors.Wrap(err, "error parsing host environment variables") + } + + // Start with env-host + + if c.Bool("env-host") { + env = envLib.Join(env, osEnv) + } + + // Image data overrides any previous variables + if data != nil { + configEnv, err := envLib.ParseSlice(data.Config.Env) + if err != nil { + return nil, errors.Wrap(err, "error passing image environment variables") + } + env = envLib.Join(env, configEnv) + } + + // env-file overrides any previous variables + if c.IsSet("env-file") { + for _, f := range c.StringSlice("env-file") { + fileEnv, err := envLib.ParseFile(f) + if err != nil { + return nil, err + } + // File env is overridden by env. + env = envLib.Join(env, fileEnv) + } + } + + if c.IsSet("env") { + // env overrides any previous variables + cmdlineEnv := c.StringSlice("env") + if len(cmdlineEnv) > 0 { + parsedEnv, err := envLib.ParseSlice(cmdlineEnv) + if err != nil { + return nil, err + } + env = envLib.Join(env, parsedEnv) + } + } + + // LABEL VARIABLES + labels, err := parse.GetAllLabels(c.StringSlice("label-file"), c.StringArray("label")) + if err != nil { + return nil, errors.Wrapf(err, "unable to process labels") + } + if data != nil { + for key, val := range data.Config.Labels { + if _, ok := labels[key]; !ok { + labels[key] = val + } + } + } + + if systemdUnit, exists := osEnv[systemdGen.EnvVariable]; exists { + labels[systemdGen.EnvVariable] = systemdUnit + } + + // ANNOTATIONS + annotations := make(map[string]string) + + // First, add our default annotations + annotations[ann.TTY] = "false" + if tty { + annotations[ann.TTY] = "true" + } + + // in the event this container is in a pod, and the pod has an infra container + // we will want to configure it as a type "container" instead defaulting to + // the behavior of a "sandbox" container + // In Kata containers: + // - "sandbox" is the annotation that denotes the container should use its own + // VM, which is the default behavior + // - "container" denotes the container should join the VM of the SandboxID + // (the infra container) + if podInfraID != "" { + annotations[ann.SandboxID] = podInfraID + annotations[ann.ContainerType] = ann.ContainerTypeContainer + } + + if data != nil { + // Next, add annotations from the image + for key, value := range data.Annotations { + annotations[key] = value + } + } + // Last, add user annotations + for _, annotation := range c.StringSlice("annotation") { + splitAnnotation := strings.SplitN(annotation, "=", 2) + if len(splitAnnotation) < 2 { + return nil, errors.Errorf("Annotations must be formatted KEY=VALUE") + } + annotations[splitAnnotation[0]] = splitAnnotation[1] + } + + // WORKING DIRECTORY + workDir := "/" + if c.IsSet("workdir") { + workDir = c.String("workdir") + } else if data != nil && data.Config.WorkingDir != "" { + workDir = data.Config.WorkingDir + } + + userCommand := []string{} + entrypoint := configureEntrypoint(c, data) + // Build the command + // If we have an entry point, it goes first + if len(entrypoint) > 0 { + command = entrypoint + } + if len(inputCommand) > 0 { + // User command overrides data CMD + command = append(command, inputCommand...) + userCommand = append(userCommand, inputCommand...) + } else if data != nil && len(data.Config.Cmd) > 0 && !c.IsSet("entrypoint") { + // If not user command, add CMD + command = append(command, data.Config.Cmd...) + userCommand = append(userCommand, data.Config.Cmd...) + } + + if data != nil && len(command) == 0 { + return nil, errors.Errorf("No command specified on command line or as CMD or ENTRYPOINT in this image") + } + + // SHM Size + shmSize, err := units.FromHumanSize(c.String("shm-size")) + if err != nil { + return nil, errors.Wrapf(err, "unable to translate --shm-size") + } + + if c.IsSet("add-host") { + // Verify the additional hosts are in correct format + for _, host := range c.StringSlice("add-host") { + if _, err := parse.ValidateExtraHost(host); err != nil { + return nil, err + } + } + } + + var ( + dnsSearches []string + dnsServers []string + dnsOptions []string + ) + if c.Changed("dns-search") { + dnsSearches = c.StringSlice("dns-search") + // Check for explicit dns-search domain of '' + if len(dnsSearches) == 0 { + return nil, errors.Errorf("'' is not a valid domain") + } + // Validate domains are good + for _, dom := range dnsSearches { + if dom == "." { + if len(dnsSearches) > 1 { + return nil, errors.Errorf("cannot pass additional search domains when also specifying '.'") + } + continue + } + if _, err := parse.ValidateDomain(dom); err != nil { + return nil, err + } + } + } + if c.IsSet("dns") { + dnsServers = append(dnsServers, c.StringSlice("dns")...) + } + if c.IsSet("dns-opt") { + dnsOptions = c.StringSlice("dns-opt") + } + + var ImageVolumes map[string]struct{} + if data != nil && c.String("image-volume") != "ignore" { + ImageVolumes = data.Config.Volumes + } + + var imageVolType = map[string]string{ + "bind": "", + "tmpfs": "", + "ignore": "", + } + if _, ok := imageVolType[c.String("image-volume")]; !ok { + return nil, errors.Errorf("invalid image-volume type %q. Pick one of bind, tmpfs, or ignore", c.String("image-volume")) + } + + systemd := c.String("systemd") == "always" + if !systemd && command != nil { + x, err := strconv.ParseBool(c.String("systemd")) + if err != nil { + return nil, errors.Wrapf(err, "cannot parse bool %s", c.String("systemd")) + } + if x && (command[0] == "/usr/sbin/init" || command[0] == "/sbin/init" || (filepath.Base(command[0]) == "systemd")) { + systemd = true + } + } + if systemd { + if signalString == "" { + stopSignal, err = util.ParseSignal("RTMIN+3") + if err != nil { + return nil, errors.Wrapf(err, "error parsing systemd signal") + } + } + } + // This is done because cobra cannot have two aliased flags. So we have to check + // both + memorySwappiness := c.Int64("memory-swappiness") + + logDriver := define.KubernetesLogging + if c.Changed("log-driver") { + logDriver = c.String("log-driver") + } + + pidsLimit := c.Int64("pids-limit") + if c.String("cgroups") == "disabled" && !c.Changed("pids-limit") { + pidsLimit = -1 + } + + pid := &cc.PidConfig{ + PidMode: pidMode, + } + ipc := &cc.IpcConfig{ + IpcMode: ipcMode, + } + + cgroup := &cc.CgroupConfig{ + Cgroups: c.String("cgroups"), + Cgroupns: c.String("cgroupns"), + CgroupParent: c.String("cgroup-parent"), + CgroupMode: cgroupMode, + } + + userns := &cc.UserConfig{ + GroupAdd: c.StringSlice("group-add"), + IDMappings: idmappings, + UsernsMode: usernsMode, + User: user, + } + + uts := &cc.UtsConfig{ + UtsMode: utsMode, + NoHosts: c.Bool("no-hosts"), + HostAdd: c.StringSlice("add-host"), + Hostname: c.String("hostname"), + } + net := &cc.NetworkConfig{ + DNSOpt: dnsOptions, + DNSSearch: dnsSearches, + DNSServers: dnsServers, + HTTPProxy: c.Bool("http-proxy"), + MacAddress: c.String("mac-address"), + Network: c.String("network"), + NetMode: netMode, + IPAddress: c.String("ip"), + Publish: c.StringSlice("publish"), + PublishAll: c.Bool("publish-all"), + PortBindings: portBindings, + } + + sysctl := map[string]string{} + if c.Changed("sysctl") { + sysctl, err = util.ValidateSysctls(c.StringSlice("sysctl")) + if err != nil { + return nil, errors.Wrapf(err, "invalid value for sysctl") + } + } + + secConfig := &cc.SecurityConfig{ + CapAdd: c.StringSlice("cap-add"), + CapDrop: c.StringSlice("cap-drop"), + Privileged: c.Bool("privileged"), + ReadOnlyRootfs: c.Bool("read-only"), + ReadOnlyTmpfs: c.Bool("read-only-tmpfs"), + Sysctl: sysctl, + } + + var securityOpt []string + if c.Changed("security-opt") { + securityOpt = c.StringArray("security-opt") + } + if err := secConfig.SetSecurityOpts(runtime, securityOpt); err != nil { + return nil, err + } + + // SECCOMP + if data != nil { + if value, exists := labels[seccomp.ContainerImageLabel]; exists { + secConfig.SeccompProfileFromImage = value + } + } + if policy, err := seccomp.LookupPolicy(c.String("seccomp-policy")); err != nil { + return nil, err + } else { + secConfig.SeccompPolicy = policy + } + rtc, err := runtime.GetConfig() + if err != nil { + return nil, err + } + volumes := rtc.Containers.Volumes + if c.Changed("volume") { + volumes = append(volumes, c.StringSlice("volume")...) + } + + devices := rtc.Containers.Devices + if c.Changed("device") { + devices = append(devices, c.StringSlice("device")...) + } + + config := &cc.CreateConfig{ + Annotations: annotations, + BuiltinImgVolumes: ImageVolumes, + ConmonPidFile: c.String("conmon-pidfile"), + ImageVolumeType: c.String("image-volume"), + CidFile: c.String("cidfile"), + Command: command, + UserCommand: userCommand, + Detach: c.Bool("detach"), + Devices: devices, + Entrypoint: entrypoint, + Env: env, + // ExposedPorts: ports, + Init: c.Bool("init"), + InitPath: c.String("init-path"), + Image: imageName, + RawImageName: rawImageName, + ImageID: imageID, + Interactive: c.Bool("interactive"), + // IP6Address: c.String("ipv6"), // Not implemented yet - needs CNI support for static v6 + Labels: labels, + // LinkLocalIP: c.StringSlice("link-local-ip"), // Not implemented yet + LogDriver: logDriver, + LogDriverOpt: c.StringSlice("log-opt"), + Name: c.String("name"), + // NetworkAlias: c.StringSlice("network-alias"), // Not implemented - does this make sense in Podman? + Pod: podName, + Quiet: c.Bool("quiet"), + Resources: cc.CreateResourceConfig{ + BlkioWeight: blkioWeight, + BlkioWeightDevice: c.StringSlice("blkio-weight-device"), + CPUShares: c.Uint64("cpu-shares"), + CPUPeriod: c.Uint64("cpu-period"), + CPUsetCPUs: c.String("cpuset-cpus"), + CPUsetMems: c.String("cpuset-mems"), + CPUQuota: c.Int64("cpu-quota"), + CPURtPeriod: c.Uint64("cpu-rt-period"), + CPURtRuntime: c.Int64("cpu-rt-runtime"), + CPUs: c.Float64("cpus"), + DeviceCgroupRules: c.StringSlice("device-cgroup-rule"), + DeviceReadBps: c.StringSlice("device-read-bps"), + DeviceReadIOps: c.StringSlice("device-read-iops"), + DeviceWriteBps: c.StringSlice("device-write-bps"), + DeviceWriteIOps: c.StringSlice("device-write-iops"), + DisableOomKiller: c.Bool("oom-kill-disable"), + ShmSize: shmSize, + Memory: memoryLimit, + MemoryReservation: memoryReservation, + MemorySwap: memorySwap, + MemorySwappiness: int(memorySwappiness), + KernelMemory: memoryKernel, + OomScoreAdj: c.Int("oom-score-adj"), + PidsLimit: pidsLimit, + Ulimit: c.StringSlice("ulimit"), + }, + RestartPolicy: c.String("restart"), + Rm: c.Bool("rm"), + Security: *secConfig, + StopSignal: stopSignal, + StopTimeout: c.Uint("stop-timeout"), + Systemd: systemd, + Tmpfs: c.StringArray("tmpfs"), + Tty: tty, + MountsFlag: c.StringArray("mount"), + Volumes: volumes, + WorkDir: workDir, + Rootfs: rootfs, + VolumesFrom: c.StringSlice("volumes-from"), + Syslog: c.Bool("syslog"), + + Pid: *pid, + Ipc: *ipc, + Cgroup: *cgroup, + User: *userns, + Uts: *uts, + Network: *net, + } + + warnings, err := verifyContainerResources(config, false) + if err != nil { + return nil, err + } + for _, warning := range warnings { + fmt.Fprintln(os.Stderr, warning) + } + return config, nil +} + +func CreateContainerFromCreateConfig(r *libpod.Runtime, createConfig *cc.CreateConfig, ctx context.Context, pod *libpod.Pod) (*libpod.Container, error) { + runtimeSpec, options, err := createConfig.MakeContainerConfig(r, pod) + if err != nil { + return nil, err + } + + // Set the CreateCommand explicitly. Some (future) consumers of libpod + // might not want to set it. + options = append(options, libpod.WithCreateCommand()) + + ctr, err := r.NewContainer(ctx, runtimeSpec, options...) + if err != nil { + return nil, err + } + return ctr, nil +} + +func makeHealthCheckFromCli(c *GenericCLIResults) (*manifest.Schema2HealthConfig, error) { + inCommand := c.String("healthcheck-command") + inInterval := c.String("healthcheck-interval") + inRetries := c.Uint("healthcheck-retries") + inTimeout := c.String("healthcheck-timeout") + inStartPeriod := c.String("healthcheck-start-period") + + // Every healthcheck requires a command + if len(inCommand) == 0 { + return nil, errors.New("Must define a healthcheck command for all healthchecks") + } + + // first try to parse option value as JSON array of strings... + cmd := []string{} + err := json.Unmarshal([]byte(inCommand), &cmd) + if err != nil { + // ...otherwise pass it to "/bin/sh -c" inside the container + cmd = []string{"CMD-SHELL", inCommand} + } + hc := manifest.Schema2HealthConfig{ + Test: cmd, + } + + if inInterval == "disable" { + inInterval = "0" + } + intervalDuration, err := time.ParseDuration(inInterval) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-interval %s ", inInterval) + } + + hc.Interval = intervalDuration + + if inRetries < 1 { + return nil, errors.New("healthcheck-retries must be greater than 0.") + } + hc.Retries = int(inRetries) + timeoutDuration, err := time.ParseDuration(inTimeout) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-timeout %s", inTimeout) + } + if timeoutDuration < time.Duration(1) { + return nil, errors.New("healthcheck-timeout must be at least 1 second") + } + hc.Timeout = timeoutDuration + + startPeriodDuration, err := time.ParseDuration(inStartPeriod) + if err != nil { + return nil, errors.Wrapf(err, "invalid healthcheck-start-period %s", inStartPeriod) + } + if startPeriodDuration < time.Duration(0) { + return nil, errors.New("healthcheck-start-period must be 0 seconds or greater") + } + hc.StartPeriod = startPeriodDuration + + return &hc, nil +} + +// GetNamespaceOptions transforms a slice of kernel namespaces +// into a slice of pod create options. Currently, not all +// kernel namespaces are supported, and they will be returned in an error +func GetNamespaceOptions(ns []string) ([]libpod.PodCreateOption, error) { + var options []libpod.PodCreateOption + var erroredOptions []libpod.PodCreateOption + for _, toShare := range ns { + switch toShare { + case "cgroup": + options = append(options, libpod.WithPodCgroups()) + case "net": + options = append(options, libpod.WithPodNet()) + case "mnt": + return erroredOptions, errors.Errorf("Mount sharing functionality not supported on pod level") + case "pid": + options = append(options, libpod.WithPodPID()) + case "user": + return erroredOptions, errors.Errorf("User sharing functionality not supported on pod level") + case "ipc": + options = append(options, libpod.WithPodIPC()) + case "uts": + options = append(options, libpod.WithPodUTS()) + case "": + case "none": + return erroredOptions, nil + default: + return erroredOptions, errors.Errorf("Invalid kernel namespace to share: %s. Options are: net, pid, ipc, uts or none", toShare) + } + } + return options, nil +} + +func addWarning(warnings []string, msg string) []string { + logrus.Warn(msg) + return append(warnings, msg) +} + +func verifyContainerResources(config *cc.CreateConfig, update bool) ([]string, error) { + warnings := []string{} + + cgroup2, err := cgroups.IsCgroup2UnifiedMode() + if err != nil || cgroup2 { + return warnings, err + } + + sysInfo := sysinfo.New(true) + + // memory subsystem checks and adjustments + if config.Resources.Memory > 0 && !sysInfo.MemoryLimit { + warnings = addWarning(warnings, "Your kernel does not support memory limit capabilities or the cgroup is not mounted. Limitation discarded.") + config.Resources.Memory = 0 + config.Resources.MemorySwap = -1 + } + if config.Resources.Memory > 0 && config.Resources.MemorySwap != -1 && !sysInfo.SwapLimit { + warnings = addWarning(warnings, "Your kernel does not support swap limit capabilities,or the cgroup is not mounted. Memory limited without swap.") + config.Resources.MemorySwap = -1 + } + if config.Resources.Memory > 0 && config.Resources.MemorySwap > 0 && config.Resources.MemorySwap < config.Resources.Memory { + return warnings, fmt.Errorf("minimum memoryswap limit should be larger than memory limit, see usage") + } + if config.Resources.Memory == 0 && config.Resources.MemorySwap > 0 && !update { + return warnings, fmt.Errorf("you should always set the memory limit when using memoryswap limit, see usage") + } + if config.Resources.MemorySwappiness != -1 { + if !sysInfo.MemorySwappiness { + msg := "Your kernel does not support memory swappiness capabilities, or the cgroup is not mounted. Memory swappiness discarded." + warnings = addWarning(warnings, msg) + config.Resources.MemorySwappiness = -1 + } else { + swappiness := config.Resources.MemorySwappiness + if swappiness < -1 || swappiness > 100 { + return warnings, fmt.Errorf("invalid value: %v, valid memory swappiness range is 0-100", swappiness) + } + } + } + if config.Resources.MemoryReservation > 0 && !sysInfo.MemoryReservation { + warnings = addWarning(warnings, "Your kernel does not support memory soft limit capabilities or the cgroup is not mounted. Limitation discarded.") + config.Resources.MemoryReservation = 0 + } + if config.Resources.Memory > 0 && config.Resources.MemoryReservation > 0 && config.Resources.Memory < config.Resources.MemoryReservation { + return warnings, fmt.Errorf("minimum memory limit cannot be less than memory reservation limit, see usage") + } + if config.Resources.KernelMemory > 0 && !sysInfo.KernelMemory { + warnings = addWarning(warnings, "Your kernel does not support kernel memory limit capabilities or the cgroup is not mounted. Limitation discarded.") + config.Resources.KernelMemory = 0 + } + if config.Resources.DisableOomKiller && !sysInfo.OomKillDisable { + // only produce warnings if the setting wasn't to *disable* the OOM Kill; no point + // warning the caller if they already wanted the feature to be off + warnings = addWarning(warnings, "Your kernel does not support OomKillDisable. OomKillDisable discarded.") + config.Resources.DisableOomKiller = false + } + + if config.Resources.PidsLimit != 0 && !sysInfo.PidsLimit { + warnings = addWarning(warnings, "Your kernel does not support pids limit capabilities or the cgroup is not mounted. PIDs limit discarded.") + config.Resources.PidsLimit = 0 + } + + if config.Resources.CPUShares > 0 && !sysInfo.CPUShares { + warnings = addWarning(warnings, "Your kernel does not support CPU shares or the cgroup is not mounted. Shares discarded.") + config.Resources.CPUShares = 0 + } + if config.Resources.CPUPeriod > 0 && !sysInfo.CPUCfsPeriod { + warnings = addWarning(warnings, "Your kernel does not support CPU cfs period or the cgroup is not mounted. Period discarded.") + config.Resources.CPUPeriod = 0 + } + if config.Resources.CPUPeriod != 0 && (config.Resources.CPUPeriod < 1000 || config.Resources.CPUPeriod > 1000000) { + return warnings, fmt.Errorf("CPU cfs period cannot be less than 1ms (i.e. 1000) or larger than 1s (i.e. 1000000)") + } + if config.Resources.CPUQuota > 0 && !sysInfo.CPUCfsQuota { + warnings = addWarning(warnings, "Your kernel does not support CPU cfs quota or the cgroup is not mounted. Quota discarded.") + config.Resources.CPUQuota = 0 + } + if config.Resources.CPUQuota > 0 && config.Resources.CPUQuota < 1000 { + return warnings, fmt.Errorf("CPU cfs quota cannot be less than 1ms (i.e. 1000)") + } + // cpuset subsystem checks and adjustments + if (config.Resources.CPUsetCPUs != "" || config.Resources.CPUsetMems != "") && !sysInfo.Cpuset { + warnings = addWarning(warnings, "Your kernel does not support cpuset or the cgroup is not mounted. CPUset discarded.") + config.Resources.CPUsetCPUs = "" + config.Resources.CPUsetMems = "" + } + cpusAvailable, err := sysInfo.IsCpusetCpusAvailable(config.Resources.CPUsetCPUs) + if err != nil { + return warnings, fmt.Errorf("invalid value %s for cpuset cpus", config.Resources.CPUsetCPUs) + } + if !cpusAvailable { + return warnings, fmt.Errorf("requested CPUs are not available - requested %s, available: %s", config.Resources.CPUsetCPUs, sysInfo.Cpus) + } + memsAvailable, err := sysInfo.IsCpusetMemsAvailable(config.Resources.CPUsetMems) + if err != nil { + return warnings, fmt.Errorf("invalid value %s for cpuset mems", config.Resources.CPUsetMems) + } + if !memsAvailable { + return warnings, fmt.Errorf("requested memory nodes are not available - requested %s, available: %s", config.Resources.CPUsetMems, sysInfo.Mems) + } + + // blkio subsystem checks and adjustments + if config.Resources.BlkioWeight > 0 && !sysInfo.BlkioWeight { + warnings = addWarning(warnings, "Your kernel does not support Block I/O weight or the cgroup is not mounted. Weight discarded.") + config.Resources.BlkioWeight = 0 + } + if config.Resources.BlkioWeight > 0 && (config.Resources.BlkioWeight < 10 || config.Resources.BlkioWeight > 1000) { + return warnings, fmt.Errorf("range of blkio weight is from 10 to 1000") + } + if len(config.Resources.BlkioWeightDevice) > 0 && !sysInfo.BlkioWeightDevice { + warnings = addWarning(warnings, "Your kernel does not support Block I/O weight_device or the cgroup is not mounted. Weight-device discarded.") + config.Resources.BlkioWeightDevice = []string{} + } + if len(config.Resources.DeviceReadBps) > 0 && !sysInfo.BlkioReadBpsDevice { + warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O read limit or the cgroup is not mounted. Block I/O BPS read limit discarded") + config.Resources.DeviceReadBps = []string{} + } + if len(config.Resources.DeviceWriteBps) > 0 && !sysInfo.BlkioWriteBpsDevice { + warnings = addWarning(warnings, "Your kernel does not support BPS Block I/O write limit or the cgroup is not mounted. Block I/O BPS write limit discarded.") + config.Resources.DeviceWriteBps = []string{} + } + if len(config.Resources.DeviceReadIOps) > 0 && !sysInfo.BlkioReadIOpsDevice { + warnings = addWarning(warnings, "Your kernel does not support IOPS Block read limit or the cgroup is not mounted. Block I/O IOPS read limit discarded.") + config.Resources.DeviceReadIOps = []string{} + } + if len(config.Resources.DeviceWriteIOps) > 0 && !sysInfo.BlkioWriteIOpsDevice { + warnings = addWarning(warnings, "Your kernel does not support IOPS Block I/O write limit or the cgroup is not mounted. Block I/O IOPS write limit discarded.") + config.Resources.DeviceWriteIOps = []string{} + } + + return warnings, nil +} diff --git a/pkg/varlinkapi/funcs.go b/pkg/varlinkapi/funcs.go new file mode 100644 index 000000000..ed90ba050 --- /dev/null +++ b/pkg/varlinkapi/funcs.go @@ -0,0 +1,121 @@ +package varlinkapi + +import ( + "fmt" + "os" + "path/filepath" + "strings" + + "github.com/containers/image/v5/types" + "github.com/containers/libpod/libpod/image" + "github.com/google/shlex" + "github.com/pkg/errors" +) + +func GetSystemContext(authfile string) (*types.SystemContext, error) { + if authfile != "" { + if _, err := os.Stat(authfile); err != nil { + return nil, errors.Wrapf(err, "error checking authfile path %s", authfile) + } + } + return image.GetSystemContext("", authfile, false), nil +} + +func substituteCommand(cmd string) (string, error) { + var ( + newCommand string + ) + + // Replace cmd with "/proc/self/exe" if "podman" or "docker" is being + // used. If "/usr/bin/docker" is provided, we also sub in podman. + // Otherwise, leave the command unchanged. + if cmd == "podman" || filepath.Base(cmd) == "docker" { + newCommand = "/proc/self/exe" + } else { + newCommand = cmd + } + + // If cmd is an absolute or relative path, check if the file exists. + // Throw an error if it doesn't exist. + if strings.Contains(newCommand, "/") || strings.HasPrefix(newCommand, ".") { + res, err := filepath.Abs(newCommand) + if err != nil { + return "", err + } + if _, err := os.Stat(res); !os.IsNotExist(err) { + return res, nil + } else if err != nil { + return "", err + } + } + + return newCommand, nil +} + +// GenerateCommand takes a label (string) and converts it to an executable command +func GenerateCommand(command, imageName, name, globalOpts string) ([]string, error) { + var ( + newCommand []string + ) + if name == "" { + name = imageName + } + + cmd, err := shlex.Split(command) + if err != nil { + return nil, err + } + + prog, err := substituteCommand(cmd[0]) + if err != nil { + return nil, err + } + newCommand = append(newCommand, prog) + + for _, arg := range cmd[1:] { + var newArg string + switch arg { + case "IMAGE": + newArg = imageName + case "$IMAGE": + newArg = imageName + case "IMAGE=IMAGE": + newArg = fmt.Sprintf("IMAGE=%s", imageName) + case "IMAGE=$IMAGE": + newArg = fmt.Sprintf("IMAGE=%s", imageName) + case "NAME": + newArg = name + case "NAME=NAME": + newArg = fmt.Sprintf("NAME=%s", name) + case "NAME=$NAME": + newArg = fmt.Sprintf("NAME=%s", name) + case "$NAME": + newArg = name + case "$GLOBAL_OPTS": + newArg = globalOpts + default: + newArg = arg + } + newCommand = append(newCommand, newArg) + } + return newCommand, nil +} + +// GenerateRunEnvironment merges the current environment variables with optional +// environment variables provided by the user +func GenerateRunEnvironment(name, imageName string, opts map[string]string) []string { + newEnv := os.Environ() + newEnv = append(newEnv, fmt.Sprintf("NAME=%s", name)) + newEnv = append(newEnv, fmt.Sprintf("IMAGE=%s", imageName)) + + if opts["opt1"] != "" { + newEnv = append(newEnv, fmt.Sprintf("OPT1=%s", opts["opt1"])) + } + if opts["opt2"] != "" { + newEnv = append(newEnv, fmt.Sprintf("OPT2=%s", opts["opt2"])) + } + if opts["opt3"] != "" { + newEnv = append(newEnv, fmt.Sprintf("OPT3=%s", opts["opt3"])) + } + return newEnv +} diff --git a/pkg/varlinkapi/generate.go b/pkg/varlinkapi/generate.go index 81a0df68e..4df185db6 100644 --- a/pkg/varlinkapi/generate.go +++ b/pkg/varlinkapi/generate.go @@ -5,13 +5,12 @@ package varlinkapi import ( "encoding/json" - "github.com/containers/libpod/cmd/podman/shared" iopodman "github.com/containers/libpod/pkg/varlink" ) // GenerateKube ... func (i *VarlinkAPI) GenerateKube(call iopodman.VarlinkCall, name string, service bool) error { - pod, serv, err := shared.GenerateKube(name, service, i.Runtime) + pod, serv, err := GenerateKube(name, service, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 49bd0b0cb..8d43b8414 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -20,7 +20,6 @@ import ( "github.com/containers/image/v5/manifest" "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" @@ -779,7 +778,7 @@ func (i *VarlinkAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman stdOut := os.Stdout stdIn := os.Stdin - runLabel, imageName, err := shared.GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, "", dockerRegistryOptions, input.Authfile, "", nil) + runLabel, imageName, err := GetRunlabel(input.Label, input.Image, ctx, i.Runtime, input.Pull, "", dockerRegistryOptions, input.Authfile, "", nil) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -787,7 +786,7 @@ func (i *VarlinkAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman return call.ReplyErrorOccurred(fmt.Sprintf("%s does not contain the label %s", input.Image, input.Label)) } - cmd, env, err := shared.GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "") + cmd, env, err := GenerateRunlabelCommand(runLabel, imageName, input.Name, input.Opts, input.ExtraArgs, "") if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/intermediate.go b/pkg/varlinkapi/intermediate.go new file mode 100644 index 000000000..f04665a86 --- /dev/null +++ b/pkg/varlinkapi/intermediate.go @@ -0,0 +1,289 @@ +package varlinkapi + +import ( + "github.com/sirupsen/logrus" +) + +/* +attention + +in this file you will see a lot of struct duplication. this was done because people wanted a strongly typed +varlink mechanism. this resulted in us creating this intermediate layer that allows us to take the input +from the cli and make an intermediate layer which can be transferred as strongly typed structures over a varlink +interface. + +we intentionally avoided heavy use of reflection here because we were concerned about performance impacts to the +non-varlink intermediate layer generation. +*/ + +// GenericCLIResult describes the overall interface for dealing with +// the create command cli in both local and remote uses +type GenericCLIResult interface { + IsSet() bool + Name() string + Value() interface{} +} + +// CRStringSlice describes a string slice cli struct +type CRStringSlice struct { + Val []string + createResult +} + +// CRString describes a string cli struct +type CRString struct { + Val string + createResult +} + +// CRUint64 describes a uint64 cli struct +type CRUint64 struct { + Val uint64 + createResult +} + +// CRFloat64 describes a float64 cli struct +type CRFloat64 struct { + Val float64 + createResult +} + +//CRBool describes a bool cli struct +type CRBool struct { + Val bool + createResult +} + +// CRInt64 describes an int64 cli struct +type CRInt64 struct { + Val int64 + createResult +} + +// CRUint describes a uint cli struct +type CRUint struct { + Val uint + createResult +} + +// CRInt describes an int cli struct +type CRInt struct { + Val int + createResult +} + +// CRStringArray describes a stringarray cli struct +type CRStringArray struct { + Val []string + createResult +} + +type createResult struct { + Flag string + Changed bool +} + +// GenericCLIResults in the intermediate object between the cobra cli +// and createconfig +type GenericCLIResults struct { + results map[string]GenericCLIResult + InputArgs []string +} + +// IsSet returns a bool if the flag was changed +func (f GenericCLIResults) IsSet(flag string) bool { + r := f.findResult(flag) + if r == nil { + return false + } + return r.IsSet() +} + +// Value returns the value of the cli flag +func (f GenericCLIResults) Value(flag string) interface{} { + r := f.findResult(flag) + if r == nil { + return "" + } + return r.Value() +} + +func (f GenericCLIResults) findResult(flag string) GenericCLIResult { + val, ok := f.results[flag] + if ok { + return val + } + logrus.Debugf("unable to find flag %s", flag) + return nil +} + +// Bool is a wrapper to get a bool value from GenericCLIResults +func (f GenericCLIResults) Bool(flag string) bool { + r := f.findResult(flag) + if r == nil { + return false + } + return r.Value().(bool) +} + +// String is a wrapper to get a string value from GenericCLIResults +func (f GenericCLIResults) String(flag string) string { + r := f.findResult(flag) + if r == nil { + return "" + } + return r.Value().(string) +} + +// Uint is a wrapper to get an uint value from GenericCLIResults +func (f GenericCLIResults) Uint(flag string) uint { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(uint) +} + +// StringSlice is a wrapper to get a stringslice value from GenericCLIResults +func (f GenericCLIResults) StringSlice(flag string) []string { + r := f.findResult(flag) + if r == nil { + return []string{} + } + return r.Value().([]string) +} + +// StringArray is a wrapper to get a stringslice value from GenericCLIResults +func (f GenericCLIResults) StringArray(flag string) []string { + r := f.findResult(flag) + if r == nil { + return []string{} + } + return r.Value().([]string) +} + +// Uint64 is a wrapper to get an uint64 value from GenericCLIResults +func (f GenericCLIResults) Uint64(flag string) uint64 { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(uint64) +} + +// Int64 is a wrapper to get an int64 value from GenericCLIResults +func (f GenericCLIResults) Int64(flag string) int64 { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(int64) +} + +// Int is a wrapper to get an int value from GenericCLIResults +func (f GenericCLIResults) Int(flag string) int { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(int) +} + +// Float64 is a wrapper to get an float64 value from GenericCLIResults +func (f GenericCLIResults) Float64(flag string) float64 { + r := f.findResult(flag) + if r == nil { + return 0 + } + return r.Value().(float64) +} + +// Float64 is a wrapper to get an float64 value from GenericCLIResults +func (f GenericCLIResults) Changed(flag string) bool { + r := f.findResult(flag) + if r == nil { + return false + } + return r.IsSet() +} + +// IsSet ... +func (c CRStringSlice) IsSet() bool { return c.Changed } + +// Name ... +func (c CRStringSlice) Name() string { return c.Flag } + +// Value ... +func (c CRStringSlice) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRString) IsSet() bool { return c.Changed } + +// Name ... +func (c CRString) Name() string { return c.Flag } + +// Value ... +func (c CRString) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRUint64) IsSet() bool { return c.Changed } + +// Name ... +func (c CRUint64) Name() string { return c.Flag } + +// Value ... +func (c CRUint64) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRFloat64) IsSet() bool { return c.Changed } + +// Name ... +func (c CRFloat64) Name() string { return c.Flag } + +// Value ... +func (c CRFloat64) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRBool) IsSet() bool { return c.Changed } + +// Name ... +func (c CRBool) Name() string { return c.Flag } + +// Value ... +func (c CRBool) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRInt64) IsSet() bool { return c.Changed } + +// Name ... +func (c CRInt64) Name() string { return c.Flag } + +// Value ... +func (c CRInt64) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRUint) IsSet() bool { return c.Changed } + +// Name ... +func (c CRUint) Name() string { return c.Flag } + +// Value ... +func (c CRUint) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRInt) IsSet() bool { return c.Changed } + +// Name ... +func (c CRInt) Name() string { return c.Flag } + +// Value ... +func (c CRInt) Value() interface{} { return c.Val } + +// IsSet ... +func (c CRStringArray) IsSet() bool { return c.Changed } + +// Name ... +func (c CRStringArray) Name() string { return c.Flag } + +// Value ... +func (c CRStringArray) Value() interface{} { return c.Val } diff --git a/pkg/varlinkapi/intermediate_varlink.go b/pkg/varlinkapi/intermediate_varlink.go new file mode 100644 index 000000000..21c57d4f4 --- /dev/null +++ b/pkg/varlinkapi/intermediate_varlink.go @@ -0,0 +1,457 @@ +// +build varlink remoteclient + +package varlinkapi + +import ( + "github.com/containers/common/pkg/config" + "github.com/containers/libpod/pkg/rootless" + iopodman "github.com/containers/libpod/pkg/varlink" + "github.com/pkg/errors" +) + +//FIXME these are duplicated here to resolve a circular +//import with cmd/podman/common. +var ( + // DefaultHealthCheckInterval default value + DefaultHealthCheckInterval = "30s" + // DefaultHealthCheckRetries default value + DefaultHealthCheckRetries uint = 3 + // DefaultHealthCheckStartPeriod default value + DefaultHealthCheckStartPeriod = "0s" + // DefaultHealthCheckTimeout default value + DefaultHealthCheckTimeout = "30s" + // DefaultImageVolume default value + DefaultImageVolume = "bind" +) + +// StringSliceToPtr converts a genericcliresult value into a *[]string +func StringSliceToPtr(g GenericCLIResult) *[]string { + if !g.IsSet() { + return nil + } + newT := g.Value().([]string) + return &newT +} + +// StringToPtr converts a genericcliresult value into a *string +func StringToPtr(g GenericCLIResult) *string { + if !g.IsSet() { + return nil + } + newT := g.Value().(string) + return &newT +} + +// BoolToPtr converts a genericcliresult value into a *bool +func BoolToPtr(g GenericCLIResult) *bool { + if !g.IsSet() { + return nil + } + newT := g.Value().(bool) + return &newT +} + +// AnyIntToInt64Ptr converts a genericcliresult value into an *int64 +func AnyIntToInt64Ptr(g GenericCLIResult) *int64 { + if !g.IsSet() { + return nil + } + var newT int64 + switch g.Value().(type) { + case int: + newT = int64(g.Value().(int)) + case int64: + newT = g.Value().(int64) + case uint64: + newT = int64(g.Value().(uint64)) + case uint: + newT = int64(g.Value().(uint)) + default: + panic(errors.Errorf("invalid int type")) + } + return &newT +} + +// Float64ToPtr converts a genericcliresult into a *float64 +func Float64ToPtr(g GenericCLIResult) *float64 { + if !g.IsSet() { + return nil + } + newT := g.Value().(float64) + return &newT +} + +// MakeVarlink creates a varlink transportable struct from GenericCLIResults +func (g GenericCLIResults) MakeVarlink() iopodman.Create { + v := iopodman.Create{ + Args: g.InputArgs, + AddHost: StringSliceToPtr(g.Find("add-host")), + Annotation: StringSliceToPtr(g.Find("annotation")), + Attach: StringSliceToPtr(g.Find("attach")), + BlkioWeight: StringToPtr(g.Find("blkio-weight")), + BlkioWeightDevice: StringSliceToPtr(g.Find("blkio-weight-device")), + CapAdd: StringSliceToPtr(g.Find("cap-add")), + CapDrop: StringSliceToPtr(g.Find("cap-drop")), + CgroupParent: StringToPtr(g.Find("cgroup-parent")), + CidFile: StringToPtr(g.Find("cidfile")), + ConmonPidfile: StringToPtr(g.Find("conmon-pidfile")), + CpuPeriod: AnyIntToInt64Ptr(g.Find("cpu-period")), + CpuQuota: AnyIntToInt64Ptr(g.Find("cpu-quota")), + CpuRtPeriod: AnyIntToInt64Ptr(g.Find("cpu-rt-period")), + CpuRtRuntime: AnyIntToInt64Ptr(g.Find("cpu-rt-runtime")), + CpuShares: AnyIntToInt64Ptr(g.Find("cpu-shares")), + Cpus: Float64ToPtr(g.Find("cpus")), + CpuSetCpus: StringToPtr(g.Find("cpuset-cpus")), + CpuSetMems: StringToPtr(g.Find("cpuset-mems")), + Detach: BoolToPtr(g.Find("detach")), + DetachKeys: StringToPtr(g.Find("detach-keys")), + Device: StringSliceToPtr(g.Find("device")), + DeviceReadBps: StringSliceToPtr(g.Find("device-read-bps")), + DeviceReadIops: StringSliceToPtr(g.Find("device-read-iops")), + DeviceWriteBps: StringSliceToPtr(g.Find("device-write-bps")), + DeviceWriteIops: StringSliceToPtr(g.Find("device-write-iops")), + Dns: StringSliceToPtr(g.Find("dns")), + DnsOpt: StringSliceToPtr(g.Find("dns-opt")), + DnsSearch: StringSliceToPtr(g.Find("dns-search")), + Entrypoint: StringToPtr(g.Find("entrypoint")), + Env: StringSliceToPtr(g.Find("env")), + EnvFile: StringSliceToPtr(g.Find("env-file")), + Expose: StringSliceToPtr(g.Find("expose")), + Gidmap: StringSliceToPtr(g.Find("gidmap")), + Groupadd: StringSliceToPtr(g.Find("group-add")), + HealthcheckCommand: StringToPtr(g.Find("healthcheck-command")), + HealthcheckInterval: StringToPtr(g.Find("healthcheck-interval")), + HealthcheckRetries: AnyIntToInt64Ptr(g.Find("healthcheck-retries")), + HealthcheckStartPeriod: StringToPtr(g.Find("healthcheck-start-period")), + HealthcheckTimeout: StringToPtr(g.Find("healthcheck-timeout")), + Hostname: StringToPtr(g.Find("hostname")), + ImageVolume: StringToPtr(g.Find("image-volume")), + Init: BoolToPtr(g.Find("init")), + InitPath: StringToPtr(g.Find("init-path")), + Interactive: BoolToPtr(g.Find("interactive")), + Ip: StringToPtr(g.Find("ip")), + Ipc: StringToPtr(g.Find("ipc")), + KernelMemory: StringToPtr(g.Find("kernel-memory")), + Label: StringSliceToPtr(g.Find("label")), + LabelFile: StringSliceToPtr(g.Find("label-file")), + LogDriver: StringToPtr(g.Find("log-driver")), + LogOpt: StringSliceToPtr(g.Find("log-opt")), + MacAddress: StringToPtr(g.Find("mac-address")), + Memory: StringToPtr(g.Find("memory")), + MemoryReservation: StringToPtr(g.Find("memory-reservation")), + MemorySwap: StringToPtr(g.Find("memory-swap")), + MemorySwappiness: AnyIntToInt64Ptr(g.Find("memory-swappiness")), + Name: StringToPtr(g.Find("name")), + Network: StringToPtr(g.Find("network")), + OomKillDisable: BoolToPtr(g.Find("oom-kill-disable")), + OomScoreAdj: AnyIntToInt64Ptr(g.Find("oom-score-adj")), + OverrideOS: StringToPtr(g.Find("override-os")), + OverrideArch: StringToPtr(g.Find("override-arch")), + Pid: StringToPtr(g.Find("pid")), + PidsLimit: AnyIntToInt64Ptr(g.Find("pids-limit")), + Pod: StringToPtr(g.Find("pod")), + Privileged: BoolToPtr(g.Find("privileged")), + Publish: StringSliceToPtr(g.Find("publish")), + PublishAll: BoolToPtr(g.Find("publish-all")), + Pull: StringToPtr(g.Find("pull")), + Quiet: BoolToPtr(g.Find("quiet")), + Readonly: BoolToPtr(g.Find("read-only")), + Readonlytmpfs: BoolToPtr(g.Find("read-only-tmpfs")), + Restart: StringToPtr(g.Find("restart")), + Rm: BoolToPtr(g.Find("rm")), + Rootfs: BoolToPtr(g.Find("rootfs")), + SecurityOpt: StringSliceToPtr(g.Find("security-opt")), + ShmSize: StringToPtr(g.Find("shm-size")), + StopSignal: StringToPtr(g.Find("stop-signal")), + StopTimeout: AnyIntToInt64Ptr(g.Find("stop-timeout")), + StorageOpt: StringSliceToPtr(g.Find("storage-opt")), + Subuidname: StringToPtr(g.Find("subuidname")), + Subgidname: StringToPtr(g.Find("subgidname")), + Sysctl: StringSliceToPtr(g.Find("sysctl")), + Systemd: StringToPtr(g.Find("systemd")), + Tmpfs: StringSliceToPtr(g.Find("tmpfs")), + Tty: BoolToPtr(g.Find("tty")), + Uidmap: StringSliceToPtr(g.Find("uidmap")), + Ulimit: StringSliceToPtr(g.Find("ulimit")), + User: StringToPtr(g.Find("user")), + Userns: StringToPtr(g.Find("userns")), + Uts: StringToPtr(g.Find("uts")), + Mount: StringSliceToPtr(g.Find("mount")), + Volume: StringSliceToPtr(g.Find("volume")), + VolumesFrom: StringSliceToPtr(g.Find("volumes-from")), + WorkDir: StringToPtr(g.Find("workdir")), + } + + return v +} + +func stringSliceFromVarlink(v *[]string, flagName string, defaultValue *[]string) CRStringSlice { + cr := CRStringSlice{} + if v == nil { + cr.Val = []string{} + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func stringFromVarlink(v *string, flagName string, defaultValue *string) CRString { + cr := CRString{} + if v == nil { + cr.Val = "" + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func boolFromVarlink(v *bool, flagName string, defaultValue bool) CRBool { + cr := CRBool{} + if v == nil { + // In case a cli bool default value is true + cr.Val = defaultValue + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func uint64FromVarlink(v *int64, flagName string, defaultValue *uint64) CRUint64 { + cr := CRUint64{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = uint64(*v) + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func int64FromVarlink(v *int64, flagName string, defaultValue *int64) CRInt64 { + cr := CRInt64{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func float64FromVarlink(v *float64, flagName string, defaultValue *float64) CRFloat64 { + cr := CRFloat64{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func uintFromVarlink(v *int64, flagName string, defaultValue *uint) CRUint { + cr := CRUint{} + if v == nil { + cr.Val = 0 + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = uint(*v) + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func stringArrayFromVarlink(v *[]string, flagName string, defaultValue *[]string) CRStringArray { + cr := CRStringArray{} + if v == nil { + cr.Val = []string{} + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Changed = false + } else { + cr.Val = *v + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +func intFromVarlink(v *int64, flagName string, defaultValue *int) CRInt { + cr := CRInt{} + if v == nil { + if defaultValue != nil { + cr.Val = *defaultValue + } + cr.Val = 0 + cr.Changed = false + } else { + cr.Val = int(*v) + cr.Changed = true + } + cr.Flag = flagName + return cr +} + +// VarlinkCreateToGeneric creates a GenericCLIResults from the varlink create +// structure. +func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { + // FIXME this will need to be fixed!!!!! With containers conf + //defaultContainerConfig := cliconfig.GetDefaultConfig() + // TODO | WARN + // We do not get a default network over varlink. Unlike the other default values for some cli + // elements, it seems it gets set to the default anyway. + + var memSwapDefault int64 = -1 + netModeDefault := "bridge" + systemdDefault := "true" + if rootless.IsRootless() { + netModeDefault = "slirp4netns" + } + + shmSize := config.DefaultShmSize + + m := make(map[string]GenericCLIResult) + m["add-host"] = stringSliceFromVarlink(opts.AddHost, "add-host", nil) + m["annotation"] = stringSliceFromVarlink(opts.Annotation, "annotation", nil) + m["attach"] = stringSliceFromVarlink(opts.Attach, "attach", nil) + m["blkio-weight"] = stringFromVarlink(opts.BlkioWeight, "blkio-weight", nil) + m["blkio-weight-device"] = stringSliceFromVarlink(opts.BlkioWeightDevice, "blkio-weight-device", nil) + m["cap-add"] = stringSliceFromVarlink(opts.CapAdd, "cap-add", nil) + m["cap-drop"] = stringSliceFromVarlink(opts.CapDrop, "cap-drop", nil) + m["cgroup-parent"] = stringFromVarlink(opts.CgroupParent, "cgroup-parent", nil) + m["cidfile"] = stringFromVarlink(opts.CidFile, "cidfile", nil) + m["conmon-pidfile"] = stringFromVarlink(opts.ConmonPidfile, "conmon-file", nil) + m["cpu-period"] = uint64FromVarlink(opts.CpuPeriod, "cpu-period", nil) + m["cpu-quota"] = int64FromVarlink(opts.CpuQuota, "quota", nil) + m["cpu-rt-period"] = uint64FromVarlink(opts.CpuRtPeriod, "cpu-rt-period", nil) + m["cpu-rt-runtime"] = int64FromVarlink(opts.CpuRtRuntime, "cpu-rt-quota", nil) + m["cpu-shares"] = uint64FromVarlink(opts.CpuShares, "cpu-shares", nil) + m["cpus"] = float64FromVarlink(opts.Cpus, "cpus", nil) + m["cpuset-cpus"] = stringFromVarlink(opts.CpuSetCpus, "cpuset-cpus", nil) + m["cpuset-mems"] = stringFromVarlink(opts.CpuSetMems, "cpuset-mems", nil) + m["detach"] = boolFromVarlink(opts.Detach, "detach", false) + m["detach-keys"] = stringFromVarlink(opts.DetachKeys, "detach-keys", nil) + m["device"] = stringSliceFromVarlink(opts.Device, "device", nil) + m["device-read-bps"] = stringSliceFromVarlink(opts.DeviceReadBps, "device-read-bps", nil) + m["device-read-iops"] = stringSliceFromVarlink(opts.DeviceReadIops, "device-read-iops", nil) + m["device-write-bps"] = stringSliceFromVarlink(opts.DeviceWriteBps, "write-device-bps", nil) + m["device-write-iops"] = stringSliceFromVarlink(opts.DeviceWriteIops, "write-device-iops", nil) + m["dns"] = stringSliceFromVarlink(opts.Dns, "dns", nil) + m["dns-opt"] = stringSliceFromVarlink(opts.DnsOpt, "dns-opt", nil) + m["dns-search"] = stringSliceFromVarlink(opts.DnsSearch, "dns-search", nil) + m["entrypoint"] = stringFromVarlink(opts.Entrypoint, "entrypoint", nil) + m["env"] = stringArrayFromVarlink(opts.Env, "env", nil) + m["env-file"] = stringSliceFromVarlink(opts.EnvFile, "env-file", nil) + m["expose"] = stringSliceFromVarlink(opts.Expose, "expose", nil) + m["gidmap"] = stringSliceFromVarlink(opts.Gidmap, "gidmap", nil) + m["group-add"] = stringSliceFromVarlink(opts.Groupadd, "group-add", nil) + m["healthcheck-command"] = stringFromVarlink(opts.HealthcheckCommand, "healthcheck-command", nil) + m["healthcheck-interval"] = stringFromVarlink(opts.HealthcheckInterval, "healthcheck-interval", &DefaultHealthCheckInterval) + m["healthcheck-retries"] = uintFromVarlink(opts.HealthcheckRetries, "healthcheck-retries", &DefaultHealthCheckRetries) + m["healthcheck-start-period"] = stringFromVarlink(opts.HealthcheckStartPeriod, "healthcheck-start-period", &DefaultHealthCheckStartPeriod) + m["healthcheck-timeout"] = stringFromVarlink(opts.HealthcheckTimeout, "healthcheck-timeout", &DefaultHealthCheckTimeout) + m["hostname"] = stringFromVarlink(opts.Hostname, "hostname", nil) + m["image-volume"] = stringFromVarlink(opts.ImageVolume, "image-volume", &DefaultImageVolume) + m["init"] = boolFromVarlink(opts.Init, "init", false) + m["init-path"] = stringFromVarlink(opts.InitPath, "init-path", nil) + m["interactive"] = boolFromVarlink(opts.Interactive, "interactive", false) + m["ip"] = stringFromVarlink(opts.Ip, "ip", nil) + m["ipc"] = stringFromVarlink(opts.Ipc, "ipc", nil) + m["kernel-memory"] = stringFromVarlink(opts.KernelMemory, "kernel-memory", nil) + m["label"] = stringArrayFromVarlink(opts.Label, "label", nil) + m["label-file"] = stringSliceFromVarlink(opts.LabelFile, "label-file", nil) + m["log-driver"] = stringFromVarlink(opts.LogDriver, "log-driver", nil) + m["log-opt"] = stringSliceFromVarlink(opts.LogOpt, "log-opt", nil) + m["mac-address"] = stringFromVarlink(opts.MacAddress, "mac-address", nil) + m["memory"] = stringFromVarlink(opts.Memory, "memory", nil) + m["memory-reservation"] = stringFromVarlink(opts.MemoryReservation, "memory-reservation", nil) + m["memory-swap"] = stringFromVarlink(opts.MemorySwap, "memory-swap", nil) + m["memory-swappiness"] = int64FromVarlink(opts.MemorySwappiness, "memory-swappiness", &memSwapDefault) + m["name"] = stringFromVarlink(opts.Name, "name", nil) + m["network"] = stringFromVarlink(opts.Network, "network", &netModeDefault) + m["no-hosts"] = boolFromVarlink(opts.NoHosts, "no-hosts", false) + m["oom-kill-disable"] = boolFromVarlink(opts.OomKillDisable, "oon-kill-disable", false) + m["oom-score-adj"] = intFromVarlink(opts.OomScoreAdj, "oom-score-adj", nil) + m["override-os"] = stringFromVarlink(opts.OverrideOS, "override-os", nil) + m["override-arch"] = stringFromVarlink(opts.OverrideArch, "override-arch", nil) + m["pid"] = stringFromVarlink(opts.Pid, "pid", nil) + m["pids-limit"] = int64FromVarlink(opts.PidsLimit, "pids-limit", nil) + m["pod"] = stringFromVarlink(opts.Pod, "pod", nil) + m["privileged"] = boolFromVarlink(opts.Privileged, "privileged", false) + m["publish"] = stringSliceFromVarlink(opts.Publish, "publish", nil) + m["publish-all"] = boolFromVarlink(opts.PublishAll, "publish-all", false) + m["pull"] = stringFromVarlink(opts.Pull, "missing", nil) + m["quiet"] = boolFromVarlink(opts.Quiet, "quiet", false) + m["read-only"] = boolFromVarlink(opts.Readonly, "read-only", false) + m["read-only-tmpfs"] = boolFromVarlink(opts.Readonlytmpfs, "read-only-tmpfs", true) + m["restart"] = stringFromVarlink(opts.Restart, "restart", nil) + m["rm"] = boolFromVarlink(opts.Rm, "rm", false) + m["rootfs"] = boolFromVarlink(opts.Rootfs, "rootfs", false) + m["security-opt"] = stringArrayFromVarlink(opts.SecurityOpt, "security-opt", nil) + m["shm-size"] = stringFromVarlink(opts.ShmSize, "shm-size", &shmSize) + m["stop-signal"] = stringFromVarlink(opts.StopSignal, "stop-signal", nil) + m["stop-timeout"] = uintFromVarlink(opts.StopTimeout, "stop-timeout", nil) + m["storage-opt"] = stringSliceFromVarlink(opts.StorageOpt, "storage-opt", nil) + m["subgidname"] = stringFromVarlink(opts.Subgidname, "subgidname", nil) + m["subuidname"] = stringFromVarlink(opts.Subuidname, "subuidname", nil) + m["sysctl"] = stringSliceFromVarlink(opts.Sysctl, "sysctl", nil) + m["systemd"] = stringFromVarlink(opts.Systemd, "systemd", &systemdDefault) + m["tmpfs"] = stringSliceFromVarlink(opts.Tmpfs, "tmpfs", nil) + m["tty"] = boolFromVarlink(opts.Tty, "tty", false) + m["uidmap"] = stringSliceFromVarlink(opts.Uidmap, "uidmap", nil) + m["ulimit"] = stringSliceFromVarlink(opts.Ulimit, "ulimit", nil) + m["user"] = stringFromVarlink(opts.User, "user", nil) + m["userns"] = stringFromVarlink(opts.Userns, "userns", nil) + m["uts"] = stringFromVarlink(opts.Uts, "uts", nil) + m["mount"] = stringArrayFromVarlink(opts.Mount, "mount", nil) + m["volume"] = stringArrayFromVarlink(opts.Volume, "volume", nil) + m["volumes-from"] = stringSliceFromVarlink(opts.VolumesFrom, "volumes-from", nil) + m["workdir"] = stringFromVarlink(opts.WorkDir, "workdir", nil) + + gcli := GenericCLIResults{m, opts.Args} + return gcli +} + +// Find returns a flag from a GenericCLIResults by name +func (g GenericCLIResults) Find(name string) GenericCLIResult { + result, ok := g.results[name] + if ok { + return result + } + panic(errors.Errorf("unable to find generic flag for varlink %s", name)) +} diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 94add1b6c..5a9360447 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -5,11 +5,14 @@ package varlinkapi import ( "encoding/json" "fmt" + "strconv" "syscall" - "github.com/containers/libpod/cmd/podman/shared" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/docker/go-connections/nat" + "github.com/pkg/errors" + "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/adapter/shortcuts" iopodman "github.com/containers/libpod/pkg/varlink" ) @@ -18,7 +21,7 @@ func (i *VarlinkAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCre var options []libpod.PodCreateOption if create.Infra { options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.GetNamespaceOptions(create.Share) + nsOptions, err := GetNamespaceOptions(create.Share) if err != nil { return err } @@ -44,7 +47,7 @@ func (i *VarlinkAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCre if !create.Infra { return call.ReplyErrorOccurred("you must have an infra container to publish port bindings to the host") } - portBindings, err := shared.CreatePortBindings(create.Publish) + portBindings, err := CreatePortBindings(create.Publish) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -70,7 +73,7 @@ func (i *VarlinkAPI) ListPods(call iopodman.VarlinkCall) error { if err != nil { return call.ReplyErrorOccurred(err.Error()) } - opts := shared.PsOptions{} + opts := PsOptions{} for _, pod := range pods { listPod, err := makeListPod(pod, opts) if err != nil { @@ -87,7 +90,7 @@ func (i *VarlinkAPI) GetPod(call iopodman.VarlinkCall, name string) error { if err != nil { return call.ReplyPodNotFound(name, err.Error()) } - opts := shared.PsOptions{} + opts := PsOptions{} listPod, err := makeListPod(pod, opts) if err != nil { @@ -100,7 +103,7 @@ func (i *VarlinkAPI) GetPod(call iopodman.VarlinkCall, name string) error { // GetPodsByStatus returns a slice of pods filtered by a libpod status func (i *VarlinkAPI) GetPodsByStatus(call iopodman.VarlinkCall, statuses []string) error { filterFuncs := func(p *libpod.Pod) bool { - state, _ := shared.GetPodStatus(p) + state, _ := p.GetPodStatus() for _, status := range statuses { if state == status { return true @@ -290,11 +293,11 @@ func (i *VarlinkAPI) GetPodStats(call iopodman.VarlinkCall, name string) error { return call.ReplyGetPodStats(pod.ID(), containersStats) } -// GetPodsByContext returns a slice of pod ids based on all, latest, or a list +// getPodsByContext returns a slice of pod ids based on all, latest, or a list func (i *VarlinkAPI) GetPodsByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { var podids []string - pods, err := shortcuts.GetPodsByContext(all, latest, input, i.Runtime) + pods, err := getPodsByContext(all, latest, input, i.Runtime) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -337,7 +340,7 @@ func (i *VarlinkAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, return call.ReplyPodNotFound(name, err.Error()) } - podStatus, err := shared.GetPodStatus(pod) + podStatus, err := pod.GetPodStatus() if err != nil { return call.ReplyErrorOccurred(fmt.Sprintf("unable to get status for pod %s", pod.ID())) } @@ -350,3 +353,36 @@ func (i *VarlinkAPI) TopPod(call iopodman.VarlinkCall, name string, latest bool, } return call.ReplyTopPod(reply) } + +// CreatePortBindings iterates ports mappings and exposed ports into a format CNI understands +func CreatePortBindings(ports []string) ([]ocicni.PortMapping, error) { + var portBindings []ocicni.PortMapping + // The conversion from []string to natBindings is temporary while mheon reworks the port + // deduplication code. Eventually that step will not be required. + _, natBindings, err := nat.ParsePortSpecs(ports) + if err != nil { + return nil, err + } + for containerPb, hostPb := range natBindings { + var pm ocicni.PortMapping + pm.ContainerPort = int32(containerPb.Int()) + for _, i := range hostPb { + var hostPort int + var err error + pm.HostIP = i.HostIP + if i.HostPort == "" { + hostPort = containerPb.Int() + } else { + hostPort, err = strconv.Atoi(i.HostPort) + if err != nil { + return nil, errors.Wrapf(err, "unable to convert host port to integer") + } + } + + pm.HostPort = int32(hostPort) + pm.Protocol = containerPb.Proto() + portBindings = append(portBindings, pm) + } + } + return portBindings, nil +} diff --git a/pkg/varlinkapi/shortcuts.go b/pkg/varlinkapi/shortcuts.go new file mode 100644 index 000000000..771129404 --- /dev/null +++ b/pkg/varlinkapi/shortcuts.go @@ -0,0 +1,66 @@ +package varlinkapi + +import ( + "github.com/containers/libpod/libpod" + "github.com/sirupsen/logrus" +) + +// getPodsByContext returns a slice of pods. Note that all, latest and pods are +// mutually exclusive arguments. +func getPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) ([]*libpod.Pod, error) { + var outpods []*libpod.Pod + if all { + return runtime.GetAllPods() + } + if latest { + p, err := runtime.GetLatestPod() + if err != nil { + return nil, err + } + outpods = append(outpods, p) + return outpods, nil + } + var err error + for _, p := range pods { + pod, e := runtime.LookupPod(p) + if e != nil { + // Log all errors here, so callers don't need to. + logrus.Debugf("Error looking up pod %q: %v", p, e) + if err == nil { + err = e + } + } else { + outpods = append(outpods, pod) + } + } + return outpods, err +} + +// getContainersByContext gets pods whether all, latest, or a slice of names/ids +// is specified. +func getContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) (ctrs []*libpod.Container, err error) { + var ctr *libpod.Container + ctrs = []*libpod.Container{} + + switch { + case all: + ctrs, err = runtime.GetAllContainers() + case latest: + ctr, err = runtime.GetLatestContainer() + ctrs = append(ctrs, ctr) + default: + for _, n := range names { + ctr, e := runtime.LookupContainer(n) + if e != nil { + // Log all errors here, so callers don't need to. + logrus.Debugf("Error looking up container %q: %v", n, e) + if err == nil { + err = e + } + } else { + ctrs = append(ctrs, ctr) + } + } + } + return +} diff --git a/pkg/varlinkapi/util.go b/pkg/varlinkapi/util.go index 6b196f384..f73e77249 100644 --- a/pkg/varlinkapi/util.go +++ b/pkg/varlinkapi/util.go @@ -9,7 +9,6 @@ import ( "time" "github.com/containers/buildah" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/pkg/channelwriter" @@ -22,12 +21,12 @@ func getContext() context.Context { return context.TODO() } -func makeListContainer(containerID string, batchInfo shared.BatchContainerStruct) iopodman.Container { +func makeListContainer(containerID string, batchInfo BatchContainerStruct) iopodman.Container { var ( mounts []iopodman.ContainerMount ports []iopodman.ContainerPortMappings ) - ns := shared.GetNamespaces(batchInfo.Pid) + ns := GetNamespaces(batchInfo.Pid) for _, mount := range batchInfo.ConConfig.Spec.Mounts { m := iopodman.ContainerMount{ @@ -85,7 +84,7 @@ func makeListContainer(containerID string, batchInfo shared.BatchContainerStruct return lc } -func makeListPodContainers(containerID string, batchInfo shared.BatchContainerStruct) iopodman.ListPodContainerInfo { +func makeListPodContainers(containerID string, batchInfo BatchContainerStruct) iopodman.ListPodContainerInfo { lc := iopodman.ListPodContainerInfo{ Id: containerID, Status: batchInfo.ConState.String(), @@ -94,10 +93,10 @@ func makeListPodContainers(containerID string, batchInfo shared.BatchContainerSt return lc } -func makeListPod(pod *libpod.Pod, batchInfo shared.PsOptions) (iopodman.ListPodData, error) { +func makeListPod(pod *libpod.Pod, batchInfo PsOptions) (iopodman.ListPodData, error) { var listPodsContainers []iopodman.ListPodContainerInfo var errPodData = iopodman.ListPodData{} - status, err := shared.GetPodStatus(pod) + status, err := pod.GetPodStatus() if err != nil { return errPodData, err } @@ -106,7 +105,7 @@ func makeListPod(pod *libpod.Pod, batchInfo shared.PsOptions) (iopodman.ListPodD return errPodData, err } for _, ctr := range containers { - batchInfo, err := shared.BatchContainerOp(ctr, batchInfo) + batchInfo, err := BatchContainerOp(ctr, batchInfo) if err != nil { return errPodData, err } @@ -179,13 +178,13 @@ func derefString(in *string) string { return *in } -func makePsOpts(inOpts iopodman.PsOpts) shared.PsOptions { +func makePsOpts(inOpts iopodman.PsOpts) PsOptions { last := 0 if inOpts.Last != nil { lastT := *inOpts.Last last = int(lastT) } - return shared.PsOptions{ + return PsOptions{ All: inOpts.All, Last: last, Latest: derefBool(inOpts.Latest), diff --git a/pkg/varlinkapi/volumes.go b/pkg/varlinkapi/volumes.go index ff72c3869..aa0eb1fb5 100644 --- a/pkg/varlinkapi/volumes.go +++ b/pkg/varlinkapi/volumes.go @@ -3,10 +3,11 @@ package varlinkapi import ( + "context" "encoding/json" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/domain/infra/abi/parse" iopodman "github.com/containers/libpod/pkg/varlink" ) @@ -24,7 +25,7 @@ func (i *VarlinkAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.Vo volumeOptions = append(volumeOptions, libpod.WithVolumeLabels(options.Labels)) } if len(options.Options) > 0 { - parsedOptions, err := shared.ParseVolumeOptions(options.Options) + parsedOptions, err := parse.ParseVolumeOptions(options.Options) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -39,7 +40,7 @@ func (i *VarlinkAPI) VolumeCreate(call iopodman.VarlinkCall, options iopodman.Vo // VolumeRemove removes volumes by options.All or options.Volumes func (i *VarlinkAPI) VolumeRemove(call iopodman.VarlinkCall, options iopodman.VolumeRemoveOpts) error { - success, failed, err := shared.SharedRemoveVolumes(getContext(), i.Runtime, options.Volumes, options.All, options.Force) + success, failed, err := SharedRemoveVolumes(getContext(), i.Runtime, options.Volumes, options.All, options.Force) if err != nil { return call.ReplyErrorOccurred(err.Error()) } @@ -122,3 +123,43 @@ func (i *VarlinkAPI) VolumesPrune(call iopodman.VarlinkCall) error { } return call.ReplyVolumesPrune(prunedNames, prunedErrors) } + +// Remove given set of volumes +func SharedRemoveVolumes(ctx context.Context, runtime *libpod.Runtime, vols []string, all, force bool) ([]string, map[string]error, error) { + var ( + toRemove []*libpod.Volume + success []string + failed map[string]error + ) + + failed = make(map[string]error) + + if all { + vols, err := runtime.Volumes() + if err != nil { + return nil, nil, err + } + toRemove = vols + } else { + for _, v := range vols { + vol, err := runtime.LookupVolume(v) + if err != nil { + failed[v] = err + continue + } + toRemove = append(toRemove, vol) + } + } + + // We could parallelize this, but I haven't heard anyone complain about + // performance here yet, so hold off. + for _, vol := range toRemove { + if err := runtime.RemoveVolume(ctx, vol, force); err != nil { + failed[vol.Name()] = err + continue + } + success = append(success, vol.Name()) + } + + return success, failed, nil +} |