diff options
-rw-r--r-- | .papr_prepare.sh | 7 | ||||
-rw-r--r-- | cmd/podman/checkpoint.go | 11 | ||||
-rw-r--r-- | cmd/podman/container.go | 1 | ||||
-rw-r--r-- | cmd/podman/exists.go | 83 | ||||
-rw-r--r-- | cmd/podman/image.go | 1 | ||||
-rw-r--r-- | cmd/podman/kube.go | 22 | ||||
-rw-r--r-- | cmd/podman/kube_generate.go | 93 | ||||
-rw-r--r-- | cmd/podman/main.go | 1 | ||||
-rw-r--r-- | cmd/podman/version.go | 33 | ||||
-rw-r--r-- | completions/bash/podman | 31 | ||||
-rw-r--r-- | docs/podman-container-checkpoint.1.md | 12 | ||||
-rw-r--r-- | docs/podman-container-exists.1.md | 40 | ||||
-rw-r--r-- | docs/podman-container-restore.1.md | 8 | ||||
-rw-r--r-- | docs/podman-container.1.md | 1 | ||||
-rw-r--r-- | docs/podman-image-exists.1.md | 40 | ||||
-rw-r--r-- | docs/podman-image.1.md | 1 | ||||
-rw-r--r-- | docs/podman-version.1.md | 23 | ||||
-rw-r--r-- | docs/tutorials/podman_tutorial.md | 2 | ||||
-rw-r--r-- | libpod/container_api.go | 11 | ||||
-rw-r--r-- | libpod/container_internal_linux.go | 16 | ||||
-rw-r--r-- | libpod/image/errors.go | 15 | ||||
-rw-r--r-- | libpod/image/image.go | 4 | ||||
-rw-r--r-- | libpod/kube.go | 270 | ||||
-rw-r--r-- | libpod/oci.go | 15 | ||||
-rw-r--r-- | test/e2e/exists_test.go | 85 |
25 files changed, 799 insertions, 27 deletions
diff --git a/.papr_prepare.sh b/.papr_prepare.sh index e0657dcd2..5d7d21530 100644 --- a/.papr_prepare.sh +++ b/.papr_prepare.sh @@ -10,6 +10,13 @@ if [[ ${DIST} != "Fedora" ]]; then PYTHON=python fi +# Since CRIU 3.11 has been pushed to Fedora 28 the checkpoint/restore +# test cases are actually run. As CRIU uses iptables to lock and unlock +# the network during checkpoint and restore it needs the following two +# modules loaded. +modprobe ip6table_nat || : +modprobe iptable_nat || : + # Build the test image ${CONTAINER_RUNTIME} build -t ${IMAGE} -f Dockerfile.${DIST} . 2>build.log diff --git a/cmd/podman/checkpoint.go b/cmd/podman/checkpoint.go index bf280920d..ddfd12bc3 100644 --- a/cmd/podman/checkpoint.go +++ b/cmd/podman/checkpoint.go @@ -24,6 +24,10 @@ var ( Usage: "keep all temporary checkpoint files", }, cli.BoolFlag{ + Name: "leave-running, R", + Usage: "leave the container running after writing checkpoint to disk", + }, + cli.BoolFlag{ Name: "all, a", Usage: "checkpoint all running containers", }, @@ -50,7 +54,10 @@ func checkpointCmd(c *cli.Context) error { } defer runtime.Shutdown(false) - keep := c.Bool("keep") + options := libpod.ContainerCheckpointOptions{ + Keep: c.Bool("keep"), + KeepRunning: c.Bool("leave-running"), + } if err := checkAllAndLatest(c); err != nil { return err @@ -59,7 +66,7 @@ func checkpointCmd(c *cli.Context) error { containers, lastError := getAllOrLatestContainers(c, runtime, libpod.ContainerStateRunning, "running") for _, ctr := range containers { - if err = ctr.Checkpoint(context.TODO(), keep); err != nil { + if err = ctr.Checkpoint(context.TODO(), options); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/container.go b/cmd/podman/container.go index ff634278f..b6262f890 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -9,6 +9,7 @@ var ( attachCommand, checkpointCommand, cleanupCommand, + containerExistsCommand, commitCommand, createCommand, diffCommand, diff --git a/cmd/podman/exists.go b/cmd/podman/exists.go new file mode 100644 index 000000000..2f7b7c185 --- /dev/null +++ b/cmd/podman/exists.go @@ -0,0 +1,83 @@ +package main + +import ( + "os" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" + "github.com/pkg/errors" + "github.com/urfave/cli" +) + +var ( + imageExistsDescription = ` + podman image exists + + Check if an image exists in local storage +` + + imageExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if an image exists in local storage", + Description: imageExistsDescription, + Action: imageExistsCmd, + ArgsUsage: "IMAGE-NAME", + OnUsageError: usageErrorHandler, + } +) + +var ( + containerExistsDescription = ` + podman container exists + + Check if a container exists in local storage +` + + containerExistsCommand = cli.Command{ + Name: "exists", + Usage: "Check if a container exists in local storage", + Description: containerExistsDescription, + Action: containerExistsCmd, + ArgsUsage: "CONTAINER-NAME", + OnUsageError: usageErrorHandler, + } +) + +func imageExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one image at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.ImageRuntime().NewFromLocal(args[0]); err != nil { + if errors.Cause(err) == image.ErrNoSuchImage { + os.Exit(1) + } + return err + } + return nil +} + +func containerExistsCmd(c *cli.Context) error { + args := c.Args() + if len(args) > 1 || len(args) < 1 { + return errors.New("you may only check for the existence of one container at a time") + } + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + if _, err := runtime.LookupContainer(args[0]); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + os.Exit(1) + } + return err + } + return nil +} diff --git a/cmd/podman/image.go b/cmd/podman/image.go index e67f61799..418b442e3 100644 --- a/cmd/podman/image.go +++ b/cmd/podman/image.go @@ -9,6 +9,7 @@ var ( buildCommand, historyCommand, importCommand, + imageExistsCommand, inspectCommand, loadCommand, lsImagesCommand, diff --git a/cmd/podman/kube.go b/cmd/podman/kube.go new file mode 100644 index 000000000..ced87e2bd --- /dev/null +++ b/cmd/podman/kube.go @@ -0,0 +1,22 @@ +package main + +import ( + "github.com/urfave/cli" +) + +var ( + kubeSubCommands = []cli.Command{ + containerKubeCommand, + } + + kubeDescription = "Work with Kubernetes objects" + kubeCommand = cli.Command{ + Name: "kube", + Usage: "Import and export Kubernetes objections from and to Podman", + Description: containerDescription, + ArgsUsage: "", + Subcommands: kubeSubCommands, + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) diff --git a/cmd/podman/kube_generate.go b/cmd/podman/kube_generate.go new file mode 100644 index 000000000..a18912668 --- /dev/null +++ b/cmd/podman/kube_generate.go @@ -0,0 +1,93 @@ +package main + +import ( + "fmt" + + "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/rootless" + "github.com/ghodss/yaml" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "github.com/urfave/cli" +) + +var ( + containerKubeFlags = []cli.Flag{ + cli.BoolFlag{ + Name: "service, s", + Usage: "only generate YAML for kubernetes service object", + }, + LatestFlag, + } + containerKubeDescription = "Generate Kubernetes Pod YAML" + containerKubeCommand = cli.Command{ + Name: "generate", + Usage: "Generate Kubernetes pod YAML for a container", + Description: containerKubeDescription, + Flags: sortFlags(containerKubeFlags), + Action: generateKubeYAMLCmd, + ArgsUsage: "CONTAINER-NAME", + UseShortOptionHandling: true, + OnUsageError: usageErrorHandler, + } +) + +// generateKubeYAMLCmdgenerates or replays kube +func generateKubeYAMLCmd(c *cli.Context) error { + var ( + container *libpod.Container + err error + output []byte + ) + + if rootless.IsRootless() { + return errors.Wrapf(libpod.ErrNotImplemented, "rootless users") + } + args := c.Args() + if len(args) > 1 || (len(args) < 1 && !c.Bool("latest")) { + return errors.Errorf("you must provide one container ID or name or --latest") + } + if c.Bool("service") { + return errors.Wrapf(libpod.ErrNotImplemented, "service generation") + } + + runtime, err := libpodruntime.GetRuntime(c) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + // Get the container in question + if c.Bool("latest") { + container, err = runtime.GetLatestContainer() + } else { + container, err = runtime.LookupContainer(args[0]) + } + if err != nil { + return err + } + + if len(container.Dependencies()) > 0 { + return errors.Wrapf(libpod.ErrNotImplemented, "containers with dependencies") + } + + podYAML, err := container.InspectForKube() + if err != nil { + return err + } + + developmentComment := []byte("# Generation of Kubenetes YAML is still under development!\n") + logrus.Warn("This function is still under heavy development.") + // Marshall the results + b, err := yaml.Marshal(podYAML) + if err != nil { + return err + } + output = append(output, developmentComment...) + output = append(output, b...) + // Output the v1.Pod with the v1.Container + fmt.Println(string(output)) + + return nil +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 38eac4504..6be192593 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -77,6 +77,7 @@ func main() { infoCommand, inspectCommand, killCommand, + kubeCommand, loadCommand, loginCommand, logoutCommand, diff --git a/cmd/podman/version.go b/cmd/podman/version.go index d80f24a14..d81deb696 100644 --- a/cmd/podman/version.go +++ b/cmd/podman/version.go @@ -4,6 +4,7 @@ import ( "fmt" "time" + "github.com/containers/libpod/cmd/podman/formats" "github.com/containers/libpod/libpod" "github.com/pkg/errors" "github.com/urfave/cli" @@ -15,6 +16,19 @@ func versionCmd(c *cli.Context) error { if err != nil { errors.Wrapf(err, "unable to determine version") } + + versionOutputFormat := c.String("format") + if versionOutputFormat != "" { + var out formats.Writer + switch versionOutputFormat { + case formats.JSONString: + out = formats.JSONStruct{Output: output} + default: + out = formats.StdoutTemplate{Output: output, Template: versionOutputFormat} + } + formats.Writer(out).Out() + return nil + } fmt.Println("Version: ", output.Version) fmt.Println("Go Version: ", output.GoVersion) if output.GitCommit != "" { @@ -30,8 +44,17 @@ func versionCmd(c *cli.Context) error { } // Cli command to print out the full version of podman -var versionCommand = cli.Command{ - Name: "version", - Usage: "Display the PODMAN Version Information", - Action: versionCmd, -} +var ( + versionCommand = cli.Command{ + Name: "version", + Usage: "Display the Podman Version Information", + Action: versionCmd, + Flags: versionFlags, + } + versionFlags = []cli.Flag{ + cli.StringFlag{ + Name: "format", + Usage: "Change the output format to JSON or a Go template", + }, + } +) diff --git a/completions/bash/podman b/completions/bash/podman index 222511a3c..3c6b6ec50 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -1906,11 +1906,16 @@ _podman_top() { } _podman_version() { - local options_with_args=" - " - local boolean_options=" - " - _complete_ "$options_with_args" "$boolean_options" + local boolean_options=" + --help + -h + " + local options_with_args=" + --format + " + local all_options="$options_with_args $boolean_options" + + _complete_ "$options_with_args" "$boolean_options" } _podman_save() { @@ -2173,6 +2178,22 @@ _podman_container_runlabel() { esac } +_podman_container_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + +_podman_image_exists() { + local options_with_args=" + " + + local boolean_options=" + " +} + _podman_pod_create() { local options_with_args=" --cgroup-parent diff --git a/docs/podman-container-checkpoint.1.md b/docs/podman-container-checkpoint.1.md index 4906e0e12..6f454dfd1 100644 --- a/docs/podman-container-checkpoint.1.md +++ b/docs/podman-container-checkpoint.1.md @@ -17,6 +17,18 @@ are not deleted if checkpointing fails for further debugging. If checkpointing s files are theoretically not needed, but if these files are needed Podman can keep the files for further analysis. +**--all, -a** + +Checkpoint all running containers. + +**--latest, -l** + +Instead of providing the container name or ID, checkpoint the last created container. + +**--leave-running, -R** + +Leave the container running after checkpointing instead of stopping it. + ## EXAMPLE podman container checkpoint mywebserver diff --git a/docs/podman-container-exists.1.md b/docs/podman-container-exists.1.md new file mode 100644 index 000000000..76701e2c2 --- /dev/null +++ b/docs/podman-container-exists.1.md @@ -0,0 +1,40 @@ +% PODMAN(1) Podman Man Pages +% Brent Baude +% November 2018 +# NAME +podman-container-exists- Check if a container exists in local storage + +# SYNOPSIS +**podman container exists** +[**-h**|**--help**] +CONTAINER + +# DESCRIPTION +**podman container exists** checks if a container exists in local storage. The **ID** or **Name** +of the container may be used as input. Podman will return an exit code +of `0` when the container is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if an container called `webclient` exists in local storage (the container does actually exist). +``` +$ sudo podman container exists webclient +$ echo $? +0 +$ +``` + +Check if an container called `webbackend` exists in local storage (the container does not actually exist). +``` +$ sudo podman container exists webbackend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman(1) + +# HISTORY +November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-container-restore.1.md b/docs/podman-container-restore.1.md index 6360bccb0..4dd5ea7c7 100644 --- a/docs/podman-container-restore.1.md +++ b/docs/podman-container-restore.1.md @@ -24,6 +24,14 @@ processes in the checkpointed container. Without the **-k**, **--keep** option the checkpoint will be consumed and cannot be used again. +**--all, -a** + +Restore all checkpointed containers. + +**--latest, -l** + +Instead of providing the container name or ID, restore the last created container. + ## EXAMPLE podman container restore mywebserver diff --git a/docs/podman-container.1.md b/docs/podman-container.1.md index 67d42bfef..aa5dfa82c 100644 --- a/docs/podman-container.1.md +++ b/docs/podman-container.1.md @@ -20,6 +20,7 @@ The container command allows you to manage containers | create | [podman-create(1)](podman-create.1.md) | Create a new container. | | diff | [podman-diff(1)](podman-diff.1.md) | Inspect changes on a container or image's filesystem. | | exec | [podman-exec(1)](podman-exec.1.md) | Execute a command in a running container. | +| exists | [podman-exists(1)](podman-container-exists.1.md) | Check if a container exists in local storage | | export | [podman-export(1)](podman-export.1.md) | Export a container's filesystem contents as a tar archive. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a container or image's configuration. | | kill | [podman-kill(1)](podman-kill.1.md) | Kill the main process in one or more containers. | diff --git a/docs/podman-image-exists.1.md b/docs/podman-image-exists.1.md new file mode 100644 index 000000000..e04c23721 --- /dev/null +++ b/docs/podman-image-exists.1.md @@ -0,0 +1,40 @@ +% PODMAN(1) Podman Man Pages +% Brent Baude +% November 2018 +# NAME +podman-image-exists- Check if an image exists in local storage + +# SYNOPSIS +**podman image exists** +[**-h**|**--help**] +IMAGE + +# DESCRIPTION +**podman image exists** checks if an image exists in local storage. The **ID** or **Name** +of the image may be used as input. Podman will return an exit code +of `0` when the image is found. A `1` will be returned otherwise. An exit code of `125` indicates there +was an issue accessing the local storage. + +## Examples ## + +Check if an image called `webclient` exists in local storage (the image does actually exist). +``` +$ sudo podman image exists webclient +$ echo $? +0 +$ +``` + +Check if an image called `webbackend` exists in local storage (the image does not actually exist). +``` +$ sudo podman image exists webbackend +$ echo $? +1 +$ +``` + +## SEE ALSO +podman(1) + +# HISTORY +November 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-image.1.md b/docs/podman-image.1.md index 33de0456f..446f8667d 100644 --- a/docs/podman-image.1.md +++ b/docs/podman-image.1.md @@ -14,6 +14,7 @@ The image command allows you to manage images | Command | Man Page | Description | | -------- | ----------------------------------------- | ------------------------------------------------------------------------------ | | build | [podman-build(1)](podman-build.1.md) | Build a container using a Dockerfile. | +| exists | [podman-exists(1)](podman-image-exists.1.md) | Check if a image exists in local storage | | history | [podman-history(1)](podman-history.1.md) | Show the history of an image. | | import | [podman-import(1)](podman-import.1.md) | Import a tarball and save it as a filesystem image. | | inspect | [podman-inspect(1)](podman-inspect.1.md) | Display a image or image's configuration. | diff --git a/docs/podman-version.1.md b/docs/podman-version.1.md index 0c9b9ceed..749a33afd 100644 --- a/docs/podman-version.1.md +++ b/docs/podman-version.1.md @@ -16,8 +16,31 @@ OS, and Architecture. Print usage statement +**--format** + +Change output format to "json" or a Go template. + +## Example + +A sample output of the `version` command: +``` +$ podman version +Version: 0.11.1 +Go Version: go1.11 +Git Commit: "8967a1d691ed44896b81ad48c863033f23c65eb0-dirty" +Built: Thu Nov 8 22:35:40 2018 +OS/Arch: linux/amd64 +``` + +Filtering out only the version: +``` +$ podman version --format '{{.Version}}' +0.11.2 +``` + ## SEE ALSO podman(1), crio(8) ## HISTORY +November 2018, Added --format flag by Tomas Tomecek <ttomecek@redhat.com> July 2017, Originally compiled by Urvashi Mohnani <umohnani@redhat.com> diff --git a/docs/tutorials/podman_tutorial.md b/docs/tutorials/podman_tutorial.md index 5a8f997b8..ce94d7d15 100644 --- a/docs/tutorials/podman_tutorial.md +++ b/docs/tutorials/podman_tutorial.md @@ -129,7 +129,7 @@ $ sudo podman inspect -l | grep IPAddress\": "IPAddress": "10.88.6.140", ``` -Note: The -l is convenience arguement for **latest container**. You can also use the container's ID instead +Note: The -l is a convenience argument for **latest container**. You can also use the container's ID instead of -l. ### Testing the httpd server diff --git a/libpod/container_api.go b/libpod/container_api.go index 390987394..df6b6e962 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -830,8 +830,15 @@ func (c *Container) Refresh(ctx context.Context) error { return nil } +// ContainerCheckpointOptions is a struct used to pass the parameters +// for checkpointing to corresponding functions +type ContainerCheckpointOptions struct { + Keep bool + KeepRunning bool +} + // Checkpoint checkpoints a container -func (c *Container) Checkpoint(ctx context.Context, keep bool) error { +func (c *Container) Checkpoint(ctx context.Context, options ContainerCheckpointOptions) error { logrus.Debugf("Trying to checkpoint container %s", c) if !c.batched { c.lock.Lock() @@ -842,7 +849,7 @@ func (c *Container) Checkpoint(ctx context.Context, keep bool) error { } } - return c.checkpoint(ctx, keep) + return c.checkpoint(ctx, options) } // Restore restores a container diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 66c7e8a04..e6071945d 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -431,7 +431,7 @@ func (c *Container) addNamespaceContainer(g *generate.Generator, ns LinuxNS, ctr return nil } -func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { +func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointOptions) (err error) { if !criu.CheckForCriu() { return errors.Errorf("checkpointing a container requires at least CRIU %d", criu.MinCriuVersion) @@ -440,7 +440,7 @@ func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State) } - if err := c.runtime.ociRuntime.checkpointContainer(c); err != nil { + if err := c.runtime.ociRuntime.checkpointContainer(c, options); err != nil { return err } @@ -457,14 +457,16 @@ func (c *Container) checkpoint(ctx context.Context, keep bool) (err error) { logrus.Debugf("Checkpointed container %s", c.ID()) - c.state.State = ContainerStateStopped + if !options.KeepRunning { + c.state.State = ContainerStateStopped - // Cleanup Storage and Network - if err := c.cleanup(ctx); err != nil { - return err + // Cleanup Storage and Network + if err := c.cleanup(ctx); err != nil { + return err + } } - if !keep { + if !options.Keep { // Remove log file os.Remove(filepath.Join(c.bundlePath(), "dump.log")) // Remove statistic file diff --git a/libpod/image/errors.go b/libpod/image/errors.go new file mode 100644 index 000000000..4088946cb --- /dev/null +++ b/libpod/image/errors.go @@ -0,0 +1,15 @@ +package image + +import ( + "errors" +) + +// Copied directly from libpod errors to avoid circular imports +var ( + // ErrNoSuchCtr indicates the requested container does not exist + ErrNoSuchCtr = errors.New("no such container") + // ErrNoSuchPod indicates the requested pod does not exist + ErrNoSuchPod = errors.New("no such pod") + // ErrNoSuchImage indicates the requested image does not exist + ErrNoSuchImage = errors.New("no such image") +) diff --git a/libpod/image/image.go b/libpod/image/image.go index 7e520d97e..a05c15160 100644 --- a/libpod/image/image.go +++ b/libpod/image/image.go @@ -252,7 +252,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) { // The image has a registry name in it and we made sure we looked for it locally // with a tag. It cannot be local. if decomposedImage.hasRegistry { - return nil, errors.Errorf("%s", imageError) + return nil, errors.Wrapf(ErrNoSuchImage, imageError) } @@ -275,7 +275,7 @@ func (i *Image) getLocalImage() (*storage.Image, error) { return repoImage, nil } - return nil, errors.Wrapf(err, imageError) + return nil, errors.Wrapf(ErrNoSuchImage, err.Error()) } // ID returns the image ID as a string diff --git a/libpod/kube.go b/libpod/kube.go new file mode 100644 index 000000000..00db0033b --- /dev/null +++ b/libpod/kube.go @@ -0,0 +1,270 @@ +package libpod + +import ( + "fmt" + "strings" + + "github.com/containers/libpod/pkg/lookup" + "github.com/containers/libpod/pkg/util" + "github.com/cri-o/ocicni/pkg/ocicni" + "github.com/opencontainers/runtime-spec/specs-go" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" + "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/api/resource" + v12 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +// InspectForKube takes a slice of libpod containers and generates +// one v1.Pod description that includes just a single container. +func (c *Container) InspectForKube() (*v1.Pod, error) { + // Generate the v1.Pod yaml description + return simplePodWithV1Container(c) +} + +// simplePodWithV1Container is a function used by inspect when kube yaml needs to be generated +// for a single container. we "insert" that container description in a pod. +func simplePodWithV1Container(ctr *Container) (*v1.Pod, error) { + var containers []v1.Container + result, err := containerToV1Container(ctr) + if err != nil { + return nil, err + } + containers = append(containers, result) + + tm := v12.TypeMeta{ + Kind: "Pod", + APIVersion: "v1", + } + + // Add a label called "app" with the containers name as a value + labels := make(map[string]string) + labels["app"] = removeUnderscores(ctr.Name()) + om := v12.ObjectMeta{ + // The name of the pod is container_name-libpod + Name: fmt.Sprintf("%s-libpod", removeUnderscores(ctr.Name())), + Labels: labels, + // CreationTimestamp seems to be required, so adding it; in doing so, the timestamp + // will reflect time this is run (not container create time) because the conversion + // of the container create time to v1 Time is probably not warranted nor worthwhile. + CreationTimestamp: v12.Now(), + } + ps := v1.PodSpec{ + Containers: containers, + } + p := v1.Pod{ + TypeMeta: tm, + ObjectMeta: om, + Spec: ps, + } + return &p, nil +} + +// containerToV1Container converts information we know about a libpod container +// to a V1.Container specification. +func containerToV1Container(c *Container) (v1.Container, error) { + kubeContainer := v1.Container{} + kubeSec, err := generateKubeSecurityContext(c) + if err != nil { + return kubeContainer, err + } + + if len(c.config.Spec.Linux.Devices) > 0 { + // TODO Enable when we can support devices and their names + devices, err := generateKubeVolumeDeviceFromLinuxDevice(c.Spec().Linux.Devices) + if err != nil { + return kubeContainer, err + } + kubeContainer.VolumeDevices = devices + return kubeContainer, errors.Wrapf(ErrNotImplemented, "linux devices") + } + + if len(c.config.UserVolumes) > 0 { + // TODO When we until we can resolve what the volume name should be, this is disabled + // Volume names need to be coordinated "globally" in the kube files. + volumes, err := libpodMountsToKubeVolumeMounts(c) + if err != nil { + return kubeContainer, err + } + kubeContainer.VolumeMounts = volumes + return kubeContainer, errors.Wrapf(ErrNotImplemented, "volume names") + } + + envVariables, err := libpodEnvVarsToKubeEnvVars(c.config.Spec.Process.Env) + if err != nil { + return kubeContainer, nil + } + + ports, err := ocicniPortMappingToContainerPort(c.PortMappings()) + if err != nil { + return kubeContainer, nil + } + + containerCommands := c.Command() + kubeContainer.Name = removeUnderscores(c.Name()) + + _, image := c.Image() + kubeContainer.Image = image + kubeContainer.Stdin = c.Stdin() + kubeContainer.Command = containerCommands + // TODO need to figure out how we handle command vs entry point. Kube appears to prefer entrypoint. + // right now we just take the container's command + //container.Args = args + kubeContainer.WorkingDir = c.WorkingDir() + kubeContainer.Ports = ports + // This should not be applicable + //container.EnvFromSource = + kubeContainer.Env = envVariables + // TODO enable resources when we can support naming conventions + //container.Resources + kubeContainer.SecurityContext = kubeSec + kubeContainer.StdinOnce = false + kubeContainer.TTY = c.config.Spec.Process.Terminal + + return kubeContainer, nil +} + +// ocicniPortMappingToContainerPort takes an ocicni portmapping and converts +// it to a v1.ContainerPort format for kube output +func ocicniPortMappingToContainerPort(portMappings []ocicni.PortMapping) ([]v1.ContainerPort, error) { + var containerPorts []v1.ContainerPort + for _, p := range portMappings { + var protocol v1.Protocol + switch strings.ToUpper(p.Protocol) { + case "TCP": + protocol = v1.ProtocolTCP + case "UDP": + protocol = v1.ProtocolUDP + default: + return containerPorts, errors.Errorf("unknown network protocol %s", p.Protocol) + } + cp := v1.ContainerPort{ + // Name will not be supported + HostPort: p.HostPort, + HostIP: p.HostIP, + ContainerPort: p.ContainerPort, + Protocol: protocol, + } + containerPorts = append(containerPorts, cp) + } + return containerPorts, nil +} + +// libpodEnvVarsToKubeEnvVars converts a key=value string slice to []v1.EnvVar +func libpodEnvVarsToKubeEnvVars(envs []string) ([]v1.EnvVar, error) { + var envVars []v1.EnvVar + for _, e := range envs { + splitE := strings.SplitN(e, "=", 2) + if len(splitE) != 2 { + return envVars, errors.Errorf("environment variable %s is malformed; should be key=value", e) + } + ev := v1.EnvVar{ + Name: splitE[0], + Value: splitE[1], + } + envVars = append(envVars, ev) + } + return envVars, nil +} + +// Is this worth it? +func libpodMaxAndMinToResourceList(c *Container) (v1.ResourceList, v1.ResourceList) { //nolint + // It does not appear we can properly calculate CPU resources from the information + // we know in libpod. Libpod knows CPUs by time, shares, etc. + + // We also only know about a memory limit; no memory minimum + maxResources := make(map[v1.ResourceName]resource.Quantity) + minResources := make(map[v1.ResourceName]resource.Quantity) + config := c.Config() + maxMem := config.Spec.Linux.Resources.Memory.Limit + + _ = maxMem + + return maxResources, minResources +} + +func generateKubeVolumeMount(hostSourcePath string, mounts []specs.Mount) (v1.VolumeMount, error) { + vm := v1.VolumeMount{} + for _, m := range mounts { + if m.Source == hostSourcePath { + // TODO Name is not provided and is required by Kube; therefore, this is disabled earlier + //vm.Name = + vm.MountPath = m.Source + vm.SubPath = m.Destination + if util.StringInSlice("ro", m.Options) { + vm.ReadOnly = true + } + return vm, nil + } + } + return vm, errors.New("unable to find mount source") +} + +// libpodMountsToKubeVolumeMounts converts the containers mounts to a struct kube understands +func libpodMountsToKubeVolumeMounts(c *Container) ([]v1.VolumeMount, error) { + // At this point, I dont think we can distinguish between the default + // volume mounts and user added ones. For now, we pass them all. + var vms []v1.VolumeMount + for _, hostSourcePath := range c.config.UserVolumes { + vm, err := generateKubeVolumeMount(hostSourcePath, c.config.Spec.Mounts) + if err != nil { + return vms, err + } + vms = append(vms, vm) + } + return vms, nil +} + +// generateKubeSecurityContext generates a securityContext based on the existing container +func generateKubeSecurityContext(c *Container) (*v1.SecurityContext, error) { + priv := c.Privileged() + ro := c.IsReadOnly() + allowPrivEscalation := !c.Spec().Process.NoNewPrivileges + + // TODO enable use of capabilities when we can figure out how to extract cap-add|remove + //caps := v1.Capabilities{ + // //Add: c.config.Spec.Process.Capabilities + //} + sc := v1.SecurityContext{ + // TODO enable use of capabilities when we can figure out how to extract cap-add|remove + //Capabilities: &caps, + Privileged: &priv, + // TODO How do we know if selinux were passed into podman + //SELinuxOptions: + // RunAsNonRoot is an optional parameter; our first implementations should be root only; however + // I'm leaving this as a bread-crumb for later + //RunAsNonRoot: &nonRoot, + ReadOnlyRootFilesystem: &ro, + AllowPrivilegeEscalation: &allowPrivEscalation, + } + + if c.User() != "" { + // It is *possible* that + logrus.Debug("Looking in container for user: %s", c.User()) + u, err := lookup.GetUser(c.state.Mountpoint, c.User()) + if err != nil { + return nil, err + } + user := int64(u.Uid) + sc.RunAsUser = &user + } + return &sc, nil +} + +// generateKubeVolumeDeviceFromLinuxDevice takes a list of devices and makes a VolumeDevice struct for kube +func generateKubeVolumeDeviceFromLinuxDevice(devices []specs.LinuxDevice) ([]v1.VolumeDevice, error) { + var volumeDevices []v1.VolumeDevice + for _, d := range devices { + vd := v1.VolumeDevice{ + // TBD How are we going to sync up these names + //Name: + DevicePath: d.Path, + } + volumeDevices = append(volumeDevices, vd) + } + return volumeDevices, nil +} + +func removeUnderscores(s string) string { + return strings.Replace(s, "_", "", -1) +} diff --git a/libpod/oci.go b/libpod/oci.go index 71da830b5..8ee2c948f 100644 --- a/libpod/oci.go +++ b/libpod/oci.go @@ -844,13 +844,22 @@ func (r *OCIRuntime) execStopContainer(ctr *Container, timeout uint) error { } // checkpointContainer checkpoints the given container -func (r *OCIRuntime) checkpointContainer(ctr *Container) error { +func (r *OCIRuntime) checkpointContainer(ctr *Container, options ContainerCheckpointOptions) error { // imagePath is used by CRIU to store the actual checkpoint files imagePath := ctr.CheckpointPath() // workPath will be used to store dump.log and stats-dump workPath := ctr.bundlePath() logrus.Debugf("Writing checkpoint to %s", imagePath) logrus.Debugf("Writing checkpoint logs to %s", workPath) - return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, "checkpoint", - "--image-path", imagePath, "--work-path", workPath, ctr.ID()) + args := []string{} + args = append(args, "checkpoint") + args = append(args, "--image-path") + args = append(args, imagePath) + args = append(args, "--work-path") + args = append(args, workPath) + if options.KeepRunning { + args = append(args, "--leave-running") + } + args = append(args, ctr.ID()) + return utils.ExecCmdWithStdStreams(os.Stdin, os.Stdout, os.Stderr, nil, r.path, args...) } diff --git a/test/e2e/exists_test.go b/test/e2e/exists_test.go new file mode 100644 index 000000000..9165e8902 --- /dev/null +++ b/test/e2e/exists_test.go @@ -0,0 +1,85 @@ +package integration + +import ( + "fmt" + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman image|container exists", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.Cleanup() + f := CurrentGinkgoTestDescription() + timedResult := fmt.Sprintf("Test: %s completed in %f seconds", f.TestText, f.Duration.Seconds()) + GinkgoWriter.Write([]byte(timedResult)) + + }) + It("podman image exists in local storage by fq name", func() { + session := podmanTest.Podman([]string{"image", "exists", ALPINE}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman image exists in local storage by short name", func() { + session := podmanTest.Podman([]string{"image", "exists", "alpine"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman image does not exist in local storage", func() { + session := podmanTest.Podman([]string{"image", "exists", "alpine9999"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) + It("podman container exists in local storage by name", func() { + setup := podmanTest.RunTopContainer("foobar") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + + session := podmanTest.Podman([]string{"container", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container exists in local storage by container ID", func() { + setup := podmanTest.RunTopContainer("") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString() + + session := podmanTest.Podman([]string{"container", "exists", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container exists in local storage by short container ID", func() { + setup := podmanTest.RunTopContainer("") + setup.WaitWithDefaultTimeout() + Expect(setup.ExitCode()).To(Equal(0)) + cid := setup.OutputToString()[0:12] + + session := podmanTest.Podman([]string{"container", "exists", cid}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + }) + It("podman container does not exist in local storage", func() { + session := podmanTest.Podman([]string{"container", "exists", "foobar"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) + +}) |