diff options
Diffstat (limited to 'pkg')
-rw-r--r-- | pkg/adapter/containers.go | 113 | ||||
-rw-r--r-- | pkg/adapter/containers_remote.go | 2 | ||||
-rw-r--r-- | pkg/adapter/pods.go | 109 | ||||
-rw-r--r-- | pkg/adapter/reset.go | 13 | ||||
-rw-r--r-- | pkg/adapter/reset_remote.go | 12 | ||||
-rw-r--r-- | pkg/adapter/runtime.go | 6 | ||||
-rw-r--r-- | pkg/adapter/runtime_remote.go | 71 | ||||
-rw-r--r-- | pkg/adapter/shortcuts/shortcuts.go | 32 | ||||
-rw-r--r-- | pkg/rootless/rootless_linux.c | 33 | ||||
-rw-r--r-- | pkg/timetype/timestamp.go | 131 | ||||
-rw-r--r-- | pkg/timetype/timestamp_test.go | 95 | ||||
-rw-r--r-- | pkg/util/utils.go | 310 | ||||
-rw-r--r-- | pkg/util/utils_supported.go | 4 | ||||
-rw-r--r-- | pkg/util/utils_test.go | 308 | ||||
-rw-r--r-- | pkg/varlinkapi/images.go | 12 | ||||
-rw-r--r-- | pkg/varlinkapi/pods.go | 2 | ||||
-rw-r--r-- | pkg/varlinkapi/system.go | 22 |
17 files changed, 1024 insertions, 251 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 287bd8474..6d139d0bf 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -79,8 +79,18 @@ func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopVa } logrus.Debugf("Setting maximum stop workers to %d", maxWorkers) - ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) - if err != nil { + names := cli.InputArgs + for _, cidFile := range cli.CIDFiles { + content, err := ioutil.ReadFile(cidFile) + if err != nil { + return nil, nil, errors.Wrap(err, "error reading CIDFile") + } + id := strings.Split(string(content), "\n")[0] + names = append(names, id) + } + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime) + if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { return nil, nil, err } @@ -203,8 +213,18 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa return ok, failures, nil } - ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) - if err != nil { + names := cli.InputArgs + for _, cidFile := range cli.CIDFiles { + content, err := ioutil.ReadFile(cidFile) + if err != nil { + return nil, nil, errors.Wrap(err, "error reading CIDFile") + } + id := strings.Split(string(content), "\n")[0] + names = append(names, id) + } + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime) + if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) { // Failed to get containers. If force is specified, get the containers ID // and evict them if !cli.Force { @@ -215,6 +235,10 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa logrus.Debugf("Evicting container %q", ctr) id, err := r.EvictContainer(ctx, ctr, cli.Volumes) if err != nil { + if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr { + logrus.Debugf("Ignoring error (--allow-missing): %v", err) + continue + } failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id) continue } @@ -232,6 +256,10 @@ func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmVa Fn: func() error { err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes) if err != nil { + if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr { + logrus.Debugf("Ignoring error (--allow-missing): %v", err) + return nil + } logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error()) } return err @@ -339,6 +367,23 @@ func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateV return ctr.ID(), nil } +// Select the detach keys to use from user input flag, config file, or default value +func (r *LocalRuntime) selectDetachKeys(flagValue string) (string, error) { + if flagValue != "" { + return flagValue, nil + } + + config, err := r.GetConfig() + if err != nil { + return "", errors.Wrapf(err, "unable to retrive runtime config") + } + if config.DetachKeys != "" { + return config.DetachKeys, nil + } + + return define.DefaultDetachKeys, nil +} + // Run a libpod container func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { results := shared.NewIntermediateLayer(&c.PodmanCommand, false) @@ -400,8 +445,13 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode } } + keys, err := r.selectDetachKeys(c.String("detach-keys")) + if err != nil { + return exitCode, err + } + // if the container was created as part of a pod, also start its dependencies, if any. - if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, c.String("detach-keys"), c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { + if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, keys, c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil { // We've manually detached from the container // Do not perform cleanup, or wait for container exit code // Just exit immediately @@ -433,7 +483,8 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode if c.IsSet("rm") { if err := r.Runtime.RemoveContainer(ctx, ctr, false, true); err != nil { - if errors.Cause(err) == define.ErrNoSuchCtr { + if errors.Cause(err) == define.ErrNoSuchCtr || + errors.Cause(err) == define.ErrCtrRemoved { logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err) } else { logrus.Errorf("Error removing container %s: %v", ctr.ID(), err) @@ -483,8 +534,14 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er if c.NoStdin { inputStream = nil } + + keys, err := r.selectDetachKeys(c.DetachKeys) + if err != nil { + return err + } + // If the container is in a pod, also set to recursively start dependencies - if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach { + if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, keys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach { return errors.Wrapf(err, "error attaching to container %s", ctr.ID()) } return nil @@ -617,9 +674,14 @@ func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigP } } + keys, err := r.selectDetachKeys(c.DetachKeys) + if err != nil { + return exitCode, err + } + // attach to the container and also start it not already running // If the container is in a pod, also set to recursively start dependencies - err = StartAttachCtr(ctx, ctr.Container, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "") + err = StartAttachCtr(ctx, ctr.Container, os.Stdout, os.Stderr, inputStream, keys, sigProxy, !ctrRunning, ctr.PodID() != "") if errors.Cause(err) == define.ErrDetach { // User manually detached // Exit cleanly immediately @@ -976,21 +1038,40 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal streams.AttachOutput = true streams.AttachError = true - ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, env, cmd, cli.User, cli.Workdir, streams, uint(cli.PreserveFDs), cli.DetachKeys) + keys, err := r.selectDetachKeys(cli.DetachKeys) + if err != nil { + return ec, err + } + + ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, env, cmd, cli.User, cli.Workdir, streams, uint(cli.PreserveFDs), keys) return define.TranslateExecErrorToExitCode(ec, err), err } // Prune removes stopped containers -func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([]string, map[string]error, error) { +func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool, filters []string) ([]string, map[string]error, error) { var ( - ok = []string{} - failures = map[string]error{} - err error + ok = []string{} + failures = map[string]error{} + err error + filterFunc []libpod.ContainerFilter ) logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) - filter := func(c *libpod.Container) bool { + for _, filter := range filters { + filterSplit := strings.SplitN(filter, "=", 2) + if len(filterSplit) < 2 { + return ok, failures, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", filter) + } + + f, err := shared.GenerateContainerFilterFuncs(filterSplit[0], filterSplit[1], r.Runtime) + if err != nil { + return ok, failures, err + } + filterFunc = append(filterFunc, f) + } + + containerStateFilter := func(c *libpod.Container) bool { state, err := c.State() if err != nil { logrus.Error(err) @@ -1004,7 +1085,9 @@ func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([ } return false } - delContainers, err := r.Runtime.GetContainers(filter) + filterFunc = append(filterFunc, containerStateFilter) + + delContainers, err := r.Runtime.GetContainers(filterFunc...) if err != nil { return ok, failures, err } diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index e34b8ffd9..36db4af68 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -922,7 +922,7 @@ func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) { } // Prune removes stopped containers -func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([]string, map[string]error, error) { +func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool, filter []string) ([]string, map[string]error, error) { var ( ok = []string{} diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index eafcc5e9b..a726153c0 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -15,8 +15,10 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/adapter/shortcuts" + ann "github.com/containers/libpod/pkg/annotations" ns "github.com/containers/libpod/pkg/namespaces" createconfig "github.com/containers/libpod/pkg/spec" "github.com/containers/libpod/pkg/util" @@ -75,7 +77,7 @@ func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneVal pool.Add(shared.Job{ ID: p.ID(), Fn: func() error { - err := r.Runtime.RemovePod(ctx, p, cli.Force, cli.Force) + err := r.Runtime.RemovePod(ctx, p, true, cli.Force) if err != nil { logrus.Debugf("Failed to remove pod %s: %s", p.ID(), err.Error()) } @@ -93,13 +95,13 @@ func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValue podids []string ) pods, err := shortcuts.GetPodsByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) - if err != nil { + if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { errs = append(errs, err) return nil, errs } for _, p := range pods { - if err := r.Runtime.RemovePod(ctx, p, cli.Force, cli.Force); err != nil { + if err := r.Runtime.RemovePod(ctx, p, true, cli.Force); err != nil { errs = append(errs, err) } else { podids = append(podids, p.ID()) @@ -150,7 +152,7 @@ func (r *LocalRuntime) StopPods(ctx context.Context, cli *cliconfig.PodStopValue podids []string ) pods, err := shortcuts.GetPodsByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) - if err != nil { + if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchPod) { errs = append(errs, err) return nil, errs } @@ -595,12 +597,17 @@ func (r *LocalRuntime) PlayKubeYAML(ctx context.Context, c *cliconfig.KubePlayVa volumes[volume.Name] = hostPath.Path } + seccompPaths, err := initializeSeccompPaths(podYAML.ObjectMeta.Annotations) + if err != nil { + return nil, err + } + for _, container := range podYAML.Spec.Containers { newImage, err := r.ImageRuntime().New(ctx, container.Image, c.SignaturePolicy, c.Authfile, writer, &dockerRegistryOptions, image.SigningOptions{}, nil, util.PullImageMissing) if err != nil { return nil, err } - createConfig, err := kubeContainerToCreateConfig(ctx, container, r.Runtime, newImage, namespaces, volumes, pod.ID()) + createConfig, err := kubeContainerToCreateConfig(ctx, container, r.Runtime, newImage, namespaces, volumes, pod.ID(), podInfraID, seccompPaths) if err != nil { return nil, err } @@ -719,7 +726,7 @@ func setupSecurityContext(securityConfig *createconfig.SecurityConfig, userConfi } // kubeContainerToCreateConfig takes a v1.Container and returns a createconfig describing a container -func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID string) (*createconfig.CreateConfig, error) { +func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container, runtime *libpod.Runtime, newImage *image.Image, namespaces map[string]string, volumes map[string]string, podID, infraID string, seccompPaths *kubeSeccompPaths) (*createconfig.CreateConfig, error) { var ( containerConfig createconfig.CreateConfig pidConfig createconfig.PidConfig @@ -751,11 +758,7 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container setupSecurityContext(&securityConfig, &userConfig, containerYAML) - var err error - containerConfig.Security.SeccompProfilePath, err = libpod.DefaultSeccompPath() - if err != nil { - return nil, err - } + securityConfig.SeccompProfilePath = seccompPaths.findForContainer(containerConfig.Name) containerConfig.Command = []string{} if imageData != nil && imageData.Config != nil { @@ -800,6 +803,13 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container // Set default environment variables and incorporate data from image, if necessary envs := shared.EnvVariablesFromData(imageData) + annotations := make(map[string]string) + if infraID != "" { + annotations[ann.SandboxID] = infraID + annotations[ann.ContainerType] = ann.ContainerTypeContainer + } + containerConfig.Annotations = annotations + // Environment Variables for _, e := range containerYAML.Env { envs[e.Name] = e.Value @@ -818,3 +828,80 @@ func kubeContainerToCreateConfig(ctx context.Context, containerYAML v1.Container } return &containerConfig, nil } + +// kubeSeccompPaths holds information about a pod YAML's seccomp configuration +// it holds both container and pod seccomp paths +type kubeSeccompPaths struct { + containerPaths map[string]string + podPath string +} + +// findForContainer checks whether a container has a seccomp path configured for it +// if not, it returns the podPath, which should always have a value +func (k *kubeSeccompPaths) findForContainer(ctrName string) string { + if path, ok := k.containerPaths[ctrName]; ok { + return path + } + return k.podPath +} + +// initializeSeccompPaths takes annotations from the pod object metadata and finds annotations pertaining to seccomp +// it parses both pod and container level +func initializeSeccompPaths(annotations map[string]string) (*kubeSeccompPaths, error) { + seccompPaths := &kubeSeccompPaths{containerPaths: make(map[string]string)} + var err error + if annotations != nil { + for annKeyValue, seccomp := range annotations { + // check if it is prefaced with container.seccomp.security.alpha.kubernetes.io/ + prefixAndCtr := strings.Split(annKeyValue, "/") + if prefixAndCtr[0]+"/" != v1.SeccompContainerAnnotationKeyPrefix { + continue + } else if len(prefixAndCtr) != 2 { + // this could be caused by a user inputting either of + // container.seccomp.security.alpha.kubernetes.io{,/} + // both of which are invalid + return nil, errors.Errorf("Invalid seccomp path: %s", prefixAndCtr[0]) + } + + path, err := verifySeccompPath(seccomp) + if err != nil { + return nil, err + } + seccompPaths.containerPaths[prefixAndCtr[1]] = path + } + + podSeccomp, ok := annotations[v1.SeccompPodAnnotationKey] + if ok { + seccompPaths.podPath, err = verifySeccompPath(podSeccomp) + } else { + seccompPaths.podPath, err = libpod.DefaultSeccompPath() + } + if err != nil { + return nil, err + } + } + return seccompPaths, nil +} + +// verifySeccompPath takes a path and checks whether it is a default, unconfined, or a path +// the available options are parsed as defined in https://kubernetes.io/docs/concepts/policy/pod-security-policy/#seccomp +func verifySeccompPath(path string) (string, error) { + switch path { + case v1.DeprecatedSeccompProfileDockerDefault: + fallthrough + case v1.SeccompProfileRuntimeDefault: + return libpod.DefaultSeccompPath() + case "unconfined": + return path, nil + default: + // TODO we have an inconsistency here + // k8s parses `localhost/<path>` which is found at `<seccomp_root>` + // we currently parse `localhost:<seccomp_root>/<path> + // to fully conform, we need to find a good location for the seccomp root + parts := strings.Split(path, ":") + if parts[0] == "localhost" { + return parts[1], nil + } + return "", errors.Errorf("invalid seccomp path: %s", path) + } +} diff --git a/pkg/adapter/reset.go b/pkg/adapter/reset.go new file mode 100644 index 000000000..0decc3d15 --- /dev/null +++ b/pkg/adapter/reset.go @@ -0,0 +1,13 @@ +// +build !remoteclient + +package adapter + +import ( + "context" +) + +// Reset the container storage back to initial states. +// Removes all Pods, Containers, Images and Volumes. +func (r *LocalRuntime) Reset() error { + return r.Runtime.Reset(context.TODO()) +} diff --git a/pkg/adapter/reset_remote.go b/pkg/adapter/reset_remote.go new file mode 100644 index 000000000..663fab639 --- /dev/null +++ b/pkg/adapter/reset_remote.go @@ -0,0 +1,12 @@ +// +build remoteclient + +package adapter + +import ( + "github.com/containers/libpod/cmd/podman/varlink" +) + +// Info returns information for the host system and its components +func (r RemoteRuntime) Reset() error { + return iopodman.Reset().Call(r.Conn) +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 81a43853c..069283bde 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -27,7 +27,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" - "k8s.io/api/core/v1" + v1 "k8s.io/api/core/v1" ) // LocalRuntime describes a typical libpod runtime @@ -147,8 +147,8 @@ func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, for } // PruneImages is wrapper into PruneImages within the image pkg -func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) { - return r.ImageRuntime().PruneImages(ctx, all) +func (r *LocalRuntime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) { + return r.ImageRuntime().PruneImages(ctx, all, filter) } // Export is a wrapper to container export to a tarfile diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 01bc8e454..f9232897c 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -136,21 +136,22 @@ type ContainerImage struct { } type remoteImage struct { - ID string - Labels map[string]string - RepoTags []string - RepoDigests []string - Parent string - Size int64 - Created time.Time - InputName string - Names []string - Digest digest.Digest - Digests []digest.Digest - isParent bool - Runtime *LocalRuntime - TopLayer string - ReadOnly bool + ID string + Labels map[string]string + RepoTags []string + RepoDigests []string + Parent string + Size int64 + Created time.Time + InputName string + Names []string + Digest digest.Digest + Digests []digest.Digest + isParent bool + Runtime *LocalRuntime + TopLayer string + ReadOnly bool + NamesHistory []string } // Container ... @@ -232,21 +233,22 @@ func imageInListToContainerImage(i iopodman.Image, name string, runtime *LocalRu digests = append(digests, digest.Digest(d)) } ri := remoteImage{ - InputName: name, - ID: i.Id, - Digest: digest.Digest(i.Digest), - Digests: digests, - Labels: i.Labels, - RepoTags: i.RepoTags, - RepoDigests: i.RepoTags, - Parent: i.ParentId, - Size: i.Size, - Created: created, - Names: i.RepoTags, - isParent: i.IsParent, - Runtime: runtime, - TopLayer: i.TopLayer, - ReadOnly: i.ReadOnly, + InputName: name, + ID: i.Id, + Digest: digest.Digest(i.Digest), + Digests: digests, + Labels: i.Labels, + RepoTags: i.RepoTags, + RepoDigests: i.RepoTags, + Parent: i.ParentId, + Size: i.Size, + Created: created, + Names: i.RepoTags, + isParent: i.IsParent, + Runtime: runtime, + TopLayer: i.TopLayer, + ReadOnly: i.ReadOnly, + NamesHistory: i.History, } return &ContainerImage{ri}, nil } @@ -337,6 +339,11 @@ func (ci *ContainerImage) Names() []string { return ci.remoteImage.Names } +// NamesHistory returns a string array of names previously associated with the image +func (ci *ContainerImage) NamesHistory() []string { + return ci.remoteImage.NamesHistory +} + // Created returns the time the image was created func (ci *ContainerImage) Created() time.Time { return ci.remoteImage.Created @@ -415,8 +422,8 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) } // PruneImages is the wrapper call for a remote-client to prune images -func (r *LocalRuntime) PruneImages(ctx context.Context, all bool) ([]string, error) { - return iopodman.ImagesPrune().Call(r.Conn, all) +func (r *LocalRuntime) PruneImages(ctx context.Context, all bool, filter []string) ([]string, error) { + return iopodman.ImagesPrune().Call(r.Conn, all, filter) } // Export is a wrapper to container export to a tarfile diff --git a/pkg/adapter/shortcuts/shortcuts.go b/pkg/adapter/shortcuts/shortcuts.go index 3e4eff555..4f6cfd6a3 100644 --- a/pkg/adapter/shortcuts/shortcuts.go +++ b/pkg/adapter/shortcuts/shortcuts.go @@ -2,9 +2,11 @@ package shortcuts import ( "github.com/containers/libpod/libpod" + "github.com/sirupsen/logrus" ) -// GetPodsByContext gets pods whether all, latest, or a slice of names/ids +// 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 { @@ -18,17 +20,24 @@ func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) outpods = append(outpods, p) return outpods, nil } + var err error for _, p := range pods { - pod, err := runtime.LookupPod(p) - if err != nil { - return nil, err + 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) } - outpods = append(outpods, pod) } - return outpods, nil + 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{} @@ -41,10 +50,15 @@ func GetContainersByContext(all, latest bool, names []string, runtime *libpod.Ru } else { for _, n := range names { ctr, e := runtime.LookupContainer(n) - if e != nil && err == nil { - err = e + 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) } - ctrs = append(ctrs, ctr) } } return diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 9604de638..193c788c0 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -19,24 +19,31 @@ #include <sys/select.h> #include <stdio.h> -#ifndef RENAME_NOREPLACE -# define RENAME_NOREPLACE (1 << 0) - -int renameat2 (int olddirfd, const char *oldpath, int newdirfd, const char *newpath, unsigned int flags) +int rename_noreplace (int olddirfd, const char *oldpath, int newdirfd, const char *newpath) { + int ret; + # ifdef SYS_renameat2 - return (int) syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, flags); -# else +# ifndef RENAME_NOREPLACE +# define RENAME_NOREPLACE (1 << 0) +# endif + + ret = (int) syscall (SYS_renameat2, olddirfd, oldpath, newdirfd, newpath, RENAME_NOREPLACE); + if (ret == 0 || errno != EINVAL) + return ret; + + /* Fallback in case of errno==EINVAL. */ +# endif + /* This might be an issue if another process is trying to read the file while it is empty. */ - int fd = open (newpath, O_EXCL|O_CREAT, 0700); - if (fd < 0) - return fd; - close (fd); + ret = open (newpath, O_EXCL|O_CREAT, 0700); + if (ret < 0) + return ret; + close (ret); + /* We are sure we created the file, let's overwrite it. */ return rename (oldpath, newpath); -# endif } -#endif #ifndef TEMP_FAILURE_RETRY #define TEMP_FAILURE_RETRY(expression) \ @@ -453,7 +460,7 @@ create_pause_process (const char *pause_pid_file_path, char **argv) /* There can be another process at this point trying to configure the user namespace and the pause process, do not override the pid file if it already exists. */ - if (renameat2 (AT_FDCWD, tmp_file_path, AT_FDCWD, pause_pid_file_path, RENAME_NOREPLACE) < 0) + if (rename_noreplace (AT_FDCWD, tmp_file_path, AT_FDCWD, pause_pid_file_path) < 0) { unlink (tmp_file_path); kill (pid, SIGKILL); diff --git a/pkg/timetype/timestamp.go b/pkg/timetype/timestamp.go new file mode 100644 index 000000000..eb904a574 --- /dev/null +++ b/pkg/timetype/timestamp.go @@ -0,0 +1,131 @@ +package timetype + +// code adapted from https://github.com/moby/moby/blob/master/api/types/time/timestamp.go + +import ( + "fmt" + "math" + "strconv" + "strings" + "time" +) + +// These are additional predefined layouts for use in Time.Format and Time.Parse +// with --since and --until parameters for `docker logs` and `docker events` +const ( + rFC3339Local = "2006-01-02T15:04:05" // RFC3339 with local timezone + rFC3339NanoLocal = "2006-01-02T15:04:05.999999999" // RFC3339Nano with local timezone + dateWithZone = "2006-01-02Z07:00" // RFC3339 with time at 00:00:00 + dateLocal = "2006-01-02" // RFC3339 with local timezone and time at 00:00:00 +) + +// GetTimestamp tries to parse given string as golang duration, +// then RFC3339 time and finally as a Unix timestamp. If +// any of these were successful, it returns a Unix timestamp +// as string otherwise returns the given value back. +// In case of duration input, the returned timestamp is computed +// as the given reference time minus the amount of the duration. +func GetTimestamp(value string, reference time.Time) (string, error) { + if d, err := time.ParseDuration(value); value != "0" && err == nil { + return strconv.FormatInt(reference.Add(-d).Unix(), 10), nil + } + + var format string + // if the string has a Z or a + or three dashes use parse otherwise use parseinlocation + parseInLocation := !(strings.ContainsAny(value, "zZ+") || strings.Count(value, "-") == 3) + + if strings.Contains(value, ".") { + if parseInLocation { + format = rFC3339NanoLocal + } else { + format = time.RFC3339Nano + } + } else if strings.Contains(value, "T") { + // we want the number of colons in the T portion of the timestamp + tcolons := strings.Count(value, ":") + // if parseInLocation is off and we have a +/- zone offset (not Z) then + // there will be an extra colon in the input for the tz offset subtract that + // colon from the tcolons count + if !parseInLocation && !strings.ContainsAny(value, "zZ") && tcolons > 0 { + tcolons-- + } + if parseInLocation { + switch tcolons { + case 0: + format = "2006-01-02T15" + case 1: + format = "2006-01-02T15:04" + default: + format = rFC3339Local + } + } else { + switch tcolons { + case 0: + format = "2006-01-02T15Z07:00" + case 1: + format = "2006-01-02T15:04Z07:00" + default: + format = time.RFC3339 + } + } + } else if parseInLocation { + format = dateLocal + } else { + format = dateWithZone + } + + var t time.Time + var err error + + if parseInLocation { + t, err = time.ParseInLocation(format, value, time.FixedZone(reference.Zone())) + } else { + t, err = time.Parse(format, value) + } + + if err != nil { + // if there is a `-` then it's an RFC3339 like timestamp + if strings.Contains(value, "-") { + return "", err // was probably an RFC3339 like timestamp but the parser failed with an error + } + if _, _, err := parseTimestamp(value); err != nil { + return "", fmt.Errorf("failed to parse value as time or duration: %q", value) + } + return value, nil // unix timestamp in and out case (meaning: the value passed at the command line is already in the right format for passing to the server) + } + + return fmt.Sprintf("%d.%09d", t.Unix(), int64(t.Nanosecond())), nil +} + +// ParseTimestamps returns seconds and nanoseconds from a timestamp that has the +// format "%d.%09d", time.Unix(), int64(time.Nanosecond())) +// if the incoming nanosecond portion is longer or shorter than 9 digits it is +// converted to nanoseconds. The expectation is that the seconds and +// seconds will be used to create a time variable. For example: +// seconds, nanoseconds, err := ParseTimestamp("1136073600.000000001",0) +// if err == nil since := time.Unix(seconds, nanoseconds) +// returns seconds as def(aultSeconds) if value == "" +func ParseTimestamps(value string, def int64) (int64, int64, error) { + if value == "" { + return def, 0, nil + } + return parseTimestamp(value) +} + +func parseTimestamp(value string) (int64, int64, error) { + sa := strings.SplitN(value, ".", 2) + s, err := strconv.ParseInt(sa[0], 10, 64) + if err != nil { + return s, 0, err + } + if len(sa) != 2 { + return s, 0, nil + } + n, err := strconv.ParseInt(sa[1], 10, 64) + if err != nil { + return s, n, err + } + // should already be in nanoseconds but just in case convert n to nanoseconds + n = int64(float64(n) * math.Pow(float64(10), float64(9-len(sa[1])))) + return s, n, nil +} diff --git a/pkg/timetype/timestamp_test.go b/pkg/timetype/timestamp_test.go new file mode 100644 index 000000000..0fffb85a9 --- /dev/null +++ b/pkg/timetype/timestamp_test.go @@ -0,0 +1,95 @@ +package timetype + +// code adapted from https://github.com/moby/moby/blob/master/api/types/time/timestamp.go + +import ( + "fmt" + "testing" + "time" +) + +func TestGetTimestamp(t *testing.T) { + now := time.Now().In(time.UTC) + cases := []struct { + in, expected string + expectedErr bool + }{ + // Partial RFC3339 strings get parsed with second precision + {"2006-01-02T15:04:05.999999999+07:00", "1136189045.999999999", false}, + {"2006-01-02T15:04:05.999999999Z", "1136214245.999999999", false}, + {"2006-01-02T15:04:05.999999999", "1136214245.999999999", false}, + {"2006-01-02T15:04:05Z", "1136214245.000000000", false}, + {"2006-01-02T15:04:05", "1136214245.000000000", false}, + {"2006-01-02T15:04:0Z", "", true}, + {"2006-01-02T15:04:0", "", true}, + {"2006-01-02T15:04Z", "1136214240.000000000", false}, + {"2006-01-02T15:04+00:00", "1136214240.000000000", false}, + {"2006-01-02T15:04-00:00", "1136214240.000000000", false}, + {"2006-01-02T15:04", "1136214240.000000000", false}, + {"2006-01-02T15:0Z", "", true}, + {"2006-01-02T15:0", "", true}, + {"2006-01-02T15Z", "1136214000.000000000", false}, + {"2006-01-02T15+00:00", "1136214000.000000000", false}, + {"2006-01-02T15-00:00", "1136214000.000000000", false}, + {"2006-01-02T15", "1136214000.000000000", false}, + {"2006-01-02T1Z", "1136163600.000000000", false}, + {"2006-01-02T1", "1136163600.000000000", false}, + {"2006-01-02TZ", "", true}, + {"2006-01-02T", "", true}, + {"2006-01-02+00:00", "1136160000.000000000", false}, + {"2006-01-02-00:00", "1136160000.000000000", false}, + {"2006-01-02-00:01", "1136160060.000000000", false}, + {"2006-01-02Z", "1136160000.000000000", false}, + {"2006-01-02", "1136160000.000000000", false}, + {"2015-05-13T20:39:09Z", "1431549549.000000000", false}, + + // unix timestamps returned as is + {"1136073600", "1136073600", false}, + {"1136073600.000000001", "1136073600.000000001", false}, + // Durations + {"1m", fmt.Sprintf("%d", now.Add(-1*time.Minute).Unix()), false}, + {"1.5h", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false}, + {"1h30m", fmt.Sprintf("%d", now.Add(-90*time.Minute).Unix()), false}, + + {"invalid", "", true}, + {"", "", true}, + } + + for _, c := range cases { + o, err := GetTimestamp(c.in, now) + if o != c.expected || + (err == nil && c.expectedErr) || + (err != nil && !c.expectedErr) { + t.Errorf("wrong value for '%s'. expected:'%s' got:'%s' with error: `%s`", c.in, c.expected, o, err) + t.Fail() + } + } +} + +func TestParseTimestamps(t *testing.T) { + cases := []struct { + in string + def, expectedS, expectedN int64 + expectedErr bool + }{ + // unix timestamps + {"1136073600", 0, 1136073600, 0, false}, + {"1136073600.000000001", 0, 1136073600, 1, false}, + {"1136073600.0000000010", 0, 1136073600, 1, false}, + {"1136073600.00000001", 0, 1136073600, 10, false}, + {"foo.bar", 0, 0, 0, true}, + {"1136073600.bar", 0, 1136073600, 0, true}, + {"", -1, -1, 0, false}, + } + + for _, c := range cases { + s, n, err := ParseTimestamps(c.in, c.def) + if s != c.expectedS || + n != c.expectedN || + (err == nil && c.expectedErr) || + (err != nil && !c.expectedErr) { + t.Errorf("wrong values for input `%s` with default `%d` expected:'%d'seconds and `%d`nanosecond got:'%d'seconds and `%d`nanoseconds with error: `%s`", c.in, c.def, c.expectedS, c.expectedN, s, n, err) + t.Fail() + } + } +} diff --git a/pkg/util/utils.go b/pkg/util/utils.go index 6906b26d5..5b4dfe9fa 100644 --- a/pkg/util/utils.go +++ b/pkg/util/utils.go @@ -1,11 +1,12 @@ package util import ( + "encoding/json" "fmt" "os" "os/user" "path/filepath" - "regexp" + "strconv" "strings" "sync" "time" @@ -18,6 +19,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage" "github.com/containers/storage/pkg/idtools" + "github.com/docker/docker/pkg/signal" v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -71,118 +73,236 @@ func StringInSlice(s string, sl []string) bool { return false } -// ParseChanges returns key, value(s) pair for given option. -func ParseChanges(option string) (key string, vals []string, err error) { - // Supported format as below - // 1. key=value - // 2. key value - // 3. key ["value","value1"] - if strings.Contains(option, " ") { - // This handles 2 & 3 conditions. - var val string - tokens := strings.SplitAfterN(option, " ", 2) - if len(tokens) < 2 { - return "", []string{}, fmt.Errorf("invalid key value %s", option) - } - key = strings.Trim(tokens[0], " ") // Need to trim whitespace part of delimiter. - val = tokens[1] - if strings.Contains(tokens[1], "[") && strings.Contains(tokens[1], "]") { - //Trim '[',']' if exist. - val = strings.TrimLeft(strings.TrimRight(tokens[1], "]"), "[") - } - vals = strings.Split(val, ",") - } else if strings.Contains(option, "=") { - // handles condition 1. - tokens := strings.Split(option, "=") - key = tokens[0] - vals = tokens[1:] - } else { - // either ` ` or `=` must be provided after command - return "", []string{}, fmt.Errorf("invalid format %s", option) - } - - if len(vals) == 0 { - return "", []string{}, errors.Errorf("no value given for instruction %q", key) - } - - for _, v := range vals { - //each option must not have ' '., `[`` or `]` & empty strings - whitespaces := regexp.MustCompile(`[\[\s\]]`) - if whitespaces.MatchString(v) || len(v) == 0 { - return "", []string{}, fmt.Errorf("invalid value %s", v) - } - } - return key, vals, nil +// ImageConfig is a wrapper around the OCIv1 Image Configuration struct exported +// by containers/image, but containing additional fields that are not supported +// by OCIv1 (but are by Docker v2) - notably OnBuild. +type ImageConfig struct { + v1.ImageConfig + OnBuild []string } -// GetImageConfig converts the --change flag values in the format "CMD=/bin/bash USER=example" -// to a type v1.ImageConfig -func GetImageConfig(changes []string) (v1.ImageConfig, error) { - // USER=value | EXPOSE=value | ENV=value | ENTRYPOINT=value | - // CMD=value | VOLUME=value | WORKDIR=value | LABEL=key=value | STOPSIGNAL=value - - var ( - user string - env []string - entrypoint []string - cmd []string - workingDir string - stopSignal string - ) - - exposedPorts := make(map[string]struct{}) - volumes := make(map[string]struct{}) - labels := make(map[string]string) - for _, ch := range changes { - key, vals, err := ParseChanges(ch) - if err != nil { - return v1.ImageConfig{}, err +// GetImageConfig produces a v1.ImageConfig from the --change flag that is +// accepted by several Podman commands. It accepts a (limited subset) of +// Dockerfile instructions. +func GetImageConfig(changes []string) (ImageConfig, error) { + // Valid changes: + // USER + // EXPOSE + // ENV + // ENTRYPOINT + // CMD + // VOLUME + // WORKDIR + // LABEL + // STOPSIGNAL + // ONBUILD + + config := ImageConfig{} + + for _, change := range changes { + // First, let's assume proper Dockerfile format - space + // separator between instruction and value + split := strings.SplitN(change, " ", 2) + + if len(split) != 2 { + split = strings.SplitN(change, "=", 2) + if len(split) != 2 { + return ImageConfig{}, errors.Errorf("invalid change %q - must be formatted as KEY VALUE", change) + } } - switch key { + outerKey := strings.ToUpper(strings.TrimSpace(split[0])) + value := strings.TrimSpace(split[1]) + switch outerKey { case "USER": - user = vals[0] + // Assume literal contents are the user. + if value == "" { + return ImageConfig{}, errors.Errorf("invalid change %q - must provide a value to USER", change) + } + config.User = value case "EXPOSE": - var st struct{} - exposedPorts[vals[0]] = st + // EXPOSE is either [portnum] or + // [portnum]/[proto] + // Protocol must be "tcp" or "udp" + splitPort := strings.Split(value, "/") + if len(splitPort) > 2 { + return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE port must be formatted as PORT[/PROTO]", change) + } + portNum, err := strconv.Atoi(splitPort[0]) + if err != nil { + return ImageConfig{}, errors.Wrapf(err, "invalid change %q - EXPOSE port must be an integer", change) + } + if portNum > 65535 || portNum <= 0 { + return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE port must be a valid port number", change) + } + proto := "tcp" + if len(splitPort) > 1 { + testProto := strings.ToLower(splitPort[1]) + switch testProto { + case "tcp", "udp": + proto = testProto + default: + return ImageConfig{}, errors.Errorf("invalid change %q - EXPOSE protocol must be TCP or UDP", change) + } + } + if config.ExposedPorts == nil { + config.ExposedPorts = make(map[string]struct{}) + } + config.ExposedPorts[fmt.Sprintf("%d/%s", portNum, proto)] = struct{}{} case "ENV": - if len(vals) < 2 { - return v1.ImageConfig{}, errors.Errorf("no value given for environment variable %q", vals[0]) + // Format is either: + // ENV key=value + // ENV key=value key=value ... + // ENV key value + // Both keys and values can be surrounded by quotes to group them. + // For now: we only support key=value + // We will attempt to strip quotation marks if present. + + var ( + key, val string + ) + + splitEnv := strings.SplitN(value, "=", 2) + key = splitEnv[0] + // We do need a key + if key == "" { + return ImageConfig{}, errors.Errorf("invalid change %q - ENV must have at least one argument", change) + } + // Perfectly valid to not have a value + if len(splitEnv) == 2 { + val = splitEnv[1] + } + + if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { + key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) + } + if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { + val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`) } - env = append(env, strings.Join(vals[0:], "=")) + config.Env = append(config.Env, fmt.Sprintf("%s=%s", key, val)) case "ENTRYPOINT": - // ENTRYPOINT and CMD can have array of strings - entrypoint = append(entrypoint, vals...) + // Two valid forms. + // First, JSON array. + // Second, not a JSON array - we interpret this as an + // argument to `sh -c`, unless empty, in which case we + // just use a blank entrypoint. + testUnmarshal := []string{} + if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { + // It ain't valid JSON, so assume it's an + // argument to sh -c if not empty. + if value != "" { + config.Entrypoint = []string{"/bin/sh", "-c", value} + } else { + config.Entrypoint = []string{} + } + } else { + // Valid JSON + config.Entrypoint = testUnmarshal + } case "CMD": - // ENTRYPOINT and CMD can have array of strings - cmd = append(cmd, vals...) + // Same valid forms as entrypoint. + // However, where ENTRYPOINT assumes that 'ENTRYPOINT ' + // means no entrypoint, CMD assumes it is 'sh -c' with + // no third argument. + testUnmarshal := []string{} + if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { + // It ain't valid JSON, so assume it's an + // argument to sh -c. + // Only include volume if it's not "" + config.Cmd = []string{"/bin/sh", "-c"} + if value != "" { + config.Cmd = append(config.Cmd, value) + } + } else { + // Valid JSON + config.Cmd = testUnmarshal + } case "VOLUME": - var st struct{} - volumes[vals[0]] = st + // Either a JSON array or a set of space-separated + // paths. + // Acts rather similar to ENTRYPOINT and CMD, but always + // appends rather than replacing, and no sh -c prepend. + testUnmarshal := []string{} + if err := json.Unmarshal([]byte(value), &testUnmarshal); err != nil { + // Not valid JSON, so split on spaces + testUnmarshal = strings.Split(value, " ") + } + if len(testUnmarshal) == 0 { + return ImageConfig{}, errors.Errorf("invalid change %q - must provide at least one argument to VOLUME", change) + } + for _, vol := range testUnmarshal { + if vol == "" { + return ImageConfig{}, errors.Errorf("invalid change %q - VOLUME paths must not be empty", change) + } + if config.Volumes == nil { + config.Volumes = make(map[string]struct{}) + } + config.Volumes[vol] = struct{}{} + } case "WORKDIR": - workingDir = vals[0] + // This can be passed multiple times. + // Each successive invocation is treated as relative to + // the previous one - so WORKDIR /A, WORKDIR b, + // WORKDIR c results in /A/b/c + // Just need to check it's not empty... + if value == "" { + return ImageConfig{}, errors.Errorf("invalid change %q - must provide a non-empty WORKDIR", change) + } + config.WorkingDir = filepath.Join(config.WorkingDir, value) case "LABEL": - if len(vals) == 2 { - labels[vals[0]] = vals[1] - } else { - labels[vals[0]] = "" + // Same general idea as ENV, but we no longer allow " " + // as a separator. + // We didn't do that for ENV either, so nice and easy. + // Potentially problematic: LABEL might theoretically + // allow an = in the key? If people really do this, we + // may need to investigate more advanced parsing. + var ( + key, val string + ) + + splitLabel := strings.SplitN(value, "=", 2) + // Unlike ENV, LABEL must have a value + if len(splitLabel) != 2 { + return ImageConfig{}, errors.Errorf("invalid change %q - LABEL must be formatted key=value", change) + } + key = splitLabel[0] + val = splitLabel[1] + + if strings.HasPrefix(key, `"`) && strings.HasSuffix(key, `"`) { + key = strings.TrimPrefix(strings.TrimSuffix(key, `"`), `"`) + } + if strings.HasPrefix(val, `"`) && strings.HasSuffix(val, `"`) { + val = strings.TrimPrefix(strings.TrimSuffix(val, `"`), `"`) + } + // Check key after we strip quotations + if key == "" { + return ImageConfig{}, errors.Errorf("invalid change %q - LABEL must have a non-empty key", change) } + if config.Labels == nil { + config.Labels = make(map[string]string) + } + config.Labels[key] = val case "STOPSIGNAL": - stopSignal = vals[0] + // Check the provided signal for validity. + // TODO: Worth checking range? ParseSignal allows + // negative numbers. + killSignal, err := signal.ParseSignal(value) + if err != nil { + return ImageConfig{}, errors.Wrapf(err, "invalid change %q - KILLSIGNAL must be given a valid signal", change) + } + config.StopSignal = fmt.Sprintf("%d", killSignal) + case "ONBUILD": + // Onbuild always appends. + if value == "" { + return ImageConfig{}, errors.Errorf("invalid change %q - ONBUILD must be given an argument", change) + } + config.OnBuild = append(config.OnBuild, value) + default: + return ImageConfig{}, errors.Errorf("invalid change %q - invalid instruction %s", change, outerKey) } } - return v1.ImageConfig{ - User: user, - ExposedPorts: exposedPorts, - Env: env, - Entrypoint: entrypoint, - Cmd: cmd, - Volumes: volumes, - WorkingDir: workingDir, - Labels: labels, - StopSignal: stopSignal, - }, nil + return config, nil } // ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping diff --git a/pkg/util/utils_supported.go b/pkg/util/utils_supported.go index 253460686..0b78a8150 100644 --- a/pkg/util/utils_supported.go +++ b/pkg/util/utils_supported.go @@ -20,6 +20,10 @@ import ( func GetRuntimeDir() (string, error) { var rootlessRuntimeDirError error + if !rootless.IsRootless() { + return "", nil + } + rootlessRuntimeDirOnce.Do(func() { runtimeDir := os.Getenv("XDG_RUNTIME_DIR") uid := fmt.Sprintf("%d", rootless.GetRootlessUID()) diff --git a/pkg/util/utils_test.go b/pkg/util/utils_test.go index c938dc592..f4b03599d 100644 --- a/pkg/util/utils_test.go +++ b/pkg/util/utils_test.go @@ -4,6 +4,7 @@ import ( "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var ( @@ -19,70 +20,247 @@ func TestStringInSlice(t *testing.T) { assert.False(t, StringInSlice("one", []string{})) } -func TestParseChanges(t *testing.T) { - // CMD=/bin/sh - _, vals, err := ParseChanges("CMD=/bin/sh") - assert.EqualValues(t, []string{"/bin/sh"}, vals) - assert.NoError(t, err) - - // CMD [/bin/sh] - _, vals, err = ParseChanges("CMD [/bin/sh]") - assert.EqualValues(t, []string{"/bin/sh"}, vals) - assert.NoError(t, err) - - // CMD ["/bin/sh"] - _, vals, err = ParseChanges(`CMD ["/bin/sh"]`) - assert.EqualValues(t, []string{`"/bin/sh"`}, vals) - assert.NoError(t, err) - - // CMD ["/bin/sh","-c","ls"] - _, vals, err = ParseChanges(`CMD ["/bin/sh","c","ls"]`) - assert.EqualValues(t, []string{`"/bin/sh"`, `"c"`, `"ls"`}, vals) - assert.NoError(t, err) - - // CMD ["/bin/sh","arg-with,comma"] - _, vals, err = ParseChanges(`CMD ["/bin/sh","arg-with,comma"]`) - assert.EqualValues(t, []string{`"/bin/sh"`, `"arg-with`, `comma"`}, vals) - assert.NoError(t, err) - - // CMD "/bin/sh"] - _, _, err = ParseChanges(`CMD "/bin/sh"]`) - assert.Error(t, err) - assert.Equal(t, `invalid value "/bin/sh"]`, err.Error()) - - // CMD [bin/sh - _, _, err = ParseChanges(`CMD "/bin/sh"]`) - assert.Error(t, err) - assert.Equal(t, `invalid value "/bin/sh"]`, err.Error()) - - // CMD ["/bin /sh"] - _, _, err = ParseChanges(`CMD ["/bin /sh"]`) - assert.Error(t, err) - assert.Equal(t, `invalid value "/bin /sh"`, err.Error()) - - // CMD ["/bin/sh", "-c","ls"] whitespace between values - _, vals, err = ParseChanges(`CMD ["/bin/sh", "c","ls"]`) - assert.Error(t, err) - assert.Equal(t, `invalid value "c"`, err.Error()) - - // CMD? - _, _, err = ParseChanges(`CMD?`) - assert.Error(t, err) - assert.Equal(t, `invalid format CMD?`, err.Error()) - - // empty values for CMD - _, _, err = ParseChanges(`CMD `) - assert.Error(t, err) - assert.Equal(t, `invalid value `, err.Error()) - - // LABEL=blue=image - _, vals, err = ParseChanges(`LABEL=blue=image`) - assert.EqualValues(t, []string{"blue", "image"}, vals) - assert.NoError(t, err) - - // LABEL = blue=image - _, vals, err = ParseChanges(`LABEL = blue=image`) - assert.Error(t, err) - assert.Equal(t, `invalid value = blue=image`, err.Error()) +func TestGetImageConfigUser(t *testing.T) { + validUser, err := GetImageConfig([]string{"USER valid"}) + require.Nil(t, err) + assert.Equal(t, validUser.User, "valid") + validUser2, err := GetImageConfig([]string{"USER test_user_2"}) + require.Nil(t, err) + assert.Equal(t, validUser2.User, "test_user_2") + + _, err = GetImageConfig([]string{"USER "}) + assert.NotNil(t, err) +} + +func TestGetImageConfigExpose(t *testing.T) { + validPortNoProto, err := GetImageConfig([]string{"EXPOSE 80"}) + require.Nil(t, err) + _, exists := validPortNoProto.ExposedPorts["80/tcp"] + assert.True(t, exists) + + validPortTCP, err := GetImageConfig([]string{"EXPOSE 80/tcp"}) + require.Nil(t, err) + _, exists = validPortTCP.ExposedPorts["80/tcp"] + assert.True(t, exists) + + validPortUDP, err := GetImageConfig([]string{"EXPOSE 80/udp"}) + require.Nil(t, err) + _, exists = validPortUDP.ExposedPorts["80/udp"] + assert.True(t, exists) + + _, err = GetImageConfig([]string{"EXPOSE 99999"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"EXPOSE 80/notaproto"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"EXPOSE "}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"EXPOSE thisisnotanumber"}) + assert.NotNil(t, err) +} + +func TestGetImageConfigEnv(t *testing.T) { + validEnvNoValue, err := GetImageConfig([]string{"ENV key"}) + require.Nil(t, err) + assert.True(t, StringInSlice("key=", validEnvNoValue.Env)) + + validEnvBareEquals, err := GetImageConfig([]string{"ENV key="}) + require.Nil(t, err) + assert.True(t, StringInSlice("key=", validEnvBareEquals.Env)) + + validEnvKeyValue, err := GetImageConfig([]string{"ENV key=value"}) + require.Nil(t, err) + assert.True(t, StringInSlice("key=value", validEnvKeyValue.Env)) + + validEnvKeyMultiEntryValue, err := GetImageConfig([]string{`ENV key="value1 value2"`}) + require.Nil(t, err) + assert.True(t, StringInSlice("key=value1 value2", validEnvKeyMultiEntryValue.Env)) + + _, err = GetImageConfig([]string{"ENV "}) + assert.NotNil(t, err) +} + +func TestGetImageConfigEntrypoint(t *testing.T) { + binShEntrypoint, err := GetImageConfig([]string{"ENTRYPOINT /bin/bash"}) + require.Nil(t, err) + require.Equal(t, 3, len(binShEntrypoint.Entrypoint)) + assert.Equal(t, binShEntrypoint.Entrypoint[0], "/bin/sh") + assert.Equal(t, binShEntrypoint.Entrypoint[1], "-c") + assert.Equal(t, binShEntrypoint.Entrypoint[2], "/bin/bash") + + entrypointWithSpaces, err := GetImageConfig([]string{"ENTRYPOINT ls -al"}) + require.Nil(t, err) + require.Equal(t, 3, len(entrypointWithSpaces.Entrypoint)) + assert.Equal(t, entrypointWithSpaces.Entrypoint[0], "/bin/sh") + assert.Equal(t, entrypointWithSpaces.Entrypoint[1], "-c") + assert.Equal(t, entrypointWithSpaces.Entrypoint[2], "ls -al") + + jsonArrayEntrypoint, err := GetImageConfig([]string{`ENTRYPOINT ["ls", "-al"]`}) + require.Nil(t, err) + require.Equal(t, 2, len(jsonArrayEntrypoint.Entrypoint)) + assert.Equal(t, jsonArrayEntrypoint.Entrypoint[0], "ls") + assert.Equal(t, jsonArrayEntrypoint.Entrypoint[1], "-al") + + emptyEntrypoint, err := GetImageConfig([]string{"ENTRYPOINT "}) + require.Nil(t, err) + assert.Equal(t, 0, len(emptyEntrypoint.Entrypoint)) + + emptyEntrypointArray, err := GetImageConfig([]string{"ENTRYPOINT []"}) + require.Nil(t, err) + assert.Equal(t, 0, len(emptyEntrypointArray.Entrypoint)) +} + +func TestGetImageConfigCmd(t *testing.T) { + binShCmd, err := GetImageConfig([]string{"CMD /bin/bash"}) + require.Nil(t, err) + require.Equal(t, 3, len(binShCmd.Cmd)) + assert.Equal(t, binShCmd.Cmd[0], "/bin/sh") + assert.Equal(t, binShCmd.Cmd[1], "-c") + assert.Equal(t, binShCmd.Cmd[2], "/bin/bash") + + cmdWithSpaces, err := GetImageConfig([]string{"CMD ls -al"}) + require.Nil(t, err) + require.Equal(t, 3, len(cmdWithSpaces.Cmd)) + assert.Equal(t, cmdWithSpaces.Cmd[0], "/bin/sh") + assert.Equal(t, cmdWithSpaces.Cmd[1], "-c") + assert.Equal(t, cmdWithSpaces.Cmd[2], "ls -al") + + jsonArrayCmd, err := GetImageConfig([]string{`CMD ["ls", "-al"]`}) + require.Nil(t, err) + require.Equal(t, 2, len(jsonArrayCmd.Cmd)) + assert.Equal(t, jsonArrayCmd.Cmd[0], "ls") + assert.Equal(t, jsonArrayCmd.Cmd[1], "-al") + + emptyCmd, err := GetImageConfig([]string{"CMD "}) + require.Nil(t, err) + require.Equal(t, 2, len(emptyCmd.Cmd)) + assert.Equal(t, emptyCmd.Cmd[0], "/bin/sh") + assert.Equal(t, emptyCmd.Cmd[1], "-c") + + blankCmd, err := GetImageConfig([]string{"CMD []"}) + require.Nil(t, err) + assert.Equal(t, 0, len(blankCmd.Cmd)) +} + +func TestGetImageConfigVolume(t *testing.T) { + oneLenJSONArrayVol, err := GetImageConfig([]string{`VOLUME ["/test1"]`}) + require.Nil(t, err) + _, exists := oneLenJSONArrayVol.Volumes["/test1"] + assert.True(t, exists) + assert.Equal(t, 1, len(oneLenJSONArrayVol.Volumes)) + + twoLenJSONArrayVol, err := GetImageConfig([]string{`VOLUME ["/test1", "/test2"]`}) + require.Nil(t, err) + assert.Equal(t, 2, len(twoLenJSONArrayVol.Volumes)) + _, exists = twoLenJSONArrayVol.Volumes["/test1"] + assert.True(t, exists) + _, exists = twoLenJSONArrayVol.Volumes["/test2"] + assert.True(t, exists) + + oneLenVol, err := GetImageConfig([]string{"VOLUME /test1"}) + require.Nil(t, err) + _, exists = oneLenVol.Volumes["/test1"] + assert.True(t, exists) + assert.Equal(t, 1, len(oneLenVol.Volumes)) + + twoLenVol, err := GetImageConfig([]string{"VOLUME /test1 /test2"}) + require.Nil(t, err) + assert.Equal(t, 2, len(twoLenVol.Volumes)) + _, exists = twoLenVol.Volumes["/test1"] + assert.True(t, exists) + _, exists = twoLenVol.Volumes["/test2"] + assert.True(t, exists) + + _, err = GetImageConfig([]string{"VOLUME []"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"VOLUME "}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{`VOLUME [""]`}) + assert.NotNil(t, err) +} + +func TestGetImageConfigWorkdir(t *testing.T) { + singleWorkdir, err := GetImageConfig([]string{"WORKDIR /testdir"}) + require.Nil(t, err) + assert.Equal(t, singleWorkdir.WorkingDir, "/testdir") + + twoWorkdirs, err := GetImageConfig([]string{"WORKDIR /testdir", "WORKDIR a"}) + require.Nil(t, err) + assert.Equal(t, twoWorkdirs.WorkingDir, "/testdir/a") + + _, err = GetImageConfig([]string{"WORKDIR "}) + assert.NotNil(t, err) +} + +func TestGetImageConfigLabel(t *testing.T) { + labelNoQuotes, err := GetImageConfig([]string{"LABEL key1=value1"}) + require.Nil(t, err) + assert.Equal(t, labelNoQuotes.Labels["key1"], "value1") + + labelWithQuotes, err := GetImageConfig([]string{`LABEL "key 1"="value 2"`}) + require.Nil(t, err) + assert.Equal(t, labelWithQuotes.Labels["key 1"], "value 2") + + labelNoValue, err := GetImageConfig([]string{"LABEL key="}) + require.Nil(t, err) + contents, exists := labelNoValue.Labels["key"] + assert.True(t, exists) + assert.Equal(t, contents, "") + + _, err = GetImageConfig([]string{"LABEL key"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"LABEL "}) + assert.NotNil(t, err) +} + +func TestGetImageConfigStopSignal(t *testing.T) { + stopSignalValidInt, err := GetImageConfig([]string{"STOPSIGNAL 9"}) + require.Nil(t, err) + assert.Equal(t, stopSignalValidInt.StopSignal, "9") + + stopSignalValidString, err := GetImageConfig([]string{"STOPSIGNAL SIGKILL"}) + require.Nil(t, err) + assert.Equal(t, stopSignalValidString.StopSignal, "9") + + _, err = GetImageConfig([]string{"STOPSIGNAL 0"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"STOPSIGNAL garbage"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"STOPSIGNAL "}) + assert.NotNil(t, err) +} + +func TestGetImageConfigOnBuild(t *testing.T) { + onBuildOne, err := GetImageConfig([]string{"ONBUILD ADD /testdir1"}) + require.Nil(t, err) + require.Equal(t, 1, len(onBuildOne.OnBuild)) + assert.Equal(t, onBuildOne.OnBuild[0], "ADD /testdir1") + + onBuildTwo, err := GetImageConfig([]string{"ONBUILD ADD /testdir1", "ONBUILD ADD /testdir2"}) + require.Nil(t, err) + require.Equal(t, 2, len(onBuildTwo.OnBuild)) + assert.Equal(t, onBuildTwo.OnBuild[0], "ADD /testdir1") + assert.Equal(t, onBuildTwo.OnBuild[1], "ADD /testdir2") + + _, err = GetImageConfig([]string{"ONBUILD "}) + assert.NotNil(t, err) +} + +func TestGetImageConfigMisc(t *testing.T) { + _, err := GetImageConfig([]string{""}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"USER"}) + assert.NotNil(t, err) + + _, err = GetImageConfig([]string{"BADINST testvalue"}) + assert.NotNil(t, err) } diff --git a/pkg/varlinkapi/images.go b/pkg/varlinkapi/images.go index 8d44e6373..604a455a5 100644 --- a/pkg/varlinkapi/images.go +++ b/pkg/varlinkapi/images.go @@ -21,7 +21,7 @@ import ( "github.com/containers/image/v5/transports/alltransports" "github.com/containers/image/v5/types" "github.com/containers/libpod/cmd/podman/shared" - "github.com/containers/libpod/cmd/podman/varlink" + iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/image" @@ -29,7 +29,7 @@ import ( "github.com/containers/libpod/pkg/util" "github.com/containers/libpod/utils" "github.com/containers/storage/pkg/archive" - "github.com/opencontainers/image-spec/specs-go/v1" + v1 "github.com/opencontainers/image-spec/specs-go/v1" "github.com/opencontainers/runtime-spec/specs-go" "github.com/pkg/errors" "github.com/sirupsen/logrus" @@ -70,6 +70,7 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error { Labels: labels, IsParent: isParent, ReadOnly: image.IsReadOnly(), + History: image.NamesHistory(), } imageList = append(imageList, i) } @@ -111,6 +112,7 @@ func (i *LibpodAPI) GetImage(call iopodman.VarlinkCall, id string) error { Labels: labels, TopLayer: newImage.TopLayer(), ReadOnly: newImage.IsReadOnly(), + History: newImage.NamesHistory(), } return call.ReplyGetImage(il) } @@ -600,7 +602,7 @@ func (i *LibpodAPI) ImportImage(call iopodman.VarlinkCall, source, reference, me {Comment: message}, } config := v1.Image{ - Config: configChanges, + Config: configChanges.ImageConfig, History: history, } newImage, err := i.Runtime.ImageRuntime().Import(getContext(), source, reference, nil, image.SigningOptions{}, config) @@ -740,8 +742,8 @@ func (i *LibpodAPI) ContainerRunlabel(call iopodman.VarlinkCall, input iopodman. } // ImagesPrune .... -func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool) error { - prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all) +func (i *LibpodAPI) ImagesPrune(call iopodman.VarlinkCall, all bool, filter []string) error { + prunedImages, err := i.Runtime.ImageRuntime().PruneImages(context.TODO(), all, []string{}) if err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index 9b659f66b..1ebe5d424 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -247,7 +247,7 @@ func (i *LibpodAPI) RemovePod(call iopodman.VarlinkCall, name string, force bool if err != nil { return call.ReplyPodNotFound(name, err.Error()) } - if err = i.Runtime.RemovePod(ctx, pod, force, force); err != nil { + if err = i.Runtime.RemovePod(ctx, pod, true, force); err != nil { return call.ReplyErrorOccurred(err.Error()) } diff --git a/pkg/varlinkapi/system.go b/pkg/varlinkapi/system.go index f6057f5fc..b81ff11ba 100644 --- a/pkg/varlinkapi/system.go +++ b/pkg/varlinkapi/system.go @@ -3,12 +3,15 @@ package varlinkapi import ( + "context" "fmt" - "github.com/containers/libpod/libpod/define" + "os" goruntime "runtime" "time" "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/define" + "github.com/sirupsen/logrus" ) // GetVersion ... @@ -105,3 +108,20 @@ func (i *LibpodAPI) GetInfo(call iopodman.VarlinkCall) error { podmanInfo.Insecure_registries = insecureRegistries return call.ReplyGetInfo(podmanInfo) } + +// GetVersion ... +func (i *LibpodAPI) Reset(call iopodman.VarlinkCall) error { + if err := i.Runtime.Reset(context.TODO()); err != nil { + logrus.Errorf("Reset Failed: %v", err) + if err := call.ReplyErrorOccurred(err.Error()); err != nil { + logrus.Errorf("Failed to send ReplyErrorOccurred: %v", err) + } + os.Exit(define.ExecErrorCodeGeneric) + } + if err := call.ReplyReset(); err != nil { + logrus.Errorf("Failed to send ReplyReset: %v", err) + os.Exit(define.ExecErrorCodeGeneric) + } + os.Exit(0) + return nil +} |