podmanv2 add core container commands

add core container commands for podmanv2: kill, pause, restart, rm, stop, unpause

Signed-off-by: Brent Baude <bbaude@redhat.com>
This commit is contained in:
Brent Baude
2020-03-21 13:47:07 -05:00
parent 2ffff3c6ab
commit 9536560b4f
24 changed files with 954 additions and 53 deletions

View File

@ -0,0 +1,71 @@
package containers
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
"github.com/spf13/cobra"
)
var (
killDescription = "The main process inside each container specified will be sent SIGKILL, or any signal specified with option --signal."
killCommand = &cobra.Command{
Use: "kill [flags] CONTAINER [CONTAINER...]",
Short: "Kill one or more running containers with a specific signal",
Long: killDescription,
RunE: kill,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, false, false)
},
Example: `podman kill mywebserver
podman kill 860a4b23
podman kill --signal TERM ctrID`,
}
)
var (
killOptions = entities.KillOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: killCommand,
})
flags := killCommand.Flags()
flags.BoolVarP(&killOptions.All, "all", "a", false, "Signal all running containers")
flags.StringVarP(&killOptions.Signal, "signal", "s", "KILL", "Signal to send to the container")
flags.BoolVarP(&killOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
if utils.IsRemote() {
_ = flags.MarkHidden("latest")
}
}
func kill(cmd *cobra.Command, args []string) error {
var (
err error
errs utils.OutputErrors
)
// Check if the signalString provided by the user is valid
// Invalid signals will return err
if _, err = signal.ParseSignalNameOrNumber(killOptions.Signal); err != nil {
return err
}
responses, err := registry.ContainerEngine().ContainerKill(context.Background(), args, killOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,63 @@
package containers
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
pauseDescription = `Pauses one or more running containers. The container name or ID can be used.`
pauseCommand = &cobra.Command{
Use: "pause [flags] CONTAINER [CONTAINER...]",
Short: "Pause all the processes in one or more containers",
Long: pauseDescription,
RunE: pause,
Example: `podman pause mywebserver
podman pause 860a4b23
podman pause -a`,
}
pauseOpts = entities.PauseUnPauseOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: pauseCommand,
})
flags := pauseCommand.Flags()
flags.BoolVarP(&pauseOpts.All, "all", "a", false, "Pause all running containers")
pauseCommand.SetHelpTemplate(registry.HelpTemplate())
pauseCommand.SetUsageTemplate(registry.UsageTemplate())
}
func pause(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if rootless.IsRootless() && !utils.IsRemote() {
return errors.New("pause is not supported for rootless containers")
}
if len(args) < 1 && !pauseOpts.All {
return errors.Errorf("you must provide at least one container name or id")
}
responses, err := registry.ContainerEngine().ContainerPause(context.Background(), args, pauseOpts)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,78 @@
package containers
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
restartDescription = `Restarts one or more running containers. The container ID or name can be used.
A timeout before forcibly stopping can be set, but defaults to 10 seconds.`
restartCommand = &cobra.Command{
Use: "restart [flags] CONTAINER [CONTAINER...]",
Short: "Restart one or more containers",
Long: restartDescription,
RunE: restart,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, false, false)
},
Example: `podman restart ctrID
podman restart --latest
podman restart ctrID1 ctrID2`,
}
)
var (
restartOptions = entities.RestartOptions{}
restartTimeout uint
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: restartCommand,
})
flags := restartCommand.Flags()
flags.BoolVarP(&restartOptions.All, "all", "a", false, "Restart all non-running containers")
flags.BoolVarP(&restartOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&restartOptions.Running, "running", false, "Restart only running containers when --all is used")
flags.UintVarP(&restartTimeout, "timeout", "t", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container")
flags.UintVar(&restartTimeout, "time", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container")
if utils.IsRemote() {
_ = flags.MarkHidden("latest")
}
}
func restart(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if len(args) < 1 && !restartOptions.Latest && !restartOptions.All {
return errors.Wrapf(define.ErrInvalidArg, "you must provide at least one container name or ID")
}
if cmd.Flag("timeout").Changed || cmd.Flag("time").Changed {
restartOptions.Timeout = &restartTimeout
}
responses, err := registry.ContainerEngine().ContainerRestart(context.Background(), args, restartOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,93 @@
package containers
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
"github.com/spf13/cobra"
)
var (
rmDescription = `Removes one or more containers from the host. The container name or ID can be used.
Command does not remove images. Running or unusable containers will not be removed without the -f option.`
rmCommand = &cobra.Command{
Use: "rm [flags] CONTAINER [CONTAINER...]",
Short: "Remove one or more containers",
Long: rmDescription,
RunE: rm,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, false, true)
},
Example: `podman rm imageID
podman rm mywebserver myflaskserver 860a4b23
podman rm --force --all
podman rm -f c684f0d469f2`,
}
)
var (
rmOptions = entities.RmOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: rmCommand,
})
flags := rmCommand.Flags()
flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all containers")
flags.BoolVarP(&rmOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Force removal of a running or unusable container. The default is false")
flags.BoolVarP(&rmOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.BoolVar(&rmOptions.Storage, "storage", false, "Remove container from storage library")
flags.BoolVarP(&rmOptions.Volumes, "volumes", "v", false, "Remove anonymous volumes associated with the container")
flags.StringArrayVarP(&rmOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
if utils.IsRemote() {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("ignore")
_ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("storage")
}
}
func rm(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
// Storage conflicts with --all/--latest/--volumes/--cidfile/--ignore
if rmOptions.Storage {
if rmOptions.All || rmOptions.Ignore || rmOptions.Latest || rmOptions.Volumes || rmOptions.CIDFiles != nil {
return errors.Errorf("--storage conflicts with --volumes, --all, --latest, --ignore and --cidfile")
}
}
responses, err := registry.ContainerEngine().ContainerRm(context.Background(), args, rmOptions)
if err != nil {
// TODO exitcode is a global main variable to track exit codes.
// we need this enabled
//if len(c.InputArgs) < 2 {
// exitCode = setExitCode(err)
//}
return err
}
for _, r := range responses {
if r.Err != nil {
// TODO this will not work with the remote client
if errors.Cause(err) == define.ErrWillDeadlock {
logrus.Errorf("Potential deadlock detected - please run 'podman system renumber' to resolve")
}
errs = append(errs, r.Err)
} else {
fmt.Println(r.Id)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,87 @@
package containers
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
stopDescription = `Stops one or more running containers. The container name or ID can be used.
A timeout to forcibly stop the container can also be set but defaults to 10 seconds otherwise.`
stopCommand = &cobra.Command{
Use: "stop [flags] CONTAINER [CONTAINER...]",
Short: "Stop one or more containers",
Long: stopDescription,
RunE: stop,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, false, true)
},
Example: `podman stop ctrID
podman stop --latest
podman stop --timeout 2 mywebserver 6e534f14da9d`,
}
)
var (
stopOptions = entities.StopOptions{}
stopTimeout uint
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: stopCommand,
})
flags := stopCommand.Flags()
flags.BoolVarP(&stopOptions.All, "all", "a", false, "Stop all running containers")
flags.BoolVarP(&stopOptions.Ignore, "ignore", "i", false, "Ignore errors when a specified container is missing")
flags.StringArrayVarP(&stopOptions.CIDFiles, "cidfile", "", nil, "Read the container ID from the file")
flags.BoolVarP(&stopOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.UintVar(&stopTimeout, "time", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container")
flags.UintVarP(&stopTimeout, "timeout", "t", define.CtrRemoveTimeout, "Seconds to wait for stop before killing the container")
if registry.EngineOpts.EngineMode == entities.ABIMode {
_ = flags.MarkHidden("latest")
_ = flags.MarkHidden("cidfile")
_ = flags.MarkHidden("ignore")
}
}
func stop(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if cmd.Flag("timeout").Changed && cmd.Flag("time").Changed {
return errors.New("the --timeout and --time flags are mutually exclusive")
}
stopOptions.Timeout = define.CtrRemoveTimeout
if cmd.Flag("timeout").Changed || cmd.Flag("time").Changed {
stopOptions.Timeout = stopTimeout
}
// TODO How do we access global attributes?
//if c.Bool("trace") {
// span, _ := opentracing.StartSpanFromContext(Ctx, "stopCmd")
// defer span.Finish()
//}
responses, err := registry.ContainerEngine().ContainerStop(context.Background(), args, stopOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1,60 @@
package containers
import (
"context"
"fmt"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/rootless"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
var (
unpauseDescription = `Unpauses one or more previously paused containers. The container name or ID can be used.`
unpauseCommand = &cobra.Command{
Use: "unpause [flags] CONTAINER [CONTAINER...]",
Short: "Unpause the processes in one or more containers",
Long: unpauseDescription,
RunE: unpause,
Example: `podman unpause ctrID
podman unpause --all`,
}
unPauseOptions = entities.PauseUnPauseOptions{}
)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: unpauseCommand,
Parent: containerCmd,
})
flags := unpauseCommand.Flags()
flags.BoolVarP(&unPauseOptions.All, "all", "a", false, "Pause all running containers")
}
func unpause(cmd *cobra.Command, args []string) error {
var (
errs utils.OutputErrors
)
if rootless.IsRootless() && !utils.IsRemote() {
return errors.New("unpause is not supported for rootless containers")
}
if len(args) < 1 && !unPauseOptions.All {
return errors.Errorf("you must provide at least one container name or id")
}
responses, err := registry.ContainerEngine().ContainerUnpause(context.Background(), args, unPauseOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Err == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Err)
}
}
return errs.PrintErrors()
}

View File

@ -0,0 +1 @@
package containers

View File

@ -5,7 +5,9 @@ import (
"fmt"
"time"
"github.com/containers/libpod/cmd/podmanV2/parse"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/utils"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/pkg/errors"
@ -20,6 +22,9 @@ var (
Short: "Block on one or more containers",
Long: waitDescription,
RunE: wait,
Args: func(cmd *cobra.Command, args []string) error {
return parse.CheckAllLatestAndCIDFile(cmd, args, false, false)
},
Example: `podman wait --latest
podman wait --interval 5000 ctrID
podman wait ctrID1 ctrID2`,
@ -27,7 +32,7 @@ var (
)
var (
waitFlags = entities.WaitOptions{}
waitOptions = entities.WaitOptions{}
waitCondition string
)
@ -35,12 +40,11 @@ func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: waitCommand,
Parent: containerCmd,
})
flags := waitCommand.Flags()
flags.DurationVarP(&waitFlags.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion")
flags.BoolVarP(&waitFlags.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.DurationVarP(&waitOptions.Interval, "interval", "i", time.Duration(250), "Milliseconds to wait before polling for completion")
flags.BoolVarP(&waitOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of")
flags.StringVar(&waitCondition, "condition", "stopped", "Condition to wait on")
if registry.EngineOpts.EngineMode == entities.ABIMode {
// TODO: This is the same as V1. We could skip creating the flag altogether in V2...
@ -50,33 +54,28 @@ func init() {
func wait(cmd *cobra.Command, args []string) error {
var (
err error
err error
errs utils.OutputErrors
)
if waitFlags.Latest && len(args) > 0 {
return errors.New("cannot combine latest flag and arguments")
}
if waitFlags.Interval == 0 {
if waitOptions.Interval == 0 {
return errors.New("interval must be greater then 0")
}
waitFlags.Condition, err = define.StringToContainerStatus(waitCondition)
waitOptions.Condition, err = define.StringToContainerStatus(waitCondition)
if err != nil {
return err
}
responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitFlags)
responses, err := registry.ContainerEngine().ContainerWait(context.Background(), args, waitOptions)
if err != nil {
return err
}
for _, r := range responses {
if r.Error == nil {
fmt.Println(r.Id)
} else {
errs = append(errs, r.Error)
}
}
for _, r := range responses {
if r.Error != nil {
fmt.Println(err)
}
}
return nil
return errs.PrintErrors()
}

View File

@ -13,6 +13,7 @@ import (
"strings"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
const (
@ -186,3 +187,47 @@ func ValidURL(urlStr string) error {
}
return nil
}
// checkAllLatestAndCIDFile checks that --all and --latest are used correctly.
// If cidfile is set, also check for the --cidfile flag.
func CheckAllLatestAndCIDFile(c *cobra.Command, args []string, ignoreArgLen bool, cidfile bool) error {
argLen := len(args)
if c.Flags().Lookup("all") == nil || c.Flags().Lookup("latest") == nil {
if !cidfile {
return errors.New("unable to lookup values for 'latest' or 'all'")
} else if c.Flags().Lookup("cidfile") == nil {
return errors.New("unable to lookup values for 'latest', 'all' or 'cidfile'")
}
}
specifiedAll, _ := c.Flags().GetBool("all")
specifiedLatest, _ := c.Flags().GetBool("latest")
specifiedCIDFile := false
if cid, _ := c.Flags().GetStringArray("cidfile"); len(cid) > 0 {
specifiedCIDFile = true
}
if specifiedCIDFile && (specifiedAll || specifiedLatest) {
return errors.Errorf("--all, --latest and --cidfile cannot be used together")
} else if specifiedAll && specifiedLatest {
return errors.Errorf("--all and --latest cannot be used together")
}
if ignoreArgLen {
return nil
}
if (argLen > 0) && (specifiedAll || specifiedLatest) {
return errors.Errorf("no arguments are needed with --all or --latest")
} else if cidfile && (argLen > 0) && (specifiedAll || specifiedLatest || specifiedCIDFile) {
return errors.Errorf("no arguments are needed with --all, --latest or --cidfile")
}
if specifiedCIDFile {
return nil
}
if argLen < 1 && !specifiedAll && !specifiedLatest && !specifiedCIDFile {
return errors.Errorf("you must provide at least one name or id")
}
return nil
}

View File

@ -23,8 +23,9 @@ var rootCmd = &cobra.Command{
func init() {
// Override default --help information of `--version` global flag}
var dummyVersion bool
rootCmd.PersistentFlags().BoolVarP(&dummyVersion, "version", "v", false, "Version of podman")
rootCmd.PersistentFlags().StringVarP(&registry.EngineOpts.Uri, "remote", "r", "", "URL to access podman service")
// TODO had to disable shorthand -v for version due to -v rm with volume
rootCmd.PersistentFlags().BoolVar(&dummyVersion, "version", false, "Version of Podman")
rootCmd.PersistentFlags().StringVarP(&registry.EngineOpts.Uri, "remote", "r", "", "URL to access Podman service")
rootCmd.PersistentFlags().StringSliceVar(&registry.EngineOpts.Identities, "identity", []string{}, "path to SSH identity file")
}

View File

@ -0,0 +1,16 @@
package utils
import "fmt"
type OutputErrors []error
func (o OutputErrors) PrintErrors() (lastError error) {
if len(o) == 0 {
return
}
lastError = o[len(o)-1]
for e := 0; e < len(o)-1; e++ {
fmt.Println(o[e])
}
return
}

View File

@ -0,0 +1,10 @@
package utils
import (
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/pkg/domain/entities"
)
func IsRemote() bool {
return registry.EngineOpts.EngineMode == entities.TunnelMode
}

View File

@ -7,7 +7,6 @@ import (
"strconv"
"strings"
"sync"
"syscall"
"time"
"github.com/containers/libpod/libpod"
@ -15,6 +14,7 @@ import (
"github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/api/handlers"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/containers/libpod/pkg/signal"
"github.com/containers/libpod/pkg/util"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@ -145,14 +145,20 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
decoder := r.Context().Value("decoder").(*schema.Decoder)
query := struct {
Signal syscall.Signal `schema:"signal"`
Signal string `schema:"signal"`
}{
Signal: syscall.SIGKILL,
Signal: "KILL",
}
if err := decoder.Decode(&query, r.URL.Query()); err != nil {
utils.Error(w, "Something went wrong.", http.StatusBadRequest, errors.Wrapf(err, "Failed to parse parameters for %s", r.URL.String()))
return
}
sig, err := signal.ParseSignalNameOrNumber(query.Signal)
if err != nil {
utils.InternalServerError(w, err)
return
}
name := utils.GetName(r)
con, err := runtime.LookupContainer(name)
if err != nil {
@ -172,7 +178,7 @@ func KillContainer(w http.ResponseWriter, r *http.Request) {
return
}
err = con.Kill(uint(query.Signal))
err = con.Kill(uint(sig))
if err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrapf(err, "unable to kill Container %s", name))
}

View File

@ -1,11 +1,9 @@
package compat
import (
"fmt"
"net/http"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/api/handlers/utils"
"github.com/gorilla/schema"
"github.com/pkg/errors"
@ -32,20 +30,6 @@ func RestartContainer(w http.ResponseWriter, r *http.Request) {
return
}
state, err := con.State()
if err != nil {
utils.InternalServerError(w, err)
return
}
// FIXME: This is not in the swagger.yml...
// If the Container is stopped already, send a 409
if state == define.ContainerStateStopped || state == define.ContainerStateExited {
msg := fmt.Sprintf("Container %s is not running", name)
utils.Error(w, msg, http.StatusConflict, errors.New(msg))
return
}
timeout := con.StopTimeout()
if _, found := r.URL.Query()["t"]; found {
timeout = uint(query.Timeout)

View File

@ -126,13 +126,13 @@ func Inspect(ctx context.Context, nameOrID string, size *bool) (*libpod.InspectC
// Kill sends a given signal to a given container. The signal should be the string
// representation of a signal like 'SIGKILL'. The nameOrID can be a container name
// or a partial/full ID
func Kill(ctx context.Context, nameOrID string, signal string) error {
func Kill(ctx context.Context, nameOrID string, sig string) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
params := url.Values{}
params.Set("signal", signal)
params.Set("signal", sig)
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/kill", params, nameOrID)
if err != nil {
return err
@ -247,14 +247,14 @@ func Exists(ctx context.Context, nameOrID string) (bool, error) {
// Stop stops a running container. The timeout is optional. The nameOrID can be a container name
// or a partial/full ID
func Stop(ctx context.Context, nameOrID string, timeout *int) error {
func Stop(ctx context.Context, nameOrID string, timeout *uint) error {
params := url.Values{}
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
if timeout != nil {
params.Set("t", strconv.Itoa(*timeout))
params.Set("t", strconv.Itoa(int(*timeout)))
}
response, err := conn.DoRequest(nil, http.MethodPost, "/containers/%s/stop", params, nameOrID)
if err != nil {

View File

@ -102,7 +102,7 @@ var _ = Describe("Podman volumes", func() {
Expect(code).To(BeNumerically("==", http.StatusConflict))
// Removing with a volume in use with force should work with a stopped container
zero := 0
zero := uint(0)
err = containers.Stop(connText, "vtest", &zero)
Expect(err).To(BeNil())
err = volumes.Remove(connText, vol.Name, &bindings.PTrue)

View File

@ -21,3 +21,63 @@ type WaitReport struct {
type BoolReport struct {
Value bool
}
type PauseUnPauseOptions struct {
All bool
}
type PauseUnpauseReport struct {
Err error
Id string
}
type StopOptions struct {
All bool
CIDFiles []string
Ignore bool
Latest bool
Timeout uint
}
type StopReport struct {
Err error
Id string
}
type KillOptions struct {
All bool
Latest bool
Signal string
}
type KillReport struct {
Err error
Id string
}
type RestartOptions struct {
All bool
Latest bool
Running bool
Timeout *uint
}
type RestartReport struct {
Err error
Id string
}
type RmOptions struct {
All bool
CIDFiles []string
Force bool
Ignore bool
Latest bool
Storage bool
Volumes bool
}
type RmReport struct {
Err error
Id string
}

View File

@ -5,9 +5,14 @@ import (
)
type ContainerEngine interface {
ContainerDelete(ctx context.Context, opts ContainerDeleteOptions) (*ContainerDeleteReport, error)
ContainerPrune(ctx context.Context) (*ContainerPruneReport, error)
ContainerExists(ctx context.Context, nameOrId string) (*BoolReport, error)
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)
ContainerUnpause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error)
ContainerStop(ctx context.Context, namesOrIds []string, options StopOptions) ([]*StopReport, error)
ContainerWait(ctx context.Context, namesOrIds []string, options WaitOptions) ([]WaitReport, error)
PodDelete(ctx context.Context, opts PodPruneOptions) (*PodDeleteReport, error)
PodExists(ctx context.Context, nameOrId string) (*BoolReport, error)

View File

@ -4,11 +4,16 @@ package abi
import (
"context"
"io/ioutil"
"strings"
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/adapter/shortcuts"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/signal"
"github.com/pkg/errors"
"github.com/sirupsen/logrus"
)
// TODO: Should return *entities.ContainerExistsReport, error
@ -41,8 +46,198 @@ func (ic *ContainerEngine) ContainerWait(ctx context.Context, namesOrIds []strin
return responses, nil
}
func (ic *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.ContainerDeleteOptions) (*entities.ContainerDeleteReport, error) {
panic("implement me")
func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) {
var (
ctrs []*libpod.Container
err error
report []*entities.PauseUnpauseReport
)
if options.All {
ctrs, err = ic.Libpod.GetAllContainers()
} else {
ctrs, err = shortcuts.GetContainersByContext(false, false, namesOrIds, ic.Libpod)
}
if err != nil {
return nil, err
}
for _, c := range ctrs {
err := c.Pause()
report = append(report, &entities.PauseUnpauseReport{Id: c.ID(), Err: err})
}
return report, nil
}
func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) {
var (
ctrs []*libpod.Container
err error
report []*entities.PauseUnpauseReport
)
if options.All {
ctrs, err = ic.Libpod.GetAllContainers()
} else {
ctrs, err = shortcuts.GetContainersByContext(false, false, namesOrIds, ic.Libpod)
}
if err != nil {
return nil, err
}
for _, c := range ctrs {
err := c.Unpause()
report = append(report, &entities.PauseUnpauseReport{Id: c.ID(), Err: err})
}
return report, nil
}
func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) {
var (
reports []*entities.StopReport
)
names := namesOrIds
for _, cidFile := range options.CIDFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
return nil, errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
names = append(names, id)
}
ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, names, ic.Libpod)
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
return nil, err
}
for _, con := range ctrs {
report := entities.StopReport{Id: con.ID()}
err = con.StopWithTimeout(options.Timeout)
if err != nil {
// These first two are considered non-fatal under the right conditions
if errors.Cause(err) == define.ErrCtrStopped {
logrus.Debugf("Container %s is already stopped", con.ID())
reports = append(reports, &report)
continue
} else if options.All && errors.Cause(err) == define.ErrCtrStateInvalid {
logrus.Debugf("Container %s is not running, could not stop", con.ID())
reports = append(reports, &report)
continue
}
report.Err = err
reports = append(reports, &report)
continue
}
reports = append(reports, &report)
}
return reports, nil
}
func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, options entities.KillOptions) ([]*entities.KillReport, error) {
var (
reports []*entities.KillReport
)
sig, err := signal.ParseSignalNameOrNumber(options.Signal)
if err != nil {
return nil, err
}
ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
if err != nil {
return nil, err
}
for _, con := range ctrs {
reports = append(reports, &entities.KillReport{
Id: con.ID(),
Err: con.Kill(uint(sig)),
})
}
return reports, nil
}
func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, options entities.RestartOptions) ([]*entities.RestartReport, error) {
var (
reports []*entities.RestartReport
)
ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
if err != nil {
return nil, err
}
for _, con := range ctrs {
timeout := con.StopTimeout()
if options.Timeout != nil {
timeout = *options.Timeout
}
reports = append(reports, &entities.RestartReport{
Id: con.ID(),
Err: con.RestartWithTimeout(ctx, timeout),
})
}
return reports, nil
}
func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, options entities.RmOptions) ([]*entities.RmReport, error) {
var (
reports []*entities.RmReport
)
if options.Storage {
for _, ctr := range namesOrIds {
report := entities.RmReport{Id: ctr}
if err := ic.Libpod.RemoveStorageContainer(ctr, options.Force); err != nil {
report.Err = err
}
reports = append(reports, &report)
}
return reports, nil
}
names := namesOrIds
for _, cidFile := range options.CIDFiles {
content, err := ioutil.ReadFile(cidFile)
if err != nil {
return nil, errors.Wrap(err, "error reading CIDFile")
}
id := strings.Split(string(content), "\n")[0]
names = append(names, id)
}
ctrs, err := shortcuts.GetContainersByContext(options.All, options.Latest, names, ic.Libpod)
if err != nil && !(options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr) {
// Failed to get containers. If force is specified, get the containers ID
// and evict them
if !options.Force {
return nil, err
}
for _, ctr := range namesOrIds {
logrus.Debugf("Evicting container %q", ctr)
report := entities.RmReport{Id: ctr}
id, err := ic.Libpod.EvictContainer(ctx, ctr, options.Volumes)
if err != nil {
if options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
reports = append(reports, &report)
continue
}
report.Err = errors.Wrapf(err, "Failed to evict container: %q", id)
reports = append(reports, &report)
continue
}
reports = append(reports, &report)
}
return reports, nil
}
for _, c := range ctrs {
report := entities.RmReport{Id: c.ID()}
err := ic.Libpod.RemoveContainer(ctx, c, options.Force, options.Volumes)
if err != nil {
if options.Ignore && errors.Cause(err) == define.ErrNoSuchCtr {
logrus.Debugf("Ignoring error (--allow-missing): %v", err)
reports = append(reports, &report)
continue
}
logrus.Debugf("Failed to remove container %s: %s", c.ID(), err.Error())
report.Err = err
reports = append(reports, &report)
continue
}
reports = append(reports, &report)
}
return reports, nil
}
func (ic *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) {

View File

@ -40,3 +40,108 @@ func (r *ContainerEngine) ContainerDelete(ctx context.Context, opts entities.Con
func (r *ContainerEngine) ContainerPrune(ctx context.Context) (*entities.ContainerPruneReport, error) {
panic("implement me")
}
func (ic *ContainerEngine) ContainerPause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) {
var (
reports []*entities.PauseUnpauseReport
)
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
for _, c := range ctrs {
err := containers.Pause(ic.ClientCxt, c.ID)
reports = append(reports, &entities.PauseUnpauseReport{Id: c.ID, Err: err})
}
return reports, nil
}
func (ic *ContainerEngine) ContainerUnpause(ctx context.Context, namesOrIds []string, options entities.PauseUnPauseOptions) ([]*entities.PauseUnpauseReport, error) {
var (
reports []*entities.PauseUnpauseReport
)
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
for _, c := range ctrs {
err := containers.Unpause(ic.ClientCxt, c.ID)
reports = append(reports, &entities.PauseUnpauseReport{Id: c.ID, Err: err})
}
return reports, nil
}
func (ic *ContainerEngine) ContainerStop(ctx context.Context, namesOrIds []string, options entities.StopOptions) ([]*entities.StopReport, error) {
var (
reports []*entities.StopReport
)
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
for _, c := range ctrs {
report := entities.StopReport{Id: c.ID}
report.Err = containers.Stop(ic.ClientCxt, c.ID, &options.Timeout)
// TODO we need to associate errors returned by http with common
// define.errors so that we can equity tests. this will allow output
// to be the same as the native client
reports = append(reports, &report)
}
return reports, nil
}
func (ic *ContainerEngine) ContainerKill(ctx context.Context, namesOrIds []string, options entities.KillOptions) ([]*entities.KillReport, error) {
var (
reports []*entities.KillReport
)
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
for _, c := range ctrs {
reports = append(reports, &entities.KillReport{
Id: c.ID,
Err: containers.Kill(ic.ClientCxt, c.ID, options.Signal),
})
}
return reports, nil
}
func (ic *ContainerEngine) ContainerRestart(ctx context.Context, namesOrIds []string, options entities.RestartOptions) ([]*entities.RestartReport, error) {
var (
reports []*entities.RestartReport
timeout *int
)
if options.Timeout != nil {
t := int(*options.Timeout)
timeout = &t
}
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
for _, c := range ctrs {
reports = append(reports, &entities.RestartReport{
Id: c.ID,
Err: containers.Restart(ic.ClientCxt, c.ID, timeout),
})
}
return reports, nil
}
func (ic *ContainerEngine) ContainerRm(ctx context.Context, namesOrIds []string, options entities.RmOptions) ([]*entities.RmReport, error) {
var (
reports []*entities.RmReport
)
ctrs, err := getContainersByContext(ic.ClientCxt, options.All, namesOrIds)
if err != nil {
return nil, err
}
// TODO there is no endpoint for container eviction. Need to discuss
for _, c := range ctrs {
reports = append(reports, &entities.RmReport{
Id: c.ID,
Err: containers.Remove(ic.ClientCxt, c.ID, &options.Force, &options.Volumes),
})
}
return reports, nil
}

View File

@ -2,6 +2,7 @@ package tunnel
import (
"context"
"strings"
"github.com/containers/libpod/pkg/api/handlers/libpod"
"github.com/containers/libpod/pkg/bindings"
@ -27,7 +28,7 @@ func getContainersByContext(contextWithConnection context.Context, all bool, nam
for _, id := range namesOrIds {
var found bool
for _, con := range c {
if id == con.ID || util.StringInSlice(id, con.Names) {
if id == con.ID || strings.HasPrefix(con.ID, id) || util.StringInSlice(id, con.Names) {
cons = append(cons, con)
found = true
break

View File

@ -104,11 +104,11 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) {
}
return syscall.Signal(s), nil
}
signal, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
sig, ok := signalMap[strings.TrimPrefix(strings.ToUpper(rawSignal), "SIG")]
if !ok {
return -1, fmt.Errorf("invalid signal: %s", rawSignal)
}
return signal, nil
return sig, nil
}
// CatchAll catches all signals and relays them to the specified channel.
@ -125,3 +125,18 @@ func StopCatch(sigc chan os.Signal) {
signal.Stop(sigc)
close(sigc)
}
// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input
// can be a name or number representation i.e. "KILL" "9"
func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
s, err := ParseSignal(rawSignal)
if err == nil {
return s, nil
}
for k, v := range signalMap {
if k == strings.ToUpper(rawSignal) {
return v, nil
}
}
return -1, fmt.Errorf("invalid signal: %s", rawSignal)
}

View File

@ -26,3 +26,9 @@ func CatchAll(sigc chan os.Signal) {
func StopCatch(sigc chan os.Signal) {
panic("Unsupported on non-linux platforms")
}
// ParseSignalNameOrNumber translates a string to a valid syscall signal. Input
// can be a name or number representation i.e. "KILL" "9"
func ParseSignalNameOrNumber(rawSignal string) (syscall.Signal, error) {
return 0, fmt.Errorf("unsupported on non-linux platforms")
}

View File

@ -309,15 +309,15 @@ func ParseSignal(rawSignal string) (syscall.Signal, error) {
// Strip off leading dash, to allow -1 or -HUP
basename := strings.TrimPrefix(rawSignal, "-")
signal, err := signal.ParseSignal(basename)
sig, err := signal.ParseSignal(basename)
if err != nil {
return -1, err
}
// 64 is SIGRTMAX; wish we could get this from a standard Go library
if signal < 1 || signal > 64 {
if sig < 1 || sig > 64 {
return -1, errors.Errorf("valid signals are 1 through 64")
}
return signal, nil
return sig, nil
}
// ParseIDMapping takes idmappings and subuid and subgid maps and returns a storage mapping