varlink containers

first pass at adding in the container related endpoints/methods for the libpod
backend. Couple of important notes:

* endpoints that can use a console are not going to be done until we have "remote" console
* several of the container methods should probably be able to stream as opposed to a one-off return

Signed-off-by: baude <bbaude@redhat.com>

Closes: #708
Approved by: baude
This commit is contained in:
baude 2018-04-27 14:00:32 -05:00 committed by Atomic Bot
parent fae5033a01
commit 8dfebd4607
9 changed files with 1948 additions and 1138 deletions

View File

@ -0,0 +1,159 @@
package batchcontainer
import (
"time"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/libpod"
"github.com/sirupsen/logrus"
"os"
"path/filepath"
"regexp"
"strconv"
"strings"
)
// PsOptions describes the struct being formed for ps
type PsOptions struct {
All bool
Filter string
Format string
Last int
Latest bool
NoTrunc bool
Quiet bool
Size bool
Label string
Namespace bool
}
// BatchContainerStruct is the return obkect from BatchContainer and contains
// container related information
type BatchContainerStruct struct {
ConConfig *libpod.ContainerConfig
ConState libpod.ContainerStatus
ExitCode int32
Pid int
RootFsSize, RwSize int64
StartedTime time.Time
}
// Namespace describes output for ps namespace
type Namespace struct {
PID string `json:"pid,omitempty"`
Cgroup string `json:"cgroup,omitempty"`
IPC string `json:"ipc,omitempty"`
MNT string `json:"mnt,omitempty"`
NET string `json:"net,omitempty"`
PIDNS string `json:"pidns,omitempty"`
User string `json:"user,omitempty"`
UTS string `json:"uts,omitempty"`
}
// BatchContainer is used in ps to reduce performance hits by "batching"
// locks.
func BatchContainerOp(ctr *libpod.Container, opts PsOptions) (BatchContainerStruct, error) {
var (
conConfig *libpod.ContainerConfig
conState libpod.ContainerStatus
err error
exitCode int32
pid int
rootFsSize, rwSize int64
startedTime time.Time
)
batchErr := ctr.Batch(func(c *libpod.Container) error {
conConfig = c.Config()
conState, err = c.State()
if err != nil {
return errors.Wrapf(err, "unable to obtain container state")
}
exitCode, err = c.ExitCode()
if err != nil {
return errors.Wrapf(err, "unable to obtain container exit code")
}
startedTime, err = c.StartedTime()
if err != nil {
logrus.Errorf("error getting started time for %q: %v", c.ID(), err)
}
if !opts.Size && !opts.Namespace {
return nil
}
if opts.Namespace {
pid, err = c.PID()
if err != nil {
return errors.Wrapf(err, "unable to obtain container pid")
}
}
if opts.Size {
rootFsSize, err = c.RootFsSize()
if err != nil {
logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err)
}
rwSize, err = c.RWSize()
if err != nil {
logrus.Errorf("error getting rw size for %q: %v", c.ID(), err)
}
}
return nil
})
if batchErr != nil {
return BatchContainerStruct{}, batchErr
}
return BatchContainerStruct{
ConConfig: conConfig,
ConState: conState,
ExitCode: exitCode,
Pid: pid,
RootFsSize: rootFsSize,
RwSize: rwSize,
StartedTime: startedTime,
}, nil
}
// GetNamespaces returns a populated namespace struct
func GetNamespaces(pid int) *Namespace {
ctrPID := strconv.Itoa(pid)
cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup"))
ipc, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc"))
mnt, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt"))
net, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net"))
pidns, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid"))
user, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user"))
uts, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts"))
return &Namespace{
PID: ctrPID,
Cgroup: cgroup,
IPC: ipc,
MNT: mnt,
NET: net,
PIDNS: pidns,
User: user,
UTS: uts,
}
}
func getNamespaceInfo(path string) (string, error) {
val, err := os.Readlink(path)
if err != nil {
return "", errors.Wrapf(err, "error getting info from %q", path)
}
return getStrFromSquareBrackets(val), nil
}
// getStrFromSquareBrackets gets the string inside [] from a string
func getStrFromSquareBrackets(cmd string) string {
reg, err := regexp.Compile(".*\\[|\\].*")
if err != nil {
return ""
}
arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",")
return strings.Join(arr, ",")
}

View File

@ -3,10 +3,7 @@ package main
import (
"encoding/json"
"fmt"
"os"
"path/filepath"
"reflect"
"regexp"
"strconv"
"strings"
"time"
@ -15,6 +12,7 @@ import (
"github.com/docker/go-units"
specs "github.com/opencontainers/runtime-spec/specs-go"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/batchcontainer"
"github.com/projectatomic/libpod/cmd/podman/formats"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
"github.com/projectatomic/libpod/libpod"
@ -26,19 +24,6 @@ import (
const mountTruncLength = 12
type psOptions struct {
all bool
filter string
format string
last int
latest bool
noTrunc bool
quiet bool
size bool
label string
namespace bool
}
type psTemplateParams struct {
ID string
Image string
@ -66,32 +51,21 @@ type psTemplateParams struct {
// psJSONParams will be populated by data from libpod.Container,
// the members of the struct are the sama data types as their sources.
type psJSONParams struct {
ID string `json:"id"`
Image string `json:"image"`
ImageID string `json:"image_id"`
Command []string `json:"command"`
CreatedAt time.Time `json:"createdAt"`
RunningFor time.Duration `json:"runningFor"`
Status string `json:"status"`
Ports []ocicni.PortMapping `json:"ports"`
RootFsSize int64 `json:"rootFsSize"`
RWSize int64 `json:"rwSize"`
Names string `json:"names"`
Labels fields.Set `json:"labels"`
Mounts []specs.Mount `json:"mounts"`
ContainerRunning bool `json:"ctrRunning"`
Namespaces *namespace `json:"namespace,omitempty"`
}
type namespace struct {
PID string `json:"pid,omitempty"`
Cgroup string `json:"cgroup,omitempty"`
IPC string `json:"ipc,omitempty"`
MNT string `json:"mnt,omitempty"`
NET string `json:"net,omitempty"`
PIDNS string `json:"pidns,omitempty"`
User string `json:"user,omitempty"`
UTS string `json:"uts,omitempty"`
ID string `json:"id"`
Image string `json:"image"`
ImageID string `json:"image_id"`
Command []string `json:"command"`
CreatedAt time.Time `json:"createdAt"`
RunningFor time.Duration `json:"runningFor"`
Status string `json:"status"`
Ports []ocicni.PortMapping `json:"ports"`
RootFsSize int64 `json:"rootFsSize"`
RWSize int64 `json:"rwSize"`
Names string `json:"names"`
Labels fields.Set `json:"labels"`
Mounts []specs.Mount `json:"mounts"`
ContainerRunning bool `json:"ctrRunning"`
Namespaces *batchcontainer.Namespace `json:"namespace,omitempty"`
}
var (
@ -168,22 +142,22 @@ func psCmd(c *cli.Context) error {
format := genPsFormat(c.String("format"), c.Bool("quiet"), c.Bool("size"), c.Bool("namespace"))
opts := psOptions{
all: c.Bool("all"),
filter: c.String("filter"),
format: format,
last: c.Int("last"),
latest: c.Bool("latest"),
noTrunc: c.Bool("no-trunc"),
quiet: c.Bool("quiet"),
size: c.Bool("size"),
namespace: c.Bool("namespace"),
opts := batchcontainer.PsOptions{
All: c.Bool("all"),
Filter: c.String("filter"),
Format: format,
Last: c.Int("last"),
Latest: c.Bool("latest"),
NoTrunc: c.Bool("no-trunc"),
Quiet: c.Bool("quiet"),
Size: c.Bool("size"),
Namespace: c.Bool("namespace"),
}
var filterFuncs []libpod.ContainerFilter
// When we are dealing with latest or last=n, we need to
// get all containers.
if !opts.all && !opts.latest && opts.last < 1 {
if !opts.All && !opts.Latest && opts.Last < 1 {
// only get running containers
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
state, _ := c.State()
@ -191,8 +165,8 @@ func psCmd(c *cli.Context) error {
})
}
if opts.filter != "" {
filters := strings.Split(opts.filter, ",")
if opts.Filter != "" {
filters := strings.Split(opts.Filter, ",")
for _, f := range filters {
filterSplit := strings.Split(f, "=")
if len(filterSplit) < 2 {
@ -208,10 +182,10 @@ func psCmd(c *cli.Context) error {
containers, err := runtime.GetContainers(filterFuncs...)
var outputContainers []*libpod.Container
if opts.latest && len(containers) > 0 {
if opts.Latest && len(containers) > 0 {
outputContainers = append(outputContainers, containers[0])
} else if opts.last > 0 && opts.last <= len(containers) {
outputContainers = append(outputContainers, containers[:opts.last]...)
} else if opts.Last > 0 && opts.Last <= len(containers) {
outputContainers = append(outputContainers, containers[:opts.Last]...)
} else {
outputContainers = containers
}
@ -397,15 +371,15 @@ func (p *psTemplateParams) headerMap() map[string]string {
}
// getTemplateOutput returns the modified container information
func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemplateParams, error) {
func getTemplateOutput(containers []*libpod.Container, opts batchcontainer.PsOptions) ([]psTemplateParams, error) {
var (
psOutput []psTemplateParams
status, size string
ns *namespace
ns *batchcontainer.Namespace
)
for _, ctr := range containers {
batchInfo, err := batchContainerOp(ctr, opts)
batchInfo, err := batchcontainer.BatchContainerOp(ctr, opts)
if err != nil {
// If the error was ErrNoSuchCtr, it was probably
// removed sometime after we got the initial list.
@ -421,11 +395,11 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
runningFor := ""
// If the container has not be started, the "zero" value of time is 0001-01-01 00:00:00 +0000 UTC
// which would make the time elapsed about a few hundred of years. So checking for the "zero" value of time.Time
if batchInfo.startedTime != (time.Time{}) {
runningFor = units.HumanDuration(time.Since(batchInfo.startedTime))
if batchInfo.StartedTime != (time.Time{}) {
runningFor = units.HumanDuration(time.Since(batchInfo.StartedTime))
}
createdAt := batchInfo.conConfig.CreatedTime.Format("2006-01-02 15:04:05 -0700 MST")
imageName := batchInfo.conConfig.RootfsImageName
createdAt := batchInfo.ConConfig.CreatedTime.Format("2006-01-02 15:04:05 -0700 MST")
imageName := batchInfo.ConConfig.RootfsImageName
var createArtifact createConfig
artifact, err := ctr.GetArtifact("create-config")
@ -436,27 +410,27 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
} else {
logrus.Errorf("couldn't get some ps information, error getting artifact %q: %v", ctr.ID(), err)
}
if opts.namespace {
ns = getNamespaces(batchInfo.pid)
if opts.Namespace {
ns = batchcontainer.GetNamespaces(batchInfo.Pid)
}
if opts.size {
if opts.Size {
size = units.HumanSizeWithPrecision(float64(batchInfo.rwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(batchInfo.rootFsSize), 3) + ")"
size = units.HumanSizeWithPrecision(float64(batchInfo.RwSize), 3) + " (virtual " + units.HumanSizeWithPrecision(float64(batchInfo.RootFsSize), 3) + ")"
}
command := strings.Join(batchInfo.conConfig.Spec.Process.Args, " ")
if !opts.noTrunc {
command := strings.Join(batchInfo.ConConfig.Spec.Process.Args, " ")
if !opts.NoTrunc {
if len(command) > 20 {
command = command[:19] + "..."
}
}
ports := portsToString(batchInfo.conConfig.PortMappings)
mounts := getMounts(createArtifact.Volumes, opts.noTrunc)
ports := portsToString(batchInfo.ConConfig.PortMappings)
mounts := getMounts(createArtifact.Volumes, opts.NoTrunc)
labels := formatLabels(ctr.Labels())
switch batchInfo.conState {
switch batchInfo.ConState {
case libpod.ContainerStateStopped:
status = fmt.Sprintf("Exited (%d) %s ago", batchInfo.exitCode, runningFor)
status = fmt.Sprintf("Exited (%d) %s ago", batchInfo.ExitCode, runningFor)
case libpod.ContainerStateRunning:
status = "Up " + runningFor + " ago"
case libpod.ContainerStatePaused:
@ -467,9 +441,9 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
status = "Dead"
}
if !opts.noTrunc {
if !opts.NoTrunc {
ctrID = shortID(ctr.ID())
imageName = batchInfo.conConfig.RootfsImageName
imageName = batchInfo.ConConfig.RootfsImageName
}
params := psTemplateParams{
@ -484,10 +458,10 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
Names: ctr.Name(),
Labels: labels,
Mounts: mounts,
PID: batchInfo.pid,
PID: batchInfo.Pid,
}
if opts.namespace {
if opts.Namespace {
params.Cgroup = ns.Cgroup
params.IPC = ns.IPC
params.MNT = ns.MNT
@ -501,65 +475,35 @@ func getTemplateOutput(containers []*libpod.Container, opts psOptions) ([]psTemp
return psOutput, nil
}
func getNamespaces(pid int) *namespace {
ctrPID := strconv.Itoa(pid)
cgroup, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "cgroup"))
ipc, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "ipc"))
mnt, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "mnt"))
net, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "net"))
pidns, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "pid"))
user, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "user"))
uts, _ := getNamespaceInfo(filepath.Join("/proc", ctrPID, "ns", "uts"))
return &namespace{
PID: ctrPID,
Cgroup: cgroup,
IPC: ipc,
MNT: mnt,
NET: net,
PIDNS: pidns,
User: user,
UTS: uts,
}
}
func getNamespaceInfo(path string) (string, error) {
val, err := os.Readlink(path)
if err != nil {
return "", errors.Wrapf(err, "error getting info from %q", path)
}
return getStrFromSquareBrackets(val), nil
}
// getJSONOutput returns the container info in its raw form
func getJSONOutput(containers []*libpod.Container, opts psOptions) ([]psJSONParams, error) {
func getJSONOutput(containers []*libpod.Container, opts batchcontainer.PsOptions) ([]psJSONParams, error) {
var (
psOutput []psJSONParams
ns *namespace
ns *batchcontainer.Namespace
)
for _, ctr := range containers {
batchInfo, err := batchContainerOp(ctr, opts)
batchInfo, err := batchcontainer.BatchContainerOp(ctr, opts)
if err != nil {
return nil, err
}
if opts.namespace {
ns = getNamespaces(batchInfo.pid)
if opts.Namespace {
ns = batchcontainer.GetNamespaces(batchInfo.Pid)
}
params := psJSONParams{
ID: ctr.ID(),
Image: batchInfo.conConfig.RootfsImageName,
ImageID: batchInfo.conConfig.RootfsImageID,
Command: batchInfo.conConfig.Spec.Process.Args,
CreatedAt: batchInfo.conConfig.CreatedTime,
RunningFor: time.Since(batchInfo.conConfig.CreatedTime),
Status: batchInfo.conState.String(),
Ports: batchInfo.conConfig.PortMappings,
RootFsSize: batchInfo.rootFsSize,
RWSize: batchInfo.rwSize,
Names: batchInfo.conConfig.Name,
Labels: batchInfo.conConfig.Labels,
Mounts: batchInfo.conConfig.Spec.Mounts,
ContainerRunning: batchInfo.conState == libpod.ContainerStateRunning,
Image: batchInfo.ConConfig.RootfsImageName,
ImageID: batchInfo.ConConfig.RootfsImageID,
Command: batchInfo.ConConfig.Spec.Process.Args,
CreatedAt: batchInfo.ConConfig.CreatedTime,
RunningFor: time.Since(batchInfo.ConConfig.CreatedTime),
Status: batchInfo.ConState.String(),
Ports: batchInfo.ConConfig.PortMappings,
RootFsSize: batchInfo.RootFsSize,
RWSize: batchInfo.RwSize,
Names: batchInfo.ConConfig.Name,
Labels: batchInfo.ConConfig.Labels,
Mounts: batchInfo.ConConfig.Spec.Mounts,
ContainerRunning: batchInfo.ConState == libpod.ContainerStateRunning,
Namespaces: ns,
}
psOutput = append(psOutput, params)
@ -567,13 +511,13 @@ func getJSONOutput(containers []*libpod.Container, opts psOptions) ([]psJSONPara
return psOutput, nil
}
func generatePsOutput(containers []*libpod.Container, opts psOptions) error {
if len(containers) == 0 && opts.format != formats.JSONString {
func generatePsOutput(containers []*libpod.Container, opts batchcontainer.PsOptions) error {
if len(containers) == 0 && opts.Format != formats.JSONString {
return nil
}
var out formats.Writer
switch opts.format {
switch opts.Format {
case formats.JSONString:
psOutput, err := getJSONOutput(containers, opts)
if err != nil {
@ -585,22 +529,12 @@ func generatePsOutput(containers []*libpod.Container, opts psOptions) error {
if err != nil {
return errors.Wrapf(err, "unable to create output")
}
out = formats.StdoutTemplateArray{Output: psToGeneric(psOutput, []psJSONParams{}), Template: opts.format, Fields: psOutput[0].headerMap()}
out = formats.StdoutTemplateArray{Output: psToGeneric(psOutput, []psJSONParams{}), Template: opts.Format, Fields: psOutput[0].headerMap()}
}
return formats.Writer(out).Out()
}
// getStrFromSquareBrackets gets the string inside [] from a string
func getStrFromSquareBrackets(cmd string) string {
reg, err := regexp.Compile(".*\\[|\\].*")
if err != nil {
return ""
}
arr := strings.Split(reg.ReplaceAllLiteralString(cmd, ""), ",")
return strings.Join(arr, ",")
}
// getLabels converts the labels to a string of the form "key=value, key2=value2"
func formatLabels(labels map[string]string) string {
var arr []string
@ -647,77 +581,3 @@ func portsToString(ports []ocicni.PortMapping) string {
}
return strings.Join(portDisplay, ", ")
}
type batchContainerStruct struct {
conConfig *libpod.ContainerConfig
conState libpod.ContainerStatus
exitCode int32
pid int
rootFsSize, rwSize int64
startedTime time.Time
}
func batchContainerOp(ctr *libpod.Container, opts psOptions) (batchContainerStruct, error) {
var (
conConfig *libpod.ContainerConfig
conState libpod.ContainerStatus
err error
exitCode int32
pid int
rootFsSize, rwSize int64
startedTime time.Time
)
batchErr := ctr.Batch(func(c *libpod.Container) error {
conConfig = c.Config()
conState, err = c.State()
if err != nil {
return errors.Wrapf(err, "unable to obtain container state")
}
exitCode, err = c.ExitCode()
if err != nil {
return errors.Wrapf(err, "unable to obtain container exit code")
}
startedTime, err = c.StartedTime()
if err != nil {
logrus.Errorf("error getting started time for %q: %v", c.ID(), err)
}
if !opts.size && !opts.namespace {
return nil
}
if opts.namespace {
pid, err = c.PID()
if err != nil {
return errors.Wrapf(err, "unable to obtain container pid")
}
}
if opts.size {
rootFsSize, err = c.RootFsSize()
if err != nil {
logrus.Errorf("error getting root fs size for %q: %v", c.ID(), err)
}
rwSize, err = c.RWSize()
if err != nil {
logrus.Errorf("error getting rw size for %q: %v", c.ID(), err)
}
}
return nil
})
if batchErr != nil {
return batchContainerStruct{}, batchErr
}
return batchContainerStruct{
conConfig: conConfig,
conState: conState,
exitCode: exitCode,
pid: pid,
rootFsSize: rootFsSize,
rwSize: rwSize,
startedTime: startedTime,
}, nil
}

View File

@ -1,6 +1,7 @@
package main
import (
"context"
"fmt"
"os"
@ -64,7 +65,7 @@ func restartCmd(c *cli.Context) error {
ctrTimeout = timeout
}
lastError = restartCtr(ctrTimeout, lastCtr)
lastError = lastCtr.RestartWithTimeout(context.TODO(), ctrTimeout)
}
}
@ -83,7 +84,7 @@ func restartCmd(c *cli.Context) error {
ctrTimeout = timeout
}
if err := restartCtr(ctrTimeout, ctr); err != nil {
if err := ctr.RestartWithTimeout(context.TODO(), ctrTimeout); err != nil {
if lastError != nil {
fmt.Fprintln(os.Stderr, lastError)
}
@ -93,24 +94,3 @@ func restartCmd(c *cli.Context) error {
return lastError
}
// Restart a single container
func restartCtr(timeout uint, ctr *libpod.Container) error {
state, err := ctr.State()
if err != nil {
return err
}
if state == libpod.ContainerStateRunning {
if err := ctr.StopWithTimeout(timeout); err != nil {
return err
}
}
if err := ctr.Start(getContext()); err != nil {
return err
}
fmt.Printf("%s\n", ctr.ID())
return nil
}

View File

@ -53,32 +53,100 @@ type ImageSearch (
star_count: int
)
# ListContainer is the returned struct for an individual container
type ListContainerData (
id: string,
image: string,
imageid: string,
command: []string,
createdat: string,
runningfor: string,
status: string,
ports: []ContainerPortMappings,
rootfssize: int,
rwsize: int,
names: string,
labels: [string]string,
mounts: []ContainerMount,
containerrunning: bool,
namespaces: ContainerNameSpace
)
# ContainerStats is the return struct for the stats of a container
type ContainerStats (
id: string,
name: string,
cpu: float,
cpu_nano: int,
system_nano: int,
mem_usage: int,
mem_limit: int,
mem_perc: float,
net_input: int,
net_output: int,
block_output: int,
block_input: int,
pids: int
)
# ContainerMount describes the struct for mounts in a container
type ContainerMount (
destination: string,
type: string,
source: string,
options: []string
)
# ContainerPortMappings describes the struct for portmappings in an existing container
type ContainerPortMappings (
host_port: string,
host_ip: string,
protocol: string,
container_port: string
)
# ContainerNamespace describes the namespace structure for an existing container
type ContainerNameSpace (
user: string,
uts: string,
pidns: string,
pid: string,
cgroup: string,
net: string,
mnt: string,
ipc: string
)
# System
method Ping() -> (ping: StringResponse)
method GetVersion() -> (version: Version)
# Containers
method ListContainers() -> (notimplemented: NotImplemented)
method ListContainers() -> (containers: []ListContainerData)
method GetContainer(name: string) -> (container: ListContainerData)
method CreateContainer() -> (notimplemented: NotImplemented)
method InspectContainer() -> (notimplemented: NotImplemented)
method ListContainerProcesses() -> (notimplemented: NotImplemented)
method GetContainerLogs() -> (notimplemented: NotImplemented)
method ListContainerChanges() -> (notimplemented: NotImplemented)
method ExportContainer() -> (notimplemented: NotImplemented)
method GetContainerStats() -> (notimplemented: NotImplemented)
method InspectContainer(name: string) -> (container: string)
# TODO: Should this be made into a streaming response as opposed to a one off?
method ListContainerProcesses(name: string, opts: []string) -> (container: []string)
# TODO: Should this be made into a streaming response as opposed to a one off?
method GetContainerLogs(name: string) -> (container: []string)
method ListContainerChanges(name: string) -> (container: [string]string)
# TODO: This should be made into a streaming response
method ExportContainer(name: string, path: string) -> (tarfile: string)
method GetContainerStats(name: string) -> (container: ContainerStats)
method ResizeContainerTty() -> (notimplemented: NotImplemented)
method StartContainer() -> (notimplemented: NotImplemented)
method StopContainer() -> (notimplemented: NotImplemented)
method RestartContainer() -> (notimplemented: NotImplemented)
method KillContainer() -> (notimplemented: NotImplemented)
method StopContainer(name: string, timeout: int) -> (container: string)
method RestartContainer(name: string, timeout: int) -> (container: string)
method KillContainer(name: string, signal: int) -> (container: string)
method UpdateContainer() -> (notimplemented: NotImplemented)
method RenameContainer() -> (notimplemented: NotImplemented)
method PauseContainer() -> (notimplemented: NotImplemented)
method UnpauseContainer() -> (notimplemented: NotImplemented)
method PauseContainer(name: string) -> (container: string)
method UnpauseContainer(name: string) -> (container: string)
method AttachToContainer() -> (notimplemented: NotImplemented)
method WaitContainer() -> (notimplemented: NotImplemented)
method RemoveContainer() -> (notimplemented: NotImplemented)
method DeleteStoppedContainers() -> (notimplemented: NotImplemented)
method WaitContainer(name: string) -> (exitcode: int)
method RemoveContainer(name: string, force: bool) -> (container: string)
method DeleteStoppedContainers() -> (containers: []string)
# Images
method ListImages() -> (images: []ImageInList)
@ -99,6 +167,7 @@ method PullImage(name: string) -> (id: string)
# Something failed
error ActionFailed (reason: string)
error ImageNotFound (imagename: string)
error ImageNotFound (name: string)
error ContainerNotFound (name: string)
error ErrorOccurred (reason: string)
error RuntimeError (reason: string)
error RuntimeError (reason: string)

File diff suppressed because it is too large Load Diff

View File

@ -705,3 +705,52 @@ func (c *Container) Sync() error {
return nil
}
// RestartWithTimeout restarts a running container and takes a given timeout in uint
func (c *Container) RestartWithTimeout(ctx context.Context, timeout uint) error {
if !c.batched {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return err
}
}
notRunning, err := c.checkDependenciesRunning()
if err != nil {
return errors.Wrapf(err, "error checking dependencies for container %s")
}
if len(notRunning) > 0 {
depString := strings.Join(notRunning, ",")
return errors.Wrapf(ErrCtrStateInvalid, "some dependencies of container %s are not started: %s", c.ID(), depString)
}
if c.state.State == ContainerStateUnknown || c.state.State == ContainerStatePaused {
return errors.Errorf("unable to restart a container in a paused or unknown state")
}
if c.state.State == ContainerStateRunning {
if err := c.stop(timeout); err != nil {
return err
}
}
if err := c.prepare(); err != nil {
return err
}
defer func() {
if err != nil {
if err2 := c.cleanup(); err2 != nil {
logrus.Errorf("error cleaning up container %s: %v", c.ID(), err2)
}
}
}()
if c.state.State == ContainerStateStopped {
// Reinitialize the container if we need to
if err := c.reinit(ctx); err != nil {
return err
}
}
return c.start()
}

View File

@ -459,7 +459,6 @@ func (c *Container) reinit(ctx context.Context) error {
if err := c.runtime.ociRuntime.deleteContainer(c); err != nil {
return errors.Wrapf(err, "error removing container %s from runtime", c.ID())
}
// Our state is now Configured, as we've removed ourself from
// the runtime
// Set and save now to make sure that, if the init() below fails
@ -545,7 +544,6 @@ func (c *Container) start() error {
if err := c.runtime.ociRuntime.startContainer(c); err != nil {
return err
}
logrus.Debugf("Started container %s", c.ID())
c.state.State = ContainerStateRunning

View File

@ -1,12 +1,67 @@
package varlinkapi
import (
ioprojectatomicpodman "github.com/projectatomic/libpod/cmd/podman/varlink"
"bufio"
"encoding/json"
"fmt"
"os"
"syscall"
"github.com/pkg/errors"
"github.com/projectatomic/libpod/cmd/podman/batchcontainer"
"github.com/projectatomic/libpod/cmd/podman/libpodruntime"
"github.com/projectatomic/libpod/cmd/podman/varlink"
"github.com/projectatomic/libpod/libpod"
)
// ListContainers ...
func (i *LibpodAPI) ListContainers(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("ListContainers")
var (
listContainers []ioprojectatomicpodman.ListContainerData
)
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
containers, err := runtime.GetAllContainers()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
opts := batchcontainer.PsOptions{
Namespace: true,
Size: true,
}
for _, ctr := range containers {
batchInfo, err := batchcontainer.BatchContainerOp(ctr, opts)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
listContainers = append(listContainers, makeListContainer(ctr.ID(), batchInfo))
}
return call.ReplyListContainers(listContainers)
}
// GetContainer ...
func (i *LibpodAPI) GetContainer(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
opts := batchcontainer.PsOptions{
Namespace: true,
Size: true,
}
batchInfo, err := batchcontainer.BatchContainerOp(ctr, opts)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyGetContainer(makeListContainer(ctr.ID(), batchInfo))
}
// CreateContainer ...
@ -15,33 +70,158 @@ func (i *LibpodAPI) CreateContainer(call ioprojectatomicpodman.VarlinkCall) erro
}
// InspectContainer ...
func (i *LibpodAPI) InspectContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("InspectContainer")
func (i *LibpodAPI) InspectContainer(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
inspectInfo, err := ctr.Inspect(true)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
b, err := json.Marshal(inspectInfo)
if err != nil {
return call.ReplyErrorOccurred(fmt.Sprintf("unable to serialize"))
}
return call.ReplyInspectContainer(string(b))
}
// ListContainerProcesses ...
func (i *LibpodAPI) ListContainerProcesses(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("ListContainerProcesses")
func (i *LibpodAPI) ListContainerProcesses(call ioprojectatomicpodman.VarlinkCall, name string, opts []string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
containerState, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if containerState != libpod.ContainerStateRunning {
return call.ReplyErrorOccurred(fmt.Sprintf("container %s is not running", name))
}
var psArgs []string
psOpts := []string{"-o", "uid,pid,ppid,c,stime,tname,time,cmd"}
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 ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("GetContainerLogs")
func (i *LibpodAPI) GetContainerLogs(call ioprojectatomicpodman.VarlinkCall, name string) error {
var logs []string
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
logPath := ctr.LogPath()
containerState, err := ctr.State()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
if _, err := os.Stat(logPath); err != nil {
if containerState == libpod.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)
for {
line, err := reader.ReadString('\n')
if err != nil {
break
}
logs = append(logs, line)
}
return call.ReplyGetContainerLogs(logs)
}
// ListContainerChanges ...
func (i *LibpodAPI) ListContainerChanges(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("ListContianerChanges")
func (i *LibpodAPI) ListContainerChanges(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
changes, err := runtime.GetDiff("", name)
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
m := make(map[string]string)
for _, change := range changes {
m[change.Path] = change.Kind.String()
}
return call.ReplyListContainerChanges(m)
}
// ExportContainer ...
func (i *LibpodAPI) ExportContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("ExportContainer")
func (i *LibpodAPI) ExportContainer(call ioprojectatomicpodman.VarlinkCall, name, path string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := ctr.Export(path); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyExportContainer(path)
}
// GetContainerStats ...
func (i *LibpodAPI) GetContainerStats(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("GetContainerStates")
func (i *LibpodAPI) GetContainerStats(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
containerStats, err := ctr.GetContainerStats(&libpod.ContainerStats{})
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
cs := ioprojectatomicpodman.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)
}
// ResizeContainerTty ...
@ -55,18 +235,56 @@ func (i *LibpodAPI) StartContainer(call ioprojectatomicpodman.VarlinkCall) error
}
// StopContainer ...
func (i *LibpodAPI) StopContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("StopContainer")
func (i *LibpodAPI) StopContainer(call ioprojectatomicpodman.VarlinkCall, name string, timeout int64) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := ctr.StopWithTimeout(uint(timeout)); err != nil && err != libpod.ErrCtrStopped {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyStopContainer(ctr.ID())
}
// RestartContainer ...
func (i *LibpodAPI) RestartContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("RestartContainer")
func (i *LibpodAPI) RestartContainer(call ioprojectatomicpodman.VarlinkCall, name string, timeout int64) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := ctr.RestartWithTimeout(getContext(), uint(timeout)); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRestartContainer(ctr.ID())
}
// KillContainer ...
func (i *LibpodAPI) KillContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("KillContainer")
// 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 ioprojectatomicpodman.VarlinkCall, name string, signal int64) error {
var killSignal uint = uint(syscall.SIGTERM)
if signal != -1 {
killSignal = uint(signal)
}
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := ctr.Kill(killSignal); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyKillContainer(ctr.ID())
}
// UpdateContainer ...
@ -80,13 +298,35 @@ func (i *LibpodAPI) RenameContainer(call ioprojectatomicpodman.VarlinkCall) erro
}
// PauseContainer ...
func (i *LibpodAPI) PauseContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("PauseContainer")
func (i *LibpodAPI) PauseContainer(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := ctr.Pause(); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyPauseContainer(ctr.ID())
}
// UnpauseContainer ...
func (i *LibpodAPI) UnpauseContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("UnpauseContainer")
func (i *LibpodAPI) UnpauseContainer(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := ctr.Unpause(); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyUnpauseContainer(ctr.ID())
}
// AttachToContainer ...
@ -96,16 +336,62 @@ func (i *LibpodAPI) AttachToContainer(call ioprojectatomicpodman.VarlinkCall) er
}
// WaitContainer ...
func (i *LibpodAPI) WaitContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("WaitContainer")
func (i *LibpodAPI) WaitContainer(call ioprojectatomicpodman.VarlinkCall, name string) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
exitCode, err := ctr.Wait()
if err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyWaitContainer(int64(exitCode))
}
// RemoveContainer ...
func (i *LibpodAPI) RemoveContainer(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("RemoveContainer")
func (i *LibpodAPI) RemoveContainer(call ioprojectatomicpodman.VarlinkCall, name string, force bool) error {
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
ctr, err := runtime.LookupContainer(name)
if err != nil {
return call.ReplyContainerNotFound(name)
}
if err := runtime.RemoveContainer(ctr, force); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
return call.ReplyRemoveContainer(ctr.ID())
}
// DeleteStoppedContainers ...
func (i *LibpodAPI) DeleteStoppedContainers(call ioprojectatomicpodman.VarlinkCall) error {
return call.ReplyMethodNotImplemented("DeleteContainer")
var deletedContainers []string
runtime, err := libpodruntime.GetRuntime(i.Cli)
if err != nil {
return call.ReplyRuntimeError(err.Error())
}
containers, err := 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 != libpod.ContainerStateRunning {
if err := runtime.RemoveContainer(ctr, false); err != nil {
return call.ReplyErrorOccurred(err.Error())
}
deletedContainers = append(deletedContainers, ctr.ID())
}
}
return call.ReplyDeleteStoppedContainers(deletedContainers)
}

View File

@ -2,9 +2,76 @@ package varlinkapi
import (
"context"
"strconv"
"time"
"github.com/projectatomic/libpod/cmd/podman/batchcontainer"
"github.com/projectatomic/libpod/cmd/podman/varlink"
"github.com/projectatomic/libpod/libpod"
)
// getContext returns a non-nil, empty context
func getContext() context.Context {
return context.TODO()
}
func makeListContainer(containerID string, batchInfo batchcontainer.BatchContainerStruct) ioprojectatomicpodman.ListContainerData {
var (
mounts []ioprojectatomicpodman.ContainerMount
ports []ioprojectatomicpodman.ContainerPortMappings
)
ns := batchcontainer.GetNamespaces(batchInfo.Pid)
for _, mount := range batchInfo.ConConfig.Spec.Mounts {
m := ioprojectatomicpodman.ContainerMount{
Destination: mount.Destination,
Type: mount.Type,
Source: mount.Source,
Options: mount.Options,
}
mounts = append(mounts, m)
}
for _, pm := range batchInfo.ConConfig.PortMappings {
p := ioprojectatomicpodman.ContainerPortMappings{
Host_port: strconv.Itoa(int(pm.HostPort)),
Host_ip: pm.HostIP,
Protocol: pm.Protocol,
Container_port: strconv.Itoa(int(pm.ContainerPort)),
}
ports = append(ports, p)
}
// If we find this needs to be done for other container endpoints, we should
// convert this to a separate function or a generic map from struct function.
namespace := ioprojectatomicpodman.ContainerNameSpace{
User: ns.User,
Uts: ns.UTS,
Pidns: ns.PIDNS,
Pid: ns.PID,
Cgroup: ns.Cgroup,
Net: ns.NET,
Mnt: ns.MNT,
Ipc: ns.IPC,
}
lc := ioprojectatomicpodman.ListContainerData{
Id: containerID,
Image: batchInfo.ConConfig.RootfsImageName,
Imageid: batchInfo.ConConfig.RootfsImageID,
Command: batchInfo.ConConfig.Spec.Process.Args,
Createdat: batchInfo.ConConfig.CreatedTime.String(),
Runningfor: time.Since(batchInfo.ConConfig.CreatedTime).String(),
Status: batchInfo.ConState.String(),
Ports: ports,
Rootfssize: batchInfo.RootFsSize,
Rwsize: batchInfo.RwSize,
Names: batchInfo.ConConfig.Name,
Labels: batchInfo.ConConfig.Labels,
Mounts: mounts,
Containerrunning: batchInfo.ConState == libpod.ContainerStateRunning,
Namespaces: namespace,
}
return lc
}