Files
podman/pkg/varlinkapi/containers.go
Peter Hunt 479eeac62c move editing of exitCode to runtime
There's no way to get the error if we successfully get an exit code (as it's just printed to stderr instead).
instead of relying on the error to be passed to podman, and edit based on the error code, process it on the varlink side instead

Also move error codes to define package

Signed-off-by: Peter Hunt <pehunt@redhat.com>
2019-07-23 13:29:33 -04:00

854 lines
25 KiB
Go

// +build varlink
package varlinkapi
import (
"bufio"
"encoding/json"
"fmt"
"io"
"io/ioutil"
"os"
"sync"
"syscall"
"time"
"github.com/containers/libpod/cmd/podman/shared"
"github.com/containers/libpod/cmd/podman/varlink"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/varlinkapi/virtwriter"
"github.com/containers/storage/pkg/archive"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"k8s.io/client-go/tools/remotecommand"
)
// ListContainers ...
func (i *LibpodAPI) ListContainers(call iopodman.VarlinkCall) error {
var (
listContainers []iopodman.Container
)
containers, err := i.Runtime.GetAllContainers()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
opts := shared.PsOptions{
Namespace: true,
Size: true,
}
for _, ctr := range containers {
batchInfo, err := shared.BatchContainerOp(ctr, opts)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
listContainers = append(listContainers, makeListContainer(ctr.ID(), batchInfo))
}
return call.ReplyListContainers(listContainers)
}
func (i *LibpodAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error {
var (
containers []iopodman.PsContainer
)
maxWorkers := shared.Parallelize("ps")
psOpts := makePsOpts(opts)
filters := []string{}
if opts.Filters != nil {
filters = *opts.Filters
}
psContainerOutputs, err := shared.GetPsContainerOutput(i.Runtime, psOpts, filters, maxWorkers)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
for _, ctr := range psContainerOutputs {
container := iopodman.PsContainer{
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: ctr.State.String(),
PidNum: int64(ctr.Pid),
Pod: ctr.Pod,
CreatedAt: ctr.CreatedAt.Format(time.RFC3339Nano),
ExitedAt: ctr.ExitedAt.Format(time.RFC3339Nano),
StartedAt: ctr.StartedAt.Format(time.RFC3339Nano),
Labels: ctr.Labels,
NsPid: ctr.PID,
Cgroup: ctr.Cgroup,
Ipc: ctr.Cgroup,
Mnt: ctr.MNT,
Net: ctr.NET,
PidNs: ctr.PIDNS,
User: ctr.User,
Uts: ctr.UTS,
Mounts: ctr.Mounts,
}
if ctr.Size != nil {
container.RootFsSize = ctr.Size.RootFsSize
container.RwSize = ctr.Size.RwSize
}
containers = append(containers, container)
}
return call.ReplyPs(containers)
}
// GetContainer ...
func (i *LibpodAPI) GetContainer(call iopodman.VarlinkCall, id string) error {
ctr, err := i.Runtime.LookupContainer(id)
if err != nil {
return call.ReplyContainerNotFound(id, err.Error())
}
opts := shared.PsOptions{
Namespace: true,
Size: true,
}
batchInfo, err := shared.BatchContainerOp(ctr, opts)
if err != nil {
return call.ReplyErrorOccurred(err.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 {
if errors.Cause(err) == define.ErrNoSuchCtr {
return call.ReplyContainerNotFound("", err.Error())
}
return call.ReplyErrorOccurred(err.Error())
}
for _, c := range ctrs {
ids = append(ids, c.ID())
}
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 := define.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)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
data, err := ctr.Inspect(true)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
b, err := json.Marshal(data)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize"))
}
return call.ReplyInspectContainer(string(b))
}
// ListContainerProcesses ...
func (i *LibpodAPI) ListContainerProcesses(call iopodman.VarlinkCall, name string, opts []string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
containerState, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if containerState != define.ContainerStateRunning {
return call.ReplyErrorOccurred(fmt.Sprintf("container %s is not running", name))
}
var psArgs []string
psOpts := []string{"user", "pid", "ppid", "pcpu", "etime", "tty", "time", "comm"}
if len(opts) > 1 {
psOpts = opts
}
psArgs = append(psArgs, psOpts...)
psOutput, err := ctr.GetContainerPidInformation(psArgs)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyListContainerProcesses(psOutput)
}
// GetContainerLogs ...
func (i *LibpodAPI) GetContainerLogs(call iopodman.VarlinkCall, name string) error {
var logs []string
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
logPath := ctr.LogPath()
containerState, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if _, err := os.Stat(logPath); err != nil {
if containerState == define.ContainerStateConfigured {
return call.ReplyGetContainerLogs(logs)
}
}
file, err := os.Open(logPath)
if err != nil {
return errors.Wrapf(err, "unable to read container log file")
}
defer file.Close()
reader := bufio.NewReader(file)
if call.WantsMore() {
call.Continues = true
}
for {
line, err := reader.ReadString('\n')
// We've read the entire file
if err == io.EOF {
if !call.WantsMore() {
// If this is a non-following log request, we return what we have
break
} else {
// If we want to follow, return what we have, wipe the slice, and make
// sure the container is still running before iterating.
call.ReplyGetContainerLogs(logs)
logs = []string{}
time.Sleep(1 * time.Second)
state, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if state != define.ContainerStateRunning && state != define.ContainerStatePaused {
return call.ReplyErrorOccurred(fmt.Sprintf("%s is no longer running", ctr.ID()))
}
}
} else if err != nil {
return call.ReplyErrorOccurred(err.Error())
} else {
logs = append(logs, line)
}
}
call.Continues = false
return call.ReplyGetContainerLogs(logs)
}
// ListContainerChanges ...
func (i *LibpodAPI) ListContainerChanges(call iopodman.VarlinkCall, name string) error {
changes, err := i.Runtime.GetDiff("", name)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
result := iopodman.ContainerChanges{}
for _, change := range changes {
switch change.Kind {
case archive.ChangeModify:
result.Changed = append(result.Changed, change.Path)
case archive.ChangeDelete:
result.Deleted = append(result.Deleted, change.Path)
case archive.ChangeAdd:
result.Added = append(result.Added, change.Path)
}
}
return call.ReplyListContainerChanges(result)
}
// ExportContainer ...
func (i *LibpodAPI) ExportContainer(call iopodman.VarlinkCall, name, outPath string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
outputFile, err := ioutil.TempFile("", "varlink_recv")
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
defer outputFile.Close()
if outPath == "" {
outPath = outputFile.Name()
}
if err := ctr.Export(outPath); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyExportContainer(outPath)
}
// GetContainerStats ...
func (i *LibpodAPI) GetContainerStats(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
if err != nil {
if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyNoContainerRunning()
}
return call.ReplyErrorOccurred(err.Error())
}
cs := iopodman.ContainerStats{
Id: ctr.ID(),
Name: ctr.Name(),
Cpu: containerStats.CPU,
Cpu_nano: int64(containerStats.CPUNano),
System_nano: int64(containerStats.SystemNano),
Mem_usage: int64(containerStats.MemUsage),
Mem_limit: int64(containerStats.MemLimit),
Mem_perc: containerStats.MemPerc,
Net_input: int64(containerStats.NetInput),
Net_output: int64(containerStats.NetOutput),
Block_input: int64(containerStats.BlockInput),
Block_output: int64(containerStats.BlockOutput),
Pids: int64(containerStats.PIDs),
}
return call.ReplyGetContainerStats(cs)
}
// StartContainer ...
func (i *LibpodAPI) StartContainer(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
state, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if state == define.ContainerStateRunning || state == define.ContainerStatePaused {
return call.ReplyErrorOccurred("container is already running or paused")
}
recursive := false
if ctr.PodID() != "" {
recursive = true
}
if err := ctr.Start(getContext(), recursive); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyStartContainer(ctr.ID())
}
// InitContainer initializes the container given by Varlink.
func (i *LibpodAPI) InitContainer(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.Init(getContext()); err != nil {
if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyInvalidState(ctr.ID(), err.Error())
}
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyInitContainer(ctr.ID())
}
// StopContainer ...
func (i *LibpodAPI) StopContainer(call iopodman.VarlinkCall, name string, timeout int64) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.StopWithTimeout(uint(timeout)); err != nil {
if errors.Cause(err) == define.ErrCtrStopped {
return call.ReplyErrCtrStopped(ctr.ID())
}
if errors.Cause(err) == define.ErrCtrStateInvalid {
return call.ReplyInvalidState(ctr.ID(), err.Error())
}
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyStopContainer(ctr.ID())
}
// RestartContainer ...
func (i *LibpodAPI) RestartContainer(call iopodman.VarlinkCall, name string, timeout int64) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.RestartWithTimeout(getContext(), uint(timeout)); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRestartContainer(ctr.ID())
}
// ContainerExists looks in local storage for the existence of a container
func (i *LibpodAPI) ContainerExists(call iopodman.VarlinkCall, name string) error {
_, err := i.Runtime.LookupContainer(name)
if errors.Cause(err) == define.ErrNoSuchCtr {
return call.ReplyContainerExists(1)
}
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyContainerExists(0)
}
// KillContainer kills a running container. If you want to use the default SIGTERM signal, just send a -1
// for the signal arg.
func (i *LibpodAPI) KillContainer(call iopodman.VarlinkCall, name string, signal int64) error {
killSignal := uint(syscall.SIGTERM)
if signal != -1 {
killSignal = uint(signal)
}
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.Kill(killSignal); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyKillContainer(ctr.ID())
}
// PauseContainer ...
func (i *LibpodAPI) PauseContainer(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.Pause(); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyPauseContainer(ctr.ID())
}
// UnpauseContainer ...
func (i *LibpodAPI) UnpauseContainer(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := ctr.Unpause(); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyUnpauseContainer(ctr.ID())
}
// WaitContainer ...
func (i *LibpodAPI) WaitContainer(call iopodman.VarlinkCall, name string, interval int64) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
exitCode, err := ctr.WaitWithInterval(time.Duration(interval))
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyWaitContainer(int64(exitCode))
}
// RemoveContainer ...
func (i *LibpodAPI) RemoveContainer(call iopodman.VarlinkCall, name string, force bool, removeVolumes bool) error {
ctx := getContext()
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
if err := i.Runtime.RemoveContainer(ctx, ctr, force, removeVolumes); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRemoveContainer(ctr.ID())
}
// DeleteStoppedContainers ...
func (i *LibpodAPI) DeleteStoppedContainers(call iopodman.VarlinkCall) error {
ctx := getContext()
var deletedContainers []string
containers, err := i.Runtime.GetAllContainers()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
for _, ctr := range containers {
state, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if state != define.ContainerStateRunning {
if err := i.Runtime.RemoveContainer(ctx, ctr, false, false); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
deletedContainers = append(deletedContainers, ctr.ID())
}
}
return call.ReplyDeleteStoppedContainers(deletedContainers)
}
// GetAttachSockets ...
func (i *LibpodAPI) GetAttachSockets(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
status, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
// If the container hasn't been run, we need to run init
// so the conmon sockets get created.
if status == define.ContainerStateConfigured || status == define.ContainerStateStopped {
if err := ctr.Init(getContext()); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
}
s := iopodman.Sockets{
Container_id: ctr.ID(),
Io_socket: ctr.AttachSocketPath(),
Control_socket: ctr.ControlSocketPath(),
}
return call.ReplyGetAttachSockets(s)
}
// ContainerCheckpoint ...
func (i *LibpodAPI) ContainerCheckpoint(call iopodman.VarlinkCall, name string, keep, leaveRunning, tcpEstablished bool) error {
ctx := getContext()
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
options := libpod.ContainerCheckpointOptions{
Keep: keep,
TCPEstablished: tcpEstablished,
KeepRunning: leaveRunning,
}
if err := ctr.Checkpoint(ctx, options); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyContainerCheckpoint(ctr.ID())
}
// ContainerRestore ...
func (i *LibpodAPI) ContainerRestore(call iopodman.VarlinkCall, name string, keep, tcpEstablished bool) error {
ctx := getContext()
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
options := libpod.ContainerCheckpointOptions{
Keep: keep,
TCPEstablished: tcpEstablished,
}
if err := ctr.Restore(ctx, options); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyContainerRestore(ctr.ID())
}
// ContainerConfig returns just the container.config struct
func (i *LibpodAPI) ContainerConfig(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
config := ctr.Config()
b, err := json.Marshal(config)
if err != nil {
return call.ReplyErrorOccurred("unable to serialize container config")
}
return call.ReplyContainerConfig(string(b))
}
// ContainerArtifacts returns an untouched container's artifact in string format
func (i *LibpodAPI) ContainerArtifacts(call iopodman.VarlinkCall, name, artifactName string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
artifacts, err := ctr.GetArtifact(artifactName)
if err != nil {
return call.ReplyErrorOccurred("unable to get container artifacts")
}
b, err := json.Marshal(artifacts)
if err != nil {
return call.ReplyErrorOccurred("unable to serialize container artifacts")
}
return call.ReplyContainerArtifacts(string(b))
}
// ContainerInspectData returns the inspect data of a container in string format
func (i *LibpodAPI) ContainerInspectData(call iopodman.VarlinkCall, name string, size bool) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
data, err := ctr.Inspect(size)
if err != nil {
return call.ReplyErrorOccurred("unable to inspect container")
}
b, err := json.Marshal(data)
if err != nil {
return call.ReplyErrorOccurred("unable to serialize container inspect data")
}
return call.ReplyContainerInspectData(string(b))
}
// ContainerStateData returns a container's state data in string format
func (i *LibpodAPI) ContainerStateData(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name, err.Error())
}
data, err := ctr.ContainerState()
if err != nil {
return call.ReplyErrorOccurred("unable to obtain container state")
}
b, err := json.Marshal(data)
if err != nil {
return call.ReplyErrorOccurred("unable to serialize container inspect data")
}
return call.ReplyContainerStateData(string(b))
}
// GetContainerStatsWithHistory is a varlink endpoint that returns container stats based on current and
// previous statistics
func (i *LibpodAPI) GetContainerStatsWithHistory(call iopodman.VarlinkCall, prevStats iopodman.ContainerStats) error {
con, err := i.Runtime.LookupContainer(prevStats.Id)
if err != nil {
return call.ReplyContainerNotFound(prevStats.Id, err.Error())
}
previousStats := ContainerStatsToLibpodContainerStats(prevStats)
stats, err := con.GetContainerStats(&previousStats)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
cStats := iopodman.ContainerStats{
Id: stats.ContainerID,
Name: stats.Name,
Cpu: stats.CPU,
Cpu_nano: int64(stats.CPUNano),
System_nano: int64(stats.SystemNano),
Mem_usage: int64(stats.MemUsage),
Mem_limit: int64(stats.MemLimit),
Mem_perc: stats.MemPerc,
Net_input: int64(stats.NetInput),
Net_output: int64(stats.NetOutput),
Block_input: int64(stats.BlockInput),
Block_output: int64(stats.BlockOutput),
Pids: int64(stats.PIDs),
}
return call.ReplyGetContainerStatsWithHistory(cStats)
}
// Spec ...
func (i *LibpodAPI) Spec(call iopodman.VarlinkCall, name string) error {
ctr, err := i.Runtime.LookupContainer(name)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
spec := ctr.Spec()
b, err := json.Marshal(spec)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplySpec(string(b))
}
// GetContainersLogs is the varlink endpoint to obtain one or more container logs
func (i *LibpodAPI) GetContainersLogs(call iopodman.VarlinkCall, names []string, follow, latest bool, since string, tail int64, timestamps bool) error {
var wg sync.WaitGroup
if call.WantsMore() {
call.Continues = true
}
sinceTime, err := time.Parse(time.RFC3339Nano, since)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
options := logs.LogOptions{
Follow: follow,
Since: sinceTime,
Tail: uint64(tail),
Timestamps: timestamps,
}
options.WaitGroup = &wg
if len(names) > 1 {
options.Multi = true
}
logChannel := make(chan *logs.LogLine, int(tail)*len(names)+1)
containers, err := shortcuts.GetContainersByContext(false, latest, names, i.Runtime)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if err := i.Runtime.Log(containers, &options, logChannel); err != nil {
return err
}
go func() {
wg.Wait()
close(logChannel)
}()
for line := range logChannel {
call.ReplyGetContainersLogs(newPodmanLogLine(line))
if !call.Continues {
break
}
}
return call.ReplyGetContainersLogs(iopodman.LogLine{})
}
func newPodmanLogLine(line *logs.LogLine) iopodman.LogLine {
return iopodman.LogLine{
Device: line.Device,
ParseLogType: line.ParseLogType,
Time: line.Time.Format(time.RFC3339Nano),
Msg: line.Msg,
Cid: line.CID,
}
}
// Top displays information about a container's running processes
func (i *LibpodAPI) Top(call iopodman.VarlinkCall, nameOrID string, descriptors []string) error {
ctr, err := i.Runtime.LookupContainer(nameOrID)
if err != nil {
return call.ReplyContainerNotFound(ctr.ID(), err.Error())
}
topInfo, err := ctr.Top(descriptors)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyTop(topInfo)
}
// ExecContainer is the varlink endpoint to execute a command in a container
func (i *LibpodAPI) ExecContainer(call iopodman.VarlinkCall, opts iopodman.ExecOpts) error {
if !call.WantsUpgrade() {
return call.ReplyErrorOccurred("client must use upgraded connection to exec")
}
ctr, err := i.Runtime.LookupContainer(opts.Name)
if err != nil {
return call.ReplyContainerNotFound(opts.Name, err.Error())
}
state, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(
fmt.Sprintf("exec failed to obtain container %s state: %s", ctr.ID(), err.Error()))
}
if state != define.ContainerStateRunning {
return call.ReplyErrorOccurred(
fmt.Sprintf("exec requires a running container, %s is %s", ctr.ID(), state.String()))
}
envs := []string{}
if opts.Env != nil {
envs = *opts.Env
}
var user string
if opts.User != nil {
user = *opts.User
}
var workDir string
if opts.Workdir != nil {
workDir = *opts.Workdir
}
// ACK the client upgrade request
call.ReplyExecContainer()
resizeChan := make(chan remotecommand.TerminalSize)
reader, writer, _, pipeWriter, streams := setupStreams(call)
//reader, _, _, pipeWriter, streams := setupStreams(call)
type ExitCodeError struct {
ExitCode uint32
Error error
}
ecErrChan := make(chan ExitCodeError, 1)
go func() {
if err := virtwriter.Reader(reader, nil, nil, pipeWriter, resizeChan, nil); err != nil {
ecErrChan <- ExitCodeError{
define.ExecErrorCodeGeneric,
err,
}
}
}()
// TODO FIXME detach keys
go func() {
ec, err := ctr.Exec(opts.Tty, opts.Privileged, envs, opts.Cmd, user, workDir, streams, 0, resizeChan, "")
if err != nil {
logrus.Errorf("ExecContainer Exec err %s, %s\n", err.Error(), errors.Cause(err).Error())
}
ecErrChan <- ExitCodeError{
uint32(ec),
err,
}
}()
ecErr := <-ecErrChan
// TODO FIXME prevent all of these conversions
exitCode := int(ecErr.ExitCode)
if errors.Cause(ecErr.Error) == define.ErrOCIRuntimePermissionDenied {
exitCode = define.ExecErrorCodeCannotInvoke
}
if errors.Cause(ecErr.Error) == define.ErrOCIRuntimeNotFound {
exitCode = define.ExecErrorCodeNotFound
}
if err = virtwriter.HangUp(writer, exitCode); err != nil {
logrus.Errorf("ExecContainer failed to HANG-UP on %s: %s", ctr.ID(), err.Error())
}
if err := call.Writer.Flush(); err != nil {
logrus.Errorf("Exec Container err: %s", err.Error())
}
return ecErr.Error
}