mirror of
https://github.com/containers/podman.git
synced 2025-08-06 19:44:14 +08:00

We can easily tell if we're going to deadlock by comparing lock IDs before actually taking the lock. Add a few checks for this in common places where deadlocks might occur. This does not yet cover pod operations, where detection is more difficult (and costly) due to the number of locks being involved being higher than 2. Also, add some error wrapping on the Podman side, so we can tell people to use `system renumber` when it occurs. Signed-off-by: Matthew Heon <matthew.heon@pm.me>
1362 lines
37 KiB
Go
1362 lines
37 KiB
Go
// +build !remoteclient
|
|
|
|
package adapter
|
|
|
|
import (
|
|
"bufio"
|
|
"context"
|
|
"fmt"
|
|
"io"
|
|
"io/ioutil"
|
|
"os"
|
|
"strconv"
|
|
"strings"
|
|
"sync"
|
|
"syscall"
|
|
"time"
|
|
|
|
"github.com/containers/buildah"
|
|
"github.com/containers/image/v5/manifest"
|
|
"github.com/containers/libpod/cmd/podman/cliconfig"
|
|
"github.com/containers/libpod/cmd/podman/shared"
|
|
"github.com/containers/libpod/cmd/podman/shared/parse"
|
|
"github.com/containers/libpod/libpod"
|
|
"github.com/containers/libpod/libpod/define"
|
|
"github.com/containers/libpod/libpod/events"
|
|
"github.com/containers/libpod/libpod/image"
|
|
"github.com/containers/libpod/libpod/logs"
|
|
"github.com/containers/libpod/pkg/adapter/shortcuts"
|
|
"github.com/containers/libpod/pkg/systemd/generate"
|
|
"github.com/containers/storage"
|
|
"github.com/pkg/errors"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// GetLatestContainer gets the latest Container and wraps it in an adapter Container
|
|
func (r *LocalRuntime) GetLatestContainer() (*Container, error) {
|
|
Container := Container{}
|
|
c, err := r.Runtime.GetLatestContainer()
|
|
Container.Container = c
|
|
return &Container, err
|
|
}
|
|
|
|
// GetAllContainers gets all Containers and wraps each one in an adapter Container
|
|
func (r *LocalRuntime) GetAllContainers() ([]*Container, error) {
|
|
var containers []*Container
|
|
allContainers, err := r.Runtime.GetAllContainers()
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
for _, c := range allContainers {
|
|
containers = append(containers, &Container{c})
|
|
}
|
|
return containers, nil
|
|
}
|
|
|
|
// LookupContainer gets a Container by name or id and wraps it in an adapter Container
|
|
func (r *LocalRuntime) LookupContainer(idOrName string) (*Container, error) {
|
|
ctr, err := r.Runtime.LookupContainer(idOrName)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return &Container{ctr}, nil
|
|
}
|
|
|
|
// StopContainers stops container(s) based on CLI inputs.
|
|
// Returns list of successful id(s), map of failed id(s) + error, or error not from container
|
|
func (r *LocalRuntime) StopContainers(ctx context.Context, cli *cliconfig.StopValues) ([]string, map[string]error, error) {
|
|
var timeout *uint
|
|
if cli.Flags().Changed("timeout") || cli.Flags().Changed("time") {
|
|
t := cli.Timeout
|
|
timeout = &t
|
|
}
|
|
|
|
maxWorkers := shared.DefaultPoolSize("stop")
|
|
if cli.GlobalIsSet("max-workers") {
|
|
maxWorkers = cli.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum stop workers to %d", maxWorkers)
|
|
|
|
names := cli.InputArgs
|
|
for _, cidFile := range cli.CIDFiles {
|
|
content, err := ioutil.ReadFile(cidFile)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "error reading CIDFile")
|
|
}
|
|
id := strings.Split(string(content), "\n")[0]
|
|
names = append(names, id)
|
|
}
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime)
|
|
if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pool := shared.NewPool("stop", maxWorkers, len(ctrs))
|
|
for _, c := range ctrs {
|
|
c := c
|
|
|
|
if timeout == nil {
|
|
t := c.StopTimeout()
|
|
timeout = &t
|
|
logrus.Debugf("Set timeout to container %s default (%d)", c.ID(), *timeout)
|
|
}
|
|
|
|
pool.Add(shared.Job{
|
|
ID: c.ID(),
|
|
Fn: func() error {
|
|
err := c.StopWithTimeout(*timeout)
|
|
if err != nil {
|
|
if errors.Cause(err) == define.ErrCtrStopped {
|
|
logrus.Debugf("Container %s is already stopped", c.ID())
|
|
return nil
|
|
} else if cli.All && errors.Cause(err) == define.ErrCtrStateInvalid {
|
|
logrus.Debugf("Container %s is not running, could not stop", c.ID())
|
|
return nil
|
|
}
|
|
logrus.Debugf("Failed to stop container %s: %s", c.ID(), err.Error())
|
|
}
|
|
return err
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// KillContainers sends signal to container(s) based on CLI inputs.
|
|
// Returns list of successful id(s), map of failed id(s) + error, or error not from container
|
|
func (r *LocalRuntime) KillContainers(ctx context.Context, cli *cliconfig.KillValues, signal syscall.Signal) ([]string, map[string]error, error) {
|
|
maxWorkers := shared.DefaultPoolSize("kill")
|
|
if cli.GlobalIsSet("max-workers") {
|
|
maxWorkers = cli.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum kill workers to %d", maxWorkers)
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pool := shared.NewPool("kill", maxWorkers, len(ctrs))
|
|
for _, c := range ctrs {
|
|
c := c
|
|
|
|
pool.Add(shared.Job{
|
|
ID: c.ID(),
|
|
Fn: func() error {
|
|
return c.Kill(uint(signal))
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// InitContainers initializes container(s) based on CLI inputs.
|
|
// Returns list of successful id(s), map of failed id(s) to errors, or a general
|
|
// error not from the container.
|
|
func (r *LocalRuntime) InitContainers(ctx context.Context, cli *cliconfig.InitValues) ([]string, map[string]error, error) {
|
|
maxWorkers := shared.DefaultPoolSize("init")
|
|
if cli.GlobalIsSet("max-workers") {
|
|
maxWorkers = cli.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum init workers to %d", maxWorkers)
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
|
|
pool := shared.NewPool("init", maxWorkers, len(ctrs))
|
|
for _, c := range ctrs {
|
|
ctr := c
|
|
|
|
pool.Add(shared.Job{
|
|
ID: ctr.ID(),
|
|
Fn: func() error {
|
|
err := ctr.Init(ctx)
|
|
if err != nil {
|
|
// If we're initializing all containers, ignore invalid state errors
|
|
if cli.All && errors.Cause(err) == define.ErrCtrStateInvalid {
|
|
return nil
|
|
}
|
|
return err
|
|
}
|
|
return nil
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// RemoveContainers removes container(s) based on CLI inputs.
|
|
func (r *LocalRuntime) RemoveContainers(ctx context.Context, cli *cliconfig.RmValues) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
)
|
|
|
|
maxWorkers := shared.DefaultPoolSize("rm")
|
|
if cli.GlobalIsSet("max-workers") {
|
|
maxWorkers = cli.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
|
|
|
|
if cli.Storage {
|
|
for _, ctr := range cli.InputArgs {
|
|
if err := r.RemoveStorageContainer(ctr, cli.Force); err != nil {
|
|
failures[ctr] = err
|
|
}
|
|
ok = append(ok, ctr)
|
|
}
|
|
return ok, failures, nil
|
|
}
|
|
|
|
names := cli.InputArgs
|
|
for _, cidFile := range cli.CIDFiles {
|
|
content, err := ioutil.ReadFile(cidFile)
|
|
if err != nil {
|
|
return nil, nil, errors.Wrap(err, "error reading CIDFile")
|
|
}
|
|
id := strings.Split(string(content), "\n")[0]
|
|
names = append(names, id)
|
|
}
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, names, r.Runtime)
|
|
if err != nil && !(cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
|
|
// Failed to get containers. If force is specified, get the containers ID
|
|
// and evict them
|
|
if !cli.Force {
|
|
return ok, failures, err
|
|
}
|
|
|
|
for _, ctr := range cli.InputArgs {
|
|
logrus.Debugf("Evicting container %q", ctr)
|
|
id, err := r.EvictContainer(ctx, ctr, cli.Volumes)
|
|
if err != nil {
|
|
if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
|
|
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
|
|
continue
|
|
}
|
|
failures[ctr] = errors.Wrapf(err, "Failed to evict container: %q", id)
|
|
continue
|
|
}
|
|
ok = append(ok, id)
|
|
}
|
|
return ok, failures, nil
|
|
}
|
|
|
|
pool := shared.NewPool("rm", maxWorkers, len(ctrs))
|
|
for _, c := range ctrs {
|
|
c := c
|
|
|
|
pool.Add(shared.Job{
|
|
ID: c.ID(),
|
|
Fn: func() error {
|
|
err := r.RemoveContainer(ctx, c, cli.Force, cli.Volumes)
|
|
if err != nil {
|
|
if cli.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
|
|
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
|
|
return nil
|
|
}
|
|
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
|
|
}
|
|
return err
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// UmountRootFilesystems removes container(s) based on CLI inputs.
|
|
func (r *LocalRuntime) UmountRootFilesystems(ctx context.Context, cli *cliconfig.UmountValues) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
)
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
|
|
for _, ctr := range ctrs {
|
|
state, err := ctr.State()
|
|
if err != nil {
|
|
logrus.Debugf("Error umounting container %s state: %s", ctr.ID(), err.Error())
|
|
continue
|
|
}
|
|
if state == define.ContainerStateRunning {
|
|
logrus.Debugf("Error umounting container %s, is running", ctr.ID())
|
|
continue
|
|
}
|
|
|
|
if err := ctr.Unmount(cli.Force); err != nil {
|
|
if cli.All && errors.Cause(err) == storage.ErrLayerNotMounted {
|
|
logrus.Debugf("Error umounting container %s, storage.ErrLayerNotMounted", ctr.ID())
|
|
continue
|
|
}
|
|
failures[ctr.ID()] = errors.Wrapf(err, "error unmounting container %s", ctr.ID())
|
|
} else {
|
|
ok = append(ok, ctr.ID())
|
|
}
|
|
}
|
|
return ok, failures, nil
|
|
}
|
|
|
|
// WaitOnContainers waits for all given container(s) to stop
|
|
func (r *LocalRuntime) WaitOnContainers(ctx context.Context, cli *cliconfig.WaitValues, interval time.Duration) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
)
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(false, cli.Latest, cli.InputArgs, r.Runtime)
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
|
|
for _, c := range ctrs {
|
|
if returnCode, err := c.WaitWithInterval(interval); err == nil {
|
|
ok = append(ok, strconv.Itoa(int(returnCode)))
|
|
} else {
|
|
failures[c.ID()] = err
|
|
}
|
|
}
|
|
return ok, failures, err
|
|
}
|
|
|
|
// Log logs one or more containers
|
|
func (r *LocalRuntime) Log(c *cliconfig.LogsValues, options *logs.LogOptions) error {
|
|
|
|
var wg sync.WaitGroup
|
|
options.WaitGroup = &wg
|
|
if len(c.InputArgs) > 1 {
|
|
options.Multi = true
|
|
}
|
|
tailLen := int(c.Tail)
|
|
if tailLen < 0 {
|
|
tailLen = 0
|
|
}
|
|
numContainers := len(c.InputArgs)
|
|
if numContainers == 0 {
|
|
numContainers = 1
|
|
}
|
|
logChannel := make(chan *logs.LogLine, tailLen*numContainers+1)
|
|
containers, err := shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if err := r.Runtime.Log(containers, options, logChannel); err != nil {
|
|
return err
|
|
}
|
|
go func() {
|
|
wg.Wait()
|
|
close(logChannel)
|
|
}()
|
|
for line := range logChannel {
|
|
fmt.Println(line.String(options))
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateContainer creates a libpod container
|
|
func (r *LocalRuntime) CreateContainer(ctx context.Context, c *cliconfig.CreateValues) (string, error) {
|
|
results := shared.NewIntermediateLayer(&c.PodmanCommand, false)
|
|
ctr, _, err := shared.CreateContainer(ctx, &results, r.Runtime)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return ctr.ID(), nil
|
|
}
|
|
|
|
// Select the detach keys to use from user input flag, config file, or default value
|
|
func (r *LocalRuntime) selectDetachKeys(flagValue string) (string, error) {
|
|
if flagValue != "" {
|
|
return flagValue, nil
|
|
}
|
|
|
|
config, err := r.GetConfig()
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "unable to retrieve runtime config")
|
|
}
|
|
if config.DetachKeys != "" {
|
|
return config.DetachKeys, nil
|
|
}
|
|
|
|
return define.DefaultDetachKeys, nil
|
|
}
|
|
|
|
// Run a libpod container
|
|
func (r *LocalRuntime) Run(ctx context.Context, c *cliconfig.RunValues, exitCode int) (int, error) {
|
|
results := shared.NewIntermediateLayer(&c.PodmanCommand, false)
|
|
|
|
ctr, createConfig, err := shared.CreateContainer(ctx, &results, r.Runtime)
|
|
if err != nil {
|
|
return exitCode, err
|
|
}
|
|
|
|
if logrus.GetLevel() == logrus.DebugLevel {
|
|
cgroupPath, err := ctr.CGroupPath()
|
|
if err == nil {
|
|
logrus.Debugf("container %q has CgroupParent %q", ctr.ID(), cgroupPath)
|
|
}
|
|
}
|
|
|
|
// Handle detached start
|
|
if createConfig.Detach {
|
|
// if the container was created as part of a pod, also start its dependencies, if any.
|
|
if err := ctr.Start(ctx, c.IsSet("pod")); err != nil {
|
|
// This means the command did not exist
|
|
return define.ExitCode(err), err
|
|
}
|
|
|
|
fmt.Printf("%s\n", ctr.ID())
|
|
exitCode = 0
|
|
return exitCode, nil
|
|
}
|
|
|
|
outputStream := os.Stdout
|
|
errorStream := os.Stderr
|
|
inputStream := os.Stdin
|
|
|
|
// If -i is not set, clear stdin
|
|
if !c.Bool("interactive") {
|
|
inputStream = nil
|
|
}
|
|
|
|
// If attach is set, clear stdin/stdout/stderr and only attach requested
|
|
if c.IsSet("attach") || c.IsSet("a") {
|
|
outputStream = nil
|
|
errorStream = nil
|
|
if !c.Bool("interactive") {
|
|
inputStream = nil
|
|
}
|
|
|
|
attachTo := c.StringSlice("attach")
|
|
for _, stream := range attachTo {
|
|
switch strings.ToLower(stream) {
|
|
case "stdout":
|
|
outputStream = os.Stdout
|
|
case "stderr":
|
|
errorStream = os.Stderr
|
|
case "stdin":
|
|
inputStream = os.Stdin
|
|
default:
|
|
return exitCode, errors.Wrapf(define.ErrInvalidArg, "invalid stream %q for --attach - must be one of stdin, stdout, or stderr", stream)
|
|
}
|
|
}
|
|
}
|
|
|
|
keys := c.String("detach-keys")
|
|
if !c.IsSet("detach-keys") {
|
|
keys, err = r.selectDetachKeys(keys)
|
|
if err != nil {
|
|
return exitCode, err
|
|
}
|
|
}
|
|
|
|
// if the container was created as part of a pod, also start its dependencies, if any.
|
|
if err := StartAttachCtr(ctx, ctr, outputStream, errorStream, inputStream, keys, c.Bool("sig-proxy"), true, c.IsSet("pod")); err != nil {
|
|
// We've manually detached from the container
|
|
// Do not perform cleanup, or wait for container exit code
|
|
// Just exit immediately
|
|
if errors.Cause(err) == define.ErrDetach {
|
|
return 0, nil
|
|
}
|
|
if c.IsSet("rm") {
|
|
if deleteError := r.Runtime.RemoveContainer(ctx, ctr, true, false); deleteError != nil {
|
|
logrus.Debugf("unable to remove container %s after failing to start and attach to it", ctr.ID())
|
|
}
|
|
}
|
|
if errors.Cause(err) == define.ErrWillDeadlock {
|
|
logrus.Debugf("Deadlock error: %v", err)
|
|
return define.ExitCode(err), errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID())
|
|
}
|
|
return define.ExitCode(err), err
|
|
}
|
|
|
|
if ecode, err := ctr.Wait(); err != nil {
|
|
if errors.Cause(err) == define.ErrNoSuchCtr {
|
|
// Check events
|
|
event, err := r.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited)
|
|
if err != nil {
|
|
logrus.Errorf("Cannot get exit code: %v", err)
|
|
exitCode = define.ExecErrorCodeNotFound
|
|
} else {
|
|
exitCode = event.ContainerExitCode
|
|
}
|
|
}
|
|
} else {
|
|
exitCode = int(ecode)
|
|
}
|
|
|
|
if c.IsSet("rm") {
|
|
if err := r.Runtime.RemoveContainer(ctx, ctr, false, true); err != nil {
|
|
if errors.Cause(err) == define.ErrNoSuchCtr ||
|
|
errors.Cause(err) == define.ErrCtrRemoved {
|
|
logrus.Warnf("Container %s does not exist: %v", ctr.ID(), err)
|
|
} else {
|
|
logrus.Errorf("Error removing container %s: %v", ctr.ID(), err)
|
|
}
|
|
}
|
|
}
|
|
|
|
return exitCode, nil
|
|
}
|
|
|
|
// Ps ...
|
|
func (r *LocalRuntime) Ps(c *cliconfig.PsValues, opts shared.PsOptions) ([]shared.PsContainerOutput, error) {
|
|
maxWorkers := shared.Parallelize("ps")
|
|
if c.GlobalIsSet("max-workers") {
|
|
maxWorkers = c.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
|
|
return shared.GetPsContainerOutput(r.Runtime, opts, c.Filter, maxWorkers)
|
|
}
|
|
|
|
// Attach ...
|
|
func (r *LocalRuntime) Attach(ctx context.Context, c *cliconfig.AttachValues) error {
|
|
var (
|
|
ctr *libpod.Container
|
|
err error
|
|
)
|
|
|
|
if c.Latest {
|
|
ctr, err = r.Runtime.GetLatestContainer()
|
|
} else {
|
|
ctr, err = r.Runtime.LookupContainer(c.InputArgs[0])
|
|
}
|
|
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to exec into %s", c.InputArgs[0])
|
|
}
|
|
|
|
conState, err := ctr.State()
|
|
if err != nil {
|
|
return errors.Wrapf(err, "unable to determine state of %s", ctr.ID())
|
|
}
|
|
if conState != define.ContainerStateRunning {
|
|
return errors.Errorf("you can only attach to running containers")
|
|
}
|
|
|
|
inputStream := os.Stdin
|
|
if c.NoStdin {
|
|
inputStream = nil
|
|
}
|
|
|
|
keys := c.DetachKeys
|
|
if !c.IsSet("detach-keys") {
|
|
keys, err = r.selectDetachKeys(keys)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
}
|
|
|
|
// If the container is in a pod, also set to recursively start dependencies
|
|
if err := StartAttachCtr(ctx, ctr, os.Stdout, os.Stderr, inputStream, keys, c.SigProxy, false, ctr.PodID() != ""); err != nil && errors.Cause(err) != define.ErrDetach {
|
|
return errors.Wrapf(err, "error attaching to container %s", ctr.ID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Checkpoint one or more containers
|
|
func (r *LocalRuntime) Checkpoint(c *cliconfig.CheckpointValues) error {
|
|
var (
|
|
containers []*libpod.Container
|
|
err, lastError error
|
|
)
|
|
|
|
options := libpod.ContainerCheckpointOptions{
|
|
Keep: c.Keep,
|
|
KeepRunning: c.LeaveRunning,
|
|
TCPEstablished: c.TcpEstablished,
|
|
TargetFile: c.Export,
|
|
IgnoreRootfs: c.IgnoreRootfs,
|
|
}
|
|
if c.Export == "" && c.IgnoreRootfs {
|
|
return errors.Errorf("--ignore-rootfs can only be used with --export")
|
|
}
|
|
if c.All {
|
|
containers, err = r.Runtime.GetRunningContainers()
|
|
} else {
|
|
containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, ctr := range containers {
|
|
if err = ctr.Checkpoint(context.TODO(), options); err != nil {
|
|
if lastError != nil {
|
|
fmt.Fprintln(os.Stderr, lastError)
|
|
}
|
|
lastError = errors.Wrapf(err, "failed to checkpoint container %v", ctr.ID())
|
|
} else {
|
|
fmt.Println(ctr.ID())
|
|
}
|
|
}
|
|
return lastError
|
|
}
|
|
|
|
// Restore one or more containers
|
|
func (r *LocalRuntime) Restore(ctx context.Context, c *cliconfig.RestoreValues) error {
|
|
var (
|
|
containers []*libpod.Container
|
|
err, lastError error
|
|
filterFuncs []libpod.ContainerFilter
|
|
)
|
|
|
|
options := libpod.ContainerCheckpointOptions{
|
|
Keep: c.Keep,
|
|
TCPEstablished: c.TcpEstablished,
|
|
TargetFile: c.Import,
|
|
Name: c.Name,
|
|
IgnoreRootfs: c.IgnoreRootfs,
|
|
IgnoreStaticIP: c.IgnoreStaticIP,
|
|
IgnoreStaticMAC: c.IgnoreStaticMAC,
|
|
}
|
|
|
|
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
|
|
state, _ := c.State()
|
|
return state == define.ContainerStateExited
|
|
})
|
|
|
|
switch {
|
|
case c.Import != "":
|
|
containers, err = crImportCheckpoint(ctx, r.Runtime, c.Import, c.Name)
|
|
case c.All:
|
|
containers, err = r.GetContainers(filterFuncs...)
|
|
default:
|
|
containers, err = shortcuts.GetContainersByContext(false, c.Latest, c.InputArgs, r.Runtime)
|
|
}
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
for _, ctr := range containers {
|
|
if err = ctr.Restore(context.TODO(), options); err != nil {
|
|
if lastError != nil {
|
|
fmt.Fprintln(os.Stderr, lastError)
|
|
}
|
|
lastError = errors.Wrapf(err, "failed to restore container %v", ctr.ID())
|
|
} else {
|
|
fmt.Println(ctr.ID())
|
|
}
|
|
}
|
|
return lastError
|
|
}
|
|
|
|
// Start will start a container
|
|
func (r *LocalRuntime) Start(ctx context.Context, c *cliconfig.StartValues, sigProxy bool) (int, error) {
|
|
var (
|
|
exitCode = define.ExecErrorCodeGeneric
|
|
lastError error
|
|
)
|
|
|
|
args := c.InputArgs
|
|
if c.Latest {
|
|
lastCtr, err := r.GetLatestContainer()
|
|
if err != nil {
|
|
return 0, errors.Wrapf(err, "unable to get latest container")
|
|
}
|
|
args = append(args, lastCtr.ID())
|
|
}
|
|
|
|
for _, container := range args {
|
|
ctr, err := r.LookupContainer(container)
|
|
if err != nil {
|
|
if lastError != nil {
|
|
fmt.Fprintln(os.Stderr, lastError)
|
|
}
|
|
lastError = errors.Wrapf(err, "unable to find container %s", container)
|
|
continue
|
|
}
|
|
|
|
ctrState, err := ctr.State()
|
|
if err != nil {
|
|
return exitCode, errors.Wrapf(err, "unable to get container state")
|
|
}
|
|
|
|
ctrRunning := ctrState == define.ContainerStateRunning
|
|
|
|
if c.Attach {
|
|
inputStream := os.Stdin
|
|
if !c.Interactive {
|
|
if !ctr.Stdin() {
|
|
inputStream = nil
|
|
}
|
|
}
|
|
|
|
keys := c.DetachKeys
|
|
if !c.IsSet("detach-keys") {
|
|
keys, err = r.selectDetachKeys(keys)
|
|
if err != nil {
|
|
return exitCode, err
|
|
}
|
|
}
|
|
|
|
// attach to the container and also start it not already running
|
|
// If the container is in a pod, also set to recursively start dependencies
|
|
err = StartAttachCtr(ctx, ctr.Container, os.Stdout, os.Stderr, inputStream, keys, sigProxy, !ctrRunning, ctr.PodID() != "")
|
|
if errors.Cause(err) == define.ErrDetach {
|
|
// User manually detached
|
|
// Exit cleanly immediately
|
|
exitCode = 0
|
|
return exitCode, nil
|
|
}
|
|
|
|
if errors.Cause(err) == define.ErrWillDeadlock {
|
|
logrus.Debugf("Deadlock error: %v", err)
|
|
return define.ExitCode(err), errors.Errorf("attempting to start container %s would cause a deadlock; please run 'podman system renumber' to resolve", ctr.ID())
|
|
}
|
|
|
|
if ctrRunning {
|
|
return 0, err
|
|
}
|
|
|
|
if err != nil {
|
|
return exitCode, errors.Wrapf(err, "unable to start container %s", ctr.ID())
|
|
}
|
|
|
|
if ecode, err := ctr.Wait(); err != nil {
|
|
if errors.Cause(err) == define.ErrNoSuchCtr {
|
|
// Check events
|
|
event, err := r.Runtime.GetLastContainerEvent(ctr.ID(), events.Exited)
|
|
if err != nil {
|
|
logrus.Errorf("Cannot get exit code: %v", err)
|
|
exitCode = define.ExecErrorCodeNotFound
|
|
} else {
|
|
exitCode = event.ContainerExitCode
|
|
}
|
|
}
|
|
} else {
|
|
exitCode = int(ecode)
|
|
}
|
|
|
|
return exitCode, nil
|
|
}
|
|
// Start the container if it's not running already.
|
|
if !ctrRunning {
|
|
// Handle non-attach start
|
|
// If the container is in a pod, also set to recursively start dependencies
|
|
if err := ctr.Start(ctx, ctr.PodID() != ""); err != nil {
|
|
if lastError != nil {
|
|
fmt.Fprintln(os.Stderr, lastError)
|
|
}
|
|
if errors.Cause(err) == define.ErrWillDeadlock {
|
|
lastError = errors.Wrapf(err, "please run 'podman system renumber' to resolve deadlocks")
|
|
continue
|
|
}
|
|
lastError = errors.Wrapf(err, "unable to start container %q", container)
|
|
continue
|
|
}
|
|
}
|
|
// Check if the container is referenced by ID or by name and print
|
|
// it accordingly.
|
|
if strings.HasPrefix(ctr.ID(), container) {
|
|
fmt.Println(ctr.ID())
|
|
} else {
|
|
fmt.Println(container)
|
|
}
|
|
}
|
|
return exitCode, lastError
|
|
}
|
|
|
|
// PauseContainers removes container(s) based on CLI inputs.
|
|
func (r *LocalRuntime) PauseContainers(ctx context.Context, cli *cliconfig.PauseValues) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
ctrs []*libpod.Container
|
|
err error
|
|
)
|
|
|
|
maxWorkers := shared.DefaultPoolSize("pause")
|
|
if cli.GlobalIsSet("max-workers") {
|
|
maxWorkers = cli.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
|
|
|
|
if cli.All {
|
|
ctrs, err = r.GetRunningContainers()
|
|
} else {
|
|
ctrs, err = shortcuts.GetContainersByContext(false, false, cli.InputArgs, r.Runtime)
|
|
}
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
|
|
pool := shared.NewPool("pause", maxWorkers, len(ctrs))
|
|
for _, c := range ctrs {
|
|
ctr := c
|
|
pool.Add(shared.Job{
|
|
ID: ctr.ID(),
|
|
Fn: func() error {
|
|
err := ctr.Pause()
|
|
if err != nil {
|
|
logrus.Debugf("Failed to pause container %s: %s", ctr.ID(), err.Error())
|
|
}
|
|
return err
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// UnpauseContainers removes container(s) based on CLI inputs.
|
|
func (r *LocalRuntime) UnpauseContainers(ctx context.Context, cli *cliconfig.UnpauseValues) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
ctrs []*libpod.Container
|
|
err error
|
|
)
|
|
|
|
maxWorkers := shared.DefaultPoolSize("pause")
|
|
if cli.GlobalIsSet("max-workers") {
|
|
maxWorkers = cli.GlobalFlags.MaxWorks
|
|
}
|
|
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
|
|
|
|
if cli.All {
|
|
var filterFuncs []libpod.ContainerFilter
|
|
filterFuncs = append(filterFuncs, func(c *libpod.Container) bool {
|
|
state, _ := c.State()
|
|
return state == define.ContainerStatePaused
|
|
})
|
|
ctrs, err = r.GetContainers(filterFuncs...)
|
|
} else {
|
|
ctrs, err = shortcuts.GetContainersByContext(false, false, cli.InputArgs, r.Runtime)
|
|
}
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
|
|
pool := shared.NewPool("pause", maxWorkers, len(ctrs))
|
|
for _, c := range ctrs {
|
|
ctr := c
|
|
pool.Add(shared.Job{
|
|
ID: ctr.ID(),
|
|
Fn: func() error {
|
|
err := ctr.Unpause()
|
|
if err != nil {
|
|
logrus.Debugf("Failed to unpause container %s: %s", ctr.ID(), err.Error())
|
|
}
|
|
return err
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// Restart containers without or without a timeout
|
|
func (r *LocalRuntime) Restart(ctx context.Context, c *cliconfig.RestartValues) ([]string, map[string]error, error) {
|
|
var (
|
|
containers []*libpod.Container
|
|
restartContainers []*libpod.Container
|
|
err error
|
|
)
|
|
useTimeout := c.Flag("timeout").Changed || c.Flag("time").Changed
|
|
inputTimeout := c.Timeout
|
|
|
|
// Handle --latest
|
|
switch {
|
|
case c.Latest:
|
|
lastCtr, err := r.Runtime.GetLatestContainer()
|
|
if err != nil {
|
|
return nil, nil, errors.Wrapf(err, "unable to get latest container")
|
|
}
|
|
restartContainers = append(restartContainers, lastCtr)
|
|
case c.Running:
|
|
containers, err = r.GetRunningContainers()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
restartContainers = append(restartContainers, containers...)
|
|
case c.All:
|
|
containers, err = r.Runtime.GetAllContainers()
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
restartContainers = append(restartContainers, containers...)
|
|
default:
|
|
for _, id := range c.InputArgs {
|
|
ctr, err := r.Runtime.LookupContainer(id)
|
|
if err != nil {
|
|
return nil, nil, err
|
|
}
|
|
restartContainers = append(restartContainers, ctr)
|
|
}
|
|
}
|
|
|
|
maxWorkers := shared.DefaultPoolSize("restart")
|
|
if c.GlobalIsSet("max-workers") {
|
|
maxWorkers = c.GlobalFlags.MaxWorks
|
|
}
|
|
|
|
logrus.Debugf("Setting maximum workers to %d", maxWorkers)
|
|
|
|
// We now have a slice of all the containers to be restarted. Iterate them to
|
|
// create restart Funcs with a timeout as needed
|
|
pool := shared.NewPool("restart", maxWorkers, len(restartContainers))
|
|
for _, c := range restartContainers {
|
|
ctr := c
|
|
timeout := ctr.StopTimeout()
|
|
if useTimeout {
|
|
timeout = inputTimeout
|
|
}
|
|
pool.Add(shared.Job{
|
|
ID: ctr.ID(),
|
|
Fn: func() error {
|
|
err := ctr.RestartWithTimeout(ctx, timeout)
|
|
if err != nil {
|
|
logrus.Debugf("Failed to restart container %s: %s", ctr.ID(), err.Error())
|
|
}
|
|
return err
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// Top display the running processes of a container
|
|
func (r *LocalRuntime) Top(cli *cliconfig.TopValues) ([]string, error) {
|
|
var (
|
|
descriptors []string
|
|
container *libpod.Container
|
|
err error
|
|
)
|
|
if cli.Latest {
|
|
descriptors = cli.InputArgs
|
|
container, err = r.Runtime.GetLatestContainer()
|
|
} else {
|
|
descriptors = cli.InputArgs[1:]
|
|
container, err = r.Runtime.LookupContainer(cli.InputArgs[0])
|
|
}
|
|
if err != nil {
|
|
return nil, errors.Wrapf(err, "unable to lookup requested container")
|
|
}
|
|
|
|
return container.Top(descriptors)
|
|
}
|
|
|
|
// ExecContainer executes a command in the container
|
|
func (r *LocalRuntime) ExecContainer(ctx context.Context, cli *cliconfig.ExecValues) (int, error) {
|
|
var (
|
|
ctr *Container
|
|
err error
|
|
cmd []string
|
|
)
|
|
// default invalid command exit code
|
|
ec := define.ExecErrorCodeGeneric
|
|
|
|
if cli.Latest {
|
|
if ctr, err = r.GetLatestContainer(); err != nil {
|
|
return ec, err
|
|
}
|
|
cmd = cli.InputArgs[0:]
|
|
} else {
|
|
if ctr, err = r.LookupContainer(cli.InputArgs[0]); err != nil {
|
|
return ec, err
|
|
}
|
|
cmd = cli.InputArgs[1:]
|
|
}
|
|
|
|
if cli.PreserveFDs > 0 {
|
|
entries, err := ioutil.ReadDir("/proc/self/fd")
|
|
if err != nil {
|
|
return ec, errors.Wrapf(err, "unable to read /proc/self/fd")
|
|
}
|
|
|
|
m := make(map[int]bool)
|
|
for _, e := range entries {
|
|
i, err := strconv.Atoi(e.Name())
|
|
if err != nil {
|
|
return ec, errors.Wrapf(err, "cannot parse %s in /proc/self/fd", e.Name())
|
|
}
|
|
m[i] = true
|
|
}
|
|
|
|
for i := 3; i < 3+cli.PreserveFDs; i++ {
|
|
if _, found := m[i]; !found {
|
|
return ec, errors.New("invalid --preserve-fds=N specified. Not enough FDs available")
|
|
}
|
|
}
|
|
}
|
|
|
|
// Validate given environment variables
|
|
env := map[string]string{}
|
|
if err := parse.ReadKVStrings(env, cli.EnvFile, cli.Env); err != nil {
|
|
return ec, errors.Wrapf(err, "unable to process environment variables")
|
|
}
|
|
|
|
streams := new(libpod.AttachStreams)
|
|
streams.OutputStream = os.Stdout
|
|
streams.ErrorStream = os.Stderr
|
|
if cli.Interactive {
|
|
streams.InputStream = bufio.NewReader(os.Stdin)
|
|
streams.AttachInput = true
|
|
}
|
|
streams.AttachOutput = true
|
|
streams.AttachError = true
|
|
|
|
keys := cli.DetachKeys
|
|
if !cli.IsSet("detach-keys") {
|
|
keys, err = r.selectDetachKeys(keys)
|
|
if err != nil {
|
|
return ec, err
|
|
}
|
|
}
|
|
|
|
ec, err = ExecAttachCtr(ctx, ctr.Container, cli.Tty, cli.Privileged, env, cmd, cli.User, cli.Workdir, streams, uint(cli.PreserveFDs), keys)
|
|
return define.TranslateExecErrorToExitCode(ec, err), err
|
|
}
|
|
|
|
// Prune removes stopped containers
|
|
func (r *LocalRuntime) Prune(ctx context.Context, maxWorkers int, filters []string) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
err error
|
|
filterFunc []libpod.ContainerFilter
|
|
)
|
|
|
|
logrus.Debugf("Setting maximum rm workers to %d", maxWorkers)
|
|
|
|
for _, filter := range filters {
|
|
filterSplit := strings.SplitN(filter, "=", 2)
|
|
if len(filterSplit) < 2 {
|
|
return ok, failures, errors.Errorf("filter input must be in the form of filter=value: %s is invalid", filter)
|
|
}
|
|
|
|
f, err := shared.GenerateContainerFilterFuncs(filterSplit[0], filterSplit[1], r.Runtime)
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
filterFunc = append(filterFunc, f)
|
|
}
|
|
|
|
containerStateFilter := func(c *libpod.Container) bool {
|
|
state, err := c.State()
|
|
if err != nil {
|
|
logrus.Error(err)
|
|
return false
|
|
}
|
|
if c.PodID() != "" {
|
|
return false
|
|
}
|
|
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
filterFunc = append(filterFunc, containerStateFilter)
|
|
|
|
delContainers, err := r.Runtime.GetContainers(filterFunc...)
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
if len(delContainers) < 1 {
|
|
return ok, failures, err
|
|
}
|
|
pool := shared.NewPool("prune", maxWorkers, len(delContainers))
|
|
for _, c := range delContainers {
|
|
ctr := c
|
|
pool.Add(shared.Job{
|
|
ID: ctr.ID(),
|
|
Fn: func() error {
|
|
err := r.Runtime.RemoveContainer(ctx, ctr, false, false)
|
|
if err != nil {
|
|
logrus.Debugf("Failed to prune container %s: %s", ctr.ID(), err.Error())
|
|
}
|
|
return err
|
|
},
|
|
})
|
|
}
|
|
return pool.Run()
|
|
}
|
|
|
|
// CleanupContainers any leftovers bits of stopped containers
|
|
func (r *LocalRuntime) CleanupContainers(ctx context.Context, cli *cliconfig.CleanupValues) ([]string, map[string]error, error) {
|
|
var (
|
|
ok = []string{}
|
|
failures = map[string]error{}
|
|
)
|
|
|
|
ctrs, err := shortcuts.GetContainersByContext(cli.All, cli.Latest, cli.InputArgs, r.Runtime)
|
|
if err != nil {
|
|
return ok, failures, err
|
|
}
|
|
|
|
for _, ctr := range ctrs {
|
|
if cli.Remove {
|
|
err = removeContainer(ctx, ctr, r)
|
|
} else {
|
|
err = cleanupContainer(ctx, ctr, r)
|
|
}
|
|
|
|
if err == nil {
|
|
ok = append(ok, ctr.ID())
|
|
} else {
|
|
failures[ctr.ID()] = err
|
|
}
|
|
}
|
|
return ok, failures, nil
|
|
}
|
|
|
|
// Only used when cleaning up containers
|
|
func removeContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error {
|
|
if err := runtime.RemoveContainer(ctx, ctr, false, true); err != nil {
|
|
return errors.Wrapf(err, "failed to cleanup and remove container %v", ctr.ID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func cleanupContainer(ctx context.Context, ctr *libpod.Container, runtime *LocalRuntime) error {
|
|
if err := ctr.Cleanup(ctx); err != nil {
|
|
return errors.Wrapf(err, "failed to cleanup container %v", ctr.ID())
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// Port displays port information about existing containers
|
|
func (r *LocalRuntime) Port(c *cliconfig.PortValues) ([]*Container, error) {
|
|
var (
|
|
portContainers []*Container
|
|
containers []*libpod.Container
|
|
err error
|
|
)
|
|
|
|
if !c.All {
|
|
names := []string{}
|
|
if len(c.InputArgs) >= 1 {
|
|
names = []string{c.InputArgs[0]}
|
|
}
|
|
containers, err = shortcuts.GetContainersByContext(false, c.Latest, names, r.Runtime)
|
|
} else {
|
|
containers, err = r.Runtime.GetRunningContainers()
|
|
}
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
//Convert libpod containers to adapter Containers
|
|
for _, con := range containers {
|
|
if state, _ := con.State(); state != define.ContainerStateRunning {
|
|
continue
|
|
}
|
|
portContainers = append(portContainers, &Container{con})
|
|
}
|
|
return portContainers, nil
|
|
}
|
|
|
|
// generateServiceName generates the container name and the service name for systemd service.
|
|
func generateServiceName(c *cliconfig.GenerateSystemdValues, ctr *libpod.Container, pod *libpod.Pod) (string, string) {
|
|
var kind, name, ctrName string
|
|
if pod == nil {
|
|
kind = "container"
|
|
name = ctr.ID()
|
|
if c.Name {
|
|
name = ctr.Name()
|
|
}
|
|
ctrName = name
|
|
} else {
|
|
kind = "pod"
|
|
name = pod.ID()
|
|
ctrName = ctr.ID()
|
|
if c.Name {
|
|
name = pod.Name()
|
|
ctrName = ctr.Name()
|
|
}
|
|
}
|
|
return ctrName, fmt.Sprintf("%s-%s", kind, name)
|
|
}
|
|
|
|
// generateSystemdgenContainerInfo is a helper to generate a
|
|
// systemdgen.ContainerInfo for `GenerateSystemd`.
|
|
func (r *LocalRuntime) generateSystemdgenContainerInfo(c *cliconfig.GenerateSystemdValues, nameOrID string, pod *libpod.Pod) (*generate.ContainerInfo, bool, error) {
|
|
ctr, err := r.Runtime.LookupContainer(nameOrID)
|
|
if err != nil {
|
|
return nil, false, err
|
|
}
|
|
|
|
timeout := int(ctr.StopTimeout())
|
|
if c.StopTimeout >= 0 {
|
|
timeout = c.StopTimeout
|
|
}
|
|
|
|
config := ctr.Config()
|
|
conmonPidFile := config.ConmonPidFile
|
|
if conmonPidFile == "" {
|
|
return nil, true, errors.Errorf("conmon PID file path is empty, try to recreate the container with --conmon-pidfile flag")
|
|
}
|
|
|
|
name, serviceName := generateServiceName(c, ctr, pod)
|
|
info := &generate.ContainerInfo{
|
|
ServiceName: serviceName,
|
|
ContainerName: name,
|
|
RestartPolicy: c.RestartPolicy,
|
|
PIDFile: conmonPidFile,
|
|
StopTimeout: timeout,
|
|
GenerateTimestamp: true,
|
|
CreateCommand: config.CreateCommand,
|
|
}
|
|
|
|
return info, true, nil
|
|
}
|
|
|
|
// GenerateSystemd creates a unit file for a container or pod.
|
|
func (r *LocalRuntime) GenerateSystemd(c *cliconfig.GenerateSystemdValues) (string, error) {
|
|
opts := generate.Options{
|
|
Files: c.Files,
|
|
New: c.New,
|
|
}
|
|
|
|
// First assume it's a container.
|
|
if info, found, err := r.generateSystemdgenContainerInfo(c, c.InputArgs[0], nil); found && err != nil {
|
|
return "", err
|
|
} else if found && err == nil {
|
|
return generate.CreateContainerSystemdUnit(info, opts)
|
|
}
|
|
|
|
// --new does not support pods.
|
|
if c.New {
|
|
return "", errors.Errorf("error generating systemd unit files: cannot generate generic files for a pod")
|
|
}
|
|
|
|
// We're either having a pod or garbage.
|
|
pod, err := r.Runtime.LookupPod(c.InputArgs[0])
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Error out if the pod has no infra container, which we require to be the
|
|
// main service.
|
|
if !pod.HasInfraContainer() {
|
|
return "", fmt.Errorf("error generating systemd unit files: Pod %q has no infra container", pod.Name())
|
|
}
|
|
|
|
// Generate a systemdgen.ContainerInfo for the infra container. This
|
|
// ContainerInfo acts as the main service of the pod.
|
|
infraID, err := pod.InfraContainerID()
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
podInfo, _, err := r.generateSystemdgenContainerInfo(c, infraID, pod)
|
|
if err != nil {
|
|
return "", nil
|
|
}
|
|
|
|
// Compute the container-dependency graph for the Pod.
|
|
containers, err := pod.AllContainers()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
if len(containers) == 0 {
|
|
return "", fmt.Errorf("error generating systemd unit files: Pod %q has no containers", pod.Name())
|
|
}
|
|
graph, err := libpod.BuildContainerGraph(containers)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
// Traverse the dependency graph and create systemdgen.ContainerInfo's for
|
|
// each container.
|
|
containerInfos := []*generate.ContainerInfo{podInfo}
|
|
for ctr, dependencies := range graph.DependencyMap() {
|
|
// Skip the infra container as we already generated it.
|
|
if ctr.ID() == infraID {
|
|
continue
|
|
}
|
|
ctrInfo, _, err := r.generateSystemdgenContainerInfo(c, ctr.ID(), nil)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
// Now add the container's dependencies and at the container as a
|
|
// required service of the infra container.
|
|
for _, dep := range dependencies {
|
|
if dep.ID() == infraID {
|
|
ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, podInfo.ServiceName)
|
|
} else {
|
|
_, serviceName := generateServiceName(c, dep, nil)
|
|
ctrInfo.BoundToServices = append(ctrInfo.BoundToServices, serviceName)
|
|
}
|
|
}
|
|
podInfo.RequiredServices = append(podInfo.RequiredServices, ctrInfo.ServiceName)
|
|
containerInfos = append(containerInfos, ctrInfo)
|
|
}
|
|
|
|
// Now generate the systemd service for all containers.
|
|
builder := strings.Builder{}
|
|
for i, info := range containerInfos {
|
|
if i > 0 {
|
|
builder.WriteByte('\n')
|
|
}
|
|
out, err := generate.CreateContainerSystemdUnit(info, opts)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
builder.WriteString(out)
|
|
}
|
|
|
|
return builder.String(), nil
|
|
}
|
|
|
|
// GetNamespaces returns namespace information about a container for PS
|
|
func (r *LocalRuntime) GetNamespaces(container shared.PsContainerOutput) *shared.Namespace {
|
|
return shared.GetNamespaces(container.Pid)
|
|
}
|
|
|
|
// Commit creates a local image from a container
|
|
func (r *LocalRuntime) Commit(ctx context.Context, c *cliconfig.CommitValues, container, imageName string) (string, error) {
|
|
var (
|
|
writer io.Writer
|
|
mimeType string
|
|
)
|
|
switch c.Format {
|
|
case "oci":
|
|
mimeType = buildah.OCIv1ImageManifest
|
|
if c.Flag("message").Changed {
|
|
return "", errors.Errorf("messages are only compatible with the docker image format (-f docker)")
|
|
}
|
|
case "docker":
|
|
mimeType = manifest.DockerV2Schema2MediaType
|
|
default:
|
|
return "", errors.Errorf("unrecognized image format %q", c.Format)
|
|
}
|
|
if !c.Quiet {
|
|
writer = os.Stderr
|
|
}
|
|
ctr, err := r.Runtime.LookupContainer(container)
|
|
if err != nil {
|
|
return "", errors.Wrapf(err, "error looking up container %q", container)
|
|
}
|
|
|
|
rtc, err := r.Runtime.GetConfig()
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
|
|
sc := image.GetSystemContext(rtc.SignaturePolicyPath, "", false)
|
|
coptions := buildah.CommitOptions{
|
|
SignaturePolicyPath: rtc.SignaturePolicyPath,
|
|
ReportWriter: writer,
|
|
SystemContext: sc,
|
|
PreferredManifestType: mimeType,
|
|
}
|
|
options := libpod.ContainerCommitOptions{
|
|
CommitOptions: coptions,
|
|
Pause: c.Pause,
|
|
IncludeVolumes: c.IncludeVolumes,
|
|
Message: c.Message,
|
|
Changes: c.Change,
|
|
Author: c.Author,
|
|
}
|
|
newImage, err := ctr.Commit(ctx, imageName, options)
|
|
if err != nil {
|
|
return "", err
|
|
}
|
|
return newImage.ID(), nil
|
|
}
|