summaryrefslogtreecommitdiff
path: root/cmd/podmanV2
diff options
context:
space:
mode:
Diffstat (limited to 'cmd/podmanV2')
-rw-r--r--cmd/podmanV2/containers/kill.go72
-rw-r--r--cmd/podmanV2/containers/pause.go64
-rw-r--r--cmd/podmanV2/containers/restart.go79
-rw-r--r--cmd/podmanV2/containers/rm.go94
-rw-r--r--cmd/podmanV2/containers/stop.go88
-rw-r--r--cmd/podmanV2/containers/unpause.go61
-rw-r--r--cmd/podmanV2/containers/utils.go1
-rw-r--r--cmd/podmanV2/containers/wait.go44
-rw-r--r--cmd/podmanV2/images/exists.go40
-rw-r--r--cmd/podmanV2/images/history.go76
-rw-r--r--cmd/podmanV2/images/image.go6
-rw-r--r--cmd/podmanV2/images/images.go31
-rw-r--r--cmd/podmanV2/images/inspect.go2
-rw-r--r--cmd/podmanV2/images/list.go212
-rw-r--r--cmd/podmanV2/images/rm.go70
-rw-r--r--cmd/podmanV2/images/rmi.go30
-rw-r--r--cmd/podmanV2/main.go16
-rw-r--r--cmd/podmanV2/parse/parse.go45
-rw-r--r--cmd/podmanV2/registry/registry.go49
-rw-r--r--cmd/podmanV2/registry/remote.go9
-rw-r--r--cmd/podmanV2/report/templates.go29
-rw-r--r--cmd/podmanV2/root.go94
-rw-r--r--cmd/podmanV2/utils/error.go16
-rw-r--r--cmd/podmanV2/volumes/inspect.go74
-rw-r--r--cmd/podmanV2/volumes/list.go98
-rw-r--r--cmd/podmanV2/volumes/prune.go74
-rw-r--r--cmd/podmanV2/volumes/rm.go64
27 files changed, 1431 insertions, 107 deletions
diff --git a/cmd/podmanV2/containers/kill.go b/cmd/podmanV2/containers/kill.go
new file mode 100644
index 000000000..6e6debfec
--- /dev/null
+++ b/cmd/podmanV2/containers/kill.go
@@ -0,0 +1,72 @@
+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,
+ PersistentPreRunE: preRunE,
+ 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 registry.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()
+}
diff --git a/cmd/podmanV2/containers/pause.go b/cmd/podmanV2/containers/pause.go
new file mode 100644
index 000000000..a9b91b68f
--- /dev/null
+++ b/cmd/podmanV2/containers/pause.go
@@ -0,0 +1,64 @@
+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,
+ PersistentPreRunE: preRunE,
+ 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() && !registry.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()
+}
diff --git a/cmd/podmanV2/containers/restart.go b/cmd/podmanV2/containers/restart.go
new file mode 100644
index 000000000..053891f79
--- /dev/null
+++ b/cmd/podmanV2/containers/restart.go
@@ -0,0 +1,79 @@
+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,
+ PersistentPreRunE: preRunE,
+ 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 registry.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()
+}
diff --git a/cmd/podmanV2/containers/rm.go b/cmd/podmanV2/containers/rm.go
new file mode 100644
index 000000000..75655e4cd
--- /dev/null
+++ b/cmd/podmanV2/containers/rm.go
@@ -0,0 +1,94 @@
+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,
+ PersistentPreRunE: preRunE,
+ 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 registry.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()
+}
diff --git a/cmd/podmanV2/containers/stop.go b/cmd/podmanV2/containers/stop.go
new file mode 100644
index 000000000..58d47fd52
--- /dev/null
+++ b/cmd/podmanV2/containers/stop.go
@@ -0,0 +1,88 @@
+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,
+ PersistentPreRunE: preRunE,
+ 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.EngineOptions.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()
+}
diff --git a/cmd/podmanV2/containers/unpause.go b/cmd/podmanV2/containers/unpause.go
new file mode 100644
index 000000000..6a3179f10
--- /dev/null
+++ b/cmd/podmanV2/containers/unpause.go
@@ -0,0 +1,61 @@
+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,
+ PersistentPreRunE: preRunE,
+ 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() && !registry.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()
+}
diff --git a/cmd/podmanV2/containers/utils.go b/cmd/podmanV2/containers/utils.go
new file mode 100644
index 000000000..0c09d3e40
--- /dev/null
+++ b/cmd/podmanV2/containers/utils.go
@@ -0,0 +1 @@
+package containers
diff --git a/cmd/podmanV2/containers/wait.go b/cmd/podmanV2/containers/wait.go
index 27acb3348..bf3c86200 100644
--- a/cmd/podmanV2/containers/wait.go
+++ b/cmd/podmanV2/containers/wait.go
@@ -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"
@@ -16,10 +18,14 @@ var (
waitDescription = `Block until one or more containers stop and then print their exit codes.
`
waitCommand = &cobra.Command{
- Use: "wait [flags] CONTAINER [CONTAINER...]",
- Short: "Block on one or more containers",
- Long: waitDescription,
- RunE: wait,
+ Use: "wait [flags] CONTAINER [CONTAINER...]",
+ Short: "Block on one or more containers",
+ Long: waitDescription,
+ RunE: wait,
+ PersistentPreRunE: preRunE,
+ 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 +33,7 @@ var (
)
var (
- waitFlags = entities.WaitOptions{}
+ waitOptions = entities.WaitOptions{}
waitCondition string
)
@@ -35,14 +41,13 @@ 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 {
+ if registry.EngineOptions.EngineMode == entities.ABIMode {
// TODO: This is the same as V1. We could skip creating the flag altogether in V2...
_ = flags.MarkHidden("latest")
}
@@ -50,33 +55,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()
}
diff --git a/cmd/podmanV2/images/exists.go b/cmd/podmanV2/images/exists.go
new file mode 100644
index 000000000..d35d6825e
--- /dev/null
+++ b/cmd/podmanV2/images/exists.go
@@ -0,0 +1,40 @@
+package images
+
+import (
+ "os"
+
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ existsCmd = &cobra.Command{
+ Use: "exists IMAGE",
+ Short: "Check if an image exists in local storage",
+ Long: `If the named image exists in local storage, podman image exists exits with 0, otherwise the exit code will be 1.`,
+ Args: cobra.ExactArgs(1),
+ RunE: exists,
+ Example: `podman image exists ID
+ podman image exists IMAGE && podman pull IMAGE`,
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: existsCmd,
+ Parent: imageCmd,
+ })
+}
+
+func exists(cmd *cobra.Command, args []string) error {
+ found, err := registry.ImageEngine().Exists(registry.GetContext(), args[0])
+ if err != nil {
+ return err
+ }
+ if !found.Value {
+ os.Exit(1)
+ }
+ return nil
+}
diff --git a/cmd/podmanV2/images/history.go b/cmd/podmanV2/images/history.go
index c75ae6ddc..f6f15e2f2 100644
--- a/cmd/podmanV2/images/history.go
+++ b/cmd/podmanV2/images/history.go
@@ -4,12 +4,15 @@ import (
"context"
"fmt"
"os"
+ "strings"
"text/tabwriter"
"text/template"
+ "time"
"github.com/containers/libpod/cmd/podmanV2/registry"
"github.com/containers/libpod/cmd/podmanV2/report"
"github.com/containers/libpod/pkg/domain/entities"
+ jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
@@ -29,14 +32,14 @@ var (
PersistentPreRunE: preRunE,
RunE: history,
}
-)
-var cmdFlags = struct {
- Human bool
- NoTrunc bool
- Quiet bool
- Format string
-}{}
+ opts = struct {
+ human bool
+ noTrunc bool
+ quiet bool
+ format string
+ }{}
+)
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
@@ -46,12 +49,13 @@ func init() {
historyCmd.SetHelpTemplate(registry.HelpTemplate())
historyCmd.SetUsageTemplate(registry.UsageTemplate())
+
flags := historyCmd.Flags()
- flags.StringVar(&cmdFlags.Format, "format", "", "Change the output to JSON or a Go template")
- flags.BoolVarP(&cmdFlags.Human, "human", "H", true, "Display sizes and dates in human readable format")
- flags.BoolVar(&cmdFlags.NoTrunc, "no-trunc", false, "Do not truncate the output")
- flags.BoolVar(&cmdFlags.NoTrunc, "notruncate", false, "Do not truncate the output")
- flags.BoolVarP(&cmdFlags.Quiet, "quiet", "q", false, "Display the numeric IDs only")
+ flags.StringVar(&opts.format, "format", "", "Change the output to JSON or a Go template")
+ flags.BoolVarP(&opts.human, "human", "H", false, "Display sizes and dates in human readable format")
+ flags.BoolVar(&opts.noTrunc, "no-trunc", false, "Do not truncate the output")
+ flags.BoolVar(&opts.noTrunc, "notruncate", false, "Do not truncate the output")
+ flags.BoolVarP(&opts.quiet, "quiet", "q", false, "Display the numeric IDs only")
}
func history(cmd *cobra.Command, args []string) error {
@@ -60,11 +64,51 @@ func history(cmd *cobra.Command, args []string) error {
return err
}
- row := "{{slice $x.ID 0 12}}\t{{toRFC3339 $x.Created}}\t{{ellipsis $x.CreatedBy 45}}\t{{$x.Size}}\t{{$x.Comment}}\n"
- if cmdFlags.Human {
- row = "{{slice $x.ID 0 12}}\t{{toHumanDuration $x.Created}}\t{{ellipsis $x.CreatedBy 45}}\t{{toHumanSize $x.Size}}\t{{$x.Comment}}\n"
+ if opts.format == "json" {
+ var err error
+ if len(results.Layers) == 0 {
+ _, err = fmt.Fprintf(os.Stdout, "[]\n")
+ } else {
+ // ah-hoc change to "Created": type and format
+ type layer struct {
+ entities.ImageHistoryLayer
+ Created string `json:"Created"`
+ }
+
+ layers := make([]layer, len(results.Layers))
+ for i, l := range results.Layers {
+ layers[i].ImageHistoryLayer = l
+ layers[i].Created = time.Unix(l.Created, 0).Format(time.RFC3339)
+ }
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ enc := json.NewEncoder(os.Stdout)
+ err = enc.Encode(layers)
+ }
+ return err
+ }
+
+ // Defaults
+ hdr := "ID\tCREATED\tCREATED BY\tSIZE\tCOMMENT\n"
+ row := "{{slice .ID 0 12}}\t{{humanDuration .Created}}\t{{ellipsis .CreatedBy 45}}\t{{.Size}}\t{{.Comment}}\n"
+
+ if len(opts.format) > 0 {
+ hdr = ""
+ row = opts.format
+ if !strings.HasSuffix(opts.format, "\n") {
+ row += "\n"
+ }
+ } else {
+ switch {
+ case opts.human:
+ row = "{{slice .ID 0 12}}\t{{humanDuration .Created}}\t{{ellipsis .CreatedBy 45}}\t{{humanSize .Size}}\t{{.Comment}}\n"
+ case opts.noTrunc:
+ row = "{{.ID}}\t{{humanDuration .Created}}\t{{.CreatedBy}}\t{{humanSize .Size}}\t{{.Comment}}\n"
+ case opts.quiet:
+ hdr = ""
+ row = "{{.ID}}\n"
+ }
}
- format := "{{range $y, $x := . }}" + row + "{{end}}"
+ format := hdr + "{{range . }}" + row + "{{end}}"
tmpl := template.Must(template.New("report").Funcs(report.PodmanTemplateFuncs()).Parse(format))
w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
diff --git a/cmd/podmanV2/images/image.go b/cmd/podmanV2/images/image.go
index a15c3e826..9fc7b21d1 100644
--- a/cmd/podmanV2/images/image.go
+++ b/cmd/podmanV2/images/image.go
@@ -28,6 +28,8 @@ func init() {
}
func preRunE(cmd *cobra.Command, args []string) error {
- _, err := registry.NewImageEngine(cmd, args)
- return err
+ if _, err := registry.NewImageEngine(cmd, args); err != nil {
+ return err
+ }
+ return nil
}
diff --git a/cmd/podmanV2/images/images.go b/cmd/podmanV2/images/images.go
index a1e56396a..f248aa65f 100644
--- a/cmd/podmanV2/images/images.go
+++ b/cmd/podmanV2/images/images.go
@@ -9,17 +9,16 @@ import (
)
var (
- // podman _images_
+ // podman _images_ Alias for podman image _list_
imagesCmd = &cobra.Command{
- Use: strings.Replace(listCmd.Use, "list", "images", 1),
- Short: listCmd.Short,
- Long: listCmd.Long,
- PersistentPreRunE: preRunE,
- RunE: images,
- Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
+ Use: strings.Replace(listCmd.Use, "list", "images", 1),
+ Args: listCmd.Args,
+ Short: listCmd.Short,
+ Long: listCmd.Long,
+ PreRunE: listCmd.PreRunE,
+ RunE: listCmd.RunE,
+ Example: strings.Replace(listCmd.Example, "podman image list", "podman images", -1),
}
-
- imagesOpts = entities.ImageListOptions{}
)
func init() {
@@ -30,17 +29,5 @@ func init() {
imagesCmd.SetHelpTemplate(registry.HelpTemplate())
imagesCmd.SetUsageTemplate(registry.UsageTemplate())
- flags := imagesCmd.Flags()
- flags.BoolVarP(&imagesOpts.All, "all", "a", false, "Show all images (default hides intermediate images)")
- flags.BoolVar(&imagesOpts.Digests, "digests", false, "Show digests")
- flags.StringSliceVarP(&imagesOpts.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])")
- flags.StringVar(&imagesOpts.Format, "format", "", "Change the output format to JSON or a Go template")
- flags.BoolVarP(&imagesOpts.Noheading, "noheading", "n", false, "Do not print column headings")
- // TODO Need to learn how to deal with second name being a string instead of a char.
- // This needs to be "no-trunc, notruncate"
- flags.BoolVar(&imagesOpts.NoTrunc, "no-trunc", false, "Do not truncate output")
- flags.BoolVar(&imagesOpts.NoTrunc, "notruncate", false, "Do not truncate output")
- flags.BoolVarP(&imagesOpts.Quiet, "quiet", "q", false, "Display only image IDs")
- flags.StringVar(&imagesOpts.Sort, "sort", "created", "Sort by created, id, repository, size, or tag")
- flags.BoolVarP(&imagesOpts.History, "history", "", false, "Display the image name history")
+ imageListFlagSet(imagesCmd.Flags())
}
diff --git a/cmd/podmanV2/images/inspect.go b/cmd/podmanV2/images/inspect.go
index 2ecbbb201..f8fd44571 100644
--- a/cmd/podmanV2/images/inspect.go
+++ b/cmd/podmanV2/images/inspect.go
@@ -52,7 +52,7 @@ func init() {
flags.BoolVarP(&inspectOpts.Size, "size", "s", false, "Display total file size")
flags.StringVarP(&inspectOpts.Format, "format", "f", "", "Change the output format to a Go template")
- if registry.EngineOpts.EngineMode == entities.ABIMode {
+ if registry.EngineOptions.EngineMode == entities.ABIMode {
// TODO: This is the same as V1. We could skip creating the flag altogether in V2...
_ = flags.MarkHidden("latest")
}
diff --git a/cmd/podmanV2/images/list.go b/cmd/podmanV2/images/list.go
index cfdfaaed2..4714af3e4 100644
--- a/cmd/podmanV2/images/list.go
+++ b/cmd/podmanV2/images/list.go
@@ -1,16 +1,40 @@
package images
import (
+ "errors"
+ "fmt"
+ "os"
+ "sort"
+ "strings"
+ "text/tabwriter"
+ "text/template"
+ "time"
+
"github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/cmd/podmanV2/report"
"github.com/containers/libpod/pkg/domain/entities"
+ jsoniter "github.com/json-iterator/go"
"github.com/spf13/cobra"
+ "github.com/spf13/pflag"
)
+type listFlagType struct {
+ format string
+ history bool
+ noHeading bool
+ noTrunc bool
+ quiet bool
+ sort string
+ readOnly bool
+ digests bool
+}
+
var (
// Command: podman image _list_
listCmd = &cobra.Command{
Use: "list [flag] [IMAGE]",
Aliases: []string{"ls"},
+ Args: cobra.MaximumNArgs(1),
Short: "List images in local storage",
Long: "Lists images previously pulled to the system or created on the system.",
RunE: images,
@@ -18,6 +42,19 @@ var (
podman image list --sort repository --format "table {{.ID}} {{.Repository}} {{.Tag}}"
podman image list --filter dangling=true`,
}
+
+ // Options to pull data
+ listOptions = entities.ImageListOptions{}
+
+ // Options for presenting data
+ listFlag = listFlagType{}
+
+ sortFields = entities.NewStringSet(
+ "created",
+ "id",
+ "repository",
+ "size",
+ "tag")
)
func init() {
@@ -26,8 +63,181 @@ func init() {
Command: listCmd,
Parent: imageCmd,
})
+ imageListFlagSet(listCmd.Flags())
+}
+
+func imageListFlagSet(flags *pflag.FlagSet) {
+ flags.BoolVarP(&listOptions.All, "all", "a", false, "Show all images (default hides intermediate images)")
+ flags.StringSliceVarP(&listOptions.Filter, "filter", "f", []string{}, "Filter output based on conditions provided (default [])")
+ flags.StringVar(&listFlag.format, "format", "", "Change the output format to JSON or a Go template")
+ flags.BoolVar(&listFlag.digests, "digests", false, "Show digests")
+ flags.BoolVarP(&listFlag.noHeading, "noheading", "n", false, "Do not print column headings")
+ flags.BoolVar(&listFlag.noTrunc, "no-trunc", false, "Do not truncate output")
+ flags.BoolVar(&listFlag.noTrunc, "notruncate", false, "Do not truncate output")
+ flags.BoolVarP(&listFlag.quiet, "quiet", "q", false, "Display only image IDs")
+ flags.StringVar(&listFlag.sort, "sort", "created", "Sort by "+sortFields.String())
+ flags.BoolVarP(&listFlag.history, "history", "", false, "Display the image name history")
}
func images(cmd *cobra.Command, args []string) error {
- return nil
+ if len(listOptions.Filter) > 0 && len(args) > 0 {
+ return errors.New("cannot specify an image and a filter(s)")
+ }
+
+ if len(listOptions.Filter) < 1 && len(args) > 0 {
+ listOptions.Filter = append(listOptions.Filter, "reference="+args[0])
+ }
+
+ if cmd.Flag("sort").Changed && !sortFields.Contains(listFlag.sort) {
+ return fmt.Errorf("\"%s\" is not a valid field for sorting. Choose from: %s",
+ listFlag.sort, sortFields.String())
+ }
+
+ summaries, err := registry.ImageEngine().List(registry.GetContext(), listOptions)
+ if err != nil {
+ return err
+ }
+
+ imageS := summaries
+ sort.Slice(imageS, sortFunc(listFlag.sort, imageS))
+
+ if cmd.Flag("format").Changed && listFlag.format == "json" {
+ return writeJSON(imageS)
+ } else {
+ return writeTemplate(imageS, err)
+ }
+}
+
+func writeJSON(imageS []*entities.ImageSummary) error {
+ type image struct {
+ entities.ImageSummary
+ Created string
+ }
+
+ imgs := make([]image, 0, len(imageS))
+ for _, e := range imageS {
+ var h image
+ h.ImageSummary = *e
+ h.Created = time.Unix(e.Created, 0).Format(time.RFC3339)
+ h.RepoTags = nil
+
+ imgs = append(imgs, h)
+ }
+
+ json := jsoniter.ConfigCompatibleWithStandardLibrary
+ enc := json.NewEncoder(os.Stdout)
+ return enc.Encode(imgs)
+}
+
+func writeTemplate(imageS []*entities.ImageSummary, err error) error {
+ type image struct {
+ entities.ImageSummary
+ Repository string `json:"repository,omitempty"`
+ Tag string `json:"tag,omitempty"`
+ }
+
+ imgs := make([]image, 0, len(imageS))
+ for _, e := range imageS {
+ for _, tag := range e.RepoTags {
+ var h image
+ h.ImageSummary = *e
+ h.Repository, h.Tag = tokenRepoTag(tag)
+ imgs = append(imgs, h)
+ }
+ if e.IsReadOnly() {
+ listFlag.readOnly = true
+ }
+ }
+
+ hdr, row := imageListFormat(listFlag)
+ format := hdr + "{{range . }}" + row + "{{end}}"
+
+ tmpl := template.Must(template.New("report").Funcs(report.PodmanTemplateFuncs()).Parse(format))
+ w := tabwriter.NewWriter(os.Stdout, 8, 2, 2, ' ', 0)
+ defer w.Flush()
+ return tmpl.Execute(w, imgs)
+}
+
+func tokenRepoTag(tag string) (string, string) {
+ tokens := strings.SplitN(tag, ":", 2)
+ switch len(tokens) {
+ case 0:
+ return tag, ""
+ case 1:
+ return tokens[0], ""
+ case 2:
+ return tokens[0], tokens[1]
+ default:
+ return "<N/A>", ""
+ }
+}
+
+func sortFunc(key string, data []*entities.ImageSummary) func(i, j int) bool {
+ switch key {
+ case "id":
+ return func(i, j int) bool {
+ return data[i].ID < data[j].ID
+ }
+ case "repository":
+ return func(i, j int) bool {
+ return data[i].RepoTags[0] < data[j].RepoTags[0]
+ }
+ case "size":
+ return func(i, j int) bool {
+ return data[i].Size < data[j].Size
+ }
+ case "tag":
+ return func(i, j int) bool {
+ return data[i].RepoTags[0] < data[j].RepoTags[0]
+ }
+ default:
+ // case "created":
+ return func(i, j int) bool {
+ return data[i].Created >= data[j].Created
+ }
+ }
+}
+
+func imageListFormat(flags listFlagType) (string, string) {
+ if flags.quiet {
+ return "", "{{slice .ID 0 12}}\n"
+ }
+
+ // Defaults
+ hdr := "REPOSITORY\tTAG"
+ row := "{{.Repository}}\t{{if .Tag}}{{.Tag}}{{else}}<none>{{end}}"
+
+ if flags.digests {
+ hdr += "\tDIGEST"
+ row += "\t{{.Digest}}"
+ }
+
+ hdr += "\tID"
+ if flags.noTrunc {
+ row += "\tsha256:{{.ID}}"
+ } else {
+ row += "\t{{slice .ID 0 12}}"
+ }
+
+ hdr += "\tCREATED\tSIZE"
+ row += "\t{{humanDuration .Created}}\t{{humanSize .Size}}"
+
+ if flags.history {
+ hdr += "\tHISTORY"
+ row += "\t{{if .History}}{{join .History \", \"}}{{else}}<none>{{end}}"
+ }
+
+ if flags.readOnly {
+ hdr += "\tReadOnly"
+ row += "\t{{.ReadOnly}}"
+ }
+
+ if flags.noHeading {
+ hdr = ""
+ } else {
+ hdr += "\n"
+ }
+
+ row += "\n"
+ return hdr, row
}
diff --git a/cmd/podmanV2/images/rm.go b/cmd/podmanV2/images/rm.go
new file mode 100644
index 000000000..bb5880de3
--- /dev/null
+++ b/cmd/podmanV2/images/rm.go
@@ -0,0 +1,70 @@
+package images
+
+import (
+ "fmt"
+ "os"
+
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ rmDescription = "Removes one or more previously pulled or locally created images."
+ rmCmd = &cobra.Command{
+ Use: "rm [flags] IMAGE [IMAGE...]",
+ Short: "Removes one or more images from local storage",
+ Long: rmDescription,
+ PreRunE: preRunE,
+ RunE: rm,
+ Example: `podman image rm imageID
+ podman image rm --force alpine
+ podman image rm c4dfb1609ee2 93fd78260bd1 c0ed59d05ff7`,
+ }
+
+ imageOpts = entities.ImageDeleteOptions{}
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: rmCmd,
+ Parent: imageCmd,
+ })
+
+ flags := rmCmd.Flags()
+ flags.BoolVarP(&imageOpts.All, "all", "a", false, "Remove all images")
+ flags.BoolVarP(&imageOpts.Force, "force", "f", false, "Force Removal of the image")
+}
+
+func rm(cmd *cobra.Command, args []string) error {
+
+ if len(args) < 1 && !imageOpts.All {
+ return errors.Errorf("image name or ID must be specified")
+ }
+ if len(args) > 0 && imageOpts.All {
+ return errors.Errorf("when using the --all switch, you may not pass any images names or IDs")
+ }
+
+ report, err := registry.ImageEngine().Delete(registry.GetContext(), args, imageOpts)
+ if err != nil {
+ switch {
+ case report != nil && report.ImageNotFound != nil:
+ fmt.Fprintln(os.Stderr, err.Error())
+ registry.SetExitCode(2)
+ case report != nil && report.ImageInUse != nil:
+ fmt.Fprintln(os.Stderr, err.Error())
+ default:
+ return err
+ }
+ }
+
+ for _, u := range report.Untagged {
+ fmt.Println("Untagged: " + u)
+ }
+ for _, d := range report.Deleted {
+ fmt.Println("Deleted: " + d)
+ }
+ return nil
+}
diff --git a/cmd/podmanV2/images/rmi.go b/cmd/podmanV2/images/rmi.go
new file mode 100644
index 000000000..7f9297bc9
--- /dev/null
+++ b/cmd/podmanV2/images/rmi.go
@@ -0,0 +1,30 @@
+package images
+
+import (
+ "strings"
+
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/spf13/cobra"
+)
+
+var (
+ rmiCmd = &cobra.Command{
+ Use: strings.Replace(rmCmd.Use, "rm ", "rmi ", 1),
+ Args: rmCmd.Args,
+ Short: rmCmd.Short,
+ Long: rmCmd.Long,
+ PreRunE: rmCmd.PreRunE,
+ RunE: rmCmd.RunE,
+ Example: strings.Replace(rmCmd.Example, "podman image rm", "podman rmi", -1),
+ }
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: rmiCmd,
+ })
+ rmiCmd.SetHelpTemplate(registry.HelpTemplate())
+ rmiCmd.SetUsageTemplate(registry.UsageTemplate())
+}
diff --git a/cmd/podmanV2/main.go b/cmd/podmanV2/main.go
index 24f21d804..bd9fbb25e 100644
--- a/cmd/podmanV2/main.go
+++ b/cmd/podmanV2/main.go
@@ -1,7 +1,6 @@
package main
import (
- "fmt"
"os"
"reflect"
"runtime"
@@ -16,7 +15,6 @@ import (
"github.com/containers/libpod/libpod"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/sirupsen/logrus"
- "github.com/spf13/cobra"
)
func init() {
@@ -24,17 +22,14 @@ func init() {
logrus.Errorf(err.Error())
os.Exit(1)
}
- initCobra()
-}
-func initCobra() {
switch runtime.GOOS {
case "darwin":
fallthrough
case "windows":
- registry.EngineOpts.EngineMode = entities.TunnelMode
+ registry.EngineOptions.EngineMode = entities.TunnelMode
case "linux":
- registry.EngineOpts.EngineMode = entities.ABIMode
+ registry.EngineOptions.EngineMode = entities.ABIMode
default:
logrus.Errorf("%s is not a supported OS", runtime.GOOS)
os.Exit(1)
@@ -43,17 +38,14 @@ func initCobra() {
// TODO: Is there a Cobra way to "peek" at os.Args?
for _, v := range os.Args {
if strings.HasPrefix(v, "--remote") {
- registry.EngineOpts.EngineMode = entities.TunnelMode
+ registry.EngineOptions.EngineMode = entities.TunnelMode
}
}
-
- cobra.OnInitialize(func() {})
}
func main() {
- fmt.Fprintf(os.Stderr, "Number of commands: %d\n", len(registry.Commands))
for _, c := range registry.Commands {
- if Contains(registry.EngineOpts.EngineMode, c.Mode) {
+ if Contains(registry.EngineOptions.EngineMode, c.Mode) {
parent := rootCmd
if c.Parent != nil {
parent = c.Parent
diff --git a/cmd/podmanV2/parse/parse.go b/cmd/podmanV2/parse/parse.go
index 03cda268c..10d2146fa 100644
--- a/cmd/podmanV2/parse/parse.go
+++ b/cmd/podmanV2/parse/parse.go
@@ -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
+}
diff --git a/cmd/podmanV2/registry/registry.go b/cmd/podmanV2/registry/registry.go
index 793d520a8..5cdb8a840 100644
--- a/cmd/podmanV2/registry/registry.go
+++ b/cmd/podmanV2/registry/registry.go
@@ -1,12 +1,17 @@
package registry
import (
+ "context"
+
+ "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)
+type CobraFuncs func(cmd *cobra.Command, args []string) error
+
type CliCommand struct {
Mode []entities.EngineMode
Command *cobra.Command
@@ -18,11 +23,21 @@ var (
imageEngine entities.ImageEngine
containerEngine entities.ContainerEngine
+ cliCtx context.Context
+
+ EngineOptions entities.EngineOptions
- EngineOpts entities.EngineOptions
- GlobalFlags entities.EngineFlags
+ ExitCode = define.ExecErrorCodeGeneric
)
+func SetExitCode(code int) {
+ ExitCode = code
+}
+
+func GetExitCode() int {
+ return ExitCode
+}
+
// HelpTemplate returns the help template for podman commands
// This uses the short and long options.
// command should not use this.
@@ -65,8 +80,8 @@ func ImageEngine() entities.ImageEngine {
// NewImageEngine is a wrapper for building an ImageEngine to be used for PreRunE functions
func NewImageEngine(cmd *cobra.Command, args []string) (entities.ImageEngine, error) {
if imageEngine == nil {
- EngineOpts.FlagSet = cmd.Flags()
- engine, err := infra.NewImageEngine(EngineOpts)
+ EngineOptions.FlagSet = cmd.Flags()
+ engine, err := infra.NewImageEngine(EngineOptions)
if err != nil {
return nil, err
}
@@ -82,8 +97,8 @@ func ContainerEngine() entities.ContainerEngine {
// NewContainerEngine is a wrapper for building an ContainerEngine to be used for PreRunE functions
func NewContainerEngine(cmd *cobra.Command, args []string) (entities.ContainerEngine, error) {
if containerEngine == nil {
- EngineOpts.FlagSet = cmd.Flags()
- engine, err := infra.NewContainerEngine(EngineOpts)
+ EngineOptions.FlagSet = cmd.Flags()
+ engine, err := infra.NewContainerEngine(EngineOptions)
if err != nil {
return nil, err
}
@@ -98,3 +113,25 @@ func SubCommandExists(cmd *cobra.Command, args []string) error {
}
return errors.Errorf("missing command '%[1]s COMMAND'\nTry '%[1]s --help' for more information.", cmd.CommandPath())
}
+
+type podmanContextKey string
+
+var podmanFactsKey = podmanContextKey("engineOptions")
+
+func NewOptions(ctx context.Context, facts *entities.EngineOptions) context.Context {
+ return context.WithValue(ctx, podmanFactsKey, facts)
+}
+
+func Options(cmd *cobra.Command) (*entities.EngineOptions, error) {
+ if f, ok := cmd.Context().Value(podmanFactsKey).(*entities.EngineOptions); ok {
+ return f, errors.New("Command Context ")
+ }
+ return nil, nil
+}
+
+func GetContext() context.Context {
+ if cliCtx == nil {
+ cliCtx = context.TODO()
+ }
+ return cliCtx
+}
diff --git a/cmd/podmanV2/registry/remote.go b/cmd/podmanV2/registry/remote.go
new file mode 100644
index 000000000..32a231ac4
--- /dev/null
+++ b/cmd/podmanV2/registry/remote.go
@@ -0,0 +1,9 @@
+package registry
+
+import (
+ "github.com/containers/libpod/pkg/domain/entities"
+)
+
+func IsRemote() bool {
+ return EngineOptions.EngineMode == entities.TunnelMode
+}
diff --git a/cmd/podmanV2/report/templates.go b/cmd/podmanV2/report/templates.go
index dc43d4f9b..f3bc06405 100644
--- a/cmd/podmanV2/report/templates.go
+++ b/cmd/podmanV2/report/templates.go
@@ -4,6 +4,7 @@ import (
"strings"
"text/template"
"time"
+ "unicode"
"github.com/docker/go-units"
)
@@ -15,7 +16,24 @@ var defaultFuncMap = template.FuncMap{
}
return s
},
- // TODO: Remove on Go 1.14 port
+ "humanDuration": func(t int64) string {
+ return units.HumanDuration(time.Since(time.Unix(t, 0))) + " ago"
+ },
+ "humanSize": func(sz int64) string {
+ s := units.HumanSizeWithPrecision(float64(sz), 3)
+ i := strings.LastIndexFunc(s, unicode.IsNumber)
+ return s[:i+1] + " " + s[i+1:]
+ },
+ "join": strings.Join,
+ "lower": strings.ToLower,
+ "rfc3339": func(t int64) string {
+ return time.Unix(t, 0).Format(time.RFC3339)
+ },
+ "replace": strings.Replace,
+ "split": strings.Split,
+ "title": strings.Title,
+ "upper": strings.ToUpper,
+ // TODO: Remove after Go 1.14 port
"slice": func(s string, i, j int) string {
if i > j || len(s) < i {
return s
@@ -25,15 +43,6 @@ var defaultFuncMap = template.FuncMap{
}
return s[i:j]
},
- "toRFC3339": func(t int64) string {
- return time.Unix(t, 0).Format(time.RFC3339)
- },
- "toHumanDuration": func(t int64) string {
- return units.HumanDuration(time.Since(time.Unix(t, 0))) + " ago"
- },
- "toHumanSize": func(sz int64) string {
- return units.HumanSize(float64(sz))
- },
}
func ReportHeader(columns ...string) []byte {
diff --git a/cmd/podmanV2/root.go b/cmd/podmanV2/root.go
index 24b083b9f..cb4cb4e00 100644
--- a/cmd/podmanV2/root.go
+++ b/cmd/podmanV2/root.go
@@ -2,35 +2,99 @@ package main
import (
"fmt"
+ "log/syslog"
"os"
"path"
"github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/libpod/define"
+ "github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/version"
+ "github.com/sirupsen/logrus"
+ logrusSyslog "github.com/sirupsen/logrus/hooks/syslog"
"github.com/spf13/cobra"
)
-var rootCmd = &cobra.Command{
- Use: path.Base(os.Args[0]),
- Long: "Manage pods, containers and images",
- SilenceUsage: true,
- SilenceErrors: true,
- TraverseChildren: true,
- RunE: registry.SubCommandExists,
- Version: version.Version,
-}
+var (
+ rootCmd = &cobra.Command{
+ Use: path.Base(os.Args[0]),
+ Long: "Manage pods, containers and images",
+ SilenceUsage: true,
+ SilenceErrors: true,
+ TraverseChildren: true,
+ PersistentPreRunE: preRunE,
+ RunE: registry.SubCommandExists,
+ Version: version.Version,
+ }
+
+ logLevels = entities.NewStringSet("debug", "info", "warn", "error", "fatal", "panic")
+ logLevel = "error"
+ useSyslog bool
+)
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")
- rootCmd.PersistentFlags().StringSliceVar(&registry.EngineOpts.Identities, "identity", []string{}, "path to SSH identity file")
+ // 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.EngineOptions.Uri, "remote", "r", "", "URL to access Podman service")
+ rootCmd.PersistentFlags().StringSliceVar(&registry.EngineOptions.Identities, "identity", []string{}, "path to SSH identity file")
+ rootCmd.PersistentFlags().StringVar(&logLevel, "log-level", "error", fmt.Sprintf("Log messages above specified level (%s)", logLevels.String()))
+ rootCmd.PersistentFlags().BoolVar(&useSyslog, "syslog", false, "Output logging information to syslog as well as the console (default false)")
+
+ cobra.OnInitialize(
+ logging,
+ syslogHook,
+ )
}
-func Execute() {
- if err := rootCmd.Execute(); err != nil {
- fmt.Println(err)
+func preRunE(cmd *cobra.Command, args []string) error {
+ cmd.SetHelpTemplate(registry.HelpTemplate())
+ cmd.SetUsageTemplate(registry.UsageTemplate())
+ return nil
+}
+
+func logging() {
+ if !logLevels.Contains(logLevel) {
+ fmt.Fprintf(os.Stderr, "Log Level \"%s\" is not supported, choose from: %s\n", logLevel, logLevels.String())
+ os.Exit(1)
+ }
+
+ level, err := logrus.ParseLevel(logLevel)
+ if err != nil {
+ fmt.Fprintf(os.Stderr, err.Error())
os.Exit(1)
}
+ logrus.SetLevel(level)
+
+ if logrus.IsLevelEnabled(logrus.InfoLevel) {
+ logrus.Infof("%s filtering at log level %s", os.Args[0], logrus.GetLevel())
+ }
+}
+
+func syslogHook() {
+ if useSyslog {
+ hook, err := logrusSyslog.NewSyslogHook("", "", syslog.LOG_INFO, "")
+ if err != nil {
+ logrus.WithError(err).Error("Failed to initialize syslog hook")
+ }
+ if err == nil {
+ logrus.AddHook(hook)
+ }
+ }
+}
+
+func Execute() {
+ o := registry.NewOptions(rootCmd.Context(), &registry.EngineOptions)
+ if err := rootCmd.ExecuteContext(o); err != nil {
+ fmt.Fprintln(os.Stderr, "Error:", err.Error())
+ } else if registry.GetExitCode() == define.ExecErrorCodeGeneric {
+ // The exitCode modified from define.ExecErrorCodeGeneric,
+ // indicates an application
+ // running inside of a container failed, as opposed to the
+ // podman command failed. Must exit with that exit code
+ // otherwise command exited correctly.
+ registry.SetExitCode(0)
+ }
+ os.Exit(registry.GetExitCode())
}
diff --git a/cmd/podmanV2/utils/error.go b/cmd/podmanV2/utils/error.go
new file mode 100644
index 000000000..3464f0779
--- /dev/null
+++ b/cmd/podmanV2/utils/error.go
@@ -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
+}
diff --git a/cmd/podmanV2/volumes/inspect.go b/cmd/podmanV2/volumes/inspect.go
new file mode 100644
index 000000000..4d9720432
--- /dev/null
+++ b/cmd/podmanV2/volumes/inspect.go
@@ -0,0 +1,74 @@
+package volumes
+
+import (
+ "encoding/json"
+ "fmt"
+ "html/template"
+ "os"
+
+ "github.com/containers/buildah/pkg/formats"
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+ "golang.org/x/net/context"
+)
+
+var (
+ volumeInspectDescription = `Display detailed information on one or more volumes.
+
+ Use a Go template to change the format from JSON.`
+ inspectCommand = &cobra.Command{
+ Use: "inspect [flags] VOLUME [VOLUME...]",
+ Short: "Display detailed information on one or more volumes",
+ Long: volumeInspectDescription,
+ RunE: inspect,
+ Example: `podman volume inspect myvol
+ podman volume inspect --all
+ podman volume inspect --format "{{.Driver}} {{.Scope}}" myvol`,
+ }
+)
+
+var (
+ inspectOpts = entities.VolumeInspectOptions{}
+ inspectFormat string
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: inspectCommand,
+ Parent: volumeCmd,
+ })
+ flags := inspectCommand.Flags()
+ flags.BoolVarP(&inspectOpts.All, "all", "a", false, "Inspect all volumes")
+ flags.StringVarP(&inspectFormat, "format", "f", "json", "Format volume output using Go template")
+}
+
+func inspect(cmd *cobra.Command, args []string) error {
+ if (inspectOpts.All && len(args) > 0) || (!inspectOpts.All && len(args) < 1) {
+ return errors.New("provide one or more volume names or use --all")
+ }
+ responses, err := registry.ContainerEngine().VolumeInspect(context.Background(), args, inspectOpts)
+ if err != nil {
+ return err
+ }
+ switch inspectFormat {
+ case "", formats.JSONString:
+ jsonOut, err := json.MarshalIndent(responses, "", " ")
+ if err != nil {
+ return errors.Wrapf(err, "error marshalling inspect JSON")
+ }
+ fmt.Println(string(jsonOut))
+ default:
+ tmpl, err := template.New("volumeInspect").Parse(inspectFormat)
+ if err != nil {
+ return err
+ }
+ if err := tmpl.Execute(os.Stdout, responses); err != nil {
+ return err
+ }
+ }
+ return nil
+
+}
diff --git a/cmd/podmanV2/volumes/list.go b/cmd/podmanV2/volumes/list.go
new file mode 100644
index 000000000..c38f78c73
--- /dev/null
+++ b/cmd/podmanV2/volumes/list.go
@@ -0,0 +1,98 @@
+package volumes
+
+import (
+ "context"
+ "html/template"
+ "io"
+ "os"
+ "strings"
+ "text/tabwriter"
+
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ volumeLsDescription = `
+podman volume ls
+
+List all available volumes. The output of the volumes can be filtered
+and the output format can be changed to JSON or a user specified Go template.`
+ lsCommand = &cobra.Command{
+ Use: "ls",
+ Aliases: []string{"list"},
+ Args: cobra.NoArgs,
+ Short: "List volumes",
+ Long: volumeLsDescription,
+ RunE: list,
+ }
+)
+
+var (
+ // Temporary struct to hold cli values.
+ cliOpts = struct {
+ Filter []string
+ Format string
+ Quiet bool
+ }{}
+ lsOpts = entities.VolumeListOptions{}
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: lsCommand,
+ Parent: volumeCmd,
+ })
+ flags := lsCommand.Flags()
+ flags.StringSliceVarP(&cliOpts.Filter, "filter", "f", []string{}, "Filter volume output")
+ flags.StringVar(&cliOpts.Format, "format", "{{.Driver}}\t{{.Name}}\n", "Format volume output using Go template")
+ flags.BoolVarP(&cliOpts.Quiet, "quiet", "q", false, "Print volume output in quiet mode")
+}
+
+func list(cmd *cobra.Command, args []string) error {
+ var w io.Writer = os.Stdout
+ if cliOpts.Quiet && cmd.Flag("format").Changed {
+ return errors.New("quiet and format flags cannot be used together")
+ }
+ for _, f := range cliOpts.Filter {
+ filterSplit := strings.Split(f, "=")
+ if len(filterSplit) < 2 {
+ return errors.Errorf("filter input must be in the form of filter=value: %s is invalid", f)
+ }
+ lsOpts.Filter[filterSplit[0]] = append(lsOpts.Filter[filterSplit[0]], filterSplit[1:]...)
+ }
+ responses, err := registry.ContainerEngine().VolumeList(context.Background(), lsOpts)
+ if err != nil {
+ return err
+ }
+ // "\t" from the command line is not being recognized as a tab
+ // replacing the string "\t" to a tab character if the user passes in "\t"
+ cliOpts.Format = strings.Replace(cliOpts.Format, `\t`, "\t", -1)
+ if cliOpts.Quiet {
+ cliOpts.Format = "{{.Name}}\n"
+ }
+ headers := "DRIVER\tVOLUME NAME\n"
+ row := cliOpts.Format
+ if !strings.HasSuffix(cliOpts.Format, "\n") {
+ row += "\n"
+ }
+ format := "{{range . }}" + row + "{{end}}"
+ if !cliOpts.Quiet && !cmd.Flag("format").Changed {
+ w = tabwriter.NewWriter(os.Stdout, 12, 2, 2, ' ', 0)
+ format = headers + format
+ }
+ tmpl, err := template.New("listVolume").Parse(format)
+ if err != nil {
+ return err
+ }
+ if err := tmpl.Execute(w, responses); err != nil {
+ return err
+ }
+ if flusher, ok := w.(interface{ Flush() error }); ok {
+ return flusher.Flush()
+ }
+ return nil
+}
diff --git a/cmd/podmanV2/volumes/prune.go b/cmd/podmanV2/volumes/prune.go
new file mode 100644
index 000000000..148065f56
--- /dev/null
+++ b/cmd/podmanV2/volumes/prune.go
@@ -0,0 +1,74 @@
+package volumes
+
+import (
+ "bufio"
+ "context"
+ "fmt"
+ "os"
+ "strings"
+
+ "github.com/containers/libpod/cmd/podmanV2/registry"
+ "github.com/containers/libpod/cmd/podmanV2/utils"
+ "github.com/containers/libpod/pkg/domain/entities"
+ "github.com/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ volumePruneDescription = `Volumes that are not currently owned by a container will be removed.
+
+ The command prompts for confirmation which can be overridden with the --force flag.
+ Note all data will be destroyed.`
+ pruneCommand = &cobra.Command{
+ Use: "prune",
+ Args: cobra.NoArgs,
+ Short: "Remove all unused volumes",
+ Long: volumePruneDescription,
+ RunE: prune,
+ }
+)
+
+var (
+ pruneOptions entities.VolumePruneOptions
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: pruneCommand,
+ Parent: volumeCmd,
+ })
+ flags := pruneCommand.Flags()
+ flags.BoolVarP(&pruneOptions.Force, "force", "f", false, "Do not prompt for confirmation")
+}
+
+func prune(cmd *cobra.Command, args []string) error {
+ var (
+ errs utils.OutputErrors
+ )
+ // Prompt for confirmation if --force is not set
+ if !pruneOptions.Force {
+ reader := bufio.NewReader(os.Stdin)
+ fmt.Println("WARNING! This will remove all volumes not used by at least one container.")
+ fmt.Print("Are you sure you want to continue? [y/N] ")
+ answer, err := reader.ReadString('\n')
+ if err != nil {
+ return errors.Wrapf(err, "error reading input")
+ }
+ if strings.ToLower(answer)[0] != 'y' {
+ return nil
+ }
+ }
+ responses, err := registry.ContainerEngine().VolumePrune(context.Background(), pruneOptions)
+ 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()
+}
diff --git a/cmd/podmanV2/volumes/rm.go b/cmd/podmanV2/volumes/rm.go
new file mode 100644
index 000000000..b019285d8
--- /dev/null
+++ b/cmd/podmanV2/volumes/rm.go
@@ -0,0 +1,64 @@
+package volumes
+
+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/pkg/errors"
+ "github.com/spf13/cobra"
+)
+
+var (
+ volumeRmDescription = `Remove one or more existing volumes.
+
+ By default only volumes that are not being used by any containers will be removed. To remove the volumes anyways, use the --force flag.`
+ rmCommand = &cobra.Command{
+ Use: "rm [flags] VOLUME [VOLUME...]",
+ Aliases: []string{"remove"},
+ Short: "Remove one or more volumes",
+ Long: volumeRmDescription,
+ RunE: rm,
+ Example: `podman volume rm myvol1 myvol2
+ podman volume rm --all
+ podman volume rm --force myvol`,
+ }
+)
+
+var (
+ rmOptions = entities.VolumeRmOptions{}
+)
+
+func init() {
+ registry.Commands = append(registry.Commands, registry.CliCommand{
+ Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
+ Command: rmCommand,
+ Parent: volumeCmd,
+ })
+ flags := rmCommand.Flags()
+ flags.BoolVarP(&rmOptions.All, "all", "a", false, "Remove all volumes")
+ flags.BoolVarP(&rmOptions.Force, "force", "f", false, "Remove a volume by force, even if it is being used by a container")
+}
+
+func rm(cmd *cobra.Command, args []string) error {
+ var (
+ errs utils.OutputErrors
+ )
+ if (len(args) > 0 && rmOptions.All) || (len(args) < 1 && !rmOptions.All) {
+ return errors.New("choose either one or more volumes or all")
+ }
+ responses, err := registry.ContainerEngine().VolumeRm(context.Background(), args, rmOptions)
+ 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()
+}