Files
podman/pkg/adapter/containers_remote.go
baude 00262edf6c make remote resize channel buffered
when doing any sort of attach to a container, a sigwinch is sent
followed by a resize event.  this is fine for the local client but when
doing things over the varlink, the first sigwinch is wiped out by the
immediate resize event and is therefore lost.  by making the channel
buffered, both events are processed after the varlink connection is
established.

Signed-off-by: baude <bbaude@redhat.com>
2019-05-21 09:13:19 -05:00

989 lines
27 KiB
Go

// +build remoteclient
package adapter
import (
"context"
"encoding/json"
"fmt"
"io"
"os"
"strconv"
"syscall"
"time"
"github.com/containers/libpod/cmd/podman/cliconfig"
"github.com/containers/libpod/cmd/podman/shared"
iopodman "github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/inspect"
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
"github.com/cri-o/ocicni/pkg/ocicni"
"github.com/docker/docker/pkg/term"
"github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/varlink/go/varlink"
"golang.org/x/crypto/ssh/terminal"
"k8s.io/client-go/tools/remotecommand"
)
// Inspect returns an inspect struct from varlink
func (c *Container) Inspect(size bool) (*inspect.ContainerInspectData, error) {
reply, err := iopodman.ContainerInspectData().Call(c.Runtime.Conn, c.ID(), size)
if err != nil {
return nil, err
}
data := inspect.ContainerInspectData{}
if err := json.Unmarshal([]byte(reply), &data); err != nil {
return nil, err
}
return &data, err
}
// ID returns the ID of the container
func (c *Container) ID() string {
return c.config.ID
}
// Restart a single container
func (c *Container) Restart(timeout int64) error {
_, err := iopodman.RestartContainer().Call(c.Runtime.Conn, c.ID(), timeout)
return err
}
// 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
}
func (c *Container) PortMappings() ([]ocicni.PortMapping, error) {
// First check if the container belongs to a network namespace (like a pod)
// Taken from libpod portmappings()
if len(c.config.NetNsCtr) > 0 {
netNsCtr, err := c.Runtime.LookupContainer(c.config.NetNsCtr)
if err != nil {
return nil, errors.Wrapf(err, "unable to lookup network namespace for container %s", c.ID())
}
return netNsCtr.PortMappings()
}
return c.config.PortMappings, nil
}
// 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
}
// Spec obtains the container spec.
func (r *LocalRuntime) Spec(name string) (*specs.Spec, error) {
reply, err := iopodman.Spec().Call(r.Conn, name)
if err != nil {
return nil, err
}
data := specs.Spec{}
if err := json.Unmarshal([]byte(reply), &data); err != nil {
return nil, err
}
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) {
state, err := r.ContainerState(idOrName)
if err != nil {
return nil, err
}
config := r.Config(idOrName)
return &Container{
remoteContainer{
r,
config,
state,
},
}, nil
}
// GetAllContainers returns all containers in a slice
func (r *LocalRuntime) GetAllContainers() ([]*Container, error) {
var containers []*Container
ctrs, err := iopodman.GetContainersByContext().Call(r.Conn, true, false, []string{})
if err != nil {
return nil, err
}
for _, ctr := range ctrs {
container, err := r.LookupContainer(ctr)
if err != nil {
return nil, err
}
containers = append(containers, container)
}
return containers, 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 {
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
reply, err := iopodman.ContainerArtifacts().Call(c.Runtime.Conn, c.ID(), name)
if err != nil {
return nil, err
}
if err := json.Unmarshal([]byte(reply), &data); err != nil {
return nil, err
}
return data, err
}
// Config returns a container's Config ... same as ctr.Config()
func (c *Container) Config() *libpod.ContainerConfig {
if c.config != nil {
return c.config
}
return c.Runtime.Config(c.ID())
}
// Name returns the name of the container
func (c *Container) Name() string {
return c.config.Name
}
// StopContainers stops requested containers using varlink.
// 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, TranslateError(err)
}
for _, id := range ids {
if _, err := iopodman.StopContainer().Call(r.Conn, id, int64(cli.Timeout)); err != nil {
transError := TranslateError(err)
if errors.Cause(transError) == libpod.ErrCtrStopped {
ok = append(ok, id)
continue
}
if errors.Cause(transError) == libpod.ErrCtrStateInvalid && cli.All {
ok = append(ok, id)
continue
}
failures[id] = err
} else {
// We should be using ID here because in varlink, only successful returns
// include the string id
ok = append(ok, id)
}
}
return ok, failures, nil
}
// InitContainers initializes container(s) based on Varlink.
// It returns a list of successful ID(s), a map of failed container ID to error,
// or an error if a more general error occurred.
func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]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 nil, nil, err
}
for _, id := range ids {
initialized, err := iopodman.InitContainer().Call(r.Conn, id)
if err != nil {
if cli.All {
switch err.(type) {
case *iopodman.InvalidState:
ok = append(ok, initialized)
default:
failures[id] = err
}
} else {
failures[id] = err
}
} else {
ok = append(ok, initialized)
}
}
return ok, failures, nil
}
// KillContainers sends signal to container(s) based on varlink.
// Returns list of successful id(s), map of failed id(s) + error, or error not from container
func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]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 {
killed, err := iopodman.KillContainer().Call(r.Conn, id, int64(signal))
if err != nil {
failures[id] = err
} else {
ok = append(ok, killed)
}
}
return ok, failures, nil
}
// RemoveContainer removes container(s) based on varlink inputs.
func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) {
ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
if err != nil {
return nil, nil, TranslateError(err)
}
var (
ok = []string{}
failures = map[string]error{}
)
for _, id := range ids {
_, err := iopodman.RemoveContainer().Call(r.Conn, id, cli.Force, cli.Volumes)
if err != nil {
failures[id] = err
} else {
ok = append(ok, id)
}
}
return ok, failures, nil
}
// UmountRootFilesystems umounts container(s) root filesystems based on varlink inputs
func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig.UmountValues) ([]string, map[string]error, error) {
ids, err := iopodman.GetContainersByContext().Call(r.Conn, cli.All, cli.Latest, cli.InputArgs)
if err != nil {
return nil, nil, err
}
var (
ok = []string{}
failures = map[string]error{}
)
for _, id := range ids {
err := iopodman.UnmountContainer().Call(r.Conn, id, cli.Force)
if err != nil {
failures[id] = err
} else {
ok = append(ok, id)
}
}
return ok, failures, nil
}
// WaitOnContainers waits for all given container(s) to stop.
// interval is currently ignored.
func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
)
ids, err := iopodman.GetContainersByContext().Call(r.Conn, false, cli.Latest, cli.InputArgs)
if err != nil {
return ok, failures, err
}
for _, id := range ids {
stopped, err := iopodman.WaitContainer().Call(r.Conn, id, int64(interval))
if err != nil {
failures[id] = err
} else {
ok = append(ok, strconv.FormatInt(stopped, 10))
}
}
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
// }
//
// size := new(shared.ContainerSize)
// size.RootFsSize = data.SizeRootFs
// size.RwSize = data.SizeRw
bcs := shared.BatchContainerStruct{
ConConfig: ctr.config,
ConState: ctr.state.State,
ExitCode: ctr.state.ExitCode,
Pid: ctr.state.PID,
StartedTime: ctr.state.StartedTime,
ExitedTime: ctr.state.FinishedTime,
// Size: size,
}
return bcs, nil
}
// Logs one or more containers over a varlink connection
func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *libpod.LogOptions) error {
// GetContainersLogs
reply, err := iopodman.GetContainersLogs().Send(r.Conn, uint64(varlink.More), c.InputArgs, c.Follow, c.Latest, options.Since.Format(time.RFC3339Nano), int64(c.Tail), c.Timestamps)
if err != nil {
return errors.Wrapf(err, "failed to get container logs")
}
if len(c.InputArgs) > 1 {
options.Multi = true
}
for {
log, flags, err := reply()
if err != nil {
return err
}
if log.Time == "" && log.Msg == "" {
// We got a blank log line which can signal end of stream
break
}
lTime, err := time.Parse(time.RFC3339Nano, log.Time)
if err != nil {
return errors.Wrapf(err, "unable to parse time of log %s", log.Time)
}
logLine := libpod.LogLine{
Device: log.Device,
ParseLogType: log.ParseLogType,
Time: lTime,
Msg: log.Msg,
CID: log.Cid,
}
fmt.Println(logLine.String(options))
if flags&varlink.Continues == 0 {
break
}
}
return nil
}
// CreateContainer creates a container from the cli over varlink
func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) {
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) {
// 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())
if err != nil {
return 0, err
}
if c.Bool("detach") {
_, err := iopodman.StartContainer().Call(r.Conn, cid)
fmt.Println(cid)
return 0, err
}
errChan, err := r.attach(ctx, os.Stdin, os.Stdout, cid, true, c.String("detach-keys"))
if err != nil {
return 0, err
}
finalError := <-errChan
return 0, finalError
}
func ReadExitFile(runtimeTmp, ctrID string) (int, error) {
return 0, libpod.ErrNotImplemented
}
// 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)
PsOpts := iopodman.PsOpts{
All: c.All,
Filters: &c.Filter,
Last: &last,
Latest: &c.Latest,
NoTrunc: &c.NoTrunct,
Pod: &c.Pod,
Quiet: &c.Quiet,
Sort: &c.Sort,
Sync: &c.Sync,
}
containers, err := iopodman.Ps().Call(r.Conn, PsOpts)
if err != nil {
return nil, err
}
for _, ctr := range containers {
createdAt, err := time.Parse(time.RFC3339Nano, ctr.CreatedAt)
if err != nil {
return nil, err
}
exitedAt, err := time.Parse(time.RFC3339Nano, ctr.ExitedAt)
if err != nil {
return nil, err
}
startedAt, err := time.Parse(time.RFC3339Nano, ctr.StartedAt)
if err != nil {
return nil, err
}
containerSize := shared.ContainerSize{
RootFsSize: ctr.RootFsSize,
RwSize: ctr.RwSize,
}
state, err := libpod.StringToContainerStatus(ctr.State)
if err != nil {
return nil, err
}
psc := shared.PsContainerOutput{
ID: ctr.Id,
Image: ctr.Image,
Command: ctr.Command,
Created: ctr.Created,
Ports: ctr.Ports,
Names: ctr.Names,
IsInfra: ctr.IsInfra,
Status: ctr.Status,
State: state,
Pid: int(ctr.PidNum),
Size: &containerSize,
Pod: ctr.Pod,
CreatedAt: createdAt,
ExitedAt: exitedAt,
StartedAt: startedAt,
Labels: ctr.Labels,
PID: ctr.NsPid,
Cgroup: ctr.Cgroup,
IPC: ctr.Ipc,
MNT: ctr.Mnt,
NET: ctr.Net,
PIDNS: ctr.PidNs,
User: ctr.User,
UTS: ctr.Uts,
Mounts: ctr.Mounts,
}
psContainers = append(psContainers, psc)
}
return psContainers, nil
}
func (r *LocalRuntime) attach(ctx context.Context, stdin, stdout *os.File, cid string, start bool, detachKeys string) (chan error, error) {
var (
oldTermState *term.State
)
errChan := make(chan error)
spec, err := r.Spec(cid)
if err != nil {
return nil, err
}
resize := make(chan remotecommand.TerminalSize, 5)
haveTerminal := terminal.IsTerminal(int(os.Stdin.Fd()))
// Check if we are attached to a terminal. If we are, generate resize
// events, and set the terminal to raw mode
if haveTerminal && spec.Process.Terminal {
logrus.Debugf("Handling terminal attach")
subCtx, cancel := context.WithCancel(ctx)
defer cancel()
resizeTty(subCtx, resize)
oldTermState, err = term.SaveState(os.Stdin.Fd())
if err != nil {
return nil, errors.Wrapf(err, "unable to save terminal state")
}
logrus.SetFormatter(&RawTtyFormatter{})
term.SetRawTerminal(os.Stdin.Fd())
}
// TODO add detach keys support
_, err = iopodman.Attach().Send(r.Conn, varlink.Upgrade, cid, detachKeys, start)
if err != nil {
restoreTerminal(oldTermState)
return nil, err
}
// These are the varlink sockets
reader := r.Conn.Reader
writer := r.Conn.Writer
// These are the special writers that encode input from the client.
varlinkStdinWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.ToStdin)
varlinkResizeWriter := virtwriter.NewVirtWriteCloser(writer, virtwriter.TerminalResize)
go func() {
// Read from the wire and direct to stdout or stderr
err := virtwriter.Reader(reader, stdout, os.Stderr, nil, nil)
defer restoreTerminal(oldTermState)
errChan <- err
}()
go func() {
for termResize := range resize {
b, err := json.Marshal(termResize)
if err != nil {
defer restoreTerminal(oldTermState)
errChan <- err
}
_, err = varlinkResizeWriter.Write(b)
if err != nil {
defer restoreTerminal(oldTermState)
errChan <- err
}
}
}()
// Takes stdinput and sends it over the wire after being encoded
go func() {
if _, err := io.Copy(varlinkStdinWriter, stdin); err != nil {
defer restoreTerminal(oldTermState)
errChan <- err
}
}()
return errChan, nil
}
// Attach to a remote terminal
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
ctr, err := r.LookupContainer(c.InputArgs[0])
if err != nil {
return nil
}
if ctr.state.State != libpod.ContainerStateRunning {
return errors.New("you can only attach to running containers")
}
inputStream := os.Stdin
if c.NoStdin {
inputStream, err = os.Open(os.DevNull)
if err != nil {
return err
}
}
errChan, err := r.attach(ctx, inputStream, os.Stdout, c.InputArgs[0], false, c.DetachKeys)
if err != nil {
return err
}
return <-errChan
}
// Checkpoint one or more containers
func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues, options libpod.ContainerCheckpointOptions) error {
var lastError error
ids, err := iopodman.GetContainersByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs)
if err != nil {
return err
}
if c.All {
// We dont have a great way to get all the running containers, so need to get all and then
// check status on them bc checkpoint considers checkpointing a stopped container an error
var runningIds []string
for _, id := range ids {
ctr, err := r.LookupContainer(id)
if err != nil {
return err
}
if ctr.state.State == libpod.ContainerStateRunning {
runningIds = append(runningIds, id)
}
}
ids = runningIds
}
for _, id := range ids {
if _, err := iopodman.ContainerCheckpoint().Call(r.Conn, id, options.Keep, options.KeepRunning, options.TCPEstablished); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to checkpoint container %v", id)
} else {
fmt.Println(id)
}
}
return lastError
}
// Restore one or more containers
func (r *LocalRuntime) Restore(c *cliconfig.RestoreValues, options libpod.ContainerCheckpointOptions) error {
var lastError error
ids, err := iopodman.GetContainersByContext().Call(r.Conn, c.All, c.Latest, c.InputArgs)
if err != nil {
return err
}
if c.All {
// We dont have a great way to get all the exited containers, so need to get all and then
// check status on them bc checkpoint considers restoring a running container an error
var exitedIDs []string
for _, id := range ids {
ctr, err := r.LookupContainer(id)
if err != nil {
return err
}
if ctr.state.State != libpod.ContainerStateRunning {
exitedIDs = append(exitedIDs, id)
}
}
ids = exitedIDs
}
for _, id := range ids {
if _, err := iopodman.ContainerRestore().Call(r.Conn, id, options.Keep, options.TCPEstablished); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
lastError = errors.Wrapf(err, "failed to restore container %v", id)
} else {
fmt.Println(id)
}
}
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
}
// Restart restarts a container over varlink
func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues) ([]string, map[string]error, error) {
var (
containers []*Container
restartContainers []*Container
err error
ok = []string{}
failures = map[string]error{}
)
useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed
inputTimeout := c.Timeout
if c.Latest {
lastCtr, err := r.GetLatestContainer()
if err != nil {
return nil, nil, errors.Wrapf(err, "unable to get latest container")
}
restartContainers = append(restartContainers, lastCtr)
} else if c.Running {
containers, err = r.LookupContainersWithStatus([]string{libpod.ContainerStateRunning.String()})
if err != nil {
return nil, nil, err
}
restartContainers = append(restartContainers, containers...)
} else if c.All {
containers, err = r.GetAllContainers()
if err != nil {
return nil, nil, err
}
restartContainers = append(restartContainers, containers...)
} else {
for _, id := range c.InputArgs {
ctr, err := r.LookupContainer(id)
if err != nil {
return nil, nil, err
}
restartContainers = append(restartContainers, ctr)
}
}
for _, c := range restartContainers {
c := c
timeout := c.config.StopTimeout
if useTimeout {
timeout = inputTimeout
}
err := c.Restart(int64(timeout))
if err != nil {
failures[c.ID()] = err
} else {
ok = append(ok, c.ID())
}
}
return ok, failures, nil
}
// Top display the running processes of a container
func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) {
var (
ctr *Container
err error
descriptors []string
)
if cli.Latest {
ctr, err = r.GetLatestContainer()
descriptors = cli.InputArgs
} else {
ctr, err = r.LookupContainer(cli.InputArgs[0])
descriptors = cli.InputArgs[1:]
}
if err != nil {
return nil, err
}
return iopodman.Top().Call(r.Conn, ctr.ID(), descriptors)
}
// Prune removes stopped containers
func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, force bool) ([]string, map[string]error, error) {
var (
ok = []string{}
failures = map[string]error{}
ctrs []*Container
err error
)
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
filters := []string{libpod.ContainerStateExited.String()}
ctrs, err = r.LookupContainersWithStatus(filters)
if err != nil {
return ok, failures, err
}
for _, c := range ctrs {
c := c
_, err := iopodman.RemoveContainer().Call(r.Conn, c.ID(), false, false)
if err != nil {
failures[c.ID()] = err
} else {
ok = append(ok, c.ID())
}
}
return ok, failures, nil
}
// Cleanup any leftovers bits of stopped containers
func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) {
return nil, nil, errors.New("container cleanup not supported for remote clients")
}
// Port displays port information about existing containers
func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
var (
containers []*Container
err error
)
// This one is a bit odd because when all is used, we only use running containers.
if !c.All {
containers, err = r.GetContainersByContext(false, c.Latest, c.InputArgs)
} else {
// we need to only use running containers if all
filters := []string{libpod.ContainerStateRunning.String()}
containers, err = r.LookupContainersWithStatus(filters)
}
if err != nil {
return nil, err
}
return containers, nil
}
// GenerateSystemd creates a systemd until for a container
func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
return iopodman.GenerateSystemd().Call(r.Conn, c.InputArgs[0], c.RestartPolicy, int64(c.StopTimeout), c.Name)
}
// GetNamespaces returns namespace information about a container for PS
func (r *LocalRuntime) GetNamespaces(container shared.PsContainerOutput) *shared.Namespace {
ns := shared.Namespace{
PID: container.PID,
Cgroup: container.Cgroup,
IPC: container.IPC,
MNT: container.MNT,
NET: container.NET,
PIDNS: container.PIDNS,
User: container.User,
UTS: container.UTS,
}
return &ns
}