mirror of
https://github.com/containers/podman.git
synced 2025-08-15 11:55:55 +08:00

As part of APIv2 Attach, we need to be able to attach to freshly created containers (in ContainerStateConfigured). This isn't something Libpod is interested in supporting, so we use Init() to get the container into ContainerStateCreated, in which attach is possible. Problem: Init() will fail if dependencies are not started, so a fresh container in a fresh pod will fail. The simplest solution is to extend the existing recursive start code from Start() to Init(), allowing dependency containers to be started when we initialize the container (optionally, controlled via bool). Also, update some comments in container_api.go to make it more clear how some of our major API calls work. Fixes #6646 Signed-off-by: Matthew Heon <mheon@redhat.com>
913 lines
27 KiB
Go
913 lines
27 KiB
Go
// +build varlink
|
|
|
|
package varlinkapi
|
|
|
|
import (
|
|
"bufio"
|
|
"encoding/json"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containers/libpod/libpod"
|
|
"github.com/containers/libpod/libpod/define"
|
|
"github.com/containers/libpod/libpod/logs"
|
|
"github.com/containers/libpod/pkg/cgroups"
|
|
"github.com/containers/libpod/pkg/rootless"
|
|
iopodman "github.com/containers/libpod/pkg/varlink"
|
|
"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 *VarlinkAPI) ListContainers(call iopodman.VarlinkCall) error {
|
|
var (
|
|
listContainers []iopodman.Container
|
|
)
|
|
|
|
containers, err := i.Runtime.GetAllContainers()
|
|
if err != nil {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
opts := PsOptions{
|
|
Namespace: true,
|
|
Size: true,
|
|
}
|
|
for _, ctr := range containers {
|
|
batchInfo, err := BatchContainerOp(ctr, opts)
|
|
if err != nil {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
|
|
listContainers = append(listContainers, makeListContainer(ctr.ID(), batchInfo))
|
|
}
|
|
return call.ReplyListContainers(listContainers)
|
|
}
|
|
|
|
func (i *VarlinkAPI) Ps(call iopodman.VarlinkCall, opts iopodman.PsOpts) error {
|
|
var (
|
|
containers []iopodman.PsContainer
|
|
)
|
|
maxWorkers := Parallelize("ps")
|
|
psOpts := makePsOpts(opts)
|
|
filters := []string{}
|
|
if opts.Filters != nil {
|
|
filters = *opts.Filters
|
|
}
|
|
psContainerOutputs, err := 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 *VarlinkAPI) GetContainer(call iopodman.VarlinkCall, id string) error {
|
|
ctr, err := i.Runtime.LookupContainer(id)
|
|
if err != nil {
|
|
return call.ReplyContainerNotFound(id, err.Error())
|
|
}
|
|
opts := PsOptions{
|
|
Namespace: true,
|
|
Size: true,
|
|
}
|
|
batchInfo, err := 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 *VarlinkAPI) GetContainersByContext(call iopodman.VarlinkCall, all, latest bool, input []string) error {
|
|
var ids []string
|
|
|
|
ctrs, err := 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 *VarlinkAPI) 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 := PsOptions{Size: true, Namespace: true}
|
|
for _, ctr := range filteredContainers {
|
|
batchInfo, err := 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) GetContainerStats(call iopodman.VarlinkCall, name string) error {
|
|
if rootless.IsRootless() {
|
|
cgroupv2, err := cgroups.IsCgroup2UnifiedMode()
|
|
if err != nil {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
if !cgroupv2 {
|
|
return call.ReplyErrRequiresCgroupsV2ForRootless("rootless containers cannot report container stats")
|
|
}
|
|
}
|
|
ctr, err := i.Runtime.LookupContainer(name)
|
|
if err != nil {
|
|
return call.ReplyContainerNotFound(name, err.Error())
|
|
}
|
|
containerStats, err := ctr.GetContainerStats(&define.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 *VarlinkAPI) 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 *VarlinkAPI) 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(), false); 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 {
|
|
if errors.Cause(err) == define.ErrNoSuchCtr {
|
|
return call.ReplyContainerExists(1)
|
|
}
|
|
if errors.Cause(err) == define.ErrCtrStateInvalid {
|
|
return call.ReplyInvalidState(ctr.ID(), err.Error())
|
|
}
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
return call.ReplyRemoveContainer(ctr.ID())
|
|
}
|
|
|
|
// EvictContainer ...
|
|
func (i *VarlinkAPI) EvictContainer(call iopodman.VarlinkCall, name string, removeVolumes bool) error {
|
|
ctx := getContext()
|
|
id, err := i.Runtime.EvictContainer(ctx, name, removeVolumes)
|
|
if err != nil {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
return call.ReplyEvictContainer(id)
|
|
}
|
|
|
|
// DeleteStoppedContainers ...
|
|
func (i *VarlinkAPI) 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 *VarlinkAPI) 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(), false); err != nil {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
}
|
|
|
|
sockPath, err := ctr.AttachSocketPath()
|
|
if err != nil {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
|
|
s := iopodman.Sockets{
|
|
Container_id: ctr.ID(),
|
|
Io_socket: sockPath,
|
|
Control_socket: ctr.ControlSocketPath(),
|
|
}
|
|
return call.ReplyGetAttachSockets(s)
|
|
}
|
|
|
|
// ContainerCheckpoint ...
|
|
func (i *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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 *VarlinkAPI) 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: tail,
|
|
Timestamps: timestamps,
|
|
}
|
|
|
|
options.WaitGroup = &wg
|
|
if len(names) > 1 {
|
|
options.Multi = true
|
|
}
|
|
tailLen := int(tail)
|
|
if tailLen < 0 {
|
|
tailLen = 0
|
|
}
|
|
logChannel := make(chan *logs.LogLine, tailLen*len(names)+1)
|
|
containers, err := 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 *VarlinkAPI) 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 *VarlinkAPI) 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()))
|
|
}
|
|
|
|
// ACK the client upgrade request
|
|
call.ReplyExecContainer()
|
|
|
|
envs := make(map[string]string)
|
|
if opts.Env != nil {
|
|
// HACK: The Varlink API uses the old []string format for env,
|
|
// storage as "k=v". Split on the = and turn into the new map
|
|
// format.
|
|
for _, env := range *opts.Env {
|
|
splitEnv := strings.SplitN(env, "=", 2)
|
|
if len(splitEnv) == 1 {
|
|
logrus.Errorf("Got badly-formatted environment variable %q in exec", env)
|
|
continue
|
|
}
|
|
envs[splitEnv[0]] = splitEnv[1]
|
|
}
|
|
}
|
|
|
|
var user string
|
|
if opts.User != nil {
|
|
user = *opts.User
|
|
}
|
|
|
|
var workDir string
|
|
if opts.Workdir != nil {
|
|
workDir = *opts.Workdir
|
|
}
|
|
|
|
resizeChan := make(chan remotecommand.TerminalSize)
|
|
|
|
reader, writer, _, 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,
|
|
}
|
|
}
|
|
}()
|
|
|
|
execConfig := new(libpod.ExecConfig)
|
|
execConfig.Command = opts.Cmd
|
|
execConfig.Terminal = opts.Tty
|
|
execConfig.Privileged = opts.Privileged
|
|
execConfig.Environment = envs
|
|
execConfig.User = user
|
|
execConfig.WorkDir = workDir
|
|
execConfig.DetachKeys = opts.DetachKeys
|
|
|
|
go func() {
|
|
ec, err := ctr.Exec(execConfig, streams, resizeChan)
|
|
if err != nil {
|
|
logrus.Errorf(err.Error())
|
|
}
|
|
ecErrChan <- ExitCodeError{
|
|
uint32(ec),
|
|
err,
|
|
}
|
|
}()
|
|
|
|
ecErr := <-ecErrChan
|
|
|
|
exitCode := define.TranslateExecErrorToExitCode(int(ecErr.ExitCode), ecErr.Error)
|
|
|
|
if err = virtwriter.HangUp(writer, uint32(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
|
|
}
|
|
|
|
// HealthCheckRun executes defined container's healthcheck command and returns the container's health status.
|
|
func (i *VarlinkAPI) HealthCheckRun(call iopodman.VarlinkCall, nameOrID string) error {
|
|
hcStatus, err := i.Runtime.HealthCheck(nameOrID)
|
|
if err != nil && hcStatus != define.HealthCheckFailure {
|
|
return call.ReplyErrorOccurred(err.Error())
|
|
}
|
|
status := define.HealthCheckUnhealthy
|
|
if hcStatus == define.HealthCheckSuccess {
|
|
status = define.HealthCheckHealthy
|
|
}
|
|
return call.ReplyHealthCheckRun(status)
|
|
}
|