summaryrefslogtreecommitdiff
path: root/pkg
diff options
context:
space:
mode:
Diffstat (limited to 'pkg')
-rw-r--r--pkg/adapter/containers.go115
-rw-r--r--pkg/adapter/containers_remote.go8
-rw-r--r--pkg/adapter/network.go51
-rw-r--r--pkg/adapter/pods.go111
-rw-r--r--pkg/adapter/pods_remote.go2
-rw-r--r--pkg/adapter/reset.go13
-rw-r--r--pkg/adapter/reset_remote.go12
-rw-r--r--pkg/adapter/runtime.go21
-rw-r--r--pkg/adapter/runtime_remote.go95
-rw-r--r--pkg/adapter/shortcuts/shortcuts.go32
-rw-r--r--pkg/hooks/docs/oci-hooks.5.md2
-rw-r--r--pkg/network/config.go16
-rw-r--r--pkg/network/netconflist.go12
-rw-r--r--pkg/rootless/rootless_linux.c33
-rw-r--r--pkg/spec/storage.go10
-rw-r--r--pkg/timetype/timestamp.go131
-rw-r--r--pkg/timetype/timestamp_test.go95
-rw-r--r--pkg/trust/trust.go2
-rw-r--r--pkg/util/utils.go310
-rw-r--r--pkg/util/utils_supported.go4
-rw-r--r--pkg/util/utils_test.go308
-rw-r--r--pkg/varlinkapi/images.go46
-rw-r--r--pkg/varlinkapi/pods.go2
-rw-r--r--pkg/varlinkapi/system.go22
24 files changed, 1179 insertions, 274 deletions
diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go
index 287bd8474..3334e9fa1 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
@@ -962,7 +1024,7 @@ func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecVal
// Validate given environment variables
env := map[string]string{}
- if err := parse.ReadKVStrings(env, []string{}, cli.Env); err != nil {
+ if err := parse.ReadKVStrings(env, cli.EnvFile, cli.Env); err != nil {
return ec, errors.Wrapf(err, "unable to process environment variables")
}
@@ -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 20471d895..36db4af68 100644
--- a/pkg/adapter/containers_remote.go
+++ b/pkg/adapter/containers_remote.go
@@ -178,7 +178,7 @@ func (r *LocalRuntime) LookupContainersWithStatus(filters []string) ([]*Containe
if err != nil {
return nil, err
}
- // This is not performance savy; if this turns out to be a problematic series of lookups, we need to
+ // This is not performance savvy; if this turns out to be a problematic series of lookups, we need to
// create a new endpoint to speed things up
for _, ctr := range ctrs {
container, err := r.LookupContainer(ctr.Id)
@@ -617,7 +617,7 @@ func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues) error {
return err
}
if c.All {
- // We dont have a great way to get all the running containers, so need to get all and then
+ // We don't have a great way to get all the running containers, so need to get all and then
// check status on them bc checkpoint considers checkpointing a stopped container an error
var runningIds []string
for _, id := range ids {
@@ -660,7 +660,7 @@ func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues)
return err
}
if c.All {
- // We dont have a great way to get all the exited containers, so need to get all and then
+ // We don't have a great way to get all the exited containers, so need to get all and then
// check status on them bc checkpoint considers restoring a running container an error
var exitedIDs []string
for _, id := range ids {
@@ -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/network.go b/pkg/adapter/network.go
index 9659ae339..160e334e9 100644
--- a/pkg/adapter/network.go
+++ b/pkg/adapter/network.go
@@ -153,8 +153,8 @@ func (r *LocalRuntime) removeNetwork(ctx context.Context, name string, container
return nil
}
-// NetworkCreate creates a CNI network
-func (r *LocalRuntime) NetworkCreate(cli *cliconfig.NetworkCreateValues) (string, error) {
+// NetworkCreateBridge creates a CNI network
+func (r *LocalRuntime) NetworkCreateBridge(cli *cliconfig.NetworkCreateValues) (string, error) {
isGateway := true
ipMasq := true
subnet := &cli.Network
@@ -262,3 +262,50 @@ func (r *LocalRuntime) NetworkCreate(cli *cliconfig.NetworkCreateValues) (string
err = ioutil.WriteFile(cniPathName, b, 0644)
return cniPathName, err
}
+
+// NetworkCreateMacVLAN creates a CNI network
+func (r *LocalRuntime) NetworkCreateMacVLAN(cli *cliconfig.NetworkCreateValues) (string, error) {
+ var (
+ name string
+ plugins []network.CNIPlugins
+ )
+ liveNetNames, err := network.GetLiveNetworkNames()
+ if err != nil {
+ return "", err
+ }
+ // Make sure the host-device exists
+ if !util.StringInSlice(cli.MacVLAN, liveNetNames) {
+ return "", errors.Errorf("failed to find network interface %q", cli.MacVLAN)
+ }
+ if len(cli.InputArgs) > 0 {
+ name = cli.InputArgs[0]
+ netNames, err := network.GetNetworkNamesFromFileSystem()
+ if err != nil {
+ return "", err
+ }
+ if util.StringInSlice(name, netNames) {
+ return "", errors.Errorf("the network name %s is already used", name)
+ }
+ }
+ if len(name) < 1 {
+ name, err = network.GetFreeDeviceName()
+ if err != nil {
+ return "", err
+ }
+ }
+ ncList := network.NewNcList(name, cniversion.Current())
+ macvlan := network.NewMacVLANPlugin(cli.MacVLAN)
+ plugins = append(plugins, macvlan)
+ ncList["plugins"] = plugins
+ b, err := json.MarshalIndent(ncList, "", " ")
+ if err != nil {
+ return "", err
+ }
+ cniConfigPath, err := getCNIConfDir(r)
+ if err != nil {
+ return "", err
+ }
+ cniPathName := filepath.Join(cniConfigPath, fmt.Sprintf("%s.conflist", name))
+ err = ioutil.WriteFile(cniPathName, b, 0644)
+ return cniPathName, err
+}
diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go
index 85f93ed3e..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"
@@ -36,7 +38,7 @@ const (
)
// PodContainerStats is struct containing an adapter Pod and a libpod
-// ContainerStats and is used primarily for outputing pod stats.
+// ContainerStats and is used primarily for outputting pod stats.
type PodContainerStats struct {
Pod *Pod
ContainerStats map[string]*libpod.ContainerStats
@@ -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/pods_remote.go b/pkg/adapter/pods_remote.go
index 0c62ac923..16d34769e 100644
--- a/pkg/adapter/pods_remote.go
+++ b/pkg/adapter/pods_remote.go
@@ -19,7 +19,7 @@ import (
)
// PodContainerStats is struct containing an adapter Pod and a libpod
-// ContainerStats and is used primarily for outputing pod stats.
+// ContainerStats and is used primarily for outputting pod stats.
type PodContainerStats struct {
Pod *Pod
ContainerStats map[string]*libpod.ContainerStats
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..ac843b655 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
@@ -84,6 +84,15 @@ func getRuntime(runtime *libpod.Runtime) (*LocalRuntime, error) {
}, nil
}
+// GetFilterImages returns a slice of images in containerimages that are "filtered"
+func (r *LocalRuntime) GetFilteredImages(filters []string, rwOnly bool) ([]*ContainerImage, error) {
+ images, err := r.ImageRuntime().GetImagesWithFilters(filters)
+ if err != nil {
+ return nil, err
+ }
+ return r.ImagestoContainerImages(images, rwOnly)
+}
+
// GetImages returns a slice of images in containerimages
func (r *LocalRuntime) GetImages() ([]*ContainerImage, error) {
return r.getImages(false)
@@ -95,11 +104,15 @@ func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) {
}
func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) {
- var containerImages []*ContainerImage
images, err := r.Runtime.ImageRuntime().GetImages()
if err != nil {
return nil, err
}
+ return r.ImagestoContainerImages(images, rwOnly)
+}
+
+func (r *LocalRuntime) ImagestoContainerImages(images []*image.Image, rwOnly bool) ([]*ContainerImage, error) {
+ var containerImages []*ContainerImage
for _, i := range images {
if rwOnly && i.IsReadOnly() {
continue
@@ -147,8 +160,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 12bf550f2..87b4999ce 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 ...
@@ -199,6 +200,28 @@ func (r *LocalRuntime) GetRWImages() ([]*ContainerImage, error) {
return r.getImages(true)
}
+func (r *LocalRuntime) GetFilteredImages(filters []string, rwOnly bool) ([]*ContainerImage, error) {
+ var newImages []*ContainerImage
+ images, err := iopodman.ListImagesWithFilters().Call(r.Conn, filters)
+ if err != nil {
+ return nil, err
+ }
+ for _, i := range images {
+ if rwOnly && i.ReadOnly {
+ continue
+ }
+ name := i.Id
+ if len(i.RepoTags) > 1 {
+ name = i.RepoTags[0]
+ }
+ newImage, err := imageInListToContainerImage(i, name, r)
+ if err != nil {
+ return nil, err
+ }
+ newImages = append(newImages, newImage)
+ }
+ return newImages, nil
+}
func (r *LocalRuntime) getImages(rwOnly bool) ([]*ContainerImage, error) {
var newImages []*ContainerImage
images, err := iopodman.ListImages().Call(r.Conn)
@@ -232,21 +255,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 +361,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 +444,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
@@ -450,7 +479,7 @@ func (r *LocalRuntime) GetFileFromRemoteHost(remoteFilePath, outputPath string,
reader := r.Conn.Reader
if _, err := io.CopyN(writer, reader, length); err != nil {
- return errors.Wrap(err, "file transer failed")
+ return errors.Wrap(err, "file transfer failed")
}
return nil
}
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/hooks/docs/oci-hooks.5.md b/pkg/hooks/docs/oci-hooks.5.md
index 0a01e1bb8..b50a6bddc 100644
--- a/pkg/hooks/docs/oci-hooks.5.md
+++ b/pkg/hooks/docs/oci-hooks.5.md
@@ -21,7 +21,7 @@ The default directory is `/usr/share/containers/oci/hooks.d`, but tools consumin
If multiple directories are configured, a JSON filename in a preferred directory masks entries with the same filename in directories with lower precedence. For example, if a consuming tool watches for hooks in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` (in order of decreasing precedence), then a hook definition in `/etc/containers/oci/hooks.d/01-my-hook.json` will mask any definition in `/usr/share/containers/oci/hooks.d/01-my-hook.json`.
-Tools consuming this format may also opt to monitor the hook directries for changes, in which case they will notice additions, changes, and removals to JSON files without needing to be restarted or otherwise signaled. When the tool monitors multiple hooks directories, the precedence discussed in the previous paragraph still applies. For example, if a consuming tool watches for hooks in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` (in order of decreasing precedence), then writing a new hook definition to `/etc/containers/oci/hooks.d/01-my-hook.json` will mask the hook previously loaded from `/usr/share/containers/oci/hooks.d/01-my-hook.json`. Subsequent changes to `/usr/share/containers/oci/hooks.d/01-my-hook.json` will have no effect on the consuming tool as long as `/etc/containers/oci/hooks.d/01-my-hook.json` exists. Removing `/etc/containers/oci/hooks.d/01-my-hook.json` will reload the hook from `/usr/share/containers/oci/hooks.d/01-my-hook.json`.
+Tools consuming this format may also opt to monitor the hook directories for changes, in which case they will notice additions, changes, and removals to JSON files without needing to be restarted or otherwise signaled. When the tool monitors multiple hooks directories, the precedence discussed in the previous paragraph still applies. For example, if a consuming tool watches for hooks in `/etc/containers/oci/hooks.d` and `/usr/share/containers/oci/hooks.d` (in order of decreasing precedence), then writing a new hook definition to `/etc/containers/oci/hooks.d/01-my-hook.json` will mask the hook previously loaded from `/usr/share/containers/oci/hooks.d/01-my-hook.json`. Subsequent changes to `/usr/share/containers/oci/hooks.d/01-my-hook.json` will have no effect on the consuming tool as long as `/etc/containers/oci/hooks.d/01-my-hook.json` exists. Removing `/etc/containers/oci/hooks.d/01-my-hook.json` will reload the hook from `/usr/share/containers/oci/hooks.d/01-my-hook.json`.
Hooks are injected in the order obtained by sorting the JSON file names, after converting them to lower case, based on their Unicode code points.
For example, a matching hook defined in `01-my-hook.json` would be injected before matching hooks defined in `02-another-hook.json` and `01-UPPERCASE.json`.
diff --git a/pkg/network/config.go b/pkg/network/config.go
index 37eb0dd64..e47b16143 100644
--- a/pkg/network/config.go
+++ b/pkg/network/config.go
@@ -90,6 +90,22 @@ func (p PortMapConfig) Bytes() ([]byte, error) {
return json.MarshalIndent(p, "", "\t")
}
+type IPAMDHCP struct {
+ DHCP string `json:"type"`
+}
+
+// MacVLANConfig describes the macvlan config
+type MacVLANConfig struct {
+ PluginType string `json:"type"`
+ Master string `json:"master"`
+ IPAM IPAMDHCP `json:"ipam"`
+}
+
+// Bytes outputs the configuration as []byte
+func (p MacVLANConfig) Bytes() ([]byte, error) {
+ return json.MarshalIndent(p, "", "\t")
+}
+
// FirewallConfig describes the firewall plugin
type FirewallConfig struct {
PluginType string `json:"type"`
diff --git a/pkg/network/netconflist.go b/pkg/network/netconflist.go
index e19051b88..a8217097a 100644
--- a/pkg/network/netconflist.go
+++ b/pkg/network/netconflist.go
@@ -132,3 +132,15 @@ func HasDNSNamePlugin(paths []string) bool {
}
return false
}
+
+// NewMacVLANPlugin creates a macvlanconfig with a given device name
+func NewMacVLANPlugin(device string) MacVLANConfig {
+ i := IPAMDHCP{DHCP: "dhcp"}
+
+ m := MacVLANConfig{
+ PluginType: "macvlan",
+ Master: device,
+ IPAM: i,
+ }
+ return m
+}
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/spec/storage.go b/pkg/spec/storage.go
index 79c065b5d..dbdab0030 100644
--- a/pkg/spec/storage.go
+++ b/pkg/spec/storage.go
@@ -136,9 +136,9 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount,
unifiedMounts[initMount.Destination] = initMount
}
- // Before superceding, we need to find volume mounts which conflict with
+ // Before superseding, we need to find volume mounts which conflict with
// named volumes, and vice versa.
- // We'll delete the conflicts here as we supercede.
+ // We'll delete the conflicts here as we supersede.
for dest := range unifiedMounts {
if _, ok := baseVolumes[dest]; ok {
delete(baseVolumes, dest)
@@ -150,7 +150,7 @@ func (config *CreateConfig) parseVolumes(runtime *libpod.Runtime) ([]spec.Mount,
}
}
- // Supercede volumes-from/image volumes with unified volumes from above.
+ // Supersede volumes-from/image volumes with unified volumes from above.
// This is an unconditional replacement.
for dest, mount := range unifiedMounts {
baseMounts[dest] = mount
@@ -336,7 +336,7 @@ func (config *CreateConfig) getMounts() (map[string]spec.Mount, map[string]*libp
// TODO(vrothberg): the manual parsing can be replaced with a regular expression
// to allow a more robust parsing of the mount format and to give
- // precise errors regarding supported format versus suppored options.
+ // precise errors regarding supported format versus supported options.
for _, mount := range config.MountsFlag {
arr := strings.SplitN(mount, ",", 2)
if len(arr) < 2 {
@@ -820,7 +820,7 @@ func (config *CreateConfig) addContainerInitBinary(path string) (spec.Mount, err
return mount, nil
}
-// Supercede existing mounts in the spec with new, user-specified mounts.
+// Supersede existing mounts in the spec with new, user-specified mounts.
// TODO: Should we unmount subtree mounts? E.g., if /tmp/ is mounted by
// one mount, and we already have /tmp/a and /tmp/b, should we remove
// the /tmp/a and /tmp/b mounts in favor of the more general /tmp?
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/trust/trust.go b/pkg/trust/trust.go
index b1febbe81..60de099fa 100644
--- a/pkg/trust/trust.go
+++ b/pkg/trust/trust.go
@@ -139,7 +139,7 @@ func LoadAndMergeConfig(dirPath string) (*RegistryConfiguration, error) {
return &mergedConfig, nil
}
-// HaveMatchRegistry checks if trust settings for the registry have been configed in yaml file
+// HaveMatchRegistry checks if trust settings for the registry have been configured in yaml file
func HaveMatchRegistry(key string, registryConfigs *RegistryConfiguration) *RegistryNamespace {
searchKey := key
if !strings.Contains(searchKey, "/") {
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index 633d8a124..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 delimeter.
- 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..1d46c5b71 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,32 +29,40 @@ 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"
)
-// ListImages lists all the images in the store
-// It requires no inputs.
-func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
- images, err := i.Runtime.ImageRuntime().GetImages()
+// ListImagesWithFilters returns a list of images that have been filtered
+func (i *LibpodAPI) ListImagesWithFilters(call iopodman.VarlinkCall, filters []string) error {
+ images, err := i.Runtime.ImageRuntime().GetImagesWithFilters(filters)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err))
}
+ imageList, err := imagesToImageList(images)
+ if err != nil {
+ return call.ReplyErrorOccurred(fmt.Sprintf("unable to parse response", err))
+ }
+ return call.ReplyListImagesWithFilters(imageList)
+}
+
+// imagesToImageList converts a slice of Images to an imagelist for varlink responses
+func imagesToImageList(images []*image.Image) ([]iopodman.Image, error) {
var imageList []iopodman.Image
for _, image := range images {
labels, _ := image.Labels(getContext())
containers, _ := image.Containers()
repoDigests, err := image.RepoDigests()
if err != nil {
- return err
+ return nil, err
}
size, _ := image.Size(getContext())
isParent, err := image.IsParent(context.TODO())
if err != nil {
- return call.ReplyErrorOccurred(err.Error())
+ return nil, err
}
i := iopodman.Image{
@@ -70,9 +78,24 @@ func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
Labels: labels,
IsParent: isParent,
ReadOnly: image.IsReadOnly(),
+ History: image.NamesHistory(),
}
imageList = append(imageList, i)
}
+ return imageList, nil
+}
+
+// ListImages lists all the images in the store
+// It requires no inputs.
+func (i *LibpodAPI) ListImages(call iopodman.VarlinkCall) error {
+ images, err := i.Runtime.ImageRuntime().GetImages()
+ if err != nil {
+ return call.ReplyErrorOccurred(fmt.Sprintf("unable to get list of images %q", err))
+ }
+ imageList, err := imagesToImageList(images)
+ if err != nil {
+ return call.ReplyErrorOccurred(fmt.Sprintf("unable to parse response", err))
+ }
return call.ReplyListImages(imageList)
}
@@ -111,6 +134,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 +624,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 +764,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
+}