From 4d13a80fa46ce57e3c889934536320525338b3a4 Mon Sep 17 00:00:00 2001 From: Jhon Honce Date: Mon, 18 Feb 2019 16:01:31 -0700 Subject: Support podman-remote stop container(s) * Clean up adapter code * Add GetContainersByContext to Varlink API * Add missing comments * Restore save command * Restore error type mapping when using varlink Signed-off-by: Jhon Honce --- API.md | 9 +++ cmd/podman/commands.go | 1 - cmd/podman/errors.go | 5 +- cmd/podman/errors_remote.go | 43 ++++++++++++++ cmd/podman/main.go | 1 + cmd/podman/stop.go | 67 ++++++++------------- cmd/podman/varlink/io.podman.varlink | 5 ++ pkg/adapter/containers.go | 72 +++++++++++++++++++++++ pkg/adapter/containers_remote.go | 109 ++++++++++++++++++++++++++++++++--- pkg/adapter/runtime.go | 9 --- pkg/adapter/runtime_remote.go | 76 +++--------------------- pkg/adapter/shortcuts/shortcuts.go | 27 +++++++++ pkg/varlinkapi/containers.go | 16 +++++ 13 files changed, 308 insertions(+), 132 deletions(-) create mode 100644 cmd/podman/errors_remote.go create mode 100644 pkg/adapter/containers.go diff --git a/API.md b/API.md index c4f23c9bd..d255037c1 100755 --- a/API.md +++ b/API.md @@ -43,6 +43,8 @@ in the [API.md](https://github.com/containers/libpod/blob/master/API.md) file in [func GetContainerStats(name: string) ContainerStats](#GetContainerStats) +[func GetContainersByContext(all: bool, latest: bool, args: []string) []string](#GetContainersByContext) + [func GetImage(id: string) Image](#GetImage) [func GetInfo() PodmanInfo](#GetInfo) @@ -460,6 +462,13 @@ $ varlink call -m unix:/run/podman/io.podman/io.podman.GetContainerStats '{"name } } ~~~ +### func GetContainersByContext +
+ +method GetContainersByContext(all: [bool](https://godoc.org/builtin#bool), latest: [bool](https://godoc.org/builtin#bool), args: [[]string](#[]string)) [[]string](#[]string)
+GetContainersByContext allows you to get a list of container ids depending on all, latest, or a list of +container names. The definition of latest container means the latest by creation date. In a multi- +user environment, results might differ from what you expect. ### func GetImage
diff --git a/cmd/podman/commands.go b/cmd/podman/commands.go index 2f9a9cfe2..635b16e0d 100644 --- a/cmd/podman/commands.go +++ b/cmd/podman/commands.go @@ -32,7 +32,6 @@ func getMainCommands() []*cobra.Command { _searchCommand, _startCommand, _statsCommand, - _stopCommand, _topCommand, _umountCommand, _unpauseCommand, diff --git a/cmd/podman/errors.go b/cmd/podman/errors.go index 2572b8779..9731037f4 100644 --- a/cmd/podman/errors.go +++ b/cmd/podman/errors.go @@ -1,3 +1,5 @@ +// +build !remoteclient + package main import ( @@ -13,7 +15,8 @@ func outputError(err error) { if MainGlobalOpts.LogLevel == "debug" { logrus.Errorf(err.Error()) } else { - if ee, ok := err.(*exec.ExitError); ok { + ee, ok := err.(*exec.ExitError) + if ok { if status, ok := ee.Sys().(syscall.WaitStatus); ok { exitCode = status.ExitStatus() } diff --git a/cmd/podman/errors_remote.go b/cmd/podman/errors_remote.go new file mode 100644 index 000000000..ab255ea56 --- /dev/null +++ b/cmd/podman/errors_remote.go @@ -0,0 +1,43 @@ +// +build remoteclient + +package main + +import ( + "fmt" + "os" + "os/exec" + "syscall" + + "github.com/containers/libpod/cmd/podman/varlink" + "github.com/pkg/errors" + "github.com/sirupsen/logrus" +) + +func outputError(err error) { + if MainGlobalOpts.LogLevel == "debug" { + logrus.Errorf(err.Error()) + } else { + if ee, ok := err.(*exec.ExitError); ok { + if status, ok := ee.Sys().(syscall.WaitStatus); ok { + 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()) + } +} diff --git a/cmd/podman/main.go b/cmd/podman/main.go index 98e2f23ca..b3faf05c0 100644 --- a/cmd/podman/main.go +++ b/cmd/podman/main.go @@ -49,6 +49,7 @@ var mainCommands = []*cobra.Command{ _pushCommand, &_rmiCommand, _saveCommand, + _stopCommand, _tagCommand, _versionCommand, imageCommand.Command, diff --git a/cmd/podman/stop.go b/cmd/podman/stop.go index ab9a2cf38..7bd160494 100644 --- a/cmd/podman/stop.go +++ b/cmd/podman/stop.go @@ -2,15 +2,14 @@ package main import ( "fmt" + "reflect" "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/containers/libpod/pkg/rootless" - opentracing "github.com/opentracing/opentracing-go" + "github.com/opentracing/opentracing-go" "github.com/pkg/errors" - "github.com/sirupsen/logrus" "github.com/spf13/cobra" ) @@ -52,63 +51,43 @@ func init() { markFlagHiddenForRemoteClient("latest", flags) } +// stopCmd stops a container or containers func stopCmd(c *cliconfig.StopValues) error { + if c.Flag("timeout").Changed && c.Flag("time").Changed { + return errors.New("the --timeout and --time flags are mutually exclusive") + } + if c.Bool("trace") { span, _ := opentracing.StartSpanFromContext(Ctx, "stopCmd") defer span.Finish() } rootless.SetSkipStorageSetup(true) - runtime, err := libpodruntime.GetRuntime(&c.PodmanCommand) + runtime, err := adapter.GetRuntime(&c.PodmanCommand) if err != nil { return errors.Wrapf(err, "could not get runtime") } defer runtime.Shutdown(false) - containers, err := getAllOrLatestContainers(&c.PodmanCommand, runtime, libpod.ContainerStateRunning, "running") + ok, failures, err := runtime.StopContainers(getContext(), c) if err != nil { - if len(containers) == 0 { - return err - } - fmt.Println(err.Error()) + return err } - if c.Flag("timeout").Changed && c.Flag("time").Changed { - return errors.New("the --timeout and --time flags are mutually exclusive") + for _, id := range ok { + fmt.Println(id) } - var stopFuncs []shared.ParallelWorkerInput - for _, ctr := range containers { - con := ctr - var stopTimeout uint - if c.Flag("timeout").Changed || c.Flag("time").Changed { - stopTimeout = c.Timeout - } else { - stopTimeout = ctr.StopTimeout() - logrus.Debugf("Set timeout to container %s default (%d)", ctr.ID(), stopTimeout) - } - f := func() error { - if err := con.StopWithTimeout(stopTimeout); err != nil { - if errors.Cause(err) == libpod.ErrCtrStopped { - logrus.Debugf("Container %s already stopped", con.ID()) - return nil - } - return err - } - return nil - } - stopFuncs = append(stopFuncs, shared.ParallelWorkerInput{ - ContainerID: con.ID(), - ParallelFunc: f, - }) - } + if len(failures) > 0 { + keys := reflect.ValueOf(failures).MapKeys() + lastKey := keys[len(keys)-1].String() + lastErr := failures[lastKey] + delete(failures, lastKey) - maxWorkers := shared.Parallelize("stop") - if c.GlobalIsSet("max-workers") { - maxWorkers = c.GlobalFlags.MaxWorks + for _, err := range failures { + outputError(err) + } + return lastErr } - logrus.Debugf("Setting maximum workers to %d", maxWorkers) - - stopErrors, errCount := shared.ParallelExecuteWorkerPool(maxWorkers, stopFuncs) - return printParallelOutput(stopErrors, errCount) + return nil } diff --git a/cmd/podman/varlink/io.podman.varlink b/cmd/podman/varlink/io.podman.varlink index 618af3481..73e6c15f9 100644 --- a/cmd/podman/varlink/io.podman.varlink +++ b/cmd/podman/varlink/io.podman.varlink @@ -459,6 +459,11 @@ method ListContainers() -> (containers: []Container) # [InspectContainer](#InspectContainer). method GetContainer(id: string) -> (container: Container) +# GetContainersByContext allows you to get a list of container ids depending on all, latest, or a list of +# container names. The definition of latest container means the latest by creation date. In a multi- +# user environment, results might differ from what you expect. +method GetContainersByContext(all: bool, latest: bool, args: []string) -> (containers: []string) + # CreateContainer creates a new container from an image. It uses a [Create](#Create) type for input. The minimum # input required for CreateContainer is an image name. If the image name is not found, an [ImageNotFound](#ImageNotFound) # error will be returned. Otherwise, the ID of the newly created container will be returned. diff --git a/pkg/adapter/containers.go b/pkg/adapter/containers.go new file mode 100644 index 000000000..b0c75cf49 --- /dev/null +++ b/pkg/adapter/containers.go @@ -0,0 +1,72 @@ +// +build !remoteclient + +package adapter + +import ( + "context" + + "github.com/containers/libpod/cmd/podman/cliconfig" + "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter/shortcuts" + "github.com/pkg/errors" +) + +// GetLatestContainer gets the latest Container and wraps it in an adapter Container +func (r *LocalRuntime) GetLatestContainer() (*Container, error) { + Container := Container{} + c, err := r.Runtime.GetLatestContainer() + Container.Container = c + return &Container, err +} + +// GetAllContainers gets all Containers and wraps each one in an adapter Container +func (r *LocalRuntime) GetAllContainers() ([]*Container, error) { + var containers []*Container + allContainers, err := r.Runtime.GetAllContainers() + if err != nil { + return nil, err + } + + for _, c := range allContainers { + containers = append(containers, &Container{c}) + } + return containers, nil +} + +// LookupContainer gets a Container by name or id and wraps it in an adapter Container +func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { + ctr, err := r.Runtime.LookupContainer(idOrName) + if err != nil { + return nil, err + } + return &Container{ctr}, nil +} + +// StopContainers stops container(s) based on CLI inputs. +// Returns list of successful id(s), map of failed id(s) + error, or error not from container +func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) { + timeout := uint(0) + if cli.Flags().Changed("timeout") { + timeout = uint(cli.Timeout) + } + + var ( + ok = []string{} + failures = map[string]error{} + ) + + ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime) + if err != nil { + return ok, failures, err + } + + for _, c := range ctrs { + err := c.StopWithTimeout(timeout) + if err != nil && errors.Cause(err) != libpod.ErrCtrStopped { + failures[c.ID()] = err + } else { + ok = append(ok, c.ID()) + } + } + return ok, failures, nil +} diff --git a/pkg/adapter/containers_remote.go b/pkg/adapter/containers_remote.go index 3f43a6905..df40c8efd 100644 --- a/pkg/adapter/containers_remote.go +++ b/pkg/adapter/containers_remote.go @@ -3,8 +3,13 @@ package adapter import ( + "context" "encoding/json" + "errors" + + "github.com/containers/libpod/cmd/podman/cliconfig" "github.com/containers/libpod/cmd/podman/shared" + "github.com/sirupsen/logrus" iopodman "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" @@ -29,6 +34,70 @@ func (c *Container) ID() string { return c.config.ID } +// 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 + // further looking into it for after devconf. + // The libpod function for this has no errors so we are kind of in a tough + // spot here. Logging the errors for now. + reply, err := iopodman.ContainerConfig().Call(r.Conn, name) + if err != nil { + logrus.Error("call to container.config failed") + } + data := libpod.ContainerConfig{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + logrus.Error("failed to unmarshal container inspect data") + } + return &data + +} + +// ContainerState returns the "state" of the container. +func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, error) { // no-lint + reply, err := iopodman.ContainerStateData().Call(r.Conn, name) + if err != nil { + return nil, err + } + data := libpod.ContainerState{} + if err := json.Unmarshal([]byte(reply), &data); err != nil { + return nil, err + } + return &data, err + +} + +// 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) { + state, err := r.ContainerState(idOrName) + if err != nil { + return nil, err + } + config := r.Config(idOrName) + if err != nil { + return nil, err + } + + return &Container{ + remoteContainer{ + r, + config, + state, + }, + }, nil +} + +func (r *LocalRuntime) GetLatestContainer() (*Container, error) { + reply, err := iopodman.GetContainersByContext().Call(r.Conn, false, true, nil) + if err != nil { + return nil, err + } + if len(reply) > 0 { + return r.LookupContainer(reply[0]) + } + return nil, errors.New("no containers exist") +} + // GetArtifact returns a container's artifacts func (c *Container) GetArtifact(name string) ([]byte, error) { var data []byte @@ -55,18 +124,42 @@ func (c *Container) Name() string { return c.config.Name } +// StopContainers stops requested containers using CLI inputs. +// Returns the list of stopped container ids, map of failed to stop container ids + errors, or any non-container error +func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) { + var ( + ok = []string{} + failures = map[string]error{} + ) + + ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs) + if err != nil { + return ok, failures, err + } + + for _, id := range ids { + stopped, err := iopodman.StopContainer().Call(r.Conn, id, int64(cli.Timeout)) + if err != nil { + failures[id] = err + } else { + ok = append(ok, stopped) + } + } + return ok, failures, nil +} + // 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 - //} + // data, err := ctr.Inspect(true) + // if err != nil { + // return shared.BatchContainerStruct{}, err + // } // - //size := new(shared.ContainerSize) - //size.RootFsSize = data.SizeRootFs - //size.RwSize = data.SizeRw + // size := new(shared.ContainerSize) + // size.RootFsSize = data.SizeRootFs + // size.RwSize = data.SizeRw bcs := shared.BatchContainerStruct{ ConConfig: ctr.config, @@ -75,7 +168,7 @@ func BatchContainerOp(ctr *Container, opts shared.PsOptions) (shared.BatchContai Pid: ctr.state.PID, StartedTime: ctr.state.StartedTime, ExitedTime: ctr.state.FinishedTime, - //Size: size, + // Size: size, } return bcs, nil } diff --git a/pkg/adapter/runtime.go b/pkg/adapter/runtime.go index 8624981b1..5be2ca150 100644 --- a/pkg/adapter/runtime.go +++ b/pkg/adapter/runtime.go @@ -108,15 +108,6 @@ func (r *LocalRuntime) RemoveImage(ctx context.Context, img *ContainerImage, for return r.Runtime.RemoveImage(ctx, img.Image, force) } -// LookupContainer ... -func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) { - ctr, err := r.Runtime.LookupContainer(idOrName) - if err != nil { - return nil, err - } - return &Container{ctr}, nil -} - // PruneImages is wrapper into PruneImages within the image pkg func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return r.ImageRuntime().PruneImages(all) diff --git a/pkg/adapter/runtime_remote.go b/pkg/adapter/runtime_remote.go index 29b43e9b0..14cda7bff 100644 --- a/pkg/adapter/runtime_remote.go +++ b/pkg/adapter/runtime_remote.go @@ -5,7 +5,6 @@ package adapter import ( "bufio" "context" - "encoding/json" "fmt" "io" "io/ioutil" @@ -49,14 +48,13 @@ func GetRuntime(c *cliconfig.PodmanCommand) (*LocalRuntime, error) { if err != nil { return nil, err } - rr := RemoteRuntime{ - Conn: conn, - Remote: true, - } - foo := LocalRuntime{ - &rr, - } - return &foo, nil + + return &LocalRuntime{ + &RemoteRuntime{ + Conn: conn, + Remote: true, + }, + }, nil } // Shutdown is a bogus wrapper for compat with the libpod runtime @@ -315,66 +313,6 @@ func (ci *ContainerImage) History(ctx context.Context) ([]*image.History, error) return imageHistories, 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) { - state, err := r.ContainerState(idOrName) - if err != nil { - return nil, err - } - config := r.Config(idOrName) - if err != nil { - return nil, err - } - - rc := remoteContainer{ - r, - config, - state, - } - - c := Container{ - rc, - } - return &c, nil -} - -func (r *LocalRuntime) GetLatestContainer() (*Container, error) { - return nil, libpod.ErrNotImplemented -} - -// ContainerState returns the "state" of the container. -func (r *LocalRuntime) ContainerState(name string) (*libpod.ContainerState, error) { //no-lint - reply, err := iopodman.ContainerStateData().Call(r.Conn, name) - if err != nil { - return nil, err - } - data := libpod.ContainerState{} - if err := json.Unmarshal([]byte(reply), &data); err != nil { - return nil, err - } - return &data, 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 - // further looking into it for after devconf. - // The libpod function for this has no errors so we are kind of in a tough - // spot here. Logging the errors for now. - reply, err := iopodman.ContainerConfig().Call(r.Conn, name) - if err != nil { - logrus.Error("call to container.config failed") - } - data := libpod.ContainerConfig{} - if err := json.Unmarshal([]byte(reply), &data); err != nil { - logrus.Error("failed to unmarshal container inspect data") - } - return &data - -} - // PruneImages is the wrapper call for a remote-client to prune images func (r *LocalRuntime) PruneImages(all bool) ([]string, error) { return iopodman.ImagesPrune().Call(r.Conn, all) diff --git a/pkg/adapter/shortcuts/shortcuts.go b/pkg/adapter/shortcuts/shortcuts.go index 0633399ae..677d88457 100644 --- a/pkg/adapter/shortcuts/shortcuts.go +++ b/pkg/adapter/shortcuts/shortcuts.go @@ -25,3 +25,30 @@ func GetPodsByContext(all, latest bool, pods []string, runtime *libpod.Runtime) } return outpods, nil } + +// GetContainersByContext gets pods whether all, latest, or a slice of names/ids +func GetContainersByContext(all, latest bool, names []string, runtime *libpod.Runtime) ([]*libpod.Container, error) { + var ctrs = []*libpod.Container{} + + if all { + return runtime.GetAllContainers() + } + + if latest { + c, err := runtime.GetLatestContainer() + if err != nil { + return nil, err + } + ctrs = append(ctrs, c) + return ctrs, nil + } + + for _, c := range names { + ctr, err := runtime.LookupContainer(c) + if err != nil { + return nil, err + } + ctrs = append(ctrs, ctr) + } + return ctrs, nil +} diff --git a/pkg/varlinkapi/containers.go b/pkg/varlinkapi/containers.go index ad9f107a7..27b8d15d2 100644 --- a/pkg/varlinkapi/containers.go +++ b/pkg/varlinkapi/containers.go @@ -13,6 +13,7 @@ import ( "github.com/containers/libpod/cmd/podman/shared" "github.com/containers/libpod/cmd/podman/varlink" "github.com/containers/libpod/libpod" + "github.com/containers/libpod/pkg/adapter/shortcuts" cc "github.com/containers/libpod/pkg/spec" "github.com/containers/storage/pkg/archive" "github.com/pkg/errors" @@ -60,6 +61,21 @@ func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error { return call.ReplyGetContainer(makeListContainer(ctr.ID(), batchInfo)) } +// GetContainersByContext returns a slice of container ids based on all, latest, or a list +func (i *LibpodAPI) GetContainersByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error { + var ids []string + + ctrs, err := shortcuts.GetContainersByContext(all, latest, input, i.Runtime) + if err != nil { + return call.ReplyErrorOccurred(err.Error()) + } + + for _, c := range ctrs { + ids = append(ids, c.ID()) + } + return call.ReplyGetContainersByContext(ids) +} + // InspectContainer ... func (i *LibpodAPI) InspectContainer(call iopodman.VarlinkCall, name string) error { ctr, err := i.Runtime.LookupContainer(name) -- cgit v1.2.3-54-g00ecf