summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
-rw-r--r--cmd/podmanV2/containers/kill.go71
-rw-r--r--cmd/podmanV2/containers/pause.go63
-rw-r--r--cmd/podmanV2/containers/restart.go78
-rw-r--r--cmd/podmanV2/containers/rm.go93
-rw-r--r--cmd/podmanV2/containers/stop.go87
-rw-r--r--cmd/podmanV2/containers/unpause.go60
-rw-r--r--cmd/podmanV2/containers/utils.go1
-rw-r--r--cmd/podmanV2/containers/wait.go33
-rw-r--r--cmd/podmanV2/parse/parse.go45
-rw-r--r--cmd/podmanV2/root.go5
-rw-r--r--cmd/podmanV2/utils/error.go16
-rw-r--r--cmd/podmanV2/utils/remote.go10
-rw-r--r--docs/tutorials/README.md4
-rw-r--r--docs/tutorials/image_signing.md194
-rw-r--r--go.mod2
-rw-r--r--go.sum2
-rw-r--r--pkg/api/handlers/compat/containers.go14
-rw-r--r--pkg/api/handlers/compat/containers_restart.go16
-rw-r--r--pkg/bindings/containers/containers.go8
-rw-r--r--pkg/bindings/test/volumes_test.go2
-rw-r--r--pkg/domain/entities/containers.go60
-rw-r--r--pkg/domain/entities/engine_container.go7
-rw-r--r--pkg/domain/infra/abi/containers.go199
-rw-r--r--pkg/domain/infra/tunnel/containers.go105
-rw-r--r--pkg/domain/infra/tunnel/helpers.go3
-rw-r--r--pkg/signal/signal_linux.go19
-rw-r--r--pkg/signal/signal_unsupported.go6
-rw-r--r--pkg/util/utils.go6
-rw-r--r--vendor/github.com/sirupsen/logrus/.golangci.yml40
-rw-r--r--vendor/github.com/sirupsen/logrus/.travis.yml14
-rw-r--r--vendor/github.com/sirupsen/logrus/README.md44
-rw-r--r--vendor/github.com/sirupsen/logrus/entry.go49
-rw-r--r--vendor/github.com/sirupsen/logrus/exported.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/go.mod3
-rw-r--r--vendor/github.com/sirupsen/logrus/go.sum6
-rw-r--r--vendor/github.com/sirupsen/logrus/json_formatter.go4
-rw-r--r--vendor/github.com/sirupsen/logrus/logger.go11
-rw-r--r--vendor/github.com/sirupsen/logrus/logrus.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/terminal_check_bsd.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/terminal_check_js.go7
-rw-r--r--vendor/github.com/sirupsen/logrus/terminal_check_unix.go2
-rw-r--r--vendor/github.com/sirupsen/logrus/text_formatter.go47
-rw-r--r--vendor/github.com/sirupsen/logrus/writer.go6
-rw-r--r--vendor/modules.txt2
44 files changed, 1333 insertions, 117 deletions
diff --git a/cmd/podmanV2/containers/kill.go b/cmd/podmanV2/containers/kill.go
new file mode 100644
index 000000000..b02dcf9da
--- /dev/null
+++ b/cmd/podmanV2/containers/kill.go
@@ -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()
+}
diff --git a/cmd/podmanV2/containers/pause.go b/cmd/podmanV2/containers/pause.go
new file mode 100644
index 000000000..3f3e7c38d
--- /dev/null
+++ b/cmd/podmanV2/containers/pause.go
@@ -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()
+}
diff --git a/cmd/podmanV2/containers/restart.go b/cmd/podmanV2/containers/restart.go
new file mode 100644
index 000000000..ee9c34361
--- /dev/null
+++ b/cmd/podmanV2/containers/restart.go
@@ -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()
+}
diff --git a/cmd/podmanV2/containers/rm.go b/cmd/podmanV2/containers/rm.go
new file mode 100644
index 000000000..af7a38fb1
--- /dev/null
+++ b/cmd/podmanV2/containers/rm.go
@@ -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()
+}
diff --git a/cmd/podmanV2/containers/stop.go b/cmd/podmanV2/containers/stop.go
new file mode 100644
index 000000000..066202298
--- /dev/null
+++ b/cmd/podmanV2/containers/stop.go
@@ -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()
+}
diff --git a/cmd/podmanV2/containers/unpause.go b/cmd/podmanV2/containers/unpause.go
new file mode 100644
index 000000000..697132a53
--- /dev/null
+++ b/cmd/podmanV2/containers/unpause.go
@@ -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()
+}
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..cded8e571 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"
@@ -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()
}
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/root.go b/cmd/podmanV2/root.go
index 24b083b9f..b0dd7643f 100644
--- a/cmd/podmanV2/root.go
+++ b/cmd/podmanV2/root.go
@@ -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")
}
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/utils/remote.go b/cmd/podmanV2/utils/remote.go
new file mode 100644
index 000000000..d0c8a272d
--- /dev/null
+++ b/cmd/podmanV2/utils/remote.go
@@ -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
+}
diff --git a/docs/tutorials/README.md b/docs/tutorials/README.md
index bcd1b01d9..191d7a4b5 100644
--- a/docs/tutorials/README.md
+++ b/docs/tutorials/README.md
@@ -23,3 +23,7 @@ A brief how-to on using the Podman remote-client.
**[How to use libpod for custom/derivative projects](podman-derivative-api.md)**
How the libpod API can be used within your own project.
+
+**[Image Signing](image_signing.md)**
+
+Learn how to setup and use image signing with Podman.
diff --git a/docs/tutorials/image_signing.md b/docs/tutorials/image_signing.md
new file mode 100644
index 000000000..f0adca9af
--- /dev/null
+++ b/docs/tutorials/image_signing.md
@@ -0,0 +1,194 @@
+# How to sign and distribute container images using Podman
+
+Signing container images originates from the motivation of trusting only
+dedicated image providers to mitigate man-in-the-middle (MITM) attacks or
+attacks on container registries. One way to sign images is to utilize a GNU
+Privacy Guard ([GPG][0]) key. This technique is generally compatible with any
+OCI compliant container registry like [Quay.io][1]. It is worth mentioning that
+the OpenShift integrated container registry supports this signing mechanism out
+of the box, which makes separate signature storage unnecessary.
+
+[0]: https://gnupg.org
+[1]: https://quay.io
+
+From a technical perspective, we can utilize Podman to sign the image before
+pushing it into a remote registry. After that, all systems running Podman have
+to be configured to retrieve the signatures from a remote server, which can
+be any simple web server. This means that every unsigned image will be rejected
+during an image pull operation. But how does this work?
+
+First of all, we have to create a GPG key pair or select an already locally
+available one. To generate a new GPG key, just run `gpg --full-gen-key` and
+follow the interactive dialog. Now we should be able to verify that the key
+exists locally:
+
+```bash
+> gpg --list-keys sgrunert@suse.com
+pub rsa2048 2018-11-26 [SC] [expires: 2020-11-25]
+ XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
+uid [ultimate] Sascha Grunert <sgrunert@suse.com>
+sub rsa2048 2018-11-26 [E] [expires: 2020-11-25]
+```
+
+Now let’s assume that we run a container registry. For example we could simply
+start one on our local machine:
+
+```bash
+> sudo podman run -d -p 5000:5000 docker.io/registry
+```
+
+The registry does not know anything about image signing, it just provides the remote
+storage for the container images. This means if we want to sign an image, we
+have to take care of how to distribute the signatures.
+
+Let’s choose a standard `alpine` image for our signing experiment:
+
+```bash
+> sudo podman pull docker://docker.io/alpine:latest
+```
+
+```bash
+> sudo podman images alpine
+REPOSITORY TAG IMAGE ID CREATED SIZE
+docker.io/library/alpine latest e7d92cdc71fe 6 weeks ago 5.86 MB
+```
+
+Now we can re-tag the image to point it to our local registry:
+
+```bash
+> sudo podman tag alpine localhost:5000/alpine
+```
+
+```bash
+> sudo podman images alpine
+REPOSITORY TAG IMAGE ID CREATED SIZE
+localhost:5000/alpine latest e7d92cdc71fe 6 weeks ago 5.86 MB
+docker.io/library/alpine latest e7d92cdc71fe 6 weeks ago 5.86 MB
+```
+
+Podman would now be able to push the image and sign it in one command. But to
+let this work, we have to modify our system-wide registries configuration at
+`/etc/containers/registries.d/default.yaml`:
+
+```yaml
+default-docker:
+ sigstore: http://localhost:8000 # Added by us
+ sigstore-staging: file:///var/lib/containers/sigstore
+```
+
+We can see that we have two signature stores configured:
+
+- `sigstore`: referencing a web server for signature reading
+- `sigstore-staging`: referencing a file path for signature writing
+
+Now, let’s push and sign the image:
+
+```bash
+> sudo -E GNUPGHOME=$HOME/.gnupg \
+ podman push \
+ --tls-verify=false \
+ --sign-by sgrunert@suse.com \
+ localhost:5000/alpine
+…
+Storing signatures
+```
+
+If we now take a look at the systems signature storage, then we see that there
+is a new signature available, which was caused by the image push:
+
+```bash
+> sudo ls /var/lib/containers/sigstore
+'alpine@sha256=e9b65ef660a3ff91d28cc50eba84f21798a6c5c39b4dd165047db49e84ae1fb9'
+```
+
+The default signature store in our edited version of
+`/etc/containers/registries.d/default.yaml` references a web server listening at
+`http://localhost:8000`. For our experiment, we simply start a new server inside
+the local staging signature store:
+
+```bash
+> sudo bash -c 'cd /var/lib/containers/sigstore && python3 -m http.server'
+Serving HTTP on 0.0.0.0 port 8000 (http://0.0.0.0:8000/) ...
+```
+
+Let’s remove the local images for our verification test:
+
+```
+> sudo podman rmi docker.io/alpine localhost:5000/alpine
+```
+
+We have to write a policy to enforce that the signature has to be valid. This
+can be done by adding a new rule in `/etc/containers/policy.json`. From the
+below example, copy the `"docker"` entry into the `"transports"` section of your
+`policy.json`.
+
+```json
+{
+ "default": [{ "type": "insecureAcceptAnything" }],
+ "transports": {
+ "docker": {
+ "localhost:5000": [
+ {
+ "type": "signedBy",
+ "keyType": "GPGKeys",
+ "keyPath": "/tmp/key.gpg"
+ }
+ ]
+ }
+ }
+}
+```
+
+The `keyPath` does not exist yet, so we have to put the GPG key there:
+
+```bash
+> gpg --output /tmp/key.gpg --armor --export sgrunert@suse.com
+```
+
+If we now pull the image:
+
+```bash
+> sudo podman pull --tls-verify=false localhost:5000/alpine
+…
+Storing signatures
+e7d92cdc71feacf90708cb59182d0df1b911f8ae022d29e8e95d75ca6a99776a
+```
+
+Then we can see in the logs of the web server that the signature has been
+accessed:
+
+```
+127.0.0.1 - - [04/Mar/2020 11:18:21] "GET /alpine@sha256=e9b65ef660a3ff91d28cc50eba84f21798a6c5c39b4dd165047db49e84ae1fb9/signature-1 HTTP/1.1" 200 -
+```
+
+As an counterpart example, if we specify the wrong key at `/tmp/key.gpg`:
+
+```bash
+> gpg --output /tmp/key.gpg --armor --export mail@saschagrunert.de
+File '/tmp/key.gpg' exists. Overwrite? (y/N) y
+```
+
+Then a pull is not possible any more:
+
+```bash
+> sudo podman pull --tls-verify=false localhost:5000/alpine
+Trying to pull localhost:5000/alpine...
+Error: error pulling image "localhost:5000/alpine": unable to pull localhost:5000/alpine: unable to pull image: Source image rejected: Invalid GPG signature: …
+```
+
+So in general there are four main things to be taken into consideration when
+signing container images with Podman and GPG:
+
+1. We need a valid private GPG key on the signing machine and corresponding
+ public keys on every system which would pull the image
+2. A web server has to run somewhere which has access to the signature storage
+3. The web server has to be configured in any
+ `/etc/containers/registries.d/*.yaml` file
+4. Every image pulling system has to be configured to contain the enforcing
+ policy configuration via `policy.conf`
+
+That’s it for image signing and GPG. The cool thing is that this setup works out
+of the box with [CRI-O][2] as well and can be used to sign container images in
+Kubernetes environments.
+
+[2]: https://cri-o.io
diff --git a/go.mod b/go.mod
index 7cac1f3b9..ce603491e 100644
--- a/go.mod
+++ b/go.mod
@@ -49,7 +49,7 @@ require (
github.com/pmezard/go-difflib v1.0.0
github.com/rootless-containers/rootlesskit v0.8.0
github.com/seccomp/containers-golang v0.0.0-20190312124753-8ca8945ccf5f
- github.com/sirupsen/logrus v1.4.2
+ github.com/sirupsen/logrus v1.5.0
github.com/spf13/cobra v0.0.6
github.com/spf13/pflag v1.0.5
github.com/stretchr/testify v1.5.1
diff --git a/go.sum b/go.sum
index 8e725dc4d..f2277b0d0 100644
--- a/go.sum
+++ b/go.sum
@@ -432,6 +432,8 @@ github.com/sirupsen/logrus v1.2.0/go.mod h1:LxeOpSwHxABJmUn/MG1IvRgCAasNZTLOkJPx
github.com/sirupsen/logrus v1.4.1/go.mod h1:ni0Sbl8bgC9z8RoU9G6nDWqqs/fq4eDPysMBDgk/93Q=
github.com/sirupsen/logrus v1.4.2 h1:SPIRibHv4MatM3XXNO2BJeFLZwZ2LvZgfQ5+UNI2im4=
github.com/sirupsen/logrus v1.4.2/go.mod h1:tLMulIdttU9McNUspp0xgXVQah82FyeX6MwdIuYE2rE=
+github.com/sirupsen/logrus v1.5.0 h1:1N5EYkVAPEywqZRJd7cwnRtCb6xJx7NH3T3WUTF980Q=
+github.com/sirupsen/logrus v1.5.0/go.mod h1:+F7Ogzej0PZc/94MaYx/nvG9jOFMD2osvC3s+Squfpo=
github.com/soheilhy/cmux v0.1.4/go.mod h1:IM3LyeVVIOuxMH7sFAkER9+bJ4dT7Ms6E4xg4kGIyLM=
github.com/spaolacci/murmur3 v0.0.0-20180118202830-f09979ecbc72/go.mod h1:JwIasOWyU6f++ZhiEuf87xNszmSA2myDM2Kzu9HwQUA=
github.com/spf13/afero v1.1.2/go.mod h1:j4pytiNVoe2o6bmDsKpLACNPDBIoEAkihy7loJ1B0CQ=
diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go
index 3a269fe50..2ce113d30 100644
--- a/pkg/api/handlers/compat/containers.go
+++ b/pkg/api/handlers/compat/containers.go
@@ -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))
}
diff --git a/pkg/api/handlers/compat/containers_restart.go b/pkg/api/handlers/compat/containers_restart.go
index 5b8fafaa4..343bf96d2 100644
--- a/pkg/api/handlers/compat/containers_restart.go
+++ b/pkg/api/handlers/compat/containers_restart.go
@@ -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)
diff --git a/pkg/bindings/containers/containers.go b/pkg/bindings/containers/containers.go
index 534555a00..231b6f232 100644
--- a/pkg/bindings/containers/containers.go
+++ b/pkg/bindings/containers/containers.go
@@ -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 {
diff --git a/pkg/bindings/test/volumes_test.go b/pkg/bindings/test/volumes_test.go
index 9da034d24..59fe48f22 100644
--- a/pkg/bindings/test/volumes_test.go
+++ b/pkg/bindings/test/volumes_test.go
@@ -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)
diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go
index 0e1208b3b..8b7406ae8 100644
--- a/pkg/domain/entities/containers.go
+++ b/pkg/domain/entities/containers.go
@@ -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
+}
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index 5820c12c3..2887f0b51 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -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)
diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go
index cdcd77246..47deb010a 100644
--- a/pkg/domain/infra/abi/containers.go
+++ b/pkg/domain/infra/abi/containers.go
@@ -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) {
diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go
index 8bf74126d..21f62df6b 100644
--- a/pkg/domain/infra/tunnel/containers.go
+++ b/pkg/domain/infra/tunnel/containers.go
@@ -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
+}
diff --git a/pkg/domain/infra/tunnel/helpers.go b/pkg/domain/infra/tunnel/helpers.go
index d5a3224c2..11fca5278 100644
--- a/pkg/domain/infra/tunnel/helpers.go
+++ b/pkg/domain/infra/tunnel/helpers.go
@@ -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
diff --git a/pkg/signal/signal_linux.go b/pkg/signal/signal_linux.go
index 3d549898f..76ab16ec7 100644
--- a/pkg/signal/signal_linux.go
+++ b/pkg/signal/signal_linux.go
@@ -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)
+}
diff --git a/pkg/signal/signal_unsupported.go b/pkg/signal/signal_unsupported.go
index 0a92a5b3a..f946d802d 100644
--- a/pkg/signal/signal_unsupported.go
+++ b/pkg/signal/signal_unsupported.go
@@ -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")
+}
diff --git a/pkg/util/utils.go b/pkg/util/utils.go
index a4df48c88..3e11c010a 100644
--- a/pkg/util/utils.go
+++ b/pkg/util/utils.go
@@ -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
diff --git a/vendor/github.com/sirupsen/logrus/.golangci.yml b/vendor/github.com/sirupsen/logrus/.golangci.yml
new file mode 100644
index 000000000..65dc28503
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/.golangci.yml
@@ -0,0 +1,40 @@
+run:
+ # do not run on test files yet
+ tests: false
+
+# all available settings of specific linters
+linters-settings:
+ errcheck:
+ # report about not checking of errors in type assetions: `a := b.(MyStruct)`;
+ # default is false: such cases aren't reported by default.
+ check-type-assertions: false
+
+ # report about assignment of errors to blank identifier: `num, _ := strconv.Atoi(numStr)`;
+ # default is false: such cases aren't reported by default.
+ check-blank: false
+
+ lll:
+ line-length: 100
+ tab-width: 4
+
+ prealloc:
+ simple: false
+ range-loops: false
+ for-loops: false
+
+ whitespace:
+ multi-if: false # Enforces newlines (or comments) after every multi-line if statement
+ multi-func: false # Enforces newlines (or comments) after every multi-line function signature
+
+linters:
+ enable:
+ - megacheck
+ - govet
+ disable:
+ - maligned
+ - prealloc
+ disable-all: false
+ presets:
+ - bugs
+ - unused
+ fast: false
diff --git a/vendor/github.com/sirupsen/logrus/.travis.yml b/vendor/github.com/sirupsen/logrus/.travis.yml
index 848938a6d..5e20aa414 100644
--- a/vendor/github.com/sirupsen/logrus/.travis.yml
+++ b/vendor/github.com/sirupsen/logrus/.travis.yml
@@ -4,21 +4,13 @@ git:
depth: 1
env:
- GO111MODULE=on
- - GO111MODULE=off
-go: [ 1.11.x, 1.12.x ]
-os: [ linux, osx ]
-matrix:
- exclude:
- - go: 1.12.x
- env: GO111MODULE=off
- - go: 1.11.x
- os: osx
+go: [1.13.x, 1.14.x]
+os: [linux, osx]
install:
- ./travis/install.sh
- - if [[ "$GO111MODULE" == "on" ]]; then go mod download; fi
- - if [[ "$GO111MODULE" == "off" ]]; then go get github.com/stretchr/testify/assert golang.org/x/sys/unix github.com/konsorten/go-windows-terminal-sequences; fi
script:
- ./travis/cross_build.sh
+ - ./travis/lint.sh
- export GOMAXPROCS=4
- export GORACE=halt_on_error=1
- go test -race -v ./...
diff --git a/vendor/github.com/sirupsen/logrus/README.md b/vendor/github.com/sirupsen/logrus/README.md
index a4796eb07..5796706db 100644
--- a/vendor/github.com/sirupsen/logrus/README.md
+++ b/vendor/github.com/sirupsen/logrus/README.md
@@ -1,8 +1,28 @@
-# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/>&nbsp;[![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus)&nbsp;[![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
+# Logrus <img src="http://i.imgur.com/hTeVwmJ.png" width="40" height="40" alt=":walrus:" class="emoji" title=":walrus:"/> [![Build Status](https://travis-ci.org/sirupsen/logrus.svg?branch=master)](https://travis-ci.org/sirupsen/logrus) [![GoDoc](https://godoc.org/github.com/sirupsen/logrus?status.svg)](https://godoc.org/github.com/sirupsen/logrus)
Logrus is a structured logger for Go (golang), completely API compatible with
the standard library logger.
+**Logrus is in maintenance-mode.** We will not be introducing new features. It's
+simply too hard to do in a way that won't break many people's projects, which is
+the last thing you want from your Logging library (again...).
+
+This does not mean Logrus is dead. Logrus will continue to be maintained for
+security, (backwards compatible) bug fixes, and performance (where we are
+limited by the interface).
+
+I believe Logrus' biggest contribution is to have played a part in today's
+widespread use of structured logging in Golang. There doesn't seem to be a
+reason to do a major, breaking iteration into Logrus V2, since the fantastic Go
+community has built those independently. Many fantastic alternatives have sprung
+up. Logrus would look like those, had it been re-designed with what we know
+about structured logging in Go today. Check out, for example,
+[Zerolog][zerolog], [Zap][zap], and [Apex][apex].
+
+[zerolog]: https://github.com/rs/zerolog
+[zap]: https://github.com/uber-go/zap
+[apex]: https://github.com/apex/log
+
**Seeing weird case-sensitive problems?** It's in the past been possible to
import Logrus as both upper- and lower-case. Due to the Go package environment,
this caused issues in the community and we needed a standard. Some environments
@@ -15,11 +35,6 @@ comments](https://github.com/sirupsen/logrus/issues/553#issuecomment-306591437).
For an in-depth explanation of the casing issue, see [this
comment](https://github.com/sirupsen/logrus/issues/570#issuecomment-313933276).
-**Are you interested in assisting in maintaining Logrus?** Currently I have a
-lot of obligations, and I am unable to provide Logrus with the maintainership it
-needs. If you'd like to help, please reach out to me at `simon at author's
-username dot com`.
-
Nicely color-coded in development (when a TTY is attached, otherwise just
plain text):
@@ -187,7 +202,7 @@ func main() {
log.Out = os.Stdout
// You could set this to any `io.Writer` such as a file
- // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY, 0666)
+ // file, err := os.OpenFile("logrus.log", os.O_CREATE|os.O_WRONLY|os.O_APPEND, 0666)
// if err == nil {
// log.Out = file
// } else {
@@ -272,7 +287,7 @@ func init() {
```
Note: Syslog hook also support connecting to local syslog (Ex. "/dev/log" or "/var/run/syslog" or "/var/run/log"). For the detail, please check the [syslog hook README](hooks/syslog/README.md).
-A list of currently known of service hook can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
+A list of currently known service hooks can be found in this wiki [page](https://github.com/sirupsen/logrus/wiki/Hooks)
#### Level logging
@@ -354,6 +369,7 @@ The built-in logging formatters are:
[github.com/mattn/go-colorable](https://github.com/mattn/go-colorable).
* When colors are enabled, levels are truncated to 4 characters by default. To disable
truncation set the `DisableLevelTruncation` field to `true`.
+ * When outputting to a TTY, it's often helpful to visually scan down a column where all the levels are the same width. Setting the `PadLevelText` field to `true` enables this behavior, by adding padding to the level text.
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#TextFormatter).
* `logrus.JSONFormatter`. Logs fields as JSON.
* All options are listed in the [generated docs](https://godoc.org/github.com/sirupsen/logrus#JSONFormatter).
@@ -364,8 +380,10 @@ Third party logging formatters:
* [`GELF`](https://github.com/fabienm/go-logrus-formatters). Formats entries so they comply to Graylog's [GELF 1.1 specification](http://docs.graylog.org/en/2.4/pages/gelf.html).
* [`logstash`](https://github.com/bshuster-repo/logrus-logstash-hook). Logs fields as [Logstash](http://logstash.net) Events.
* [`prefixed`](https://github.com/x-cray/logrus-prefixed-formatter). Displays log entry source along with alternative layout.
-* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the P͉̫o̳̼̊w̖͈̰͎e̬͔̭͂r͚̼̹̲ ̫͓͉̳͈ō̠͕͖̚f̝͍̠ ͕̲̞͖͑Z̖̫̤̫ͪa͉̬͈̗l͖͎g̳̥o̰̥̅!̣͔̲̻͊̄ ̙̘̦̹̦.
+* [`zalgo`](https://github.com/aybabtme/logzalgo). Invoking the Power of Zalgo.
* [`nested-logrus-formatter`](https://github.com/antonfisher/nested-logrus-formatter). Converts logrus fields to a nested structure.
+* [`powerful-logrus-formatter`](https://github.com/zput/zxcTool). get fileName, log's line number and the latest function's name when print log; Sava log to files.
+* [`caption-json-formatter`](https://github.com/nolleh/caption_json_formatter). logrus's message json formatter with human-readable caption added.
You can define your formatter by implementing the `Formatter` interface,
requiring a `Format` method. `Format` takes an `*Entry`. `entry.Data` is a
@@ -430,14 +448,14 @@ entries. It should not be a feature of the application-level logger.
| Tool | Description |
| ---- | ----------- |
-|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will generated with different config at different environment.|
+|[Logrus Mate](https://github.com/gogap/logrus_mate)|Logrus mate is a tool for Logrus to manage loggers, you can initial logger's level, hook and formatter by config file, the logger will be generated with different configs in different environments.|
|[Logrus Viper Helper](https://github.com/heirko/go-contrib/tree/master/logrusHelper)|An Helper around Logrus to wrap with spf13/Viper to load configuration with fangs! And to simplify Logrus configuration use some behavior of [Logrus Mate](https://github.com/gogap/logrus_mate). [sample](https://github.com/heirko/iris-contrib/blob/master/middleware/logrus-logger/example) |
#### Testing
Logrus has a built in facility for asserting the presence of log messages. This is implemented through the `test` hook and provides:
-* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just add the `test` hook
+* decorators for existing logger (`test.NewLocal` and `test.NewGlobal`) which basically just adds the `test` hook
* a test logger (`test.NewNullLogger`) that just records log messages (and does not output any):
```go
@@ -465,7 +483,7 @@ func TestSomething(t*testing.T){
Logrus can register one or more functions that will be called when any `fatal`
level message is logged. The registered handlers will be executed before
-logrus performs a `os.Exit(1)`. This behavior may be helpful if callers need
+logrus performs an `os.Exit(1)`. This behavior may be helpful if callers need
to gracefully shutdown. Unlike a `panic("Something went wrong...")` call which can be intercepted with a deferred `recover` a call to `os.Exit(1)` can not be intercepted.
```
@@ -490,6 +508,6 @@ Situation when locking is not needed includes:
1) logger.Out is protected by locks.
- 2) logger.Out is a os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allow multi-thread/multi-process writing)
+ 2) logger.Out is an os.File handler opened with `O_APPEND` flag, and every write is smaller than 4k. (This allows multi-thread/multi-process writing)
(Refer to http://www.notthewizard.com/2014/06/17/are-files-appends-really-atomic/)
diff --git a/vendor/github.com/sirupsen/logrus/entry.go b/vendor/github.com/sirupsen/logrus/entry.go
index 63e25583c..27b14bfb1 100644
--- a/vendor/github.com/sirupsen/logrus/entry.go
+++ b/vendor/github.com/sirupsen/logrus/entry.go
@@ -85,10 +85,15 @@ func NewEntry(logger *Logger) *Entry {
}
}
+// Returns the bytes representation of this entry from the formatter.
+func (entry *Entry) Bytes() ([]byte, error) {
+ return entry.Logger.Formatter.Format(entry)
+}
+
// Returns the string representation from the reader and ultimately the
// formatter.
func (entry *Entry) String() (string, error) {
- serialized, err := entry.Logger.Formatter.Format(entry)
+ serialized, err := entry.Bytes()
if err != nil {
return "", err
}
@@ -103,7 +108,11 @@ func (entry *Entry) WithError(err error) *Entry {
// Add a context to the Entry.
func (entry *Entry) WithContext(ctx context.Context) *Entry {
- return &Entry{Logger: entry.Logger, Data: entry.Data, Time: entry.Time, err: entry.err, Context: ctx}
+ dataCopy := make(Fields, len(entry.Data))
+ for k, v := range entry.Data {
+ dataCopy[k] = v
+ }
+ return &Entry{Logger: entry.Logger, Data: dataCopy, Time: entry.Time, err: entry.err, Context: ctx}
}
// Add a single field to the Entry.
@@ -113,6 +122,8 @@ func (entry *Entry) WithField(key string, value interface{}) *Entry {
// Add a map of fields to the Entry.
func (entry *Entry) WithFields(fields Fields) *Entry {
+ entry.Logger.mu.Lock()
+ defer entry.Logger.mu.Unlock()
data := make(Fields, len(entry.Data)+len(fields))
for k, v := range entry.Data {
data[k] = v
@@ -144,7 +155,11 @@ func (entry *Entry) WithFields(fields Fields) *Entry {
// Overrides the time of the Entry.
func (entry *Entry) WithTime(t time.Time) *Entry {
- return &Entry{Logger: entry.Logger, Data: entry.Data, Time: t, err: entry.err, Context: entry.Context}
+ dataCopy := make(Fields, len(entry.Data))
+ for k, v := range entry.Data {
+ dataCopy[k] = v
+ }
+ return &Entry{Logger: entry.Logger, Data: dataCopy, Time: t, err: entry.err, Context: entry.Context}
}
// getPackageName reduces a fully qualified function name to the package name
@@ -165,15 +180,20 @@ func getPackageName(f string) string {
// getCaller retrieves the name of the first non-logrus calling function
func getCaller() *runtime.Frame {
-
// cache this package's fully-qualified name
callerInitOnce.Do(func() {
- pcs := make([]uintptr, 2)
+ pcs := make([]uintptr, maximumCallerDepth)
_ = runtime.Callers(0, pcs)
- logrusPackage = getPackageName(runtime.FuncForPC(pcs[1]).Name())
- // now that we have the cache, we can skip a minimum count of known-logrus functions
- // XXX this is dubious, the number of frames may vary
+ // dynamic get the package name and the minimum caller depth
+ for i := 0; i < maximumCallerDepth; i++ {
+ funcName := runtime.FuncForPC(pcs[i]).Name()
+ if strings.Contains(funcName, "getCaller") {
+ logrusPackage = getPackageName(funcName)
+ break
+ }
+ }
+
minimumCallerDepth = knownLogrusFrames
})
@@ -187,7 +207,7 @@ func getCaller() *runtime.Frame {
// If the caller isn't part of this package, we're done
if pkg != logrusPackage {
- return &f
+ return &f //nolint:scopelint
}
}
@@ -217,9 +237,11 @@ func (entry Entry) log(level Level, msg string) {
entry.Level = level
entry.Message = msg
+ entry.Logger.mu.Lock()
if entry.Logger.ReportCaller {
entry.Caller = getCaller()
}
+ entry.Logger.mu.Unlock()
entry.fireHooks()
@@ -255,11 +277,10 @@ func (entry *Entry) write() {
serialized, err := entry.Logger.Formatter.Format(entry)
if err != nil {
fmt.Fprintf(os.Stderr, "Failed to obtain reader, %v\n", err)
- } else {
- _, err = entry.Logger.Out.Write(serialized)
- if err != nil {
- fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
- }
+ return
+ }
+ if _, err = entry.Logger.Out.Write(serialized); err != nil {
+ fmt.Fprintf(os.Stderr, "Failed to write to log, %v\n", err)
}
}
diff --git a/vendor/github.com/sirupsen/logrus/exported.go b/vendor/github.com/sirupsen/logrus/exported.go
index 62fc2f219..42b04f6c8 100644
--- a/vendor/github.com/sirupsen/logrus/exported.go
+++ b/vendor/github.com/sirupsen/logrus/exported.go
@@ -80,7 +80,7 @@ func WithFields(fields Fields) *Entry {
return std.WithFields(fields)
}
-// WithTime creats an entry from the standard logger and overrides the time of
+// WithTime creates an entry from the standard logger and overrides the time of
// logs generated with it.
//
// Note that it doesn't log until you call Debug, Print, Info, Warn, Fatal
diff --git a/vendor/github.com/sirupsen/logrus/go.mod b/vendor/github.com/sirupsen/logrus/go.mod
index 12fdf9898..9ea6e841b 100644
--- a/vendor/github.com/sirupsen/logrus/go.mod
+++ b/vendor/github.com/sirupsen/logrus/go.mod
@@ -4,7 +4,8 @@ require (
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/konsorten/go-windows-terminal-sequences v1.0.1
github.com/pmezard/go-difflib v1.0.0 // indirect
- github.com/stretchr/objx v0.1.1 // indirect
github.com/stretchr/testify v1.2.2
golang.org/x/sys v0.0.0-20190422165155-953cdadca894
)
+
+go 1.13
diff --git a/vendor/github.com/sirupsen/logrus/go.sum b/vendor/github.com/sirupsen/logrus/go.sum
index 596c318b9..95a3f07de 100644
--- a/vendor/github.com/sirupsen/logrus/go.sum
+++ b/vendor/github.com/sirupsen/logrus/go.sum
@@ -1,16 +1,10 @@
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
-github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe h1:CHRGQ8V7OlCYtwaKPJi3iA7J+YdNKdo8j7nG5IgDhjs=
-github.com/konsorten/go-windows-terminal-sequences v0.0.0-20180402223658-b729f2633dfe/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/konsorten/go-windows-terminal-sequences v1.0.1 h1:mweAR1A6xJ3oS2pRaGiHgQ4OO8tzTaLawm8vnODuwDk=
github.com/konsorten/go-windows-terminal-sequences v1.0.1/go.mod h1:T0+1ngSBFLxvqU3pZ+m/2kptfBszLMUkC4ZK/EgS/cQ=
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
-github.com/stretchr/objx v0.1.1 h1:2vfRuCMp5sSVIDSqO8oNnWJq7mPa6KVP3iPIwFBuy8A=
-github.com/stretchr/objx v0.1.1/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/testify v1.2.2 h1:bSDNvY7ZPG5RlJ8otE/7V6gMiyenm9RtJ7IUVIAoJ1w=
github.com/stretchr/testify v1.2.2/go.mod h1:a8OnRcib4nhh0OaRAV+Yts87kKdq0PP7pXfy6kDkUVs=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33 h1:I6FyU15t786LL7oL/hn43zqTuEGr4PN7F4XJ1p4E3Y8=
-golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894 h1:Cz4ceDQGXuKRnVBDTS23GTn/pU5OE2C0WrNTOYK1Uuc=
golang.org/x/sys v0.0.0-20190422165155-953cdadca894/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
diff --git a/vendor/github.com/sirupsen/logrus/json_formatter.go b/vendor/github.com/sirupsen/logrus/json_formatter.go
index 098a21a06..ba7f23711 100644
--- a/vendor/github.com/sirupsen/logrus/json_formatter.go
+++ b/vendor/github.com/sirupsen/logrus/json_formatter.go
@@ -28,6 +28,9 @@ type JSONFormatter struct {
// DisableTimestamp allows disabling automatic timestamps in output
DisableTimestamp bool
+ // DisableHTMLEscape allows disabling html escaping in output
+ DisableHTMLEscape bool
+
// DataKey allows users to put all the log entry parameters into a nested dictionary at a given key.
DataKey string
@@ -110,6 +113,7 @@ func (f *JSONFormatter) Format(entry *Entry) ([]byte, error) {
}
encoder := json.NewEncoder(b)
+ encoder.SetEscapeHTML(!f.DisableHTMLEscape)
if f.PrettyPrint {
encoder.SetIndent("", " ")
}
diff --git a/vendor/github.com/sirupsen/logrus/logger.go b/vendor/github.com/sirupsen/logrus/logger.go
index c0c0b1e55..6fdda748e 100644
--- a/vendor/github.com/sirupsen/logrus/logger.go
+++ b/vendor/github.com/sirupsen/logrus/logger.go
@@ -68,10 +68,10 @@ func (mw *MutexWrap) Disable() {
// `Out` and `Hooks` directly on the default logger instance. You can also just
// instantiate your own:
//
-// var log = &Logger{
+// var log = &logrus.Logger{
// Out: os.Stderr,
-// Formatter: new(JSONFormatter),
-// Hooks: make(LevelHooks),
+// Formatter: new(logrus.JSONFormatter),
+// Hooks: make(logrus.LevelHooks),
// Level: logrus.DebugLevel,
// }
//
@@ -100,8 +100,9 @@ func (logger *Logger) releaseEntry(entry *Entry) {
logger.entryPool.Put(entry)
}
-// Adds a field to the log entry, note that it doesn't log until you call
-// Debug, Print, Info, Warn, Error, Fatal or Panic. It only creates a log entry.
+// WithField allocates a new entry and adds a field to it.
+// Debug, Print, Info, Warn, Error, Fatal or Panic must be then applied to
+// this new returned entry.
// If you want multiple fields, use `WithFields`.
func (logger *Logger) WithField(key string, value interface{}) *Entry {
entry := logger.newEntry()
diff --git a/vendor/github.com/sirupsen/logrus/logrus.go b/vendor/github.com/sirupsen/logrus/logrus.go
index 8644761f7..2f16224cb 100644
--- a/vendor/github.com/sirupsen/logrus/logrus.go
+++ b/vendor/github.com/sirupsen/logrus/logrus.go
@@ -51,7 +51,7 @@ func (level *Level) UnmarshalText(text []byte) error {
return err
}
- *level = Level(l)
+ *level = l
return nil
}
diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
index 3c4f43f91..499789984 100644
--- a/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
+++ b/vendor/github.com/sirupsen/logrus/terminal_check_bsd.go
@@ -1,4 +1,5 @@
// +build darwin dragonfly freebsd netbsd openbsd
+// +build !js
package logrus
@@ -10,4 +11,3 @@ func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}
-
diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_js.go b/vendor/github.com/sirupsen/logrus/terminal_check_js.go
new file mode 100644
index 000000000..ebdae3ec6
--- /dev/null
+++ b/vendor/github.com/sirupsen/logrus/terminal_check_js.go
@@ -0,0 +1,7 @@
+// +build js
+
+package logrus
+
+func isTerminal(fd int) bool {
+ return false
+}
diff --git a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go
index 355dc966f..cc4fe6e31 100644
--- a/vendor/github.com/sirupsen/logrus/terminal_check_unix.go
+++ b/vendor/github.com/sirupsen/logrus/terminal_check_unix.go
@@ -1,4 +1,5 @@
// +build linux aix
+// +build !js
package logrus
@@ -10,4 +11,3 @@ func isTerminal(fd int) bool {
_, err := unix.IoctlGetTermios(fd, ioctlReadTermios)
return err == nil
}
-
diff --git a/vendor/github.com/sirupsen/logrus/text_formatter.go b/vendor/github.com/sirupsen/logrus/text_formatter.go
index e01587c43..2d15a239f 100644
--- a/vendor/github.com/sirupsen/logrus/text_formatter.go
+++ b/vendor/github.com/sirupsen/logrus/text_formatter.go
@@ -6,9 +6,11 @@ import (
"os"
"runtime"
"sort"
+ "strconv"
"strings"
"sync"
"time"
+ "unicode/utf8"
)
const (
@@ -32,6 +34,9 @@ type TextFormatter struct {
// Force disabling colors.
DisableColors bool
+ // Force quoting of all values
+ ForceQuote bool
+
// Override coloring based on CLICOLOR and CLICOLOR_FORCE. - https://bixense.com/clicolors/
EnvironmentOverrideColors bool
@@ -57,6 +62,10 @@ type TextFormatter struct {
// Disables the truncation of the level text to 4 characters.
DisableLevelTruncation bool
+ // PadLevelText Adds padding the level text so that all the levels output at the same length
+ // PadLevelText is a superset of the DisableLevelTruncation option
+ PadLevelText bool
+
// QuoteEmptyFields will wrap empty fields in quotes if true
QuoteEmptyFields bool
@@ -79,23 +88,32 @@ type TextFormatter struct {
CallerPrettyfier func(*runtime.Frame) (function string, file string)
terminalInitOnce sync.Once
+
+ // The max length of the level text, generated dynamically on init
+ levelTextMaxLength int
}
func (f *TextFormatter) init(entry *Entry) {
if entry.Logger != nil {
f.isTerminal = checkIfTerminal(entry.Logger.Out)
}
+ // Get the max length of the level text
+ for _, level := range AllLevels {
+ levelTextLength := utf8.RuneCount([]byte(level.String()))
+ if levelTextLength > f.levelTextMaxLength {
+ f.levelTextMaxLength = levelTextLength
+ }
+ }
}
func (f *TextFormatter) isColored() bool {
isColored := f.ForceColors || (f.isTerminal && (runtime.GOOS != "windows"))
if f.EnvironmentOverrideColors {
- if force, ok := os.LookupEnv("CLICOLOR_FORCE"); ok && force != "0" {
+ switch force, ok := os.LookupEnv("CLICOLOR_FORCE"); {
+ case ok && force != "0":
isColored = true
- } else if ok && force == "0" {
- isColored = false
- } else if os.Getenv("CLICOLOR") == "0" {
+ case ok && force == "0", os.Getenv("CLICOLOR") == "0":
isColored = false
}
}
@@ -217,9 +235,18 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
levelText := strings.ToUpper(entry.Level.String())
- if !f.DisableLevelTruncation {
+ if !f.DisableLevelTruncation && !f.PadLevelText {
levelText = levelText[0:4]
}
+ if f.PadLevelText {
+ // Generates the format string used in the next line, for example "%-6s" or "%-7s".
+ // Based on the max level text length.
+ formatString := "%-" + strconv.Itoa(f.levelTextMaxLength) + "s"
+ // Formats the level text by appending spaces up to the max length, for example:
+ // - "INFO "
+ // - "WARNING"
+ levelText = fmt.Sprintf(formatString, levelText)
+ }
// Remove a single newline if it already exists in the message to keep
// the behavior of logrus text_formatter the same as the stdlib log package
@@ -243,11 +270,12 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
}
- if f.DisableTimestamp {
+ switch {
+ case f.DisableTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m%s %-44s ", levelColor, levelText, caller, entry.Message)
- } else if !f.FullTimestamp {
+ case !f.FullTimestamp:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%04d]%s %-44s ", levelColor, levelText, int(entry.Time.Sub(baseTimestamp)/time.Second), caller, entry.Message)
- } else {
+ default:
fmt.Fprintf(b, "\x1b[%dm%s\x1b[0m[%s]%s %-44s ", levelColor, levelText, entry.Time.Format(timestampFormat), caller, entry.Message)
}
for _, k := range keys {
@@ -258,6 +286,9 @@ func (f *TextFormatter) printColored(b *bytes.Buffer, entry *Entry, keys []strin
}
func (f *TextFormatter) needsQuoting(text string) bool {
+ if f.ForceQuote {
+ return true
+ }
if f.QuoteEmptyFields && len(text) == 0 {
return true
}
diff --git a/vendor/github.com/sirupsen/logrus/writer.go b/vendor/github.com/sirupsen/logrus/writer.go
index 9e1f75135..72e8e3a1b 100644
--- a/vendor/github.com/sirupsen/logrus/writer.go
+++ b/vendor/github.com/sirupsen/logrus/writer.go
@@ -6,10 +6,16 @@ import (
"runtime"
)
+// Writer at INFO level. See WriterLevel for details.
func (logger *Logger) Writer() *io.PipeWriter {
return logger.WriterLevel(InfoLevel)
}
+// WriterLevel returns an io.Writer that can be used to write arbitrary text to
+// the logger at the given log level. Each line written to the writer will be
+// printed in the usual way using formatters and hooks. The writer is part of an
+// io.Pipe and it is the callers responsibility to close the writer when done.
+// This can be used to override the standard library logger easily.
func (logger *Logger) WriterLevel(level Level) *io.PipeWriter {
return NewEntry(logger).WriterLevel(level)
}
diff --git a/vendor/modules.txt b/vendor/modules.txt
index b143eea5a..4502c6ebc 100644
--- a/vendor/modules.txt
+++ b/vendor/modules.txt
@@ -469,7 +469,7 @@ github.com/safchain/ethtool
github.com/seccomp/containers-golang
# github.com/seccomp/libseccomp-golang v0.9.1
github.com/seccomp/libseccomp-golang
-# github.com/sirupsen/logrus v1.4.2
+# github.com/sirupsen/logrus v1.5.0
github.com/sirupsen/logrus
github.com/sirupsen/logrus/hooks/syslog
# github.com/spf13/cobra v0.0.6