diff options
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(®istry.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(®istry.EngineOpts.Uri, "remote", "r", "", "URL to access Podman service") rootCmd.PersistentFlags().StringSliceVar(®istry.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 @@ -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 @@ -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:"/> [![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 <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 |