diff options
59 files changed, 1388 insertions, 303 deletions
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index a7663f3e3..2946f0b91 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -52,7 +52,7 @@ Briefly describe the problem you are having in a few paragraphs. (paste your output here) ``` -**Output of `podman info`:** +**Output of `podman info --debug`:** ``` (paste your output here) diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 26e5473b2..b1b166fef 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -1,5 +1,5 @@ ![PODMAN logo](logo/podman-logo-source.svg) -# Contributing to Libpod +# Contributing to libpod We'd love to have you join the community! Below summarizes the processes that we follow. @@ -7,6 +7,7 @@ that we follow. ## Topics * [Reporting Issues](#reporting-issues) +* [Contributing to libpod](#contributing-to-libpod) * [Submitting Pull Requests](#submitting-pull-requests) * [Communications](#communications) @@ -26,6 +27,87 @@ The easier it is for us to reproduce it, the faster it'll be fixed! Please don't include any private/sensitive information in your issue! +## Contributing to libpod + +This section describes how to start a contribution to libpod. + +### Prepare your environment + +Read the [install documentation to see how to install dependencies](install.md) . + +The install documentation will illustrate the following steps: +- install libs and tools +- check installed versions +- configure network +- how to install libpod from sources + +### Fork and clone libpod + +First you need to fork this project on GitHub. + +Be sure to have [defined your `$GOPATH` environment variable](https://github.com/golang/go/wiki/GOPATH). + +Create a path that correspond to your clone `mkdir -p $GOPATH/github.com/<you>`. + +Clone your fork locally: +```shell +$ git clone git@github.com:<you>/libpod github.com/<you> $GOPATH/github.com/<you>/libpod +$ cd $GOPATH/github.com/<you>/libpod +``` + +You can also use `go get` to clone your fork: +```shell +$ go get github.com:<you>/libpod +$ cd $GOPATH/github.com/<you>/libpod +``` + +### Deal with make + +Libpod use a Makefile to realize common action like building etc... + +You can list available actions by using: +```shell +$ make help +Usage: make <target> +...output... +``` + +### Install tools + +Makefile allow you to install needed tools: +```shell +$ make install.tools +``` + +### Building binaries and test your changes + +To test your changes do `make binaries` to generate your binaries. + +Your binaries are created inside the `bin/` directory and you can test your changes: +```shell +$ bin/podman -h +bin/podman -h +NAME: + podman - manage pods and images + +USAGE: + podman [global options] command [command options] [arguments...] + +VERSION: + 1.0.1-dev + +COMMANDS: + attach Attach to a running container + build Build an image using instructions from Dockerfiles + commit Create new image based on the changed container + container Manage Containers + cp Copy files/folders between a container and the local filesystem +``` + +Well, you can now create your own branch, apply changes on it, and then submitting your pull request. + +For further reading about branching [you can read this document](https://herve.beraud.io/containers/linux/podman/isolate/environment/2019/02/06/how-to-hack-on-podman.html). + ## Submitting Pull Requests No Pull Request (PR) is too small! Typos, additional comments in the code, @@ -31,10 +31,19 @@ This project tests all builds against each supported version of Fedora, the late ## Out of scope -* Signing and pushing images to various image storages. - See [Skopeo](https://github.com/containers/skopeo/). -* Container Runtimes daemons for working with the Kubernetes CRI interface. - See [CRI-O](https://github.com/kubernetes-sigs/cri-o). +* Specializing in signing and pushing images to various storage backends. + See [Skopeo](https://github.com/containers/skopeo/) for those tasks. +* Container runtimes daemons for working with the Kubernetes CRI interface. + [CRI-O](https://github.com/kubernetes-sigs/cri-o) specializes in that. +* Supporting `docker-compose`. We believe that Kubernetes is the defacto + standard for composing Pods and for orchestrating containers, making + Kubernetes YAML a defacto standard file format. Hence, Podman allows the + creation and execution of Pods from a Kubernetes YAML file (see + [podman-play-kube](https://github.com/containers/libpod/blob/master/docs/podman-play-kube.1.md)). + Podman can also generate Kubernetes YAML based on a container or Pod (see + [podman-generate-kube](https://github.com/containers/libpod/blob/master/docs/podman-generate-kube.1.md)), + which allows for an easy transition from a local development environment + to a production Kubernetes cluster. ## OCI Projects Plans @@ -83,18 +92,23 @@ Information about contributing to this project. ## Buildah and Podman relationship -Buildah and Podman are two complementary Open-source projects that are available on -most Linux platforms and both projects reside at [GitHub.com](https://github.com) -with [Buildah](https://buildah.io) [(GitHub)](https://github.com/containers/buildah) and -[Podman](https://podman.io) [(GitHub)](https://github.com/containers/libpod). Both Buildah and Podman are -command line tools that work on OCI images and containers. The two projects -differentiate in their specialization. +Buildah and Podman are two complementary open-source projects that are +available on most Linux platforms and both projects reside at +[GitHub.com](https://github.com) with Buildah +[here](https://github.com/containers/buildah) and Podman +[here](https://github.com/containers/libpod). Both, Buildah and Podman are +command line tools that work on Open Container Initiative (OCI) images and +containers. The two projects differentiate in their specialization. Buildah specializes in building OCI images. Buildah's commands replicate all -of the commands that are found in a Dockerfile. Buildah’s goal is also to -provide a lower level coreutils interface to build images, allowing people to build -containers without requiring a Dockerfile. The intent with Buildah is to allow other -scripting languages to build container images, without requiring a daemon. +of the commands that are found in a Dockerfile. This allows building images +with and without Dockerfiles while not requiring any root privileges. +Buildah’s ultimate goal is to provide a lower-level coreutils interface to +build images. The flexibility of building images without Dockerfiles allows +for the integration of other scripting languages into the build process. +Buildah follows a simple fork-exec model and does not run as a daemon +but it is based on a comprehensive API in golang, which can be vendored +into other tools. Podman specializes in all of the commands and functions that help you to maintain and modify OCI images, such as pulling and tagging. It also allows you to create, run, and maintain those containers @@ -103,12 +117,12 @@ created from those images. A major difference between Podman and Buildah is their concept of a container. Podman allows users to create "traditional containers" where the intent of these containers is to be long lived. While Buildah containers are really just created to allow content -to be added back to the container image. An easy way to think of it is the +to be added back to the container image. An easy way to think of it is the `buildah run` command emulates the RUN command in a Dockerfile while the `podman run` command emulates the `docker run` command in functionality. Because of this and their underlying -storage differences, you cannot see Podman containers from within Buildah or vice versa. +storage differences, you can not see Podman containers from within Buildah or vice versa. -In short Buildah is an efficient way to create OCI images while Podman allows +In short, Buildah is an efficient way to create OCI images while Podman allows you to manage and maintain those images and containers in a production environment using familiar container cli commands. For more details, see the [Container Tools Guide](https://github.com/containers/buildah/tree/master/docs/containertools). diff --git a/cmd/podman/cleanup.go b/cmd/podman/cleanup.go index d68255aa2..33d456643 100644 --- a/cmd/podman/cleanup.go +++ b/cmd/podman/cleanup.go @@ -60,7 +60,7 @@ func cleanupCmd(c *cliconfig.CleanupValues) error { for _, ctr := range cleanupContainers { hadError := false if c.Remove { - if err := runtime.RemoveContainer(ctx, ctr, false, false); err != nil { + if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil { if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index fef5f1763..fadcca689 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -17,7 +17,6 @@ func getMainCommands() []*cobra.Command { _diffCommand, _execCommand, _generateCommand, - _containerKubeCommand, _playCommand, _psCommand, _loginCommand, @@ -39,7 +38,6 @@ func getMainCommands() []*cobra.Command { _topCommand, _umountCommand, _unpauseCommand, - volumeCommand.Command, _waitCommand, } @@ -94,13 +92,8 @@ func getContainerSubCommands() []*cobra.Command { // Commands that the local client implements func getPodSubCommands() []*cobra.Command { return []*cobra.Command{ - _podCreateCommand, - _podPauseCommand, - _podPsCommand, - _podRestartCommand, _podStatsCommand, _podTopCommand, - _podUnpauseCommand, } } diff --git a/cmd/podman/errors.go b/cmd/podman/errors.go index 192f97049..2572b8779 100644 --- a/cmd/podman/errors.go +++ b/cmd/podman/errors.go @@ -6,8 +6,6 @@ import ( "os/exec" "syscall" - "github.com/containers/libpod/cmd/podman/varlink" - "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -20,22 +18,6 @@ func outputError(err error) { exitCode = status.ExitStatus() } } - var ne error - switch e := err.(type) { - // For some reason golang wont let me list them with commas so listing them all. - case *iopodman.ImageNotFound: - ne = errors.New(e.Reason) - case *iopodman.ContainerNotFound: - ne = errors.New(e.Reason) - case *iopodman.PodNotFound: - ne = errors.New(e.Reason) - case *iopodman.VolumeNotFound: - ne = errors.New(e.Reason) - case *iopodman.ErrorOccurred: - ne = errors.New(e.Reason) - default: - ne = err - } - fmt.Fprintln(os.Stderr, "Error:", ne.Error()) + fmt.Fprintln(os.Stderr, "Error:", err.Error()) } } diff --git a/cmd/podman/libpodruntime/runtime.go b/cmd/podman/libpodruntime/runtime.go index 880b281bd..f4ddf3521 100644 --- a/cmd/podman/libpodruntime/runtime.go +++ b/cmd/podman/libpodruntime/runtime.go @@ -21,7 +21,7 @@ func GetRuntime(c *cliconfig.PodmanCommand) (*libpod.Runtime, error) { func getRuntime(c *cliconfig.PodmanCommand, renumber bool) (*libpod.Runtime, error) { options := []libpod.RuntimeOption{} - storageOpts, volumePath, err := util.GetDefaultStoreOptions() + storageOpts, _, err := util.GetDefaultStoreOptions() if err != nil { return nil, err } @@ -120,7 +120,6 @@ func getRuntime(c *cliconfig.PodmanCommand, renumber bool) (*libpod.Runtime, err infraCommand, _ := c.Flags().GetString("infra-command") options = append(options, libpod.WithDefaultInfraCommand(infraCommand)) } - options = append(options, libpod.WithVolumePath(volumePath)) if c.Flags().Changed("config") { return libpod.NewRuntimeFromConfig(c.GlobalFlags.Config, options...) } diff --git a/cmd/podman/logs.go b/cmd/podman/logs.go index 97d835d8f..40ae2c846 100644 --- a/cmd/podman/logs.go +++ b/cmd/podman/logs.go @@ -38,7 +38,7 @@ func init() { flags := logsCommand.Flags() flags.BoolVar(&logsCommand.Details, "details", false, "Show extra details provided to the logs") flags.BoolVarP(&logsCommand.Follow, "follow", "f", false, "Follow log output. The default is false") - flags.BoolVarP(&waitCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.BoolVarP(&logsCommand.Latest, "latest", "l", false, "Act on the latest container podman is aware of") flags.StringVar(&logsCommand.Since, "since", "", "Show logs since TIMESTAMP") flags.Uint64Var(&logsCommand.Tail, "tail", 0, "Output the specified number of LINES at the end of the logs. Defaults to 0, which prints all lines") flags.BoolVarP(&logsCommand.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") diff --git a/cmd/podman/play_kube.go b/cmd/podman/play_kube.go index 9fc06dde9..1a45cbed9 100644 --- a/cmd/podman/play_kube.go +++ b/cmd/podman/play_kube.go @@ -52,8 +52,6 @@ func init() { flags.BoolVarP(&playKubeCommand.Quiet, "quiet", "q", false, "Suppress output information when pulling images") flags.StringVar(&playKubeCommand.SignaturePolicy, "signature-policy", "", "`Pathname` of signature policy file (not usually used)") flags.BoolVar(&playKubeCommand.TlsVerify, "tls-verify", true, "Require HTTPS and verify certificates when contacting registries (default: true)") - - rootCmd.AddCommand(playKubeCommand.Command) } func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { @@ -155,7 +153,7 @@ func playKubeYAMLCmd(c *cliconfig.KubePlayValues) error { // start the containers for _, ctr := range containers { - if err := ctr.Start(ctx, false); err != nil { + if err := ctr.Start(ctx, true); err != nil { // Making this a hard failure here to avoid a mess // the other containers are in created status return err diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 4c7c416ce..c1350bd4d 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -20,12 +20,17 @@ var podCommand = cliconfig.PodmanCommand{ //podSubCommands are implemented both in local and remote clients var podSubCommands = []*cobra.Command{ + _podCreateCommand, _podExistsCommand, _podInspectCommand, _podKillCommand, + _podPauseCommand, + _podPsCommand, + _podRestartCommand, _podRmCommand, _podStartCommand, _podStopCommand, + _podUnpauseCommand, } func init() { diff --git a/cmd/podman/pod_create.go b/cmd/podman/pod_create.go index d5ca5e13a..f1bbecb84 100644 --- a/cmd/podman/pod_create.go +++ b/cmd/podman/pod_create.go @@ -3,12 +3,10 @@ package main import ( "fmt" "os" - "strings" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" - "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -56,20 +54,29 @@ func init() { } func podCreateCmd(c *cliconfig.PodCreateValues) error { - var options []libpod.PodCreateOption - var err error + var ( + err error + podIdFile *os.File + ) if len(c.InputArgs) > 0 { return errors.New("podman pod create does not accept any arguments") } - - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - var podIdFile *os.File + if len(c.Publish) > 0 { + if !c.Infra { + return errors.Errorf("you must have an infra container to publish port bindings to the host") + } + } + + if !c.Infra && c.Flag("share").Changed && c.Share != "none" && c.Share != "" { + return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") + } if c.Flag("pod-id-file").Changed && os.Geteuid() == 0 { podIdFile, err = libpod.OpenExclusiveFile(c.PodIDFile) if err != nil && os.IsExist(err) { @@ -82,67 +89,21 @@ func podCreateCmd(c *cliconfig.PodCreateValues) error { defer podIdFile.Sync() } - if len(c.Publish) > 0 { - if !c.Infra { - return errors.Errorf("you must have an infra container to publish port bindings to the host") - } - } - - if !c.Infra && c.Flag("share").Changed && c.Share != "none" && c.Share != "" { - return errors.Errorf("You cannot share kernel namespaces on the pod level without an infra container") - } - - if c.Flag("cgroup-parent").Changed { - options = append(options, libpod.WithPodCgroupParent(c.CgroupParent)) - } - labels, err := getAllLabels(c.LabelFile, c.Labels) if err != nil { return errors.Wrapf(err, "unable to process labels") } - if len(labels) != 0 { - options = append(options, libpod.WithPodLabels(labels)) - } - - if c.Flag("name").Changed { - options = append(options, libpod.WithPodName(c.Name)) - } - - if c.Infra { - options = append(options, libpod.WithInfraContainer()) - nsOptions, err := shared.GetNamespaceOptions(strings.Split(c.Share, ",")) - if err != nil { - return err - } - options = append(options, nsOptions...) - } - if len(c.Publish) > 0 { - portBindings, err := shared.CreatePortBindings(c.Publish) - if err != nil { - return err - } - options = append(options, libpod.WithInfraContainerPorts(portBindings)) - - } - // always have containers use pod cgroups - // User Opt out is not yet supported - options = append(options, libpod.WithPodCgroups()) - - ctx := getContext() - pod, err := runtime.NewPod(ctx, options...) + podID, err := runtime.CreatePod(getContext(), c, labels) if err != nil { - return err + return errors.Wrapf(err, "unable to create pod") } - if podIdFile != nil { - _, err = podIdFile.WriteString(pod.ID()) + _, err = podIdFile.WriteString(podID) if err != nil { logrus.Error(err) } } - - fmt.Printf("%s\n", pod.ID()) - + fmt.Printf("%s\n", podID) return nil } diff --git a/cmd/podman/pod_pause.go b/cmd/podman/pod_pause.go index e01d73c9b..284740d22 100644 --- a/cmd/podman/pod_pause.go +++ b/cmd/podman/pod_pause.go @@ -3,7 +3,7 @@ package main import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -40,37 +40,33 @@ func init() { } func podPauseCmd(c *cliconfig.PodPauseValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + var lastError error + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) + pauseIDs, conErrors, pauseErrors := runtime.PausePods(c) - for _, pod := range pods { - ctr_errs, err := pod.Pause() - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to pause container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { + for _, p := range pauseIDs { + fmt.Println(p) + } + if conErrors != nil && len(conErrors) > 0 { + for ctr, err := range conErrors { if lastError != nil { logrus.Errorf("%q", lastError) } - lastError = errors.Wrapf(err, "unable to pause pod %q", pod.ID()) - continue + lastError = errors.Wrapf(err, "unable to pause container %s", ctr) } - fmt.Println(pod.ID()) } - + if len(pauseErrors) > 0 { + lastError = pauseErrors[len(pauseErrors)-1] + // Remove the last error from the error slice + pauseErrors = pauseErrors[:len(pauseErrors)-1] + } + for _, err := range pauseErrors { + logrus.Errorf("%q", err) + } return lastError } diff --git a/cmd/podman/pod_ps.go b/cmd/podman/pod_ps.go index 85467b6ad..70e077651 100644 --- a/cmd/podman/pod_ps.go +++ b/cmd/podman/pod_ps.go @@ -10,9 +10,9 @@ import ( "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/formats" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter" "github.com/containers/libpod/pkg/util" "github.com/docker/go-units" "github.com/pkg/errors" @@ -29,6 +29,8 @@ const ( NUM_CTR_INFO = 10 ) +type PodFilter func(*adapter.Pod) bool + var ( bc_opts shared.PsOptions ) @@ -152,7 +154,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { return errors.Wrapf(err, "error with flags passed") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } @@ -173,7 +175,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { opts.Format = genPodPsFormat(c) - var filterFuncs []libpod.PodFilter + var filterFuncs []PodFilter if c.Filter != "" { filters := strings.Split(c.Filter, ",") for _, f := range filters { @@ -181,7 +183,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { if len(filterSplit) < 2 { return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f) } - generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1], runtime) + generatedFunc, err := generatePodFilterFuncs(filterSplit[0], filterSplit[1]) if err != nil { return errors.Wrapf(err, "invalid filter") } @@ -189,7 +191,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { } } - var pods []*libpod.Pod + var pods []*adapter.Pod if c.Latest { pod, err := runtime.GetLatestPod() if err != nil { @@ -203,7 +205,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { } } - podsFiltered := make([]*libpod.Pod, 0, len(pods)) + podsFiltered := make([]*adapter.Pod, 0, len(pods)) for _, pod := range pods { include := true for _, filter := range filterFuncs { @@ -215,7 +217,7 @@ func podPsCmd(c *cliconfig.PodPsValues) error { } } - return generatePodPsOutput(podsFiltered, opts, runtime) + return generatePodPsOutput(podsFiltered, opts) } // podPsCheckFlagsPassed checks if mutually exclusive flags are passed together @@ -234,10 +236,10 @@ func podPsCheckFlagsPassed(c *cliconfig.PodPsValues) error { return nil } -func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) (func(pod *libpod.Pod) bool, error) { +func generatePodFilterFuncs(filter, filterValue string) (func(pod *adapter.Pod) bool, error) { switch filter { case "ctr-ids": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctrIds, err := p.AllContainersByID() if err != nil { return false @@ -245,7 +247,7 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) return util.StringInSlice(filterValue, ctrIds) }, nil case "ctr-names": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctrs, err := p.AllContainers() if err != nil { return false @@ -258,7 +260,7 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) return false }, nil case "ctr-number": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctrIds, err := p.AllContainersByID() if err != nil { return false @@ -274,7 +276,7 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) if !util.StringInSlice(filterValue, []string{"created", "restarting", "running", "paused", "exited", "unknown"}) { return nil, errors.Errorf("%s is not a valid status", filterValue) } - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { ctr_statuses, err := p.Status() if err != nil { return false @@ -291,19 +293,19 @@ func generatePodFilterFuncs(filter, filterValue string, runtime *libpod.Runtime) return false }, nil case "id": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { return strings.Contains(p.ID(), filterValue) }, nil case "name": - return func(p *libpod.Pod) bool { + return func(p *adapter.Pod) bool { return strings.Contains(p.Name(), filterValue) }, nil case "status": if !util.StringInSlice(filterValue, []string{"stopped", "running", "paused", "exited", "dead", "created"}) { return nil, errors.Errorf("%s is not a valid pod status", filterValue) } - return func(p *libpod.Pod) bool { - status, err := shared.GetPodStatus(p) + return func(p *adapter.Pod) bool { + status, err := p.GetPodStatus() if err != nil { return false } @@ -448,7 +450,7 @@ func getPodTemplateOutput(psParams []podPsJSONParams, opts podPsOptions) ([]podP return psOutput, nil } -func getNamespaces(pod *libpod.Pod) []string { +func getNamespaces(pod *adapter.Pod) []string { var shared []string if pod.SharesPID() { shared = append(shared, "pid") @@ -475,7 +477,7 @@ func getNamespaces(pod *libpod.Pod) []string { } // getAndSortPodJSONOutput returns the container info in its raw, sorted form -func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) ([]podPsJSONParams, error) { +func getAndSortPodJSONParams(pods []*adapter.Pod, opts podPsOptions) ([]podPsJSONParams, error) { var ( psOutput []podPsJSONParams ) @@ -487,7 +489,7 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib return nil, err } ctrNum := len(ctrs) - status, err := shared.GetPodStatus(pod) + status, err := pod.GetPodStatus() if err != nil { return nil, err } @@ -497,7 +499,7 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib return nil, err } for _, ctr := range ctrs { - batchInfo, err := shared.BatchContainerOp(ctr, bc_opts) + batchInfo, err := adapter.BatchContainerOp(ctr, bc_opts) if err != nil { return nil, err } @@ -539,11 +541,11 @@ func getAndSortPodJSONParams(pods []*libpod.Pod, opts podPsOptions, runtime *lib return sortPodPsOutput(opts.Sort, psOutput) } -func generatePodPsOutput(pods []*libpod.Pod, opts podPsOptions, runtime *libpod.Runtime) error { +func generatePodPsOutput(pods []*adapter.Pod, opts podPsOptions) error { if len(pods) == 0 && opts.Format != formats.JSONString { return nil } - psOutput, err := getAndSortPodJSONParams(pods, opts, runtime) + psOutput, err := getAndSortPodJSONParams(pods, opts) if err != nil { return err } diff --git a/cmd/podman/pod_restart.go b/cmd/podman/pod_restart.go index be54630db..741fce588 100644 --- a/cmd/podman/pod_restart.go +++ b/cmd/podman/pod_restart.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -42,37 +42,33 @@ func init() { } func podRestartCmd(c *cliconfig.PodRestartValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + var lastError error + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) + restartIDs, conErrors, restartErrors := runtime.RestartPods(getContext(), c) - ctx := getContext() - for _, pod := range pods { - ctr_errs, err := pod.Restart(ctx) - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to restart container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { + for _, p := range restartIDs { + fmt.Println(p) + } + if conErrors != nil && len(conErrors) > 0 { + for ctr, err := range conErrors { if lastError != nil { logrus.Errorf("%q", lastError) } - lastError = errors.Wrapf(err, "unable to restart pod %q", pod.ID()) - continue + lastError = errors.Wrapf(err, "unable to pause container %s", ctr) } - fmt.Println(pod.ID()) + } + if len(restartErrors) > 0 { + lastError = restartErrors[len(restartErrors)-1] + // Remove the last error from the error slice + restartErrors = restartErrors[:len(restartErrors)-1] + } + for _, err := range restartErrors { + logrus.Errorf("%q", err) } return lastError } diff --git a/cmd/podman/pod_unpause.go b/cmd/podman/pod_unpause.go index 35128e87b..16481d0e2 100644 --- a/cmd/podman/pod_unpause.go +++ b/cmd/podman/pod_unpause.go @@ -4,7 +4,7 @@ import ( "fmt" "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" + "github.com/containers/libpod/pkg/adapter" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -41,37 +41,33 @@ func init() { } func podUnpauseCmd(c *cliconfig.PodUnpauseValues) error { - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + var lastError error + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "error creating libpod runtime") } defer runtime.Shutdown(false) - // getPodsFromContext returns an error when a requested pod - // isn't found. The only fatal error scenerio is when there are no pods - // in which case the following loop will be skipped. - pods, lastError := getPodsFromContext(&c.PodmanCommand, runtime) + unpauseIDs, conErrors, unpauseErrors := runtime.UnpausePods(c) - for _, pod := range pods { - ctr_errs, err := pod.Unpause() - if ctr_errs != nil { - for ctr, err := range ctr_errs { - if lastError != nil { - logrus.Errorf("%q", lastError) - } - lastError = errors.Wrapf(err, "unable to unpause container %q on pod %q", ctr, pod.ID()) - } - continue - } - if err != nil { + for _, p := range unpauseIDs { + fmt.Println(p) + } + if conErrors != nil && len(conErrors) > 0 { + for ctr, err := range conErrors { if lastError != nil { logrus.Errorf("%q", lastError) } - lastError = errors.Wrapf(err, "unable to unpause pod %q", pod.ID()) - continue + lastError = errors.Wrapf(err, "unable to unpause container %s", ctr) } - fmt.Println(pod.ID()) } - + if len(unpauseErrors) > 0 { + lastError = unpauseErrors[len(unpauseErrors)-1] + // Remove the last error from the error slice + unpauseErrors = unpauseErrors[:len(unpauseErrors)-1] + } + for _, err := range unpauseErrors { + logrus.Errorf("%q", err) + } return lastError } diff --git a/cmd/podman/rm.go b/cmd/podman/rm.go index 01ed70f52..2dcb491d7 100644 --- a/cmd/podman/rm.go +++ b/cmd/podman/rm.go @@ -7,6 +7,7 @@ import ( "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/libpod/image" "github.com/pkg/errors" "github.com/sirupsen/logrus" "github.com/spf13/cobra" @@ -61,15 +62,21 @@ func rmCmd(c *cliconfig.RmValues) error { } defer runtime.Shutdown(false) + failureCnt := 0 delContainers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, -1, "all") if err != nil { if c.Force && len(c.InputArgs) > 0 { if errors.Cause(err) == libpod.ErrNoSuchCtr { err = nil + } else { + failureCnt++ } runtime.RemoveContainersFromStorage(c.InputArgs) } if len(delContainers) == 0 { + if err != nil && failureCnt == 0 { + exitCode = 1 + } return err } if err != nil { @@ -96,5 +103,16 @@ func rmCmd(c *cliconfig.RmValues) error { // Run the parallel funcs deleteErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, deleteFuncs) - return printParallelOutput(deleteErrors, errCount) + err = printParallelOutput(deleteErrors, errCount) + if err != nil { + for _, result := range deleteErrors { + if result != nil && errors.Cause(result) != image.ErrNoSuchCtr { + failureCnt++ + } + } + if failureCnt == 0 { + exitCode = 1 + } + } + return err } diff --git a/cmd/podman/rmi.go b/cmd/podman/rmi.go index fbaa19336..709ed14e0 100644 --- a/cmd/podman/rmi.go +++ b/cmd/podman/rmi.go @@ -5,6 +5,8 @@ import ( "os" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/libpod/image" "github.com/containers/libpod/pkg/adapter" "github.com/containers/storage" "github.com/pkg/errors" @@ -29,6 +31,17 @@ var ( } ) +func imageNotFound(err error) bool { + if errors.Cause(err) == image.ErrNoSuchImage { + return true + } + switch err.(type) { + case *iopodman.ImageNotFound: + return true + } + return false +} + func init() { rmiCommand.Command = _rmiCommand rmiCommand.SetUsageTemplate(UsageTemplate()) @@ -39,10 +52,8 @@ func init() { func rmiCmd(c *cliconfig.RmiValues) error { var ( - lastError error - deleted bool - deleteErr error - msg string + lastError error + failureCnt int ) ctx := getContext() @@ -64,19 +75,21 @@ func rmiCmd(c *cliconfig.RmiValues) error { images := args[:] removeImage := func(img *adapter.ContainerImage) { - deleted = true - msg, deleteErr = runtime.RemoveImage(ctx, img, c.Force) - if deleteErr != nil { - if errors.Cause(deleteErr) == storage.ErrImageUsedByContainer { + msg, err := runtime.RemoveImage(ctx, img, c.Force) + if err != nil { + if errors.Cause(err) == storage.ErrImageUsedByContainer { fmt.Printf("A container associated with containers/storage, i.e. via Buildah, CRI-O, etc., may be associated with this image: %-12.12s\n", img.ID()) } + if !imageNotFound(err) { + failureCnt++ + } if lastError != nil { fmt.Fprintln(os.Stderr, lastError) } - lastError = deleteErr - } else { - fmt.Println(msg) + lastError = err + return } + fmt.Println(msg) } if removeAll { @@ -121,22 +134,21 @@ func rmiCmd(c *cliconfig.RmiValues) error { for _, i := range images { newImage, err := runtime.NewImageFromLocal(i) if err != nil { - fmt.Fprintln(os.Stderr, err) + if lastError != nil { + if !imageNotFound(lastError) { + failureCnt++ + } + fmt.Fprintln(os.Stderr, lastError) + } + lastError = err continue } removeImage(newImage) } } - // If the user calls remove all and there are none, it should not be a - // non-zero exit - if !deleted && removeAll { - return nil - } - // the user tries to remove images that do not exist, that should be a - // non-zero exit - if !deleted { - return errors.Errorf("no valid images to delete") + if imageNotFound(lastError) && failureCnt == 0 { + exitCode = 1 } return lastError diff --git a/cmd/podman/shared/pod.go b/cmd/podman/shared/pod.go index 30dd14845..5f65c40ac 100644 --- a/cmd/podman/shared/pod.go +++ b/cmd/podman/shared/pod.go @@ -26,6 +26,10 @@ func GetPodStatus(pod *libpod.Pod) (string, error) { if err != nil { return errored, err } + return CreatePodStatusResults(ctrStatuses) +} + +func CreatePodStatusResults(ctrStatuses map[string]libpod.ContainerStatus) (string, error) { ctrNum := len(ctrStatuses) if ctrNum == 0 { return created, nil diff --git a/cmd/podman/utils.go b/cmd/podman/utils.go index 0fbea417b..4ec0f8a13 100644 --- a/cmd/podman/utils.go +++ b/cmd/podman/utils.go @@ -172,7 +172,7 @@ func getPodsFromContext(c *cliconfig.PodmanCommand, r *libpod.Runtime) ([]*libpo var err error if c.Bool("all") { - pods, err = r.Pods() + pods, err = r.GetAllPods() if err != nil { return nil, errors.Wrapf(err, "unable to get running pods") } diff --git a/contrib/cirrus/packer/fedora_setup.sh b/contrib/cirrus/packer/fedora_setup.sh index 01c468901..de7ad4506 100644 --- a/contrib/cirrus/packer/fedora_setup.sh +++ b/contrib/cirrus/packer/fedora_setup.sh @@ -40,6 +40,7 @@ ooe.sh sudo dnf install -y \ golang-github-cpuguy83-go-md2man \ gpgme-devel \ iptables \ + iproute \ libassuan-devel \ libcap-devel \ libnet \ diff --git a/contrib/cirrus/packer/ubuntu_setup.sh b/contrib/cirrus/packer/ubuntu_setup.sh index 7d49c5dc7..5b7e1d714 100644 --- a/contrib/cirrus/packer/ubuntu_setup.sh +++ b/contrib/cirrus/packer/ubuntu_setup.sh @@ -48,6 +48,7 @@ ooe.sh sudo -E apt-get -qq install \ gettext \ go-md2man \ golang \ + iproute \ iptables \ libaio-dev \ libapparmor-dev \ diff --git a/docs/libpod.conf.5.md b/docs/libpod.conf.5.md index 0c11e2013..9a19e1224 100644 --- a/docs/libpod.conf.5.md +++ b/docs/libpod.conf.5.md @@ -13,7 +13,7 @@ libpod to manage containers. Default transport method for pulling and pushing images **runtime**="" - Default OCI runtime to use if nothing is specified + Default OCI runtime to use if nothing is specified in **runtimes** **runtimes** For each OCI runtime, specify a list of paths to look for. The first one found is used. @@ -82,6 +82,15 @@ libpod to manage containers. **label**="true|false" Indicates whether the containers should use label separation. +**num_locks**="" + Number of locks available for containers and pods. Each created container or pod consumes one lock. + The default number available is 2048. + If this is changed, a lock renumbering must be performed, using the `podman system renumber` command. + +**volume_path**="" + Directory where named volumes will be created in using the default volume driver. + By default this will be configured relative to where containers/storage stores containers. + ## FILES `/usr/share/containers/libpod.conf`, default libpod configuration path diff --git a/docs/podman-container-cleanup.1.md b/docs/podman-container-cleanup.1.md index e375c12ec..2ad39d214 100644 --- a/docs/podman-container-cleanup.1.md +++ b/docs/podman-container-cleanup.1.md @@ -30,7 +30,7 @@ The latest option is not supported on the remote client. `podman container cleanup 860a4b23` -`podman container-cleanup -a` +`podman container cleanup -a` `podman container cleanup --latest` diff --git a/docs/podman-generate-kube.1.md b/docs/podman-generate-kube.1.md index 5236f23fe..d4bed8ab1 100644 --- a/docs/podman-generate-kube.1.md +++ b/docs/podman-generate-kube.1.md @@ -19,6 +19,8 @@ The **service** option can be used to generate a Service specification for the c if the object has portmap bindings, the service specification will include a NodePort declaration to expose the service. A random port is assigned by Podman in the specification. +Note that the generated Kubernetes YAML file can be used to re-run the deployment via podman-play-kube(1). + # OPTIONS: **s** **--service** @@ -145,7 +147,7 @@ status: ``` ## SEE ALSO -podman(1), podman-container, podman-pod, podman-play +podman(1), podman-container(1), podman-pod(1), podman-play-kube(1) # HISTORY Decemeber 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-play-kube.1.md b/docs/podman-play-kube.1.md index 3fd9746a5..2264f7a88 100644 --- a/docs/podman-play-kube.1.md +++ b/docs/podman-play-kube.1.md @@ -20,7 +20,7 @@ kubernetes_input.yml the pod and containers described in the YAML. The containers within the pod are then started and the ID of the new Pod is output. -Ideally the input file would be one created by Podman. This would guarantee a smooth import and expected results. +Ideally the input file would be one created by Podman (see podman-generate-kube(1)). This would guarantee a smooth import and expected results. # OPTIONS: @@ -72,7 +72,7 @@ $ podman play kube demo.yml ``` ## SEE ALSO -podman(1), podman-container(1), podman-pod(1), podman-generate(1), podman-play(1) +podman(1), podman-container(1), podman-pod(1), podman-generate-kube(1), podman-play(1) # HISTORY Decemeber 2018, Originally compiled by Brent Baude (bbaude at redhat dot com) diff --git a/docs/podman-rm.1.md b/docs/podman-rm.1.md index 10ebe97f9..dc1729188 100644 --- a/docs/podman-rm.1.md +++ b/docs/podman-rm.1.md @@ -1,9 +1,11 @@ -% podman-rm(1) +% podman-container-rm(1) ## NAME -podman\-rm - Remove one or more containers +podman\-container\-rm (podman\-rm) - Remove one or more containers ## SYNOPSIS +**podman container rm** [*options*] *container* + **podman rm** [*options*] *container* ## DESCRIPTION @@ -57,8 +59,13 @@ Forcibly remove the latest container created. podman rm -f --latest ``` +## Exit Status +**_0_** if all specified containers removed +**_1_** if one of the specified containers did not exist, and no other failures +**_125_** if command fails for a reason other then an container did not exist + ## SEE ALSO -podman(1), podman-rmi(1) +podman(1), podman-image-rm(1) ## HISTORY August 2017, Originally compiled by Ryan Cole <rycole@redhat.com> diff --git a/docs/podman-rmi.1.md b/docs/podman-rmi.1.md index 9c080c9f1..8c22bba2c 100644 --- a/docs/podman-rmi.1.md +++ b/docs/podman-rmi.1.md @@ -1,9 +1,11 @@ -% podman-rmi(1) +% podman-image-rm(1) ## NAME -podman\-rmi - Removes one or more images +podman\-image\-rm (podman\-rmi) - Removes one or more images ## SYNOPSIS +**podman image rm** *image* ... + **podman rmi** *image* ... ## DESCRIPTION @@ -38,6 +40,10 @@ Remove all images and containers. ``` podman rmi -a -f ``` +## Exit Status +**_0_** if all specified images removed +**_1_** if one of the specified images did not exist, and no other failures +**_125_** if command fails for a reason other then an image did not exist ## SEE ALSO podman(1) diff --git a/docs/podman-run.1.md b/docs/podman-run.1.md index b928f61f5..bbf10a2ce 100644 --- a/docs/podman-run.1.md +++ b/docs/podman-run.1.md @@ -28,6 +28,8 @@ servers in the created `resolv.conf`). Additionally, an empty file is created in each container to indicate to programs they are running in a container. This file is located at `/run/.containerenv`. +When running from a user defined network namespace, the /etc/netns/NSNAME/resolv.conf will be used if it exists, otherwise /etc/resolv.conf will be used. + ## OPTIONS **--add-host**=[] @@ -694,21 +696,21 @@ Current supported mount TYPES are bind, and tmpfs. Common Options: - · src, source: mount source spec for bind and volume. Mandatory for bind. + · src, source: mount source spec for bind and volume. Mandatory for bind. - · dst, destination, target: mount destination spec. + · dst, destination, target: mount destination spec. - · ro, read-only: true or false (default). + · ro, read-only: true or false (default). Options specific to bind: - · bind-propagation: Z, z, shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2). + · bind-propagation: Z, z, shared, slave, private, rshared, rslave, or rprivate(default). See also mount(2). Options specific to tmpfs: - · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. + · tmpfs-size: Size of the tmpfs mount in bytes. Unlimited by default in Linux. - · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. + · tmpfs-mode: File mode of the tmpfs in octal. (e.g. 700 or 0700.) Defaults to 1777 in Linux. **--userns**="" diff --git a/libpod.conf b/libpod.conf index c4e7dc628..211ba106d 100644 --- a/libpod.conf +++ b/libpod.conf @@ -88,6 +88,18 @@ pause_command = "/pause" # Default libpod support for container labeling # label=true +# Number of locks available for containers and pods. +# If this is changed, a lock renumber must be performed (e.g. with the +# 'podman system renumber' command). +num_locks = 2048 + +# Directory for libpod named volumes. +# By default, this will be configured relative to where containers/storage +# stores containers. +# Uncomment to change location from this default. +#volume_path = "/var/lib/containers/storage/volumes" + +# Default OCI runtime runtime = "runc" # Paths to look for a valid OCI runtime (runc, runv, etc) diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 25ef5cd0e..c226a0617 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -261,12 +261,14 @@ func (s *BoltState) GetDBConfig() (*DBConfig, error) { storageRoot := configBucket.Get(graphRootKey) storageTmp := configBucket.Get(runRootKey) graphDriver := configBucket.Get(graphDriverKey) + volumePath := configBucket.Get(volPathKey) cfg.LibpodRoot = string(libpodRoot) cfg.LibpodTmp = string(libpodTmp) cfg.StorageRoot = string(storageRoot) cfg.StorageTmp = string(storageTmp) cfg.GraphDriver = string(graphDriver) + cfg.VolumePath = string(volumePath) return nil }) diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 3d749849d..936ccbf4c 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -38,6 +38,7 @@ const ( graphRootName = "graph-root" graphDriverName = "graph-driver-name" osName = "os" + volPathName = "volume-path" ) var ( @@ -67,6 +68,7 @@ var ( graphRootKey = []byte(graphRootName) graphDriverKey = []byte(graphDriverName) osKey = []byte(osName) + volPathKey = []byte(volPathName) ) // Check if the configuration of the database is compatible with the @@ -105,10 +107,15 @@ func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { return err } - return validateDBAgainstConfig(configBkt, "storage graph driver", + if err := validateDBAgainstConfig(configBkt, "storage graph driver", rt.config.StorageConfig.GraphDriverName, graphDriverKey, - storage.DefaultStoreOptions.GraphDriverName) + storage.DefaultStoreOptions.GraphDriverName); err != nil { + return err + } + + return validateDBAgainstConfig(configBkt, "volume path", + rt.config.VolumePath, volPathKey, "") }) return err diff --git a/libpod/container_internal_linux.go b/libpod/container_internal_linux.go index 86f94477e..b074efa3a 100644 --- a/libpod/container_internal_linux.go +++ b/libpod/container_internal_linux.go @@ -26,7 +26,6 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage/pkg/idtools" - "github.com/mrunalp/fileutils" "github.com/opencontainers/runc/libcontainer/user" spec "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" @@ -482,6 +481,19 @@ func (c *Container) checkpoint(ctx context.Context, options ContainerCheckpointO if c.state.State != ContainerStateRunning { return errors.Wrapf(ErrCtrStateInvalid, "%q is not running, cannot checkpoint", c.state.State) } + + // Create the CRIU log file and label it + dumpLog := filepath.Join(c.bundlePath(), "dump.log") + + logFile, err := os.OpenFile(dumpLog, os.O_CREATE, 0600) + if err != nil { + return errors.Wrapf(err, "failed to create CRIU log file %q", dumpLog) + } + logFile.Close() + if err = label.SetFileLabel(dumpLog, c.MountLabel()); err != nil { + return errors.Wrapf(err, "failed to label CRIU log file %q", dumpLog) + } + if err := c.runtime.ociRuntime.checkpointContainer(c, options); err != nil { return err } @@ -677,20 +689,12 @@ func (c *Container) makeBindMounts() error { // If it doesn't, don't copy them resolvPath, exists := bindMounts["/etc/resolv.conf"] if exists { - resolvDest := filepath.Join(c.state.RunDir, "resolv.conf") - if err := fileutils.CopyFile(resolvPath, resolvDest); err != nil { - return errors.Wrapf(err, "error copying resolv.conf from dependency container %s of container %s", depCtr.ID(), c.ID()) - } - c.state.BindMounts["/etc/resolv.conf"] = resolvDest - } + c.state.BindMounts["/etc/resolv.conf"] = resolvPath + } hostsPath, exists := bindMounts["/etc/hosts"] if exists { - hostsDest := filepath.Join(c.state.RunDir, "hosts") - if err := fileutils.CopyFile(hostsPath, hostsDest); err != nil { - return errors.Wrapf(err, "error copying hosts file from dependency container %s of container %s", depCtr.ID(), c.ID()) - } - c.state.BindMounts["/etc/hosts"] = hostsDest + c.state.BindMounts["/etc/hosts"] = hostsPath } } else { newResolv, err := c.generateResolvConf() @@ -705,6 +709,14 @@ func (c *Container) makeBindMounts() error { } c.state.BindMounts["/etc/hosts"] = newHosts } + + if err := label.Relabel(c.state.BindMounts["/etc/hosts"], c.config.MountLabel, true); err != nil { + return err + } + + if err := label.Relabel(c.state.BindMounts["/etc/resolv.conf"], c.config.MountLabel, true); err != nil { + return err + } } // SHM is always added when we mount the container @@ -758,8 +770,24 @@ func (c *Container) makeBindMounts() error { // generateResolvConf generates a containers resolv.conf func (c *Container) generateResolvConf() (string, error) { + resolvConf := "/etc/resolv.conf" + for _, ns := range c.config.Spec.Linux.Namespaces { + if ns.Type == spec.NetworkNamespace { + if ns.Path != "" && !strings.HasPrefix(ns.Path, "/proc/") { + definedPath := filepath.Join("/etc/netns", filepath.Base(ns.Path), "resolv.conf") + _, err := os.Stat(definedPath) + if err == nil { + resolvConf = definedPath + } else if !os.IsNotExist(err) { + return "", errors.Wrapf(err, "failed to stat %s", definedPath) + } + } + break + } + } + // Determine the endpoint for resolv.conf in case it is a symlink - resolvPath, err := filepath.EvalSymlinks("/etc/resolv.conf") + resolvPath, err := filepath.EvalSymlinks(resolvConf) if err != nil { return "", err } @@ -809,7 +837,7 @@ func (c *Container) generateResolvConf() (string, error) { } // Relabel resolv.conf for the container - if err := label.Relabel(destPath, c.config.MountLabel, false); err != nil { + if err := label.Relabel(destPath, c.config.MountLabel, true); err != nil { return "", err } diff --git a/libpod/info.go b/libpod/info.go index 191ce6810..62088b730 100644 --- a/libpod/info.go +++ b/libpod/info.go @@ -121,6 +121,7 @@ func (r *Runtime) storeInfo() (map[string]interface{}, error) { info["RunRoot"] = r.store.RunRoot() info["GraphDriverName"] = r.store.GraphDriverName() info["GraphOptions"] = r.store.GraphOptions() + info["VolumePath"] = r.config.VolumePath statusPairs, err := r.store.Status() if err != nil { return nil, err diff --git a/libpod/options.go b/libpod/options.go index 9aa020b56..1e8592a25 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -47,6 +47,11 @@ func WithStorageConfig(config storage.StoreOptions) RuntimeOption { rt.config.StaticDir = filepath.Join(config.GraphRoot, "libpod") rt.configuredFrom.libpodStaticDirSet = true + // Also set libpod volume path, so we are a subdirectory + // of the c/storage store by default + rt.config.VolumePath = filepath.Join(config.GraphRoot, "volumes") + rt.configuredFrom.volPathSet = true + setField = true } @@ -359,6 +364,7 @@ func WithVolumePath(volPath string) RuntimeOption { } rt.config.VolumePath = volPath + rt.configuredFrom.volPathSet = true return nil } @@ -904,10 +910,10 @@ func WithNetNS(portMappings []ocicni.PortMapping, postConfigureNetNS bool, netmo } ctr.config.PostConfigureNetNS = postConfigureNetNS - ctr.config.CreateNetNS = true + ctr.config.NetMode = namespaces.NetworkMode(netmode) + ctr.config.CreateNetNS = !ctr.config.NetMode.IsUserDefined() ctr.config.PortMappings = portMappings ctr.config.Networks = networks - ctr.config.NetMode = namespaces.NetworkMode(netmode) return nil } @@ -1242,6 +1248,22 @@ func WithVolumeOptions(options map[string]string) VolumeCreateOption { } } +// withSetCtrSpecific sets a bool notifying libpod that a volume was created +// specifically for a container. +// These volumes will be removed when the container is removed and volumes are +// also specified for removal. +func withSetCtrSpecific() VolumeCreateOption { + return func(volume *Volume) error { + if volume.valid { + return ErrVolumeFinalized + } + + volume.config.IsCtrSpecific = true + + return nil + } +} + // Pod Creation Options // WithPodName sets the name of the pod. diff --git a/libpod/runtime.go b/libpod/runtime.go index 94dbf37dd..827c22f5b 100644 --- a/libpod/runtime.go +++ b/libpod/runtime.go @@ -123,7 +123,10 @@ type RuntimeConfig struct { // Not included in on-disk config, use the dedicated containers/storage // configuration file instead StorageConfig storage.StoreOptions `toml:"-"` - VolumePath string `toml:"volume_path"` + // VolumePath is the default location that named volumes will be created + // under. This convention is followed by the default volume driver, but + // may not be by other drivers. + VolumePath string `toml:"volume_path"` // ImageDefaultTransport is the default transport method used to fetch // images ImageDefaultTransport string `toml:"image_default_transport"` @@ -232,12 +235,14 @@ type runtimeConfiguredFrom struct { storageRunRootSet bool libpodStaticDirSet bool libpodTmpDirSet bool + volPathSet bool } var ( defaultRuntimeConfig = RuntimeConfig{ // Leave this empty so containers/storage will use its defaults StorageConfig: storage.StoreOptions{}, + VolumePath: filepath.Join(storage.DefaultStoreOptions.GraphRoot, "volumes"), ImageDefaultTransport: DefaultTransport, StateType: BoltDBStateStore, OCIRuntime: "runc", @@ -400,6 +405,9 @@ func NewRuntime(options ...RuntimeOption) (runtime *Runtime, err error) { if tmpConfig.TmpDir != "" { runtime.configuredFrom.libpodTmpDirSet = true } + if tmpConfig.VolumePath != "" { + runtime.configuredFrom.volPathSet = true + } if _, err := toml.Decode(string(contents), runtime.config); err != nil { return nil, errors.Wrapf(err, "error decoding configuration file %s", configPath) @@ -526,6 +534,16 @@ func makeRuntime(runtime *Runtime) (err error) { if runtime.config.OCIRuntime != "" && runtime.config.OCIRuntime[0] == '/' { foundRuntime = true runtime.ociRuntimePath = OCIRuntimePath{Name: filepath.Base(runtime.config.OCIRuntime), Paths: []string{runtime.config.OCIRuntime}} + stat, err := os.Stat(runtime.config.OCIRuntime) + if err != nil { + if os.IsNotExist(err) { + return errors.Wrapf(err, "the specified OCI runtime %s does not exist", runtime.config.OCIRuntime) + } + return errors.Wrapf(err, "cannot stat the OCI runtime path %s", runtime.config.OCIRuntime) + } + if !stat.Mode().IsRegular() { + return fmt.Errorf("the specified OCI runtime %s is not a valid file", runtime.config.OCIRuntime) + } } else { // If not, look it up in the configuration. paths := runtime.config.OCIRuntimes[runtime.config.OCIRuntime] @@ -614,29 +632,52 @@ func makeRuntime(runtime *Runtime) (err error) { if !runtime.configuredFrom.storageGraphDriverSet && dbConfig.GraphDriver != "" { if runtime.config.StorageConfig.GraphDriverName != dbConfig.GraphDriver && runtime.config.StorageConfig.GraphDriverName != "" { - logrus.Errorf("User-selected graph driver %s overwritten by graph driver %s from database - delete libpod local files to resolve", + logrus.Errorf("User-selected graph driver %q overwritten by graph driver %q from database - delete libpod local files to resolve", runtime.config.StorageConfig.GraphDriverName, dbConfig.GraphDriver) } runtime.config.StorageConfig.GraphDriverName = dbConfig.GraphDriver } if !runtime.configuredFrom.storageGraphRootSet && dbConfig.StorageRoot != "" { + if runtime.config.StorageConfig.GraphRoot != dbConfig.StorageRoot && + runtime.config.StorageConfig.GraphRoot != "" { + logrus.Debugf("Overriding graph root %q with %q from database", + runtime.config.StorageConfig.GraphRoot, dbConfig.StorageRoot) + } runtime.config.StorageConfig.GraphRoot = dbConfig.StorageRoot } if !runtime.configuredFrom.storageRunRootSet && dbConfig.StorageTmp != "" { + if runtime.config.StorageConfig.RunRoot != dbConfig.StorageTmp && + runtime.config.StorageConfig.RunRoot != "" { + logrus.Debugf("Overriding run root %q with %q from database", + runtime.config.StorageConfig.RunRoot, dbConfig.StorageTmp) + } runtime.config.StorageConfig.RunRoot = dbConfig.StorageTmp } if !runtime.configuredFrom.libpodStaticDirSet && dbConfig.LibpodRoot != "" { + if runtime.config.StaticDir != dbConfig.LibpodRoot && runtime.config.StaticDir != "" { + logrus.Debugf("Overriding static dir %q with %q from database", runtime.config.StaticDir, dbConfig.LibpodRoot) + } runtime.config.StaticDir = dbConfig.LibpodRoot } if !runtime.configuredFrom.libpodTmpDirSet && dbConfig.LibpodTmp != "" { + if runtime.config.TmpDir != dbConfig.LibpodTmp && runtime.config.TmpDir != "" { + logrus.Debugf("Overriding tmp dir %q with %q from database", runtime.config.TmpDir, dbConfig.LibpodTmp) + } runtime.config.TmpDir = dbConfig.LibpodTmp } + if !runtime.configuredFrom.volPathSet && dbConfig.VolumePath != "" { + if runtime.config.VolumePath != dbConfig.VolumePath && runtime.config.VolumePath != "" { + logrus.Debugf("Overriding volume path %q with %q from database", runtime.config.VolumePath, dbConfig.VolumePath) + } + runtime.config.VolumePath = dbConfig.VolumePath + } logrus.Debugf("Using graph driver %s", runtime.config.StorageConfig.GraphDriverName) logrus.Debugf("Using graph root %s", runtime.config.StorageConfig.GraphRoot) logrus.Debugf("Using run root %s", runtime.config.StorageConfig.RunRoot) logrus.Debugf("Using static dir %s", runtime.config.StaticDir) logrus.Debugf("Using tmp dir %s", runtime.config.TmpDir) + logrus.Debugf("Using volume path %s", runtime.config.VolumePath) // Validate our config against the database, now that we've set our // final storage configuration diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 2ec8d0795..cfa4f9654 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -180,7 +180,7 @@ func (r *Runtime) newContainer(ctx context.Context, rSpec *spec.Spec, options .. if vol.Source[0] != '/' && isNamedVolume(vol.Source) { volInfo, err := r.state.Volume(vol.Source) if err != nil { - newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source)) + newVol, err := r.newVolume(ctx, WithVolumeName(vol.Source), withSetCtrSpecific()) if err != nil { return nil, errors.Wrapf(err, "error creating named volume %q", vol.Source) } @@ -421,6 +421,9 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, for _, v := range volumes { if volume, err := runtime.state.Volume(v); err == nil { + if !volume.IsCtrSpecific() { + continue + } if err := runtime.removeVolume(ctx, volume, false); err != nil && err != ErrNoSuchVolume && err != ErrVolumeBeingUsed { logrus.Errorf("cleanup volume (%s): %v", v, err) } diff --git a/libpod/state.go b/libpod/state.go index 98282fc83..4296fc3cd 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -8,6 +8,7 @@ type DBConfig struct { StorageRoot string StorageTmp string GraphDriver string + VolumePath string } // State is a storage backend for libpod's current state. diff --git a/libpod/volume.go b/libpod/volume.go index 74878b6a4..0c7618841 100644 --- a/libpod/volume.go +++ b/libpod/volume.go @@ -15,11 +15,12 @@ type VolumeConfig struct { // Name of the volume Name string `json:"name"` - Labels map[string]string `json:"labels"` - MountPoint string `json:"mountPoint"` - Driver string `json:"driver"` - Options map[string]string `json:"options"` - Scope string `json:"scope"` + Labels map[string]string `json:"labels"` + MountPoint string `json:"mountPoint"` + Driver string `json:"driver"` + Options map[string]string `json:"options"` + Scope string `json:"scope"` + IsCtrSpecific bool `json:"ctrSpecific"` } // Name retrieves the volume's name @@ -60,3 +61,10 @@ func (v *Volume) Options() map[string]string { func (v *Volume) Scope() string { return v.config.Scope } + +// IsCtrSpecific returns whether this volume was created specifically for a +// given container. Images with this set to true will be removed when the +// container is removed with the Volumes parameter set to true. +func (v *Volume) IsCtrSpecific() bool { + return v.config.IsCtrSpecific +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 9623304e5..3f43a6905 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -4,6 +4,7 @@ package adapter import ( "encoding/json" + "github.com/containers/libpod/cmd/podman/shared" iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" @@ -48,3 +49,33 @@ func (c *Container) Config() *libpod.ContainerConfig { } return c.Runtime.Config(c.ID()) } + +// Name returns the name of the container +func (c *Container) Name() string { + return c.config.Name +} + +// BatchContainerOp is wrapper func to mimic shared's function with a similar name meant for libpod +func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContainerStruct, error) { + // TODO If pod ps ever shows container's sizes, re-enable this code; otherwise it isn't needed + // and would be a perf hit + //data, err := ctr.Inspect(true) + //if err != nil { + // return shared.BatchContainerStruct{}, err + //} + // + //size := new(shared.ContainerSize) + //size.RootFsSize = data.SizeRootFs + //size.RwSize = data.SizeRw + + bcs := shared.BatchContainerStruct{ + ConConfig: ctr.config, + ConState: ctr.state.State, + ExitCode: ctr.state.ExitCode, + Pid: ctr.state.PID, + StartedTime: ctr.state.StartedTime, + ExitedTime: ctr.state.FinishedTime, + //Size: size, + } + return bcs, nil +} diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index f2bcbd473..706a8fe96 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -4,10 +4,12 @@ package adapter import ( "context" - "github.com/containers/libpod/pkg/adapter/shortcuts" + "strings" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter/shortcuts" ) // Pod ... @@ -45,6 +47,21 @@ func (r *LocalRuntime) GetLatestPod() (*Pod, error) { return &pod, err } +// GetAllPods gets all pods and wraps it in an adapter pod +func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { + var pods []*Pod + allPods, err := r.Runtime.GetAllPods() + if err != nil { + return nil, err + } + for _, p := range allPods { + pod := Pod{} + pod.Pod = p + pods = append(pods, &pod) + } + return pods, nil +} + // LookupPod gets a pod by name or id and wraps it in an adapter pod func (r *LocalRuntime) LookupPod(nameOrID string) (*Pod, error) { pod := Pod{} @@ -150,3 +167,157 @@ func (r *LocalRuntime) StartPods(ctx context.Context, cli *cliconfig.PodStartVal } return podids, errs } + +// CreatePod is a wrapper for libpod and creating a new pod from the cli context +func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateValues, labels map[string]string) (string, error) { + var ( + options []libpod.PodCreateOption + err error + ) + + if cli.Flag("cgroup-parent").Changed { + options = append(options, libpod.WithPodCgroupParent(cli.CgroupParent)) + } + + if len(labels) != 0 { + options = append(options, libpod.WithPodLabels(labels)) + } + + if cli.Flag("name").Changed { + options = append(options, libpod.WithPodName(cli.Name)) + } + + if cli.Infra { + options = append(options, libpod.WithInfraContainer()) + nsOptions, err := shared.GetNamespaceOptions(strings.Split(cli.Share, ",")) + if err != nil { + return "", err + } + options = append(options, nsOptions...) + } + + if len(cli.Publish) > 0 { + portBindings, err := shared.CreatePortBindings(cli.Publish) + if err != nil { + return "", err + } + options = append(options, libpod.WithInfraContainerPorts(portBindings)) + + } + // always have containers use pod cgroups + // User Opt out is not yet supported + options = append(options, libpod.WithPodCgroups()) + + pod, err := r.NewPod(ctx, options...) + if err != nil { + return "", err + } + return pod.ID(), nil +} + +// GetPodStatus is a wrapper to get the status of a local libpod pod +func (p *Pod) GetPodStatus() (string, error) { + return shared.GetPodStatus(p.Pod) +} + +// BatchContainerOp is a wrapper for the shared function of the same name +func BatchContainerOp(ctr *libpod.Container, opts shared.PsOptions) (shared.BatchContainerStruct, error) { + return shared.BatchContainerOp(ctr, opts) +} + +// PausePods is a wrapper for pausing pods via libpod +func (r *LocalRuntime) PausePods(c *cliconfig.PodPauseValues) ([]string, map[string]error, []error) { + var ( + pauseIDs []string + pauseErrors []error + ) + containerErrors := make(map[string]error) + + pods, err := shortcuts.GetPodsByContext(c.All, c.Latest, c.InputArgs, r.Runtime) + if err != nil { + pauseErrors = append(pauseErrors, err) + return nil, containerErrors, pauseErrors + } + + for _, pod := range pods { + ctrErrs, err := pod.Pause() + if err != nil { + pauseErrors = append(pauseErrors, err) + continue + } + if ctrErrs != nil { + for ctr, err := range ctrErrs { + containerErrors[ctr] = err + } + continue + } + pauseIDs = append(pauseIDs, pod.ID()) + + } + return pauseIDs, containerErrors, pauseErrors +} + +// UnpausePods is a wrapper for unpausing pods via libpod +func (r *LocalRuntime) UnpausePods(c *cliconfig.PodUnpauseValues) ([]string, map[string]error, []error) { + var ( + unpauseIDs []string + unpauseErrors []error + ) + containerErrors := make(map[string]error) + + pods, err := shortcuts.GetPodsByContext(c.All, c.Latest, c.InputArgs, r.Runtime) + if err != nil { + unpauseErrors = append(unpauseErrors, err) + return nil, containerErrors, unpauseErrors + } + + for _, pod := range pods { + ctrErrs, err := pod.Unpause() + if err != nil { + unpauseErrors = append(unpauseErrors, err) + continue + } + if ctrErrs != nil { + for ctr, err := range ctrErrs { + containerErrors[ctr] = err + } + continue + } + unpauseIDs = append(unpauseIDs, pod.ID()) + + } + return unpauseIDs, containerErrors, unpauseErrors +} + +// RestartPods is a wrapper to restart pods via libpod +func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartValues) ([]string, map[string]error, []error) { + var ( + restartIDs []string + restartErrors []error + ) + containerErrors := make(map[string]error) + + pods, err := shortcuts.GetPodsByContext(c.All, c.Latest, c.InputArgs, r.Runtime) + if err != nil { + restartErrors = append(restartErrors, err) + return nil, containerErrors, restartErrors + } + + for _, pod := range pods { + ctrErrs, err := pod.Restart(ctx) + if err != nil { + restartErrors = append(restartErrors, err) + continue + } + if ctrErrs != nil { + for ctr, err := range ctrErrs { + containerErrors[ctr] = err + } + continue + } + restartIDs = append(restartIDs, pod.ID()) + + } + return restartIDs, containerErrors, restartErrors + +} diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 04484780a..220f7163f 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -5,8 +5,11 @@ package adapter import ( "context" "encoding/json" + "strings" + "time" "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" "github.com/pkg/errors" @@ -168,3 +171,231 @@ func (r *LocalRuntime) StartPods(ctx context.Context, cli *cliconfig.PodStartVal } return startPods, startErrs } + +// CreatePod creates a pod for the remote client over a varlink connection +func (r *LocalRuntime) CreatePod(ctx context.Context, cli *cliconfig.PodCreateValues, labels map[string]string) (string, error) { + pc := iopodman.PodCreate{ + Name: cli.Name, + CgroupParent: cli.CgroupParent, + Labels: labels, + Share: strings.Split(cli.Share, ","), + Infra: cli.Infra, + InfraCommand: cli.InfraCommand, + InfraImage: cli.InfraCommand, + Publish: cli.Publish, + } + + return iopodman.CreatePod().Call(r.Conn, pc) +} + +// GetAllPods is a helper function that gets all pods for the remote client +func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { + var pods []*Pod + podIDs, err := iopodman.GetPodsByContext().Call(r.Conn, true, false, []string{}) + if err != nil { + return nil, err + } + for _, p := range podIDs { + pod, err := r.LookupPod(p) + if err != nil { + return nil, err + } + pods = append(pods, pod) + } + return pods, nil +} + +// ID returns the id of a remote pod +func (p *Pod) ID() string { + return p.config.ID +} + +// Name returns the name of the remote pod +func (p *Pod) Name() string { + return p.config.Name +} + +// AllContainersByID returns a slice of a pod's container IDs +func (p *Pod) AllContainersByID() ([]string, error) { + var containerIDs []string + for _, ctr := range p.containers { + containerIDs = append(containerIDs, ctr.ID) + } + return containerIDs, nil +} + +// AllContainers returns a pods containers +func (p *Pod) AllContainers() ([]*Container, error) { + var containers []*Container + for _, ctr := range p.containers { + container, err := p.Runtime.LookupContainer(ctr.ID) + if err != nil { + return nil, err + } + containers = append(containers, container) + } + return containers, nil +} + +// Status ... +func (p *Pod) Status() (map[string]libpod.ContainerStatus, error) { + ctrs := make(map[string]libpod.ContainerStatus) + for _, i := range p.containers { + var status libpod.ContainerStatus + switch i.State { + case "exited": + status = libpod.ContainerStateExited + case "stopped": + status = libpod.ContainerStateStopped + case "running": + status = libpod.ContainerStateRunning + case "paused": + status = libpod.ContainerStatePaused + case "created": + status = libpod.ContainerStateCreated + case "configured": + status = libpod.ContainerStateConfigured + default: + status = libpod.ContainerStateUnknown + } + ctrs[i.ID] = status + } + return ctrs, nil +} + +// GetPodStatus is a wrapper to get the string version of the status +func (p *Pod) GetPodStatus() (string, error) { + ctrStatuses, err := p.Status() + if err != nil { + return "", err + } + return shared.CreatePodStatusResults(ctrStatuses) +} + +// InfraContainerID returns the ID of the infra container in a pod +func (p *Pod) InfraContainerID() (string, error) { + return p.state.InfraContainerID, nil +} + +// CreatedTime returns the time the container was created as a time.Time +func (p *Pod) CreatedTime() time.Time { + return p.config.CreatedTime +} + +// SharesPID .... +func (p *Pod) SharesPID() bool { + return p.config.UsePodPID +} + +// SharesIPC returns whether containers in pod +// default to use IPC namespace of first container in pod +func (p *Pod) SharesIPC() bool { + return p.config.UsePodIPC +} + +// SharesNet returns whether containers in pod +// default to use network namespace of first container in pod +func (p *Pod) SharesNet() bool { + return p.config.UsePodNet +} + +// SharesMount returns whether containers in pod +// default to use PID namespace of first container in pod +func (p *Pod) SharesMount() bool { + return p.config.UsePodMount +} + +// SharesUser returns whether containers in pod +// default to use user namespace of first container in pod +func (p *Pod) SharesUser() bool { + return p.config.UsePodUser +} + +// SharesUTS returns whether containers in pod +// default to use UTS namespace of first container in pod +func (p *Pod) SharesUTS() bool { + return p.config.UsePodUTS +} + +// SharesCgroup returns whether containers in the pod will default to this pod's +// cgroup instead of the default libpod parent +func (p *Pod) SharesCgroup() bool { + return p.config.UsePodCgroup +} + +// CgroupParent returns the pod's CGroup parent +func (p *Pod) CgroupParent() string { + return p.config.CgroupParent +} + +// PausePods pauses a pod using varlink and the remote client +func (r *LocalRuntime) PausePods(c *cliconfig.PodPauseValues) ([]string, map[string]error, []error) { + var ( + pauseIDs []string + pauseErrors []error + ) + containerErrors := make(map[string]error) + + pods, err := iopodman.GetPodsByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs) + if err != nil { + pauseErrors = append(pauseErrors, err) + return nil, containerErrors, pauseErrors + } + for _, pod := range pods { + reply, err := iopodman.PausePod().Call(r.Conn, pod) + if err != nil { + pauseErrors = append(pauseErrors, err) + continue + } + pauseIDs = append(pauseIDs, reply) + } + return pauseIDs, nil, pauseErrors +} + +// UnpausePods unpauses a pod using varlink and the remote client +func (r *LocalRuntime) UnpausePods(c *cliconfig.PodUnpauseValues) ([]string, map[string]error, []error) { + var ( + unpauseIDs []string + unpauseErrors []error + ) + containerErrors := make(map[string]error) + + pods, err := iopodman.GetPodsByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs) + if err != nil { + unpauseErrors = append(unpauseErrors, err) + return nil, containerErrors, unpauseErrors + } + for _, pod := range pods { + reply, err := iopodman.UnpausePod().Call(r.Conn, pod) + if err != nil { + unpauseErrors = append(unpauseErrors, err) + continue + } + unpauseIDs = append(unpauseIDs, reply) + } + return unpauseIDs, nil, unpauseErrors +} + +// RestartPods restarts pods using varlink and the remote client +func (r *LocalRuntime) RestartPods(ctx context.Context, c *cliconfig.PodRestartValues) ([]string, map[string]error, []error) { + var ( + restartIDs []string + restartErrors []error + ) + containerErrors := make(map[string]error) + + pods, err := iopodman.GetPodsByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs) + if err != nil { + restartErrors = append(restartErrors, err) + return nil, containerErrors, restartErrors + } + for _, pod := range pods { + reply, err := iopodman.RestartPod().Call(r.Conn, pod) + if err != nil { + restartErrors = append(restartErrors, err) + continue + } + restartIDs = append(restartIDs, reply) + } + return restartIDs, nil, restartErrors +} diff --git a/pkg/spec/createconfig.go b/pkg/spec/createconfig.go index 8da44a2f0..31039bfdf 100644 --- a/pkg/spec/createconfig.go +++ b/pkg/spec/createconfig.go @@ -240,7 +240,7 @@ func (c *CreateConfig) GetVolumeMounts(specMounts []spec.Mount) ([]spec.Mount, e } for vol := range c.BuiltinImgVolumes { - if libpod.MountExists(specMounts, vol) { + if libpod.MountExists(specMounts, vol) || libpod.MountExists(m, vol) { continue } @@ -446,7 +446,15 @@ func (c *CreateConfig) GetContainerCreateOptions(runtime *libpod.Runtime, pod *l } if IsNS(string(c.NetMode)) { - // pass + split := strings.SplitN(string(c.NetMode), ":", 2) + if len(split[0]) != 2 { + return nil, errors.Errorf("invalid user defined network namespace %q", c.NetMode.UserDefined()) + } + _, err := os.Stat(split[1]) + if err != nil { + return nil, err + } + options = append(options, libpod.WithNetNS(portBindings, false, string(c.NetMode), networks)) } else if c.NetMode.IsContainer() { connectedCtr, err := c.Runtime.LookupContainer(c.NetMode.Container()) if err != nil { diff --git a/pkg/spec/spec.go b/pkg/spec/spec.go index 76b8963ff..28a636fa6 100644 --- a/pkg/spec/spec.go +++ b/pkg/spec/spec.go @@ -3,10 +3,12 @@ package createconfig import ( "os" "path" + "path/filepath" "strings" "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/mount" + pmount "github.com/containers/storage/pkg/mount" "github.com/docker/docker/daemon/caps" "github.com/docker/go-units" "github.com/opencontainers/runc/libcontainer/user" @@ -392,9 +394,65 @@ func CreateConfigToOCISpec(config *CreateConfig) (*spec.Spec, error) { //nolint configSpec.Linux.Resources = &spec.LinuxResources{} } + // Make sure that the bind mounts keep options like nosuid, noexec, nodev. + mounts, err := pmount.GetMounts() + if err != nil { + return nil, err + } + for i := range configSpec.Mounts { + m := &configSpec.Mounts[i] + isBind := false + for _, o := range m.Options { + if o == "bind" || o == "rbind" { + isBind = true + break + } + } + if !isBind { + continue + } + mount, err := findMount(m.Source, mounts) + if err != nil { + return nil, err + } + if mount == nil { + continue + } + next_option: + for _, o := range strings.Split(mount.Opts, ",") { + if o == "nosuid" || o == "noexec" || o == "nodev" { + for _, e := range m.Options { + if e == o { + continue next_option + } + } + m.Options = append(m.Options, o) + } + } + } + return configSpec, nil } +func findMount(target string, mounts []*pmount.Info) (*pmount.Info, error) { + var err error + target, err = filepath.Abs(target) + if err != nil { + return nil, errors.Wrapf(err, "cannot resolve %s", target) + } + var bestSoFar *pmount.Info + for _, i := range mounts { + if bestSoFar != nil && len(bestSoFar.Mountpoint) > len(i.Mountpoint) { + // Won't be better than what we have already found + continue + } + if strings.HasPrefix(target, i.Mountpoint) { + bestSoFar = i + } + } + return bestSoFar, nil +} + func blockAccessToKernelFilesystems(config *CreateConfig, g *generate.Generator) { if config.PidMode.IsHost() && rootless.IsRootless() { return diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index ceeed6681..4ca4c4270 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -14,10 +14,6 @@ import ( // CreatePod ... func (i *LibpodAPI) CreatePod(call iopodman.VarlinkCall, create iopodman.PodCreate) error { var options []libpod.PodCreateOption - - if create.InfraCommand != "" || create.InfraImage != "" { - return call.ReplyErrorOccurred("the infra-command and infra-image options are not supported yet") - } if create.CgroupParent != "" { options = append(options, libpod.WithPodCgroupParent(create.CgroupParent)) } diff --git a/test/e2e/rm_test.go b/test/e2e/rm_test.go index bc1431bce..71dacfa80 100644 --- a/test/e2e/rm_test.go +++ b/test/e2e/rm_test.go @@ -128,4 +128,9 @@ var _ = Describe("Podman rm", func() { Expect(podmanTest.NumberOfContainers()).To(Equal(1)) }) + It("podman rm bogus container", func() { + session := podmanTest.Podman([]string{"rm", "bogus"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(1)) + }) }) diff --git a/test/e2e/rmi_test.go b/test/e2e/rmi_test.go index c160e1bc5..dcbda2df4 100644 --- a/test/e2e/rmi_test.go +++ b/test/e2e/rmi_test.go @@ -36,7 +36,7 @@ var _ = Describe("Podman rmi", func() { It("podman rmi bogus image", func() { session := podmanTest.Podman([]string{"rmi", "debian:6.0.10"}) session.WaitWithDefaultTimeout() - Expect(session.ExitCode()).To(Equal(125)) + Expect(session.ExitCode()).To(Equal(1)) }) diff --git a/test/e2e/run_networking_test.go b/test/e2e/run_networking_test.go index 1c09a4d0b..a07e4d047 100644 --- a/test/e2e/run_networking_test.go +++ b/test/e2e/run_networking_test.go @@ -36,19 +36,19 @@ var _ = Describe("Podman run networking", func() { }) It("podman run network connection with default bridge", func() { - session := podmanTest.Podman([]string{"run", "-dt", ALPINE, "wget", "www.projectatomic.io"}) + session := podmanTest.Podman([]string{"run", "-dt", ALPINE, "wget", "www.podman.io"}) session.Wait(90) Expect(session.ExitCode()).To(Equal(0)) }) It("podman run network connection with host", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.projectatomic.io"}) + session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.podman.io"}) session.Wait(90) Expect(session.ExitCode()).To(Equal(0)) }) It("podman run network connection with loopback", func() { - session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.projectatomic.io"}) + session := podmanTest.Podman([]string{"run", "-dt", "--network", "host", ALPINE, "wget", "www.podman.io"}) session.Wait(90) Expect(session.ExitCode()).To(Equal(0)) }) @@ -178,4 +178,37 @@ var _ = Describe("Podman run networking", func() { Expect(exec4.ExitCode()).To(Equal(0)) Expect(exec4.OutputToString()).To(ContainSubstring("192.0.2.2 test1")) }) + + It("podman run network in user created network namespace", func() { + if Containerized() { + Skip("Can not be run within a container.") + } + SystemExec("ip", []string{"netns", "add", "xxx"}) + session := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxx", ALPINE, "wget", "www.podman.io"}) + session.Wait(90) + Expect(session.ExitCode()).To(Equal(0)) + SystemExec("ip", []string{"netns", "delete", "xxx"}) + }) + + It("podman run n user created network namespace with resolv.conf", func() { + if Containerized() { + Skip("Can not be run within a container.") + } + SystemExec("ip", []string{"netns", "add", "xxx"}) + SystemExec("mkdir", []string{"-p", "/etc/netns/xxx"}) + SystemExec("bash", []string{"-c", "echo nameserver 11.11.11.11 > /etc/netns/xxx/resolv.conf"}) + session := podmanTest.Podman([]string{"run", "--net", "ns:/run/netns/xxx", ALPINE, "cat", "/etc/resolv.conf"}) + session.Wait(90) + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("11.11.11.11")) + SystemExec("ip", []string{"netns", "delete", "xxx"}) + SystemExec("rm", []string{"-rf", "/etc/netns/xxx"}) + }) + + It("podman run network in bogus user created network namespace", func() { + session := podmanTest.Podman([]string{"run", "-dt", "--net", "ns:/run/netns/xxy", ALPINE, "wget", "www.podman.io"}) + session.Wait(90) + Expect(session.ExitCode()).To(Not(Equal(0))) + Expect(session.ErrorToString()).To(ContainSubstring("stat /run/netns/xxy: no such file or directory")) + }) }) diff --git a/vendor.conf b/vendor.conf index 445f0844a..f739c76f4 100644 --- a/vendor.conf +++ b/vendor.conf @@ -15,7 +15,7 @@ github.com/containerd/cgroups 39b18af02c4120960f517a3a4c2588fabb61d02c github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-alpha1 github.com/containernetworking/plugins v0.7.4 -github.com/containers/image v1.4 +github.com/containers/image v1.5 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 @@ -93,7 +93,7 @@ k8s.io/apimachinery kubernetes-1.10.13-beta.0 https://github.com/kubernetes/apim k8s.io/client-go kubernetes-1.10.13-beta.0 https://github.com/kubernetes/client-go github.com/mrunalp/fileutils 7d4729fb36185a7c1719923406c9d40e54fb93c7 github.com/varlink/go 3ac79db6fd6aec70924193b090962f92985fe199 -github.com/containers/buildah v1.7 +github.com/containers/buildah v1.7.1 # TODO: Gotty has not been updated since 2012. Can we find replacement? github.com/Nvveen/Gotty cd527374f1e5bff4938207604a14f2e38a9cf512 # do not go beyond the below commit as the next one requires a more recent diff --git a/vendor/github.com/containers/buildah/README.md b/vendor/github.com/containers/buildah/README.md index 12eafdf88..913a4336f 100644 --- a/vendor/github.com/containers/buildah/README.md +++ b/vendor/github.com/containers/buildah/README.md @@ -35,18 +35,23 @@ For blogs, release announcements and more, please checkout the [buildah.io](http ## Buildah and Podman relationship -Buildah and Podman are two complementary Open-source projects that are available on -most Linux platforms and both projects reside at [GitHub.com](https://github.com) -with Buildah [here](https://github.com/containers/buildah) and -Podman [here](https://github.com/containers/libpod). Both Buildah and Podman are -command line tools that work on OCI images and containers. The two projects -differentiate in their specialization. +Buildah and Podman are two complementary open-source projects that are +available on most Linux platforms and both projects reside at +[GitHub.com](https://github.com) with Buildah +[here](https://github.com/containers/buildah) and Podman +[here](https://github.com/containers/libpod). Both, Buildah and Podman are +command line tools that work on Open Container Initiative (OCI) images and +containers. The two projects differentiate in their specialization. Buildah specializes in building OCI images. Buildah's commands replicate all -of the commands that are found in a Dockerfile. Buildah’s goal is also to -provide a lower level coreutils interface to build images, allowing people to build -containers without requiring a Dockerfile. The intent with Buildah is to allow other -scripting languages to build container images, without requiring a daemon. +of the commands that are found in a Dockerfile. This allows building images +with and without Dockerfiles while not requiring any root privileges. +Buildah’s ultimate goal is to provide a lower-level coreutils interface to +build images. The flexibility of building images without Dockerfiles allows +for the integration of other scripting languages into the build process. +Buildah follows a simple fork-exec model and does not run as a daemon +but it is based on a comprehensive API in golang, which can be vendored +into other tools. Podman specializes in all of the commands and functions that help you to maintain and modify OCI images, such as pulling and tagging. It also allows you to create, run, and maintain those containers @@ -55,12 +60,12 @@ created from those images. A major difference between Podman and Buildah is their concept of a container. Podman allows users to create "traditional containers" where the intent of these containers is to be long lived. While Buildah containers are really just created to allow content -to be added back to the container image. An easy way to think of it is the +to be added back to the container image. An easy way to think of it is the `buildah run` command emulates the RUN command in a Dockerfile while the `podman run` command emulates the `docker run` command in functionality. Because of this and their underlying storage differences, you can not see Podman containers from within Buildah or vice versa. -In short Buildah is an efficient way to create OCI images while Podman allows +In short, Buildah is an efficient way to create OCI images while Podman allows you to manage and maintain those images and containers in a production environment using familiar container cli commands. For more details, see the [Container Tools Guide](https://github.com/containers/buildah/tree/master/docs/containertools). diff --git a/vendor/github.com/containers/buildah/buildah.go b/vendor/github.com/containers/buildah/buildah.go index 755bc348e..cca80a308 100644 --- a/vendor/github.com/containers/buildah/buildah.go +++ b/vendor/github.com/containers/buildah/buildah.go @@ -26,7 +26,7 @@ const ( Package = "buildah" // Version for the Package. Bump version in contrib/rpm/buildah.spec // too. - Version = "1.7" + Version = "1.7.1" // The value we use to identify what type of information, currently a // serialized Builder structure, we are using as per-container state. // This should only be changed when we make incompatible changes to diff --git a/vendor/github.com/containers/buildah/new.go b/vendor/github.com/containers/buildah/new.go index 01c2e733f..768cdd0c6 100644 --- a/vendor/github.com/containers/buildah/new.go +++ b/vendor/github.com/containers/buildah/new.go @@ -303,7 +303,7 @@ func newBuilder(ctx context.Context, store storage.Store, options BuilderOptions } conflict := 100 - for true { + for { coptions := storage.ContainerOptions{ LabelOpts: options.CommonBuildOpts.LabelOpts, IDMappingOptions: newContainerIDMappingOptions(options.IDMappingOptions), diff --git a/vendor/github.com/containers/buildah/pkg/cli/common.go b/vendor/github.com/containers/buildah/pkg/cli/common.go index 09f951b35..f167353b8 100644 --- a/vendor/github.com/containers/buildah/pkg/cli/common.go +++ b/vendor/github.com/containers/buildah/pkg/cli/common.go @@ -125,7 +125,7 @@ func GetNameSpaceFlags(flags *NameSpaceResults) pflag.FlagSet { func GetLayerFlags(flags *LayerResults) pflag.FlagSet { fs := pflag.FlagSet{} fs.BoolVar(&flags.ForceRm, "force-rm", false, "Always remove intermediate containers after a build, even if the build is unsuccessful.") - fs.BoolVar(&flags.Layers, "layers", false, fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override. (default %t)", UseLayers())) + fs.BoolVar(&flags.Layers, "layers", UseLayers(), fmt.Sprintf("cache intermediate layers during build. Use BUILDAH_LAYERS environment variable to override.")) return fs } @@ -152,7 +152,7 @@ func GetBudFlags(flags *BudResults) pflag.FlagSet { fs.BoolVar(&flags.Pull, "pull", true, "pull the image if not present") fs.BoolVar(&flags.PullAlways, "pull-always", false, "pull the image, even if a version is present") fs.BoolVarP(&flags.Quiet, "quiet", "q", false, "refrain from announcing build instructions and image read/write progress") - fs.BoolVar(&flags.Rm, "rm", true, "Remove intermediate containers after a successful build (default true)") + fs.BoolVar(&flags.Rm, "rm", true, "Remove intermediate containers after a successful build") fs.StringVar(&flags.Runtime, "runtime", util.Runtime(), "`path` to an alternate runtime. Use BUILDAH_RUNTIME environment variable to override.") fs.StringSliceVar(&flags.RuntimeFlags, "runtime-flag", []string{}, "add global flags for the container runtime") fs.StringVar(&flags.SignaturePolicy, "signature-policy", "", "`pathname` of signature policy file (not usually used)") diff --git a/vendor/github.com/containers/buildah/pkg/secrets/secrets.go b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go new file mode 100644 index 000000000..242953609 --- /dev/null +++ b/vendor/github.com/containers/buildah/pkg/secrets/secrets.go @@ -0,0 +1,319 @@ +package secrets + +import ( + "bufio" + "io/ioutil" + "os" + "path/filepath" + "strings" + + "github.com/containers/libpod/pkg/rootless" + rspec "github.com/opencontainers/runtime-spec/specs-go" + "github.com/opencontainers/selinux/go-selinux/label" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +var ( + // DefaultMountsFile holds the default mount paths in the form + // "host_path:container_path" + DefaultMountsFile = "/usr/share/containers/mounts.conf" + // OverrideMountsFile holds the default mount paths in the form + // "host_path:container_path" overridden by the user + OverrideMountsFile = "/etc/containers/mounts.conf" + // UserOverrideMountsFile holds the default mount paths in the form + // "host_path:container_path" overridden by the rootless user + UserOverrideMountsFile = filepath.Join(os.Getenv("HOME"), ".config/containers/mounts.conf") +) + +// secretData stores the name of the file and the content read from it +type secretData struct { + name string + data []byte +} + +// saveTo saves secret data to given directory +func (s secretData) saveTo(dir string) error { + path := filepath.Join(dir, s.name) + if err := os.MkdirAll(filepath.Dir(path), 0700); err != nil && !os.IsExist(err) { + return err + } + return ioutil.WriteFile(path, s.data, 0700) +} + +func readAll(root, prefix string) ([]secretData, error) { + path := filepath.Join(root, prefix) + + data := []secretData{} + + files, err := ioutil.ReadDir(path) + if err != nil { + if os.IsNotExist(err) { + return data, nil + } + + return nil, err + } + + for _, f := range files { + fileData, err := readFile(root, filepath.Join(prefix, f.Name())) + if err != nil { + // If the file did not exist, might be a dangling symlink + // Ignore the error + if os.IsNotExist(err) { + continue + } + return nil, err + } + data = append(data, fileData...) + } + + return data, nil +} + +func readFile(root, name string) ([]secretData, error) { + path := filepath.Join(root, name) + + s, err := os.Stat(path) + if err != nil { + return nil, err + } + + if s.IsDir() { + dirData, err := readAll(root, name) + if err != nil { + return nil, err + } + return dirData, nil + } + bytes, err := ioutil.ReadFile(path) + if err != nil { + return nil, err + } + return []secretData{{name: name, data: bytes}}, nil +} + +func getHostSecretData(hostDir string) ([]secretData, error) { + var allSecrets []secretData + hostSecrets, err := readAll(hostDir, "") + if err != nil { + return nil, errors.Wrapf(err, "failed to read secrets from %q", hostDir) + } + return append(allSecrets, hostSecrets...), nil +} + +func getMounts(filePath string) []string { + file, err := os.Open(filePath) + if err != nil { + // This is expected on most systems + logrus.Debugf("file %q not found, skipping...", filePath) + return nil + } + defer file.Close() + scanner := bufio.NewScanner(file) + if err = scanner.Err(); err != nil { + logrus.Errorf("error reading file %q, %v skipping...", filePath, err) + return nil + } + var mounts []string + for scanner.Scan() { + mounts = append(mounts, scanner.Text()) + } + return mounts +} + +// getHostAndCtrDir separates the host:container paths +func getMountsMap(path string) (string, string, error) { + arr := strings.SplitN(path, ":", 2) + if len(arr) == 2 { + return arr[0], arr[1], nil + } + return "", "", errors.Errorf("unable to get host and container dir") +} + +// SecretMounts copies, adds, and mounts the secrets to the container root filesystem +func SecretMounts(mountLabel, containerWorkingDir, mountFile string) []rspec.Mount { + return SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, containerWorkingDir, 0, 0) +} + +// SecretMountsWithUIDGID specifies the uid/gid of the owner +func SecretMountsWithUIDGID(mountLabel, containerWorkingDir, mountFile, mountPrefix string, uid, gid int) []rspec.Mount { + var ( + secretMounts []rspec.Mount + mountFiles []string + ) + // Add secrets from paths given in the mounts.conf files + // mountFile will have a value if the hidden --default-mounts-file flag is set + // Note for testing purposes only + if mountFile == "" { + mountFiles = append(mountFiles, []string{OverrideMountsFile, DefaultMountsFile}...) + if rootless.IsRootless() { + mountFiles = append([]string{UserOverrideMountsFile}, mountFiles...) + _, err := os.Stat(UserOverrideMountsFile) + if err != nil && os.IsNotExist(err) { + os.MkdirAll(filepath.Dir(UserOverrideMountsFile), 0755) + if f, err := os.Create(UserOverrideMountsFile); err != nil { + logrus.Warnf("could not create file %s: %v", UserOverrideMountsFile, err) + } else { + f.Close() + } + } + } + } else { + mountFiles = append(mountFiles, mountFile) + } + for _, file := range mountFiles { + if _, err := os.Stat(file); err == nil { + mounts, err := addSecretsFromMountsFile(file, mountLabel, containerWorkingDir, mountPrefix, uid, gid) + if err != nil { + logrus.Warnf("error mounting secrets, skipping: %v", err) + } + secretMounts = mounts + break + } + } + + // Add FIPS mode secret if /etc/system-fips exists on the host + _, err := os.Stat("/etc/system-fips") + if err == nil { + if err := addFIPSModeSecret(&secretMounts, containerWorkingDir); err != nil { + logrus.Errorf("error adding FIPS mode secret to container: %v", err) + } + } else if os.IsNotExist(err) { + logrus.Debug("/etc/system-fips does not exist on host, not mounting FIPS mode secret") + } else { + logrus.Errorf("stat /etc/system-fips failed for FIPS mode secret: %v", err) + } + return secretMounts +} + +func rchown(chowndir string, uid, gid int) error { + return filepath.Walk(chowndir, func(filePath string, f os.FileInfo, err error) error { + return os.Lchown(filePath, uid, gid) + }) +} + +// addSecretsFromMountsFile copies the contents of host directory to container directory +// and returns a list of mounts +func addSecretsFromMountsFile(filePath, mountLabel, containerWorkingDir, mountPrefix string, uid, gid int) ([]rspec.Mount, error) { + var mounts []rspec.Mount + defaultMountsPaths := getMounts(filePath) + for _, path := range defaultMountsPaths { + hostDir, ctrDir, err := getMountsMap(path) + if err != nil { + return nil, err + } + // skip if the hostDir path doesn't exist + if _, err = os.Stat(hostDir); err != nil { + if os.IsNotExist(err) { + logrus.Warnf("Path %q from %q doesn't exist, skipping", hostDir, filePath) + continue + } + return nil, errors.Wrapf(err, "failed to stat %q", hostDir) + } + + ctrDirOnHost := filepath.Join(containerWorkingDir, ctrDir) + + // In the event of a restart, don't want to copy secrets over again as they already would exist in ctrDirOnHost + _, err = os.Stat(ctrDirOnHost) + if os.IsNotExist(err) { + if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { + return nil, errors.Wrapf(err, "making container directory %q failed", ctrDirOnHost) + } + hostDir, err = resolveSymbolicLink(hostDir) + if err != nil { + return nil, err + } + + data, err := getHostSecretData(hostDir) + if err != nil { + return nil, errors.Wrapf(err, "getting host secret data failed") + } + for _, s := range data { + if err := s.saveTo(ctrDirOnHost); err != nil { + return nil, errors.Wrapf(err, "error saving data to container filesystem on host %q", ctrDirOnHost) + } + } + + err = label.Relabel(ctrDirOnHost, mountLabel, false) + if err != nil { + return nil, errors.Wrap(err, "error applying correct labels") + } + if uid != 0 || gid != 0 { + if err := rchown(ctrDirOnHost, uid, gid); err != nil { + return nil, err + } + } + } else if err != nil { + return nil, errors.Wrapf(err, "error getting status of %q", ctrDirOnHost) + } + + m := rspec.Mount{ + Source: filepath.Join(mountPrefix, ctrDir), + Destination: ctrDir, + Type: "bind", + Options: []string{"bind", "rprivate"}, + } + + mounts = append(mounts, m) + } + return mounts, nil +} + +// addFIPSModeSecret creates /run/secrets/system-fips in the container +// root filesystem if /etc/system-fips exists on hosts. +// This enables the container to be FIPS compliant and run openssl in +// FIPS mode as the host is also in FIPS mode. +func addFIPSModeSecret(mounts *[]rspec.Mount, containerWorkingDir string) error { + secretsDir := "/run/secrets" + ctrDirOnHost := filepath.Join(containerWorkingDir, secretsDir) + if _, err := os.Stat(ctrDirOnHost); os.IsNotExist(err) { + if err = os.MkdirAll(ctrDirOnHost, 0755); err != nil { + return errors.Wrapf(err, "making container directory on host failed") + } + } + fipsFile := filepath.Join(ctrDirOnHost, "system-fips") + // In the event of restart, it is possible for the FIPS mode file to already exist + if _, err := os.Stat(fipsFile); os.IsNotExist(err) { + file, err := os.Create(fipsFile) + if err != nil { + return errors.Wrapf(err, "error creating system-fips file in container for FIPS mode") + } + defer file.Close() + } + + if !mountExists(*mounts, secretsDir) { + m := rspec.Mount{ + Source: ctrDirOnHost, + Destination: secretsDir, + Type: "bind", + Options: []string{"bind", "rprivate"}, + } + *mounts = append(*mounts, m) + } + + return nil +} + +// mountExists checks if a mount already exists in the spec +func mountExists(mounts []rspec.Mount, dest string) bool { + for _, mount := range mounts { + if mount.Destination == dest { + return true + } + } + return false +} + +// resolveSymbolicLink resolves a possbile symlink path. If the path is a symlink, returns resolved +// path; if not, returns the original path. +func resolveSymbolicLink(path string) (string, error) { + info, err := os.Lstat(path) + if err != nil { + return "", err + } + if info.Mode()&os.ModeSymlink != os.ModeSymlink { + return path, nil + } + return filepath.EvalSymlinks(path) +} diff --git a/vendor/github.com/containers/buildah/pull.go b/vendor/github.com/containers/buildah/pull.go index d1f33fb01..363cf5ce2 100644 --- a/vendor/github.com/containers/buildah/pull.go +++ b/vendor/github.com/containers/buildah/pull.go @@ -194,12 +194,12 @@ func Pull(ctx context.Context, imageName string, options PullOptions) error { errs = multierror.Append(errs, err) continue } - img, err := is.Transport.GetStoreImage(options.Store, ref) + taggedImg, err := is.Transport.GetStoreImage(options.Store, ref) if err != nil { errs = multierror.Append(errs, err) continue } - fmt.Printf("%s\n", img.ID) + fmt.Printf("%s\n", taggedImg.ID) } } else { fmt.Printf("%s\n", img.ID) diff --git a/vendor/github.com/containers/buildah/run.go b/vendor/github.com/containers/buildah/run.go index 3a248f4f2..4d6d28380 100644 --- a/vendor/github.com/containers/buildah/run.go +++ b/vendor/github.com/containers/buildah/run.go @@ -21,15 +21,15 @@ import ( "github.com/containernetworking/cni/libcni" "github.com/containers/buildah/bind" "github.com/containers/buildah/chroot" + "github.com/containers/buildah/pkg/secrets" "github.com/containers/buildah/util" - "github.com/containers/libpod/pkg/secrets" "github.com/containers/storage/pkg/idtools" "github.com/containers/storage/pkg/ioutils" "github.com/containers/storage/pkg/reexec" "github.com/containers/storage/pkg/stringid" units "github.com/docker/go-units" digest "github.com/opencontainers/go-digest" - "github.com/opencontainers/runtime-spec/specs-go" + specs "github.com/opencontainers/runtime-spec/specs-go" "github.com/opencontainers/runtime-tools/generate" "github.com/opencontainers/selinux/go-selinux/label" "github.com/pkg/errors" @@ -840,7 +840,7 @@ func setupNamespaces(g *generate.Generator, namespaceOptions NamespaceOptions, i // valid resolution. func runLookupPath(g *generate.Generator, command []string) []string { // Look for the configured $PATH. - spec := g.Spec() + spec := g.Config envPath := "" for i := range spec.Process.Env { if strings.HasPrefix(spec.Process.Env[i], "PATH=") { @@ -953,7 +953,7 @@ func (b *Builder) configureNamespaces(g *generate.Generator, options RunOptions) } found := false - spec := g.Spec() + spec := g.Config for i := range spec.Process.Env { if strings.HasPrefix(spec.Process.Env[i], "HOSTNAME=") { found = true @@ -1054,7 +1054,7 @@ func (b *Builder) Run(command []string, options RunOptions) error { // Now grab the spec from the generator. Set the generator to nil so that future contributors // will quickly be able to tell that they're supposed to be modifying the spec directly from here. - spec := g.Spec() + spec := g.Config g = nil logrus.Debugf("ensuring working directory %q exists", filepath.Join(mountPoint, spec.Process.Cwd)) diff --git a/vendor/github.com/containers/buildah/unshare/unshare_unsupported.go b/vendor/github.com/containers/buildah/unshare/unshare_unsupported.go deleted file mode 100644 index feeceae66..000000000 --- a/vendor/github.com/containers/buildah/unshare/unshare_unsupported.go +++ /dev/null @@ -1 +0,0 @@ -package unshare diff --git a/vendor/github.com/containers/buildah/vendor.conf b/vendor/github.com/containers/buildah/vendor.conf index 7438fc909..27bf45541 100644 --- a/vendor/github.com/containers/buildah/vendor.conf +++ b/vendor/github.com/containers/buildah/vendor.conf @@ -3,7 +3,7 @@ github.com/blang/semver v3.5.0 github.com/BurntSushi/toml v0.2.0 github.com/containerd/continuity 004b46473808b3e7a4a3049c20e4376c91eb966d github.com/containernetworking/cni v0.7.0-alpha1 -github.com/containers/image v1.4 +github.com/containers/image v1.5 github.com/vbauerster/mpb v3.3.4 github.com/mattn/go-isatty v0.0.4 github.com/VividCortex/ewma v1.1.1 diff --git a/vendor/github.com/containers/image/pkg/blobinfocache/memory.go b/vendor/github.com/containers/image/pkg/blobinfocache/memory.go index 1ce7dee13..cf6ca5263 100644 --- a/vendor/github.com/containers/image/pkg/blobinfocache/memory.go +++ b/vendor/github.com/containers/image/pkg/blobinfocache/memory.go @@ -1,6 +1,7 @@ package blobinfocache import ( + "sync" "time" "github.com/containers/image/types" @@ -17,6 +18,7 @@ type locationKey struct { // memoryCache implements an in-memory-only BlobInfoCache type memoryCache struct { + mutex *sync.Mutex // synchronizes concurrent accesses uncompressedDigests map[digest.Digest]digest.Digest digestsByUncompressed map[digest.Digest]map[digest.Digest]struct{} // stores a set of digests for each uncompressed digest knownLocations map[locationKey]map[types.BICLocationReference]time.Time // stores last known existence time for each location reference @@ -28,6 +30,7 @@ type memoryCache struct { // Manual users of types.{ImageSource,ImageDestination} might also use this instead of a persistent cache. func NewMemoryCache() types.BlobInfoCache { return &memoryCache{ + mutex: new(sync.Mutex), uncompressedDigests: map[digest.Digest]digest.Digest{}, digestsByUncompressed: map[digest.Digest]map[digest.Digest]struct{}{}, knownLocations: map[locationKey]map[types.BICLocationReference]time.Time{}, @@ -38,6 +41,15 @@ func NewMemoryCache() types.BlobInfoCache { // May return anyDigest if it is known to be uncompressed. // Returns "" if nothing is known about the digest (it may be compressed or uncompressed). func (mem *memoryCache) UncompressedDigest(anyDigest digest.Digest) digest.Digest { + mem.mutex.Lock() + defer mem.mutex.Unlock() + return mem.uncompressedDigest(anyDigest) +} + +// uncompressedDigest returns an uncompressed digest corresponding to anyDigest. +// May return anyDigest if it is known to be uncompressed. +// Returns "" if nothing is known about the digest (it may be compressed or uncompressed). +func (mem *memoryCache) uncompressedDigest(anyDigest digest.Digest) digest.Digest { if d, ok := mem.uncompressedDigests[anyDigest]; ok { return d } @@ -56,6 +68,8 @@ func (mem *memoryCache) UncompressedDigest(anyDigest digest.Digest) digest.Diges // because a manifest/config pair exists); otherwise the cache could be poisoned and allow substituting unexpected blobs. // (Eventually, the DiffIDs in image config could detect the substitution, but that may be too late, and not all image formats contain that data.) func (mem *memoryCache) RecordDigestUncompressedPair(anyDigest digest.Digest, uncompressed digest.Digest) { + mem.mutex.Lock() + defer mem.mutex.Unlock() if previous, ok := mem.uncompressedDigests[anyDigest]; ok && previous != uncompressed { logrus.Warnf("Uncompressed digest for blob %s previously recorded as %s, now %s", anyDigest, previous, uncompressed) } @@ -72,6 +86,8 @@ func (mem *memoryCache) RecordDigestUncompressedPair(anyDigest digest.Digest, un // RecordKnownLocation records that a blob with the specified digest exists within the specified (transport, scope) scope, // and can be reused given the opaque location data. func (mem *memoryCache) RecordKnownLocation(transport types.ImageTransport, scope types.BICTransportScope, blobDigest digest.Digest, location types.BICLocationReference) { + mem.mutex.Lock() + defer mem.mutex.Unlock() key := locationKey{transport: transport.Name(), scope: scope, blobDigest: blobDigest} locationScope, ok := mem.knownLocations[key] if !ok { @@ -103,11 +119,13 @@ func (mem *memoryCache) appendReplacementCandidates(candidates []candidateWithTi // data from previous RecordDigestUncompressedPair calls is used to also look up variants of the blob which have the same // uncompressed digest. func (mem *memoryCache) CandidateLocations(transport types.ImageTransport, scope types.BICTransportScope, primaryDigest digest.Digest, canSubstitute bool) []types.BICReplacementCandidate { + mem.mutex.Lock() + defer mem.mutex.Unlock() res := []candidateWithTime{} res = mem.appendReplacementCandidates(res, transport, scope, primaryDigest) var uncompressedDigest digest.Digest // = "" if canSubstitute { - if uncompressedDigest = mem.UncompressedDigest(primaryDigest); uncompressedDigest != "" { + if uncompressedDigest = mem.uncompressedDigest(primaryDigest); uncompressedDigest != "" { otherDigests := mem.digestsByUncompressed[uncompressedDigest] // nil if not present in the map for d := range otherDigests { if d != primaryDigest && d != uncompressedDigest { diff --git a/vendor/github.com/containers/image/version/version.go b/vendor/github.com/containers/image/version/version.go index 10075992d..2a3bc1b5c 100644 --- a/vendor/github.com/containers/image/version/version.go +++ b/vendor/github.com/containers/image/version/version.go @@ -11,7 +11,7 @@ const ( VersionPatch = 5 // VersionDev indicates development branch. Releases will be empty string. - VersionDev = "-dev" + VersionDev = "" ) // Version is the specification version that the package types support. |