diff options
43 files changed, 1162 insertions, 583 deletions
diff --git a/cmd/podman/cliconfig/config.go b/cmd/podman/cliconfig/config.go index 982c77c17..640a4bff4 100644 --- a/cmd/podman/cliconfig/config.go +++ b/cmd/podman/cliconfig/config.go @@ -159,6 +159,11 @@ type PruneContainersValues struct { Force bool } +type PodPruneValues struct { + PodmanCommand + Force bool +} + type ImportValues struct { PodmanCommand Change []string diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 0e5deb627..c36452cfe 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -17,15 +17,12 @@ func getMainCommands() []*cobra.Command { _loginCommand, _logoutCommand, _mountCommand, - _pauseCommand, _portCommand, _refreshCommand, _restartCommand, _searchCommand, - _startCommand, _statsCommand, _topCommand, - _unpauseCommand, } if len(_varlinkCommand.Use) > 0 { @@ -50,19 +47,16 @@ func getContainerSubCommands() []*cobra.Command { _commitCommand, _execCommand, _mountCommand, - _pauseCommand, _portCommand, _pruneContainersCommand, _refreshCommand, _restartCommand, _restoreCommand, _runlabelCommand, - _startCommand, _statsCommand, _stopCommand, _topCommand, _umountCommand, - _unpauseCommand, } } diff --git a/cmd/podman/commit.go b/cmd/podman/commit.go index 5963f8686..8d79c1e28 100644 --- a/cmd/podman/commit.go +++ b/cmd/podman/commit.go @@ -42,7 +42,7 @@ func init() { commitCommand.SetHelpTemplate(HelpTemplate()) commitCommand.SetUsageTemplate(UsageTemplate()) flags := commitCommand.Flags() - flags.StringSliceVarP(&commitCommand.Change, "change", "c", []string{}, fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | "))) + flags.StringArrayVarP(&commitCommand.Change, "change", "c", []string{}, fmt.Sprintf("Apply the following possible instructions to the created image (default []): %s", strings.Join(libpod.ChangeCmds, " | "))) flags.StringVarP(&commitCommand.Format, "format", "f", "oci", "`Format` of the image manifest and metadata") flags.StringVarP(&commitCommand.Message, "message", "m", "", "Set commit message for imported image") flags.StringVarP(&commitCommand.Author, "author", "a", "", "Set the author for the image committed") @@ -83,6 +83,9 @@ func commitCmd(c *cliconfig.CommitValues) error { if c.Flag("change").Changed { for _, change := range c.Change { splitChange := strings.Split(strings.ToUpper(change), "=") + if len(splitChange) == 1 { + splitChange = strings.Split(strings.ToUpper(change), " ") + } if !util.StringInSlice(splitChange[0], libpod.ChangeCmds) { return errors.Errorf("invalid syntax for --change: %s", change) } diff --git a/cmd/podman/container.go b/cmd/podman/container.go index 39c4f0c5d..7733c8eef 100644 --- a/cmd/podman/container.go +++ b/cmd/podman/container.go @@ -59,8 +59,11 @@ var ( _killCommand, _listSubCommand, _logsCommand, + _pauseCommand, _runCommand, _rmCommand, + _startCommand, + _unpauseCommand, _waitCommand, } ) diff --git a/cmd/podman/main.go b/cmd/podman/main.go index e8c3e14ea..15f4a5d71 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -45,6 +45,7 @@ var mainCommands = []*cobra.Command{ _killCommand, _loadCommand, _logsCommand, + _pauseCommand, podCommand.Command, &_psCommand, _pullCommand, @@ -56,9 +57,11 @@ var mainCommands = []*cobra.Command{ _stopCommand, _tagCommand, _umountCommand, + _unpauseCommand, _versionCommand, _waitCommand, imageCommand.Command, + _startCommand, systemCommand.Command, } diff --git a/cmd/podman/pause.go b/cmd/podman/pause.go index 3e6d36571..ca137150a 100644 --- a/cmd/podman/pause.go +++ b/cmd/podman/pause.go @@ -4,11 +4,9 @@ import ( "os" "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" ) @@ -41,15 +39,11 @@ func init() { } func pauseCmd(c *cliconfig.PauseValues) error { - var ( - pauseContainers []*libpod.Container - pauseFuncs []shared.ParallelWorkerInput - ) if os.Geteuid() != 0 { return errors.New("pause is not supported for rootless containers") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -59,41 +53,19 @@ func pauseCmd(c *cliconfig.PauseValues) error { if len(args) < 1 && !c.All { return errors.Errorf("you must provide at least one container name or id") } - if c.All { - containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") - if err != nil { - return err - } - pauseContainers = append(pauseContainers, containers...) - } else { - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) - if err != nil { - return err + ok, failures, err := runtime.PauseContainers(getContext(), c) + if err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + if len(c.InputArgs) > 1 { + exitCode = 125 + } else { + exitCode = 1 } - pauseContainers = append(pauseContainers, ctr) - } - } - - // Now assemble the slice of pauseFuncs - for _, ctr := range pauseContainers { - con := ctr - - f := func() error { - return con.Pause() } - pauseFuncs = append(pauseFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) + return err } - - maxWorkers := shared.Parallelize("pause") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks + if len(failures) > 0 { + exitCode = 125 } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - pauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, pauseFuncs) - return printParallelOutput(pauseErrors, errCount) + return printCmdResults(ok, failures) } diff --git a/cmd/podman/pod.go b/cmd/podman/pod.go index 2d9bca21d..ed331965e 100644 --- a/cmd/podman/pod.go +++ b/cmd/podman/pod.go @@ -24,6 +24,7 @@ var podSubCommands = []*cobra.Command{ _podInspectCommand, _podKillCommand, _podPauseCommand, + _prunePodsCommand, _podPsCommand, _podRestartCommand, _podRmCommand, diff --git a/cmd/podman/pods_prune.go b/cmd/podman/pods_prune.go new file mode 100644 index 000000000..e6946f068 --- /dev/null +++ b/cmd/podman/pods_prune.go @@ -0,0 +1,47 @@ +package main + +import ( + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/pkg/adapter" + "github.com/pkg/errors" + "github.com/spf13/cobra" +) + +var ( + podPruneCommand cliconfig.PodPruneValues + podPruneDescription = ` + podman pod prune + + Removes all exited pods +` + _prunePodsCommand = &cobra.Command{ + Use: "prune", + Args: noSubArgs, + Short: "Remove all stopped pods", + Long: podPruneDescription, + RunE: func(cmd *cobra.Command, args []string) error { + podPruneCommand.InputArgs = args + podPruneCommand.GlobalFlags = MainGlobalOpts + return podPruneCmd(&podPruneCommand) + }, + } +) + +func init() { + podPruneCommand.Command = _prunePodsCommand + podPruneCommand.SetHelpTemplate(HelpTemplate()) + podPruneCommand.SetUsageTemplate(UsageTemplate()) + flags := podPruneCommand.Flags() + flags.BoolVarP(&podPruneCommand.Force, "force", "f", false, "Force removal of a running pods. The default is false") +} + +func podPruneCmd(c *cliconfig.PodPruneValues) error { + runtime, err := adapter.GetRuntime(&c.PodmanCommand) + if err != nil { + return errors.Wrapf(err, "could not get runtime") + } + defer runtime.Shutdown(false) + + ok, failures, err := runtime.PrunePods(getContext(), c) + return printCmdResults(ok, failures) +} diff --git a/cmd/podman/ps.go b/cmd/podman/ps.go index a9e46d6b9..df1ea2765 100644 --- a/cmd/podman/ps.go +++ b/cmd/podman/ps.go @@ -17,7 +17,6 @@ import ( "github.com/containers/libpod/pkg/adapter" "github.com/cri-o/ocicni/pkg/ocicni" "github.com/docker/go-units" - "github.com/opentracing/opentracing-go" "github.com/pkg/errors" "github.com/spf13/cobra" "k8s.io/apimachinery/pkg/fields" @@ -198,11 +197,6 @@ func init() { } func psCmd(c *cliconfig.PsValues) error { - if c.Bool("trace") { - span, _ := opentracing.StartSpanFromContext(Ctx, "psCmd") - defer span.Finish() - } - var watch bool if c.Watch > 0 { diff --git a/cmd/podman/shared/intermediate_varlink.go b/cmd/podman/shared/intermediate_varlink.go index 95a0d6287..d62a65955 100644 --- a/cmd/podman/shared/intermediate_varlink.go +++ b/cmd/podman/shared/intermediate_varlink.go @@ -4,8 +4,10 @@ package shared import ( "fmt" + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/varlink" + "github.com/containers/libpod/pkg/rootless" "github.com/pkg/errors" ) @@ -318,6 +320,12 @@ func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { // We do not get a default network over varlink. Unlike the other default values for some cli // elements, it seems it gets set to the default anyway. + var memSwapDefault int64 = -1 + netModeDefault := "bridge" + if rootless.IsRootless() { + netModeDefault = "slirp4netns" + } + m := make(map[string]GenericCLIResult) m["add-host"] = stringSliceFromVarlink(opts.AddHost, "add-host", nil) m["annotation"] = stringSliceFromVarlink(opts.Annotation, "annotation", nil) @@ -374,10 +382,10 @@ func VarlinkCreateToGeneric(opts iopodman.Create) GenericCLIResults { m["memory"] = stringFromVarlink(opts.Memory, "memory", nil) m["memory-reservation"] = stringFromVarlink(opts.MemoryReservation, "memory-reservation", nil) m["memory-swap"] = stringFromVarlink(opts.MemorySwap, "memory-swap", nil) - m["memory-swappiness"] = int64FromVarlink(opts.MemorySwappiness, "memory-swappiness", nil) + m["memory-swappiness"] = int64FromVarlink(opts.MemorySwappiness, "memory-swappiness", &memSwapDefault) m["name"] = stringFromVarlink(opts.Name, "name", nil) - m["net"] = stringFromVarlink(opts.Net, "net", nil) - m["network"] = stringFromVarlink(opts.Network, "network", nil) + m["net"] = stringFromVarlink(opts.Net, "net", &netModeDefault) + m["network"] = stringFromVarlink(opts.Network, "network", &netModeDefault) m["no-hosts"] = boolFromVarlink(opts.NoHosts, "no-hosts", false) m["oom-kill-disable"] = boolFromVarlink(opts.OomKillDisable, "oon-kill-disable", false) m["oom-score-adj"] = intFromVarlink(opts.OomScoreAdj, "oom-score-adj", nil) diff --git a/cmd/podman/shared/pod.go b/cmd/podman/shared/pod.go index 4d936d61c..3f4cb0312 100644 --- a/cmd/podman/shared/pod.go +++ b/cmd/podman/shared/pod.go @@ -10,12 +10,12 @@ import ( ) const ( - stopped = "Stopped" - running = "Running" - paused = "Paused" - exited = "Exited" - errored = "Error" - created = "Created" + PodStateStopped = "Stopped" + PodStateRunning = "Running" + PodStatePaused = "Paused" + PodStateExited = "Exited" + PodStateErrored = "Error" + PodStateCreated = "Created" ) // GetPodStatus determines the status of the pod based on the @@ -24,7 +24,7 @@ const ( func GetPodStatus(pod *libpod.Pod) (string, error) { ctrStatuses, err := pod.Status() if err != nil { - return errored, err + return PodStateErrored, err } return CreatePodStatusResults(ctrStatuses) } @@ -32,44 +32,44 @@ func GetPodStatus(pod *libpod.Pod) (string, error) { func CreatePodStatusResults(ctrStatuses map[string]libpod.ContainerStatus) (string, error) { ctrNum := len(ctrStatuses) if ctrNum == 0 { - return created, nil + return PodStateCreated, nil } statuses := map[string]int{ - stopped: 0, - running: 0, - paused: 0, - created: 0, - errored: 0, + PodStateStopped: 0, + PodStateRunning: 0, + PodStatePaused: 0, + PodStateCreated: 0, + PodStateErrored: 0, } for _, ctrStatus := range ctrStatuses { switch ctrStatus { case libpod.ContainerStateExited: fallthrough case libpod.ContainerStateStopped: - statuses[stopped]++ + statuses[PodStateStopped]++ case libpod.ContainerStateRunning: - statuses[running]++ + statuses[PodStateRunning]++ case libpod.ContainerStatePaused: - statuses[paused]++ + statuses[PodStatePaused]++ case libpod.ContainerStateCreated, libpod.ContainerStateConfigured: - statuses[created]++ + statuses[PodStateCreated]++ default: - statuses[errored]++ + statuses[PodStateErrored]++ } } - if statuses[running] > 0 { - return running, nil - } else if statuses[paused] == ctrNum { - return paused, nil - } else if statuses[stopped] == ctrNum { - return exited, nil - } else if statuses[stopped] > 0 { - return stopped, nil - } else if statuses[errored] > 0 { - return errored, nil + if statuses[PodStateRunning] > 0 { + return PodStateRunning, nil + } else if statuses[PodStatePaused] == ctrNum { + return PodStatePaused, nil + } else if statuses[PodStateStopped] == ctrNum { + return PodStateExited, nil + } else if statuses[PodStateStopped] > 0 { + return PodStateStopped, nil + } else if statuses[PodStateErrored] > 0 { + return PodStateErrored, nil } - return created, nil + return PodStateCreated, nil } // GetNamespaceOptions transforms a slice of kernel namespaces diff --git a/cmd/podman/start.go b/cmd/podman/start.go index ec05ce90e..9f93061f9 100644 --- a/cmd/podman/start.go +++ b/cmd/podman/start.go @@ -1,16 +1,11 @@ package main import ( - "fmt" - "os" - "github.com/containers/libpod/cmd/podman/cliconfig" - "github.com/containers/libpod/cmd/podman/libpodruntime" "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/adapter" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -48,7 +43,7 @@ func init() { } func startCmd(c *cliconfig.StartValues) error { - if c.Bool("trace") { + if !remoteclient && c.Bool("trace") { span, _ := opentracing.StartSpanFromContext(Ctx, "startCmd") defer span.Finish() } @@ -70,100 +65,11 @@ func startCmd(c *cliconfig.StartValues) error { return errors.Wrapf(libpod.ErrInvalidArg, "you cannot use sig-proxy without --attach") } - 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) - if c.Latest { - lastCtr, err := runtime.GetLatestContainer() - if err != nil { - return errors.Wrapf(err, "unable to get latest container") - } - args = append(args, lastCtr.ID()) - } - - ctx := getContext() - - var lastError error - for _, container := range args { - ctr, err := runtime.LookupContainer(container) - if err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to find container %s", container) - continue - } - - ctrState, err := ctr.State() - if err != nil { - return errors.Wrapf(err, "unable to get container state") - } - - ctrRunning := ctrState == libpod.ContainerStateRunning - - if attach { - inputStream := os.Stdin - if !c.Interactive { - inputStream = nil - } - - // attach to the container and also start it not already running - // If the container is in a pod, also set to recursively start dependencies - err = adapter.StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "") - if errors.Cause(err) == libpod.ErrDetach { - // User manually detached - // Exit cleanly immediately - exitCode = 0 - return nil - } - - if ctrRunning { - return err - } - - if err != nil { - return errors.Wrapf(err, "unable to start container %s", ctr.ID()) - } - - if ecode, err := ctr.Wait(); err != nil { - if errors.Cause(err) == libpod.ErrNoSuchCtr { - // The container may have been removed - // Go looking for an exit file - rtc, err := runtime.GetConfig() - if err != nil { - return err - } - ctrExitCode, err := adapter.ReadExitFile(rtc.TmpDir, ctr.ID()) - if err != nil { - logrus.Errorf("Cannot get exit code: %v", err) - exitCode = 127 - } else { - exitCode = ctrExitCode - } - } - } else { - exitCode = int(ecode) - } - - return nil - } - if ctrRunning { - fmt.Println(ctr.ID()) - continue - } - // Handle non-attach start - // If the container is in a pod, also set to recursively start dependencies - if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { - if lastError != nil { - fmt.Fprintln(os.Stderr, lastError) - } - lastError = errors.Wrapf(err, "unable to start container %q", container) - continue - } - fmt.Println(container) - } - - return lastError + exitCode, err = runtime.Start(getContext(), c, sigProxy) + return err } diff --git a/cmd/podman/system_prune.go b/cmd/podman/system_prune.go index 436d54823..8900e2644 100644 --- a/cmd/podman/system_prune.go +++ b/cmd/podman/system_prune.go @@ -59,6 +59,7 @@ func pruneSystemCmd(c *cliconfig.SystemPruneValues) error { fmt.Printf(` WARNING! This will remove: - all stopped containers%s + - all stopped pods - all dangling images - all build cache Are you sure you want to continue? [y/N] `, volumeString) @@ -77,9 +78,25 @@ Are you sure you want to continue? [y/N] `, volumeString) } defer runtime.Shutdown(false) + rmWorkers := shared.Parallelize("rm") ctx := getContext() fmt.Println("Deleted Containers") - lasterr := pruneContainers(runtime, ctx, shared.Parallelize("rm"), false, false) + lasterr := pruneContainers(runtime, ctx, rmWorkers, false, false) + + fmt.Println("Deleted Pods") + pruneValues := cliconfig.PodPruneValues{ + PodmanCommand: c.PodmanCommand, + Force: c.Force, + } + ok, failures, err := runtime.PrunePods(ctx, &pruneValues) + if err != nil { + if lasterr != nil { + logrus.Errorf("%q", lasterr) + } + lasterr = err + } + printCmdResults(ok, failures) + if c.Bool("volumes") { fmt.Println("Deleted Volumes") err := volumePrune(runtime, getContext()) diff --git a/cmd/podman/unpause.go b/cmd/podman/unpause.go index 65e841b36..fa946bfd7 100644 --- a/cmd/podman/unpause.go +++ b/cmd/podman/unpause.go @@ -4,11 +4,9 @@ import ( "os" "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" ) @@ -40,15 +38,11 @@ func init() { } func unpauseCmd(c *cliconfig.UnpauseValues) error { - var ( - unpauseContainers []*libpod.Container - unpauseFuncs []shared.ParallelWorkerInput - ) if os.Geteuid() != 0 { return errors.New("unpause is not supported for rootless containers") } - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } @@ -58,41 +52,19 @@ func unpauseCmd(c *cliconfig.UnpauseValues) error { if len(args) < 1 && !c.All { return errors.Errorf("you must provide at least one container name or id") } - if c.All { - cs, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStatePaused, "paused") - if err != nil { - return err - } - unpauseContainers = append(unpauseContainers, cs...) - } else { - for _, arg := range args { - ctr, err := runtime.LookupContainer(arg) - if err != nil { - return err + ok, failures, err := runtime.UnpauseContainers(getContext(), c) + if err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + if len(c.InputArgs) > 1 { + exitCode = 125 + } else { + exitCode = 1 } - unpauseContainers = append(unpauseContainers, ctr) - } - } - - // Assemble the unpause funcs - for _, ctr := range unpauseContainers { - con := ctr - f := func() error { - return con.Unpause() } - - unpauseFuncs = append(unpauseFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) + return err } - - maxWorkers := shared.Parallelize("unpause") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks + if len(failures) > 0 { + exitCode = 125 } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - unpauseErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, unpauseFuncs) - return printParallelOutput(unpauseErrors, errCount) + return printCmdResults(ok, failures) } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index b5295273a..1fde72164 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -522,6 +522,8 @@ method ListContainers() -> (containers: []Container) method Ps(opts: PsOpts) -> (containers: []PsContainer) +method GetContainersByStatus(status: []string) -> (containerS: []Container) + # GetContainer returns information about a single container. If a container # with the given id doesn't exist, a [ContainerNotFound](#ContainerNotFound) # error will be returned. See also [ListContainers](ListContainers) and @@ -1053,6 +1055,9 @@ method TopPod(pod: string, latest: bool, descriptors: []string) -> (stats: []str # ~~~ method GetPodStats(name: string) -> (pod: string, containers: []ContainerStats) +# GetPodsByStatus searches for pods whose status is included in statuses +method GetPodsByStatus(statuses: []string) -> (pods: []string) + # ImageExists talks a full or partial image ID or name and returns an int as to whether # the image exists in local storage. An int result of 0 means the image does exist in # local storage; whereas 1 indicates the image does not exists in local storage. diff --git a/completions/bash/podman b/completions/bash/podman index 3616c6ca1..dce23df2b 100644 --- a/completions/bash/podman +++ b/completions/bash/podman @@ -2740,6 +2740,22 @@ _podman_pod_ps() { __podman_pod_ps } +_podman_pod_prune() { + local options_with_args=" + " + + local boolean_options=" + -f + -h + --help + " + case "$cur" in + -*) + COMPREPLY=($(compgen -W "$boolean_options $options_with_args" -- "$cur")) + ;; + esac +} + _podman_pod_restart() { local options_with_args=" " diff --git a/contrib/cirrus/container_test.sh b/contrib/cirrus/container_test.sh index e6c1a3a47..1fd9551db 100644 --- a/contrib/cirrus/container_test.sh +++ b/contrib/cirrus/container_test.sh @@ -32,6 +32,7 @@ integrationtest=0 unittest=0 validate=0 options=0 +noremote=0 install_tools_made=0 while getopts "biptuv" opt; do @@ -45,6 +46,9 @@ while getopts "biptuv" opt; do t) integrationtest=1 options=1 ;; + n) noremote=1 + options=1 + ;; u) unittest=1 options=1 ;; @@ -127,5 +131,7 @@ if [ $integrationtest -eq 1 ]; then make TAGS="${TAGS}" test-binaries make varlink_generate make ginkgo $INTEGRATION_TEST_ENVS - make ginkgo-remote $INTEGRATION_TEST_ENVS + if [ $noremote -eq 0 ]; then + make ginkgo-remote $INTEGRATION_TEST_ENVS + fi fi diff --git a/contrib/cirrus/integration_test.sh b/contrib/cirrus/integration_test.sh index 8a2507f38..95387ff49 100755 --- a/contrib/cirrus/integration_test.sh +++ b/contrib/cirrus/integration_test.sh @@ -34,7 +34,7 @@ then -e "CONMON_BINARY=/usr/libexec/podman/conmon" \ -e "DIST=$OS_RELEASE_ID" \ -e "CONTAINER_RUNTIME=$CONTAINER_RUNTIME" \ - ${OS_RELEASE_ID}podmanbuild bash $GOSRC/$SCRIPT_BASE/container_test.sh -b -i -t + ${OS_RELEASE_ID}podmanbuild bash $GOSRC/$SCRIPT_BASE/container_test.sh -b -i -t -n exit $? elif [[ "$SPECIALMODE" == "rootless" ]] diff --git a/contrib/spec/podman.spec.in b/contrib/spec/podman.spec.in index 396304f8d..4b9cbd4cf 100644 --- a/contrib/spec/podman.spec.in +++ b/contrib/spec/podman.spec.in @@ -66,6 +66,7 @@ BuildRequires: libselinux-devel BuildRequires: ostree-devel BuildRequires: pkgconfig BuildRequires: make +BuildRequires: systemd-devel Requires: runc Requires: skopeo-containers Requires: containernetworking-plugins >= 0.6.0-3 diff --git a/docs/podman-container-prune.1.md b/docs/podman-container-prune.1.md index 194dd3dae..6fd741a3d 100644 --- a/docs/podman-container-prune.1.md +++ b/docs/podman-container-prune.1.md @@ -1,4 +1,4 @@ -% PODMAN(1) Podman Man Pages +% podman-container-prune (1) % Brent Baude % December 2018 # NAME diff --git a/docs/podman-pod-prune.1.md b/docs/podman-pod-prune.1.md new file mode 100644 index 000000000..121198de7 --- /dev/null +++ b/docs/podman-pod-prune.1.md @@ -0,0 +1,29 @@ +% % podman-pod-prune (1) +% Peter Hunt +% April 2019 +# NAME +podman-pod-prune - Remove all stopped pods + +# SYNOPSIS +**podman pod prune** [*-h*|*--help*] + +# DESCRIPTION +**podman pod prune** removes all stopped pods from local storage. + +## Examples ## + +Remove all stopped pods from local storage +``` +$ sudo podman pod prune +22b8813332948064b6566370088c5e0230eeaf15a58b1c5646859fd9fc364fe7 +2afb26869fe5beab979c234afb75c7506063cd4655b1a73557c9d583ff1aebe9 +49161ad2a722cf18722f0e17199a9e840703a17d1158cdeda502b6d54080f674 +5ca429f37fb83a9f54eea89e3a9102b7780a6e6ae5f132db0672da551d862c4a +6bb06573787efb8b0675bc88ebf8361f1a56d3ac7922d1a6436d8f59ffd955f1 +``` + +## SEE ALSO +podman-pod(1), podman-pod-ps(1), podman-pod-rm(1) + +# HISTORY +April 2019, Originally compiled by Peter Hunt (pehunt at redhat dot com) diff --git a/docs/podman-pod.1.md b/docs/podman-pod.1.md index 1846d0411..d11614358 100644 --- a/docs/podman-pod.1.md +++ b/docs/podman-pod.1.md @@ -11,21 +11,22 @@ podman pod is a set of subcommands that manage pods, or groups of containers. ## SUBCOMMANDS -| Command | Man Page | Description | -| ------- | ------------------------------------------------- | ------------------------------------------------------------------------------ | -| create | [podman-pod-create(1)](podman-pod-create.1.md) | Create a new pod. | -| exists | [podman-pod-exists(1)](podman-pod-exists.1.md) | Check if a pod exists in local storage. | -| inspect | [podman-pod-inspect(1)](podman-pod-inspect.1.md) | Displays information describing a pod. | -| kill | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. | -| pause | [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. | -| ps | [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. | -| restart | [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or more pods. | -| rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more pods. | -| start | [podman-pod-start(1)](podman-pod-start.1.md) | Start one or more pods. | -| stats | [podman-pod-stats(1)](podman-pod-stats.1.md) | Display live stream resource usage stats for containers in one or more pods. | -| stop | [podman-pod-stop(1)](podman-pod-stop.1.md) | Stop one or more pods. | -| top | [podman-pod-top(1)](podman-pod-top.1.md) | Display the running processes of containers in a pod. | -| unpause | [podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. | +| Command | Man Page | Description | +| ------- | -------------------------------------------------------- | ------------------------------------------------------------------------------ | +| create | [podman-pod-create(1)](podman-pod-create.1.md) | Create a new pod. | +| exists | [podman-pod-exists(1)](podman-pod-exists.1.md) | Check if a pod exists in local storage. | +| inspect | [podman-pod-inspect(1)](podman-pod-inspect.1.md) | Displays information describing a pod. | +| kill | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in pod. | +| pause | [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. | +| prune | [podman-container-prune(1)](podman-container-prune.1.md) | Remove all stopped containers from local storage. | +| ps | [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. | +| restart | [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or more pods. | +| rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more pods. | +| start | [podman-pod-start(1)](podman-pod-start.1.md) | Start one or more pods. | +| stats | [podman-pod-stats(1)](podman-pod-stats.1.md) | Display live stream resource usage stats for containers in one or more pods. | +| stop | [podman-pod-stop(1)](podman-pod-stop.1.md) | Stop one or more pods. | +| top | [podman-pod-top(1)](podman-pod-top.1.md) | Display the running processes of containers in a pod. | +| unpause | [podman-pod-unpause(1)](podman-pod-unpause.1.md) | Unpause one or more pods. | ## SEE ALSO podman(1) diff --git a/docs/podman-system-prune.1.md b/docs/podman-system-prune.1.md index 1cdafb774..6a284a110 100644 --- a/docs/podman-system-prune.1.md +++ b/docs/podman-system-prune.1.md @@ -11,7 +11,7 @@ podman\-system\-prune - Remove all unused container, image and volume data [**-volumes**|**--v**] ## DESCRIPTION -**podman system prune** removes all unused containers, (both dangling and unreferenced) from local storage and optionally, volumes. +**podman system prune** removes all unused containers (both dangling and unreferenced), pods and optionally, volumes from local storage. With the `all` option, you can delete all unused images. Unused images are dangling images as well as any image that does not have any containers based on it. @@ -31,7 +31,7 @@ Do not prompt for confirmation Prune volumes not used by at least one container ## SEE ALSO -podman(1), podman-image-prune(1), podman-container-prune(1), podman-volume-prune(1) +podman(1), podman-image-prune(1), podman-container-prune(1), podman-pod-prune(1), podman-volume-prune(1) # HISTORY February 2019, Originally compiled by Dan Walsh (dwalsh at redhat dot com) diff --git a/libpod/container_commit.go b/libpod/container_commit.go index 3cc4b2c92..ae04f67bb 100644 --- a/libpod/container_commit.go +++ b/libpod/container_commit.go @@ -125,23 +125,48 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai // Workdir importBuilder.SetWorkDir(c.Spec().Process.Cwd) + genCmd := func(cmd string) []string { + trim := func(cmd []string) []string { + if len(cmd) == 0 { + return cmd + } + + retCmd := []string{} + for _, c := range cmd { + if len(c) >= 2 { + if c[0] == '"' && c[len(c)-1] == '"' { + retCmd = append(retCmd, c[1:len(c)-1]) + continue + } + } + retCmd = append(retCmd, c) + } + return retCmd + } + if strings.HasPrefix(cmd, "[") { + cmd = strings.TrimPrefix(cmd, "[") + cmd = strings.TrimSuffix(cmd, "]") + return trim(strings.Split(cmd, ",")) + } + return []string{"/bin/sh", "-c", cmd} + } // Process user changes for _, change := range options.Changes { - splitChange := strings.SplitN(change, " ", 2) + splitChange := strings.SplitN(change, "=", 2) if len(splitChange) != 2 { - splitChange = strings.SplitN(change, "=", 2) + splitChange = strings.SplitN(change, " ", 2) if len(splitChange) < 2 { return nil, errors.Errorf("invalid change %s format", change) } } - change := strings.Split(splitChange[1], " ") switch strings.ToUpper(splitChange[0]) { case "CMD": - importBuilder.SetCmd(change) + importBuilder.SetCmd(genCmd(splitChange[1])) case "ENTRYPOINT": - importBuilder.SetEntrypoint(change) + importBuilder.SetEntrypoint(genCmd(splitChange[1])) case "ENV": + change := strings.Split(splitChange[1], " ") name := change[0] val := "" if len(change) < 2 { @@ -168,6 +193,7 @@ func (c *Container) Commit(ctx context.Context, destImage string, options Contai } importBuilder.SetPort(splitChange[1]) case "LABEL": + change := strings.Split(splitChange[1], " ") if len(change) < 2 { change = strings.Split(change[0], "=") } diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go index 931c55a57..5279f11b2 100644 --- a/pkg/adapter/containers.go +++ b/pkg/adapter/containers.go @@ -510,3 +510,187 @@ func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.Contai } return lastError } + +// Start will start a container +func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigProxy bool) (int, error) { + var ( + exitCode = 125 + lastError error + ) + + args := c.InputArgs + if c.Latest { + lastCtr, err := r.GetLatestContainer() + if err != nil { + return 0, errors.Wrapf(err, "unable to get latest container") + } + args = append(args, lastCtr.ID()) + } + + for _, container := range args { + ctr, err := r.LookupContainer(container) + if err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "unable to find container %s", container) + continue + } + + ctrState, err := ctr.State() + if err != nil { + return exitCode, errors.Wrapf(err, "unable to get container state") + } + + ctrRunning := ctrState == libpod.ContainerStateRunning + + if c.Attach { + inputStream := os.Stdin + if !c.Interactive { + inputStream = nil + } + + // attach to the container and also start it not already running + // If the container is in a pod, also set to recursively start dependencies + err = StartAttachCtr(ctx, ctr.Container, os.Stdout, os.Stderr, inputStream, c.DetachKeys, sigProxy, !ctrRunning, ctr.PodID() != "") + if errors.Cause(err) == libpod.ErrDetach { + // User manually detached + // Exit cleanly immediately + exitCode = 0 + return exitCode, nil + } + + if ctrRunning { + return 0, err + } + + if err != nil { + return exitCode, errors.Wrapf(err, "unable to start container %s", ctr.ID()) + } + + if ecode, err := ctr.Wait(); err != nil { + if errors.Cause(err) == libpod.ErrNoSuchCtr { + // The container may have been removed + // Go looking for an exit file + rtc, err := r.GetConfig() + if err != nil { + return 0, err + } + ctrExitCode, err := ReadExitFile(rtc.TmpDir, ctr.ID()) + if err != nil { + logrus.Errorf("Cannot get exit code: %v", err) + exitCode = 127 + } else { + exitCode = ctrExitCode + } + } + } else { + exitCode = int(ecode) + } + + return exitCode, nil + } + if ctrRunning { + fmt.Println(ctr.ID()) + continue + } + // Handle non-attach start + // If the container is in a pod, also set to recursively start dependencies + if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil { + if lastError != nil { + fmt.Fprintln(os.Stderr, lastError) + } + lastError = errors.Wrapf(err, "unable to start container %q", container) + continue + } + fmt.Println(container) + } + return exitCode, lastError +} + +// PauseContainers removes container(s) based on CLI inputs. +func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ctrs []*libpod.Container + err error + ) + + maxWorkers := shared.DefaultPoolSize("pause") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + + if cli.All { + ctrs, err = r.GetRunningContainers() + } else { + ctrs, err = shortcuts.GetContainersByContext(false, false, cli.InputArgs, r.Runtime) + } + if err != nil { + return ok, failures, err + } + + pool := shared.NewPool("pause", maxWorkers, len(ctrs)) + for _, c := range ctrs { + ctr := c + pool.Add(shared.Job{ + ID: ctr.ID(), + Fn: func() error { + err := ctr.Pause() + if err != nil { + logrus.Debugf("Failed to pause container %s: %s", ctr.ID(), err.Error()) + } + return err + }, + }) + } + return pool.Run() +} + +// UnpauseContainers removes container(s) based on CLI inputs. +func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.UnpauseValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ctrs []*libpod.Container + err error + ) + + maxWorkers := shared.DefaultPoolSize("pause") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + + if cli.All { + var filterFuncs []libpod.ContainerFilter + filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { + state, _ := c.State() + return state == libpod.ContainerStatePaused + }) + ctrs, err = r.GetContainers(filterFuncs...) + } else { + ctrs, err = shortcuts.GetContainersByContext(false, false, cli.InputArgs, r.Runtime) + } + if err != nil { + return ok, failures, err + } + + pool := shared.NewPool("pause", maxWorkers, len(ctrs)) + for _, c := range ctrs { + ctr := c + pool.Add(shared.Job{ + ID: ctr.ID(), + Fn: func() error { + err := ctr.Unpause() + if err != nil { + logrus.Debugf("Failed to unpause container %s: %s", ctr.ID(), err.Error()) + } + return err + }, + }) + } + return pool.Run() +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 50cff9fa0..cb61871bf 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -45,6 +45,18 @@ func (c *Container) ID() string { return c.config.ID } +// Pause a container +func (c *Container) Pause() error { + _, err := iopodman.PauseContainer().Call(c.Runtime.Conn, c.ID()) + return err +} + +// Unpause a container +func (c *Container) Unpause() error { + _, err := iopodman.UnpauseContainer().Call(c.Runtime.Conn, c.ID()) + return err +} + // Config returns a container config func (r *LocalRuntime) Config(name string) *libpod.ContainerConfig { // TODO the Spec being returned is not populated. Matt and I could not figure out why. Will defer @@ -90,6 +102,19 @@ func (r *LocalRuntime) Spec(name string) (*specs.Spec, error) { return &data, nil } +// LookupContainers is a wrapper for LookupContainer +func (r *LocalRuntime) LookupContainers(idsOrNames []string) ([]*Container, error) { + var containers []*Container + for _, name := range idsOrNames { + ctr, err := r.LookupContainer(name) + if err != nil { + return nil, err + } + containers = append(containers, ctr) + } + return containers, nil +} + // LookupContainer gets basic information about container over a varlink // connection and then translates it to a *Container func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { @@ -107,6 +132,24 @@ func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { }, nil } +func (r *LocalRuntime) LookupContainersWithStatus(filters []string) ([]*Container, error) { + var containers []*Container + ctrs, err := iopodman.GetContainersByStatus().Call(r.Conn, filters) + if err != nil { + return nil, err + } + // This is not performance savy; if this turns out to be a problematic series of lookups, we need to + // create a new endpoint to speed things up + for _, ctr := range ctrs { + container, err := r.LookupContainer(ctr.Id) + if err != nil { + return nil, err + } + containers = append(containers, container) + } + return containers, nil +} + func (r *LocalRuntime) GetLatestContainer() (*Container, error) { reply, err := iopodman.GetContainersByContext().Call(r.Conn, false, true, nil) if err != nil { @@ -327,22 +370,12 @@ func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) // CreateContainer creates a container from the cli over varlink func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) { - if !c.Bool("detach") { - // TODO need to add attach when that function becomes available - return "", errors.New("the remote client only supports detached containers") - } results := shared.NewIntermediateLayer(&c.PodmanCommand, true) return iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) } // Run creates a container overvarlink and then starts it func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) { - // FIXME - // podman-remote run -it alpine ls DOES NOT WORK YET - // podman-remote run -it alpine /bin/sh does, i suspect there is some sort of - // timing issue between the socket availability and terminal setup and the command - // being run. - // TODO the exit codes for run need to be figured out for remote connections results := shared.NewIntermediateLayer(&c.PodmanCommand, true) cid, err := iopodman.CreateContainer().Call(r.Conn, results.MakeVarlink()) @@ -354,8 +387,7 @@ func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode fmt.Println(cid) return 0, err } - - errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid, true) + errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid, true, c.String("detach-keys")) if err != nil { return 0, err } @@ -367,7 +399,7 @@ func ReadExitFile(runtimeTmp, ctrID string) (int, error) { return 0, libpod.ErrNotImplemented } -// Ps ... +// Ps lists containers based on criteria from user func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) { var psContainers []shared.PsContainerOutput last := int64(c.Last) @@ -439,7 +471,7 @@ func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]share return psContainers, nil } -func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool) (chan error, error) { +func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) { var ( oldTermState *term.State ) @@ -470,7 +502,7 @@ func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid s } // TODO add detach keys support - _, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, "", start) + _, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start) if err != nil { restoreTerminal(oldTermState) return nil, err @@ -531,7 +563,7 @@ func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) er if c.NoStdin { inputStream = nil } - errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false) + errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys) if err != nil { return err } @@ -609,3 +641,115 @@ func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.Contai } return lastError } + +// Start starts an already created container +func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigProxy bool) (int, error) { + var ( + finalErr error + exitCode = 125 + ) + // TODO Figure out how to deal with exit codes + inputStream := os.Stdin + if !c.Interactive { + inputStream = nil + } + + containerIDs, err := iopodman.GetContainersByContext().Call(r.Conn, false, c.Latest, c.InputArgs) + if err != nil { + return exitCode, err + } + if len(containerIDs) < 1 { + return exitCode, errors.New("failed to find containers to start") + } + // start.go makes sure that if attach, there can be only one ctr + if c.Attach { + errChan, err := r.attach(ctx, inputStream, os.Stdout, containerIDs[0], true, c.DetachKeys) + if err != nil { + return exitCode, nil + } + err = <-errChan + return 0, err + } + + // TODO the notion of starting a pod container and its deps still needs to be worked through + // Everything else is detached + for _, cid := range containerIDs { + reply, err := iopodman.StartContainer().Call(r.Conn, cid) + if err != nil { + if finalErr != nil { + fmt.Println(err) + } + finalErr = err + } else { + fmt.Println(reply) + } + } + return exitCode, finalErr +} + +// PauseContainers pauses container(s) based on CLI inputs. +func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) { + var ( + ok []string + failures = map[string]error{} + ctrs []*Container + err error + ) + + if cli.All { + filters := []string{libpod.ContainerStateRunning.String()} + ctrs, err = r.LookupContainersWithStatus(filters) + } else { + ctrs, err = r.LookupContainers(cli.InputArgs) + } + if err != nil { + return ok, failures, err + } + + for _, c := range ctrs { + c := c + err := c.Pause() + if err != nil { + failures[c.ID()] = err + } else { + ok = append(ok, c.ID()) + } + } + return ok, failures, nil +} + +// UnpauseContainers unpauses containers based on input +func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.UnpauseValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ctrs []*Container + err error + ) + + maxWorkers := shared.DefaultPoolSize("unpause") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + + if cli.All { + filters := []string{libpod.ContainerStatePaused.String()} + ctrs, err = r.LookupContainersWithStatus(filters) + } else { + ctrs, err = r.LookupContainers(cli.InputArgs) + } + if err != nil { + return ok, failures, err + } + for _, c := range ctrs { + c := c + err := c.Unpause() + if err != nil { + failures[c.ID()] = err + } else { + ok = append(ok, c.ID()) + } + } + return ok, failures, nil +} diff --git a/pkg/adapter/pods.go b/pkg/adapter/pods.go index 669971789..bb7d9cce6 100644 --- a/pkg/adapter/pods.go +++ b/pkg/adapter/pods.go @@ -4,20 +4,16 @@ package adapter import ( "context" - "github.com/pkg/errors" "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" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// Pod ... -type Pod struct { - *libpod.Pod -} - // PodContainerStats is struct containing an adapter Pod and a libpod // ContainerStats and is used primarily for outputing pod stats. type PodContainerStats struct { @@ -25,6 +21,49 @@ type PodContainerStats struct { ContainerStats map[string]*libpod.ContainerStats } +// PrunePods removes pods +func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + maxWorkers := shared.DefaultPoolSize("rm") + if cli.GlobalIsSet("max-workers") { + maxWorkers = cli.GlobalFlags.MaxWorks + } + logrus.Debugf("Setting maximum rm workers to %d", maxWorkers) + + states := []string{shared.PodStateStopped, shared.PodStateExited} + if cli.Force { + states = append(states, shared.PodStateRunning) + } + + pods, err := r.GetPodsByStatus(states) + if err != nil { + return ok, failures, err + } + if len(pods) < 1 { + return ok, failures, nil + } + + pool := shared.NewPool("pod_prune", maxWorkers, len(pods)) + for _, p := range pods { + p := p + + pool.Add(shared.Job{p.ID(), + func() error { + err := r.Runtime.RemovePod(ctx, p, cli.Force, cli.Force) + if err != nil { + logrus.Debugf("Failed to remove pod %s: %s", p.ID(), err.Error()) + } + return err + }, + }) + } + return pool.Run() +} + // RemovePods ... func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValues) ([]string, []error) { var ( @@ -38,7 +77,7 @@ func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValue } for _, p := range pods { - if err := r.RemovePod(ctx, p, cli.Force, cli.Force); err != nil { + if err := r.Runtime.RemovePod(ctx, p, cli.Force, cli.Force); err != nil { errs = append(errs, err) } else { podids = append(podids, p.ID()) diff --git a/pkg/adapter/pods_remote.go b/pkg/adapter/pods_remote.go index 4a32607a2..7cf38aac0 100644 --- a/pkg/adapter/pods_remote.go +++ b/pkg/adapter/pods_remote.go @@ -14,13 +14,9 @@ import ( "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/varlinkapi" "github.com/pkg/errors" + "github.com/sirupsen/logrus" ) -// Pod ... -type Pod struct { - remotepod -} - // PodContainerStats is struct containing an adapter Pod and a libpod // ContainerStats and is used primarily for outputing pod stats. type PodContainerStats struct { @@ -28,13 +24,6 @@ type PodContainerStats struct { ContainerStats map[string]*libpod.ContainerStats } -type remotepod struct { - config *libpod.PodConfig - state *libpod.PodInspectState - containers []libpod.PodContainerInfo - Runtime *LocalRuntime -} - // RemovePods removes one or more based on the cli context. func (r *LocalRuntime) RemovePods(ctx context.Context, cli *cliconfig.PodRmValues) ([]string, []error) { var ( @@ -214,6 +203,23 @@ func (r *LocalRuntime) GetAllPods() ([]*Pod, error) { return pods, nil } +// GetPodsByStatus returns a slice of pods filtered by a libpod status +func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*Pod, error) { + podIDs, err := iopodman.GetPodsByStatus().Call(r.Conn, statuses) + if err != nil { + return nil, err + } + pods := make([]*Pod, 0, len(podIDs)) + 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 @@ -508,3 +514,48 @@ func (p *Pod) GetPodStats(previousContainerStats map[string]*libpod.ContainerSta } return newContainerStats, nil } + +// RemovePod removes a pod +// If removeCtrs is specified, containers will be removed +// Otherwise, a pod that is not empty will return an error and not be removed +// If force is specified with removeCtrs, all containers will be stopped before +// being removed +// Otherwise, the pod will not be removed if any containers are running +func (r *LocalRuntime) RemovePod(ctx context.Context, p *Pod, removeCtrs, force bool) error { + _, err := iopodman.RemovePod().Call(r.Conn, p.ID(), force) + if err != nil { + return err + } + return nil +} + +// PrunePods... +func (r *LocalRuntime) PrunePods(ctx context.Context, cli *cliconfig.PodPruneValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + states := []string{shared.PodStateStopped, shared.PodStateExited} + if cli.Force { + states = append(states, shared.PodStateRunning) + } + + ids, err := iopodman.GetPodsByStatus().Call(r.Conn, states) + if err != nil { + return ok, failures, err + } + if len(ids) < 1 { + return ok, failures, nil + } + + for _, id := range ids { + _, err := iopodman.RemovePod().Call(r.Conn, id, cli.Force) + if err != nil { + logrus.Debugf("Failed to remove pod %s: %s", id, err.Error()) + failures[id] = err + } else { + ok = append(ok, id) + } + } + return ok, failures, nil +} diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index b5ec9f7a9..753f7c944 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -7,7 +7,6 @@ import ( "context" "io" "io/ioutil" - "k8s.io/api/core/v1" "os" "text/template" @@ -25,6 +24,7 @@ import ( "github.com/containers/libpod/pkg/rootless" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" + "k8s.io/api/core/v1" ) // LocalRuntime describes a typical libpod runtime @@ -43,6 +43,11 @@ type Container struct { *libpod.Container } +// Pod encapsulates the libpod.Pod structure, helps with remote vs. local +type Pod struct { + *libpod.Pod +} + // Volume ... type Volume struct { *libpod.Volume @@ -369,3 +374,24 @@ func (r *LocalRuntime) Diff(c *cliconfig.DiffValues, to string) ([]archive.Chang func (r *LocalRuntime) GenerateKube(c *cliconfig.GenerateKubeValues) (*v1.Pod, *v1.Service, error) { return shared.GenerateKube(c.InputArgs[0], c.Service, r.Runtime) } + +// GetPodsByStatus returns a slice of pods filtered by a libpod status +func (r *LocalRuntime) GetPodsByStatus(statuses []string) ([]*libpod.Pod, error) { + + filterFunc := func(p *libpod.Pod) bool { + state, _ := shared.GetPodStatus(p) + for _, status := range statuses { + if state == status { + return true + } + } + return false + } + + pods, err := r.Runtime.Pods(filterFunc) + if err != nil { + return nil, err + } + + return pods, nil +} diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 71f7380db..dcb0924ce 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -99,6 +99,18 @@ type remoteContainer struct { state *libpod.ContainerState } +// Pod ... +type Pod struct { + remotepod +} + +type remotepod struct { + config *libpod.PodConfig + state *libpod.PodInspectState + containers []libpod.PodContainerInfo + Runtime *LocalRuntime +} + type VolumeFilter func(*Volume) bool // Volume is embed for libpod volumes diff --git a/pkg/rootless/rootless_linux.c b/pkg/rootless/rootless_linux.c index 9cb79ed4d..1d32b1adb 100644 --- a/pkg/rootless/rootless_linux.c +++ b/pkg/rootless/rootless_linux.c @@ -16,11 +16,13 @@ #include <sys/types.h> #include <sys/prctl.h> #include <dirent.h> +#include <sys/select.h> static const char *_max_user_namespaces = "/proc/sys/user/max_user_namespaces"; static const char *_unprivileged_user_namespaces = "/proc/sys/kernel/unprivileged_userns_clone"; -static int n_files; +static int open_files_max_fd; +fd_set open_files_set; static void __attribute__((constructor)) init() { @@ -32,11 +34,16 @@ static void __attribute__((constructor)) init() { struct dirent *ent; + FD_ZERO (&open_files_set); for (ent = readdir (d); ent; ent = readdir (d)) { int fd = atoi (ent->d_name); - if (fd > n_files && fd != dirfd (d)) - n_files = fd; + if (fd != dirfd (d)) + { + if (fd > open_files_max_fd) + open_files_max_fd = fd; + FD_SET (fd, &open_files_set); + } } closedir (d); } @@ -164,8 +171,11 @@ reexec_userns_join (int userns, int mountns) { /* We passed down these fds, close them. */ int f; - for (f = 3; f < n_files; f++) - close (f); + for (f = 3; f < open_files_max_fd; f++) + { + if (FD_ISSET (f, &open_files_set)) + close (f); + } return pid; } @@ -274,22 +284,25 @@ reexec_in_user_namespace (int ready) check_proc_sys_userns_file (_max_user_namespaces); check_proc_sys_userns_file (_unprivileged_user_namespaces); } - if (pid) { - if (do_socket_activation) { - long num_fds; - num_fds = strtol(listen_fds, NULL, 10); - if (num_fds != LONG_MIN && num_fds != LONG_MAX) { - long i; - for (i = 0; i < num_fds; i++) { - close(3+i); + if (pid) + { + if (do_socket_activation) + { + long num_fds; + num_fds = strtol (listen_fds, NULL, 10); + if (num_fds != LONG_MIN && num_fds != LONG_MAX) + { + long i; + for (i = 3; i < num_fds + 3; i++) + if (FD_ISSET (i, &open_files_set)) + close (i); + } + unsetenv ("LISTEN_PID"); + unsetenv ("LISTEN_FDS"); + unsetenv ("LISTEN_FDNAMES"); } - } - unsetenv("LISTEN_PID"); - unsetenv("LISTEN_FDS"); - unsetenv("LISTEN_FDNAMES"); + return pid; } - return pid; - } argv = get_cmd_line_args (ppid); if (argv == NULL) @@ -300,8 +313,8 @@ reexec_in_user_namespace (int ready) if (do_socket_activation) { char s[32]; - sprintf(s, "%d", getpid()); - setenv("LISTEN_PID", s, true); + sprintf (s, "%d", getpid()); + setenv ("LISTEN_PID", s, true); } setenv ("_CONTAINERS_USERNS_CONFIGURED", "init", 1); diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index 17792ccfe..237407050 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -128,6 +128,37 @@ func (i *LibpodAPI) GetContainersByContext(call iopodman.VarlinkCall, all, lates return call.ReplyGetContainersByContext(ids) } +// GetContainersByStatus returns a slice of containers filtered by a libpod status +func (i *LibpodAPI) GetContainersByStatus(call iopodman.VarlinkCall, statuses []string) error { + var ( + filterFuncs []libpod.ContainerFilter + containers []iopodman.Container + ) + for _, status := range statuses { + lpstatus, err := libpod.StringToContainerStatus(status) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + filterFuncs = append(filterFuncs, func(c *libpod.Container) bool { + state, _ := c.State() + return state == lpstatus + }) + } + filteredContainers, err := i.Runtime.GetContainers(filterFuncs...) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + opts := shared.PsOptions{Size: true, Namespace: true} + for _, ctr := range filteredContainers { + batchInfo, err := shared.BatchContainerOp(ctr, opts) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + containers = append(containers, makeListContainer(ctr.ID(), batchInfo)) + } + return call.ReplyGetContainersByStatus(containers) +} + // InspectContainer ... func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) diff --git a/pkg/varlinkapi/pods.go b/pkg/varlinkapi/pods.go index ac8e24747..f34375bf5 100644 --- a/pkg/varlinkapi/pods.go +++ b/pkg/varlinkapi/pods.go @@ -101,6 +101,28 @@ func (i *LibpodAPI) GetPod(call iopodman.VarlinkCall, name string) error { return call.ReplyGetPod(listPod) } +// GetPodsByStatus returns a slice of pods filtered by a libpod status +func (i *LibpodAPI) GetPodsByStatus(call iopodman.VarlinkCall, statuses []string) error { + filterFuncs := func(p *libpod.Pod) bool { + state, _ := shared.GetPodStatus(p) + for _, status := range statuses { + if state == status { + return true + } + } + return false + } + filteredPods, err := i.Runtime.Pods(filterFuncs) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + podIDs := make([]string, 0, len(filteredPods)) + for _, p := range filteredPods { + podIDs = append(podIDs, p.ID()) + } + return call.ReplyGetPodsByStatus(podIDs) +} + // InspectPod ... func (i *LibpodAPI) InspectPod(call iopodman.VarlinkCall, name string) error { pod, err := i.Runtime.LookupPod(name) diff --git a/test/e2e/commit_test.go b/test/e2e/commit_test.go index 93e1ea7af..3ece4887e 100644 --- a/test/e2e/commit_test.go +++ b/test/e2e/commit_test.go @@ -117,6 +117,31 @@ var _ = Describe("Podman commit", func() { Expect(foundBlue).To(Equal(true)) }) + It("podman commit container with change CMD flag", func() { + test := podmanTest.Podman([]string{"run", "--name", "test1", "-d", ALPINE, "ls"}) + test.WaitWithDefaultTimeout() + Expect(test.ExitCode()).To(Equal(0)) + Expect(podmanTest.NumberOfContainers()).To(Equal(1)) + + session := podmanTest.Podman([]string{"commit", "--change", "CMD a b c", "test1", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"inspect", "--format", "{{.Config.Cmd}}", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(ContainSubstring("sh -c a b c")) + + session = podmanTest.Podman([]string{"commit", "--change", "CMD=[\"a\",\"b\",\"c\"]", "test1", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"inspect", "--format", "{{.Config.Cmd}}", "foobar.com/test1-image:latest"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + Expect(session.OutputToString()).To(Not(ContainSubstring("sh -c"))) + }) + It("podman commit container with pause flag", func() { _, ec, _ := podmanTest.RunLsContainer("test1") Expect(ec).To(Equal(0)) diff --git a/test/e2e/common_test.go b/test/e2e/common_test.go index 58f94f27e..a30a9b20b 100644 --- a/test/e2e/common_test.go +++ b/test/e2e/common_test.go @@ -11,14 +11,16 @@ import ( "strings" "testing" - "github.com/containers/libpod/pkg/rootless" - "github.com/containers/storage" - + "github.com/containers/libpod/libpod" "github.com/containers/libpod/pkg/inspect" + "github.com/containers/libpod/pkg/rootless" . "github.com/containers/libpod/test/utils" + "github.com/containers/storage" "github.com/containers/storage/pkg/reexec" + "github.com/containers/storage/pkg/stringid" . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "github.com/onsi/gomega/gexec" ) var ( @@ -262,6 +264,10 @@ func PodmanTestCreateUtil(tempDir string, remote bool) *PodmanTestIntegration { } if remote { p.PodmanTest.RemotePodmanBinary = podmanRemoteBinary + if !rootless.IsRootless() { + uuid := stringid.GenerateNonCryptoID() + p.VarlinkEndpoint = fmt.Sprintf("unix:/run/podman/io.podman-%s", uuid) + } } // Setup registries.conf ENV variable @@ -337,3 +343,159 @@ func GetPortLock(port string) storage.Locker { lock.Lock() return lock } + +// RunTopContainer runs a simple container in the background that +// runs top. If the name passed != "", it will have a name +func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration { + var podmanArgs = []string{"run"} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-d", ALPINE, "top") + return p.Podman(podmanArgs) +} + +// RunLsContainer runs a simple container in the background that +// simply runs ls. If the name passed != "", it will have a name +func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) { + var podmanArgs = []string{"run"} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") + session := p.Podman(podmanArgs) + session.WaitWithDefaultTimeout() + return session, session.ExitCode(), session.OutputToString() +} + +func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { + var podmanArgs = []string{"run", "--pod", pod} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") + session := p.Podman(podmanArgs) + session.WaitWithDefaultTimeout() + return session, session.ExitCode(), session.OutputToString() +} + +// BuildImage uses podman build and buildah to build an image +// called imageName based on a string dockerfile +func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) { + dockerfilePath := filepath.Join(p.TempDir, "Dockerfile") + err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755) + Expect(err).To(BeNil()) + session := p.Podman([]string{"build", "--layers=" + layers, "-t", imageName, "--file", dockerfilePath, p.TempDir}) + session.Wait(120) + Expect(session.ExitCode()).To(Equal(0)) +} + +// PodmanPID execs podman and returns its PID +func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { + podmanOptions := p.MakeOptions(args) + fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) + command := exec.Command(p.PodmanBinary, podmanOptions...) + session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) + if err != nil { + Fail(fmt.Sprintf("unable to run podman command: %s", strings.Join(podmanOptions, " "))) + } + podmanSession := &PodmanSession{session} + return &PodmanSessionIntegration{podmanSession}, command.Process.Pid +} + +// Cleanup cleans up the temporary store +func (p *PodmanTestIntegration) Cleanup() { + // Remove all containers + stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) + // stopall.WaitWithDefaultTimeout() + stopall.Wait(90) + + session := p.Podman([]string{"rm", "-fa"}) + session.Wait(90) + + p.StopVarlink() + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } + + // Clean up the registries configuration file ENV variable set in Create + resetRegistriesConfigEnv() +} + +// CleanupPod cleans up the temporary store +func (p *PodmanTestIntegration) CleanupPod() { + // Remove all containers + session := p.Podman([]string{"pod", "rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + +// CleanupVolume cleans up the temporary store +func (p *PodmanTestIntegration) CleanupVolume() { + // Remove all containers + session := p.Podman([]string{"volume", "rm", "-fa"}) + session.Wait(90) + // Nuke tempdir + if err := os.RemoveAll(p.TempDir); err != nil { + fmt.Printf("%q\n", err) + } +} + +// PullImages pulls multiple images +func (p *PodmanTestIntegration) PullImages(images []string) error { + for _, i := range images { + p.PullImage(i) + } + return nil +} + +// PullImage pulls a single image +// TODO should the timeout be configurable? +func (p *PodmanTestIntegration) PullImage(image string) error { + session := p.Podman([]string{"pull", image}) + session.Wait(60) + Expect(session.ExitCode()).To(Equal(0)) + return nil +} + +// InspectContainerToJSON takes the session output of an inspect +// container and returns json +func (s *PodmanSessionIntegration) InspectContainerToJSON() []inspect.ContainerData { + var i []inspect.ContainerData + err := json.Unmarshal(s.Out.Contents(), &i) + Expect(err).To(BeNil()) + return i +} + +// InspectPodToJSON takes the sessions output from a pod inspect and returns json +func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect { + var i libpod.PodInspect + err := json.Unmarshal(s.Out.Contents(), &i) + Expect(err).To(BeNil()) + return i +} + +// CreatePod creates a pod with no infra container +// it optionally takes a pod name +func (p *PodmanTestIntegration) CreatePod(name string) (*PodmanSessionIntegration, int, string) { + var podmanArgs = []string{"pod", "create", "--infra=false", "--share", ""} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + session := p.Podman(podmanArgs) + session.WaitWithDefaultTimeout() + return session, session.ExitCode(), session.OutputToString() +} + +func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration { + var podmanArgs = []string{"run", "--pod", pod} + if name != "" { + podmanArgs = append(podmanArgs, "--name", name) + } + podmanArgs = append(podmanArgs, "-d", ALPINE, "top") + return p.Podman(podmanArgs) +} diff --git a/test/e2e/libpod_suite_remoteclient_test.go b/test/e2e/libpod_suite_remoteclient_test.go index a85d21a48..1e477fe2f 100644 --- a/test/e2e/libpod_suite_remoteclient_test.go +++ b/test/e2e/libpod_suite_remoteclient_test.go @@ -4,14 +4,14 @@ package integration import ( "fmt" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/inspect" - "github.com/onsi/ginkgo" + "github.com/containers/libpod/pkg/rootless" "io/ioutil" "os" "os/exec" "path/filepath" "strings" + + "github.com/onsi/ginkgo" ) func SkipIfRemote() { @@ -24,48 +24,12 @@ func SkipIfRootless() { } } -// Cleanup cleans up the temporary store -func (p *PodmanTestIntegration) Cleanup() { - p.StopVarlink() - // TODO - // Stop all containers - // Rm all containers - - if err := os.RemoveAll(p.TempDir); err != nil { - fmt.Printf("%q\n", err) - } - - // Clean up the registries configuration file ENV variable set in Create - resetRegistriesConfigEnv() -} - // Podman is the exec call to podman on the filesystem func (p *PodmanTestIntegration) Podman(args []string) *PodmanSessionIntegration { podmanSession := p.PodmanBase(args) return &PodmanSessionIntegration{podmanSession} } -//RunTopContainer runs a simple container in the background that -// runs top. If the name passed != "", it will have a name -func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration { - // TODO - return nil -} - -//RunLsContainer runs a simple container in the background that -// simply runs ls. If the name passed != "", it will have a name -func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) { - // TODO - return nil, 0, "" -} - -// InspectImageJSON takes the session output of an inspect -// image and returns json -//func (s *PodmanSessionIntegration) InspectImageJSON() []inspect.ImageData { -// // TODO -// return nil -//} - func (p *PodmanTestIntegration) setDefaultRegistriesConfigEnv() { defaultFile := filepath.Join(INTEGRATION_ROOT, "test/registries.conf") os.Setenv("REGISTRIES_CONFIG_PATH", defaultFile) @@ -80,64 +44,6 @@ func (p *PodmanTestIntegration) setRegistriesConfigEnv(b []byte) { func resetRegistriesConfigEnv() { os.Setenv("REGISTRIES_CONFIG_PATH", "") } - -// InspectContainerToJSON takes the session output of an inspect -// container and returns json -func (s *PodmanSessionIntegration) InspectContainerToJSON() []inspect.ContainerData { - // TODO - return nil -} - -// CreatePod creates a pod with no infra container -// it optionally takes a pod name -func (p *PodmanTestIntegration) CreatePod(name string) (*PodmanSessionIntegration, int, string) { - // TODO - return nil, 0, "" -} - -func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration { - // TODO - return nil -} - -// BuildImage uses podman build and buildah to build an image -// called imageName based on a string dockerfile -func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) { - // TODO -} - -// CleanupPod cleans up the temporary store -func (p *PodmanTestIntegration) CleanupPod() { - // TODO -} - -// InspectPodToJSON takes the sessions output from a pod inspect and returns json -func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect { - // TODO - return libpod.PodInspect{} -} -func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { - // TODO - return nil, 0, "" -} - -// PullImages pulls multiple images -func (p *PodmanTestIntegration) PullImages(images []string) error { - // TODO - return libpod.ErrNotImplemented -} - -// PodmanPID execs podman and returns its PID -func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { - // TODO - return nil, 0 -} - -// CleanupVolume cleans up the temporary store -func (p *PodmanTestIntegration) CleanupVolume() { - // TODO -} - func PodmanTestCreate(tempDir string) *PodmanTestIntegration { pti := PodmanTestCreateUtil(tempDir, true) pti.StartVarlink() @@ -148,7 +54,7 @@ func (p *PodmanTestIntegration) StartVarlink() { if os.Geteuid() == 0 { os.MkdirAll("/run/podman", 0755) } - varlinkEndpoint := "unix:/run/podman/io.podman" + varlinkEndpoint := p.VarlinkEndpoint if addr := os.Getenv("PODMAN_VARLINK_ADDRESS"); addr != "" { varlinkEndpoint = addr } @@ -165,6 +71,13 @@ func (p *PodmanTestIntegration) StopVarlink() { varlinkSession := p.VarlinkSession varlinkSession.Kill() varlinkSession.Wait() + + if !rootless.IsRootless() { + socket := strings.Split(p.VarlinkEndpoint, ":")[1] + if err := os.Remove(socket); err != nil { + fmt.Println(err) + } + } } //MakeOptions assembles all the podman main options diff --git a/test/e2e/libpod_suite_test.go b/test/e2e/libpod_suite_test.go index a69c1ba9a..867844c32 100644 --- a/test/e2e/libpod_suite_test.go +++ b/test/e2e/libpod_suite_test.go @@ -3,27 +3,17 @@ package integration import ( - "encoding/json" "fmt" "io/ioutil" "os" - "os/exec" "path/filepath" "strings" - "github.com/containers/libpod/libpod" - "github.com/containers/libpod/pkg/inspect" - . "github.com/containers/libpod/test/utils" "github.com/onsi/ginkgo" - . "github.com/onsi/ginkgo" - . "github.com/onsi/gomega" - "github.com/onsi/gomega/gexec" ) func SkipIfRemote() { - if os.Geteuid() != 0 { - ginkgo.Skip("This function is not enabled for rootless podman") - } + ginkgo.Skip("This function is not enabled for remote podman") } func SkipIfRootless() { @@ -44,161 +34,6 @@ func (p *PodmanTestIntegration) PodmanAsUser(args []string, uid, gid uint32, cwd return &PodmanSessionIntegration{podmanSession} } -// PodmanPID execs podman and returns its PID -func (p *PodmanTestIntegration) PodmanPID(args []string) (*PodmanSessionIntegration, int) { - podmanOptions := p.MakeOptions(args) - fmt.Printf("Running: %s %s\n", p.PodmanBinary, strings.Join(podmanOptions, " ")) - command := exec.Command(p.PodmanBinary, podmanOptions...) - session, err := gexec.Start(command, GinkgoWriter, GinkgoWriter) - if err != nil { - Fail(fmt.Sprintf("unable to run podman command: %s", strings.Join(podmanOptions, " "))) - } - podmanSession := &PodmanSession{session} - return &PodmanSessionIntegration{podmanSession}, command.Process.Pid -} - -// Cleanup cleans up the temporary store -func (p *PodmanTestIntegration) Cleanup() { - // Remove all containers - stopall := p.Podman([]string{"stop", "-a", "--timeout", "0"}) - // stopall.WaitWithDefaultTimeout() - stopall.Wait(90) - - session := p.Podman([]string{"rm", "-fa"}) - session.Wait(90) - - // Nuke tempdir - if err := os.RemoveAll(p.TempDir); err != nil { - fmt.Printf("%q\n", err) - } - - // Clean up the registries configuration file ENV variable set in Create - resetRegistriesConfigEnv() -} - -// CleanupPod cleans up the temporary store -func (p *PodmanTestIntegration) CleanupPod() { - // Remove all containers - session := p.Podman([]string{"pod", "rm", "-fa"}) - session.Wait(90) - // Nuke tempdir - if err := os.RemoveAll(p.TempDir); err != nil { - fmt.Printf("%q\n", err) - } -} - -// CleanupVolume cleans up the temporary store -func (p *PodmanTestIntegration) CleanupVolume() { - // Remove all containers - session := p.Podman([]string{"volume", "rm", "-fa"}) - session.Wait(90) - // Nuke tempdir - if err := os.RemoveAll(p.TempDir); err != nil { - fmt.Printf("%q\n", err) - } -} - -// PullImages pulls multiple images -func (p *PodmanTestIntegration) PullImages(images []string) error { - for _, i := range images { - p.PullImage(i) - } - return nil -} - -// PullImage pulls a single image -// TODO should the timeout be configurable? -func (p *PodmanTestIntegration) PullImage(image string) error { - session := p.Podman([]string{"pull", image}) - session.Wait(60) - Expect(session.ExitCode()).To(Equal(0)) - return nil -} - -// InspectContainerToJSON takes the session output of an inspect -// container and returns json -func (s *PodmanSessionIntegration) InspectContainerToJSON() []inspect.ContainerData { - var i []inspect.ContainerData - err := json.Unmarshal(s.Out.Contents(), &i) - Expect(err).To(BeNil()) - return i -} - -// InspectPodToJSON takes the sessions output from a pod inspect and returns json -func (s *PodmanSessionIntegration) InspectPodToJSON() libpod.PodInspect { - var i libpod.PodInspect - err := json.Unmarshal(s.Out.Contents(), &i) - Expect(err).To(BeNil()) - return i -} - -// CreatePod creates a pod with no infra container -// it optionally takes a pod name -func (p *PodmanTestIntegration) CreatePod(name string) (*PodmanSessionIntegration, int, string) { - var podmanArgs = []string{"pod", "create", "--infra=false", "--share", ""} - if name != "" { - podmanArgs = append(podmanArgs, "--name", name) - } - session := p.Podman(podmanArgs) - session.WaitWithDefaultTimeout() - return session, session.ExitCode(), session.OutputToString() -} - -// RunTopContainer runs a simple container in the background that -// runs top. If the name passed != "", it will have a name -func (p *PodmanTestIntegration) RunTopContainer(name string) *PodmanSessionIntegration { - var podmanArgs = []string{"run"} - if name != "" { - podmanArgs = append(podmanArgs, "--name", name) - } - podmanArgs = append(podmanArgs, "-d", ALPINE, "top") - return p.Podman(podmanArgs) -} - -func (p *PodmanTestIntegration) RunTopContainerInPod(name, pod string) *PodmanSessionIntegration { - var podmanArgs = []string{"run", "--pod", pod} - if name != "" { - podmanArgs = append(podmanArgs, "--name", name) - } - podmanArgs = append(podmanArgs, "-d", ALPINE, "top") - return p.Podman(podmanArgs) -} - -// RunLsContainer runs a simple container in the background that -// simply runs ls. If the name passed != "", it will have a name -func (p *PodmanTestIntegration) RunLsContainer(name string) (*PodmanSessionIntegration, int, string) { - var podmanArgs = []string{"run"} - if name != "" { - podmanArgs = append(podmanArgs, "--name", name) - } - podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") - session := p.Podman(podmanArgs) - session.WaitWithDefaultTimeout() - return session, session.ExitCode(), session.OutputToString() -} - -func (p *PodmanTestIntegration) RunLsContainerInPod(name, pod string) (*PodmanSessionIntegration, int, string) { - var podmanArgs = []string{"run", "--pod", pod} - if name != "" { - podmanArgs = append(podmanArgs, "--name", name) - } - podmanArgs = append(podmanArgs, "-d", ALPINE, "ls") - session := p.Podman(podmanArgs) - session.WaitWithDefaultTimeout() - return session, session.ExitCode(), session.OutputToString() -} - -// BuildImage uses podman build and buildah to build an image -// called imageName based on a string dockerfile -func (p *PodmanTestIntegration) BuildImage(dockerfile, imageName string, layers string) { - dockerfilePath := filepath.Join(p.TempDir, "Dockerfile") - err := ioutil.WriteFile(dockerfilePath, []byte(dockerfile), 0755) - Expect(err).To(BeNil()) - session := p.Podman([]string{"build", "--layers=" + layers, "-t", imageName, "--file", dockerfilePath, p.TempDir}) - session.Wait(120) - Expect(session.ExitCode()).To(Equal(0)) -} - func (p *PodmanTestIntegration) setDefaultRegistriesConfigEnv() { defaultFile := filepath.Join(INTEGRATION_ROOT, "test/registries.conf") os.Setenv("REGISTRIES_CONFIG_PATH", defaultFile) @@ -245,3 +80,4 @@ func (p *PodmanTestIntegration) RestoreArtifact(image string) error { restore.Wait(90) return nil } +func (p *PodmanTestIntegration) StopVarlink() {} diff --git a/test/e2e/pause_test.go b/test/e2e/pause_test.go index e28c31c3a..c47189a0e 100644 --- a/test/e2e/pause_test.go +++ b/test/e2e/pause_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( @@ -70,7 +68,6 @@ var _ = Describe("Podman pause", func() { session.WaitWithDefaultTimeout() Expect(session.ExitCode()).To(Equal(0)) cid := session.OutputToString() - result := podmanTest.Podman([]string{"pause", cid}) result.WaitWithDefaultTimeout() diff --git a/test/e2e/pod_prune_test.go b/test/e2e/pod_prune_test.go new file mode 100644 index 000000000..c20f602ad --- /dev/null +++ b/test/e2e/pod_prune_test.go @@ -0,0 +1,78 @@ +// +build !remoteclient + +package integration + +import ( + "os" + + . "github.com/containers/libpod/test/utils" + . "github.com/onsi/ginkgo" + . "github.com/onsi/gomega" +) + +var _ = Describe("Podman pod prune", func() { + var ( + tempdir string + err error + podmanTest *PodmanTestIntegration + ) + + BeforeEach(func() { + tempdir, err = CreateTempDirInTempDir() + if err != nil { + os.Exit(1) + } + podmanTest = PodmanTestCreate(tempdir) + podmanTest.Setup() + podmanTest.RestoreAllArtifacts() + }) + + AfterEach(func() { + podmanTest.CleanupPod() + f := CurrentGinkgoTestDescription() + processTestResult(f) + + }) + + It("podman pod prune empty pod", func() { + _, ec, _ := podmanTest.CreatePod("") + Expect(ec).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "prune"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + }) + + It("podman pod prune doesn't remove a pod with a container", func() { + _, ec, podid := podmanTest.CreatePod("") + Expect(ec).To(Equal(0)) + + _, ec2, _ := podmanTest.RunLsContainerInPod("", podid) + Expect(ec2).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "prune"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(125)) + + result = podmanTest.Podman([]string{"ps", "-qa"}) + result.WaitWithDefaultTimeout() + Expect(len(result.OutputToStringArray())).To(Equal(1)) + }) + + It("podman pod prune -f does remove a running container", func() { + _, ec, podid := podmanTest.CreatePod("") + Expect(ec).To(Equal(0)) + + session := podmanTest.RunTopContainerInPod("", podid) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + result := podmanTest.Podman([]string{"pod", "prune", "-f"}) + result.WaitWithDefaultTimeout() + Expect(result.ExitCode()).To(Equal(0)) + + result = podmanTest.Podman([]string{"ps", "-q"}) + result.WaitWithDefaultTimeout() + Expect(result.OutputToString()).To(BeEmpty()) + }) +}) diff --git a/test/e2e/prune_test.go b/test/e2e/prune_test.go index 682f7ff2b..544d54b50 100644 --- a/test/e2e/prune_test.go +++ b/test/e2e/prune_test.go @@ -14,7 +14,7 @@ LABEL RUN podman --version RUN apk update RUN apk add bash` -var _ = Describe("Podman rm", func() { +var _ = Describe("Podman prune", func() { var ( tempdir string err error @@ -101,4 +101,37 @@ var _ = Describe("Podman rm", func() { Expect(len(images.OutputToStringArray())).To(Equal(0)) }) + It("podman system prune pods", func() { + SkipIfRemote() + + session := podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "create"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "start", "-l"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + session = podmanTest.Podman([]string{"pod", "stop", "-l"}) + session.WaitWithDefaultTimeout() + Expect(session.ExitCode()).To(Equal(0)) + + pods := podmanTest.Podman([]string{"pod", "ps"}) + pods.WaitWithDefaultTimeout() + Expect(pods.ExitCode()).To(Equal(0)) + Expect(len(pods.OutputToStringArray())).To(Equal(3)) + + prune := podmanTest.Podman([]string{"system", "prune", "-f"}) + prune.WaitWithDefaultTimeout() + Expect(prune.ExitCode()).To(Equal(0)) + + pods = podmanTest.Podman([]string{"pod", "ps"}) + pods.WaitWithDefaultTimeout() + Expect(pods.ExitCode()).To(Equal(0)) + Expect(len(pods.OutputToStringArray())).To(Equal(2)) + }) }) diff --git a/test/e2e/search_test.go b/test/e2e/search_test.go index 61d581c6d..72b083de8 100644 --- a/test/e2e/search_test.go +++ b/test/e2e/search_test.go @@ -344,7 +344,7 @@ registries = ['{{.Host}}:{{.Port}}']` defer lock8.Unlock() podmanTest.RestoreArtifact(registry) - registryLocal := podmanTest.Podman([]string{"run", "-d", "-p", fmt.Sprintf("%s:5000", registryEndpoints[7].Port), + registryLocal := podmanTest.Podman([]string{"run", "-d", "--net=host", "-p", fmt.Sprintf("%s:5000", registryEndpoints[7].Port), "--name", "registry7", registry}) registryLocal.WaitWithDefaultTimeout() Expect(registryLocal.ExitCode()).To(Equal(0)) diff --git a/test/e2e/start_test.go b/test/e2e/start_test.go index 28f1c2393..c92da9777 100644 --- a/test/e2e/start_test.go +++ b/test/e2e/start_test.go @@ -1,5 +1,3 @@ -// +build !remoteclient - package integration import ( diff --git a/test/utils/utils.go b/test/utils/utils.go index 6308197b8..1e0391d2e 100644 --- a/test/utils/utils.go +++ b/test/utils/utils.go @@ -4,6 +4,7 @@ import ( "bufio" "encoding/json" "fmt" + "github.com/containers/libpod/pkg/rootless" "io/ioutil" "os" "os/exec" @@ -40,6 +41,7 @@ type PodmanTest struct { RemoteTest bool RemotePodmanBinary string VarlinkSession *os.Process + VarlinkEndpoint string } // PodmanSession wraps the gexec.session so we can extend it @@ -67,7 +69,11 @@ func (p *PodmanTest) PodmanAsUserBase(args []string, uid, gid uint32, cwd string podmanBinary := p.PodmanBinary if p.RemoteTest { podmanBinary = p.RemotePodmanBinary + if !rootless.IsRootless() { + env = append(env, fmt.Sprintf("PODMAN_VARLINK_ADDRESS=%s", p.VarlinkEndpoint)) + } } + if env == nil { fmt.Printf("Running: %s %s\n", podmanBinary, strings.Join(podmanOptions, " ")) } else { |