diff options
author | Valentin Rothberg <rothberg@redhat.com> | 2020-04-07 16:54:32 +0200 |
---|---|---|
committer | Valentin Rothberg <rothberg@redhat.com> | 2020-04-09 11:55:10 +0200 |
commit | 7a3bfbf0767675472451d7328cada39b5797cdf8 (patch) | |
tree | cc9f24c405c4bfbf636a0ada96fa22f11bba87c0 | |
parent | 46227e0b030ac4180d8f3179f2b33c5d6d8fd200 (diff) | |
download | podman-7a3bfbf0767675472451d7328cada39b5797cdf8.tar.gz podman-7a3bfbf0767675472451d7328cada39b5797cdf8.tar.bz2 podman-7a3bfbf0767675472451d7328cada39b5797cdf8.zip |
podmanV2: implement logs
Implement the `podman {container} logs` for the v2 client. The remote
client does not yet support it. There's some more work needed for the
rest api; some options are missing (e.g., printing names) while others
are broken (e.g., the until http parameter).
The remote parts will be tackled in a future change.
Signed-off-by: Valentin Rothberg <rothberg@redhat.com>
-rw-r--r-- | cmd/podmanV2/containers/logs.go | 108 | ||||
-rw-r--r-- | pkg/api/handlers/compat/containers.go | 1 | ||||
-rw-r--r-- | pkg/domain/entities/containers.go | 20 | ||||
-rw-r--r-- | pkg/domain/entities/engine_container.go | 1 | ||||
-rw-r--r-- | pkg/domain/infra/abi/containers.go | 48 | ||||
-rw-r--r-- | pkg/domain/infra/tunnel/containers.go | 5 |
6 files changed, 183 insertions, 0 deletions
diff --git a/cmd/podmanV2/containers/logs.go b/cmd/podmanV2/containers/logs.go new file mode 100644 index 000000000..d1a179495 --- /dev/null +++ b/cmd/podmanV2/containers/logs.go @@ -0,0 +1,108 @@ +package containers + +import ( + "os" + + "github.com/containers/libpod/cmd/podmanV2/registry" + "github.com/containers/libpod/pkg/domain/entities" + "github.com/containers/libpod/pkg/util" + "github.com/pkg/errors" + "github.com/spf13/cobra" + "github.com/spf13/pflag" +) + +// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking +// CLI-only fields into the API types. +type logsOptionsWrapper struct { + entities.ContainerLogsOptions + + SinceRaw string +} + +var ( + logsOptions logsOptionsWrapper + logsDescription = `Retrieves logs for one or more containers. + + This does not guarantee execution order when combined with podman run (i.e., your run may not have generated any logs at the time you execute podman logs). +` + logsCommand = &cobra.Command{ + Use: "logs [flags] CONTAINER [CONTAINER...]", + Short: "Fetch the logs of one or more container", + Long: logsDescription, + RunE: logs, + PreRunE: preRunE, + Example: `podman logs ctrID + podman logs --names ctrID1 ctrID2 + podman logs --tail 2 mywebserver + podman logs --follow=true --since 10m ctrID + podman logs mywebserver mydbserver`, + } + + containerLogsCommand = &cobra.Command{ + Use: logsCommand.Use, + Short: logsCommand.Short, + Long: logsCommand.Long, + PreRunE: logsCommand.PreRunE, + RunE: logsCommand.RunE, + Example: `podman container logs ctrID + podman container logs --names ctrID1 ctrID2 + podman container logs --tail 2 mywebserver + podman container logs --follow=true --since 10m ctrID + podman container logs mywebserver mydbserver`, + } +) + +func init() { + // logs + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: logsCommand, + }) + + logsCommand.SetHelpTemplate(registry.HelpTemplate()) + logsCommand.SetUsageTemplate(registry.UsageTemplate()) + + flags := logsCommand.Flags() + logsFlags(flags) + + // container logs + registry.Commands = append(registry.Commands, registry.CliCommand{ + Mode: []entities.EngineMode{entities.ABIMode}, + Command: containerLogsCommand, + Parent: containerCmd, + }) + + containerLogsFlags := containerLogsCommand.Flags() + logsFlags(containerLogsFlags) +} + +func logsFlags(flags *pflag.FlagSet) { + flags.BoolVar(&logsOptions.Details, "details", false, "Show extra details provided to the logs") + flags.BoolVarP(&logsOptions.Follow, "follow", "f", false, "Follow log output. The default is false") + flags.BoolVarP(&logsOptions.Latest, "latest", "l", false, "Act on the latest container podman is aware of") + flags.StringVar(&logsOptions.SinceRaw, "since", "", "Show logs since TIMESTAMP") + flags.Int64Var(&logsOptions.Tail, "tail", -1, "Output the specified number of LINES at the end of the logs. Defaults to -1, which prints all lines") + flags.BoolVarP(&logsOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log") + flags.BoolVarP(&logsOptions.Names, "names", "n", false, "Output the container name in the log") + flags.SetInterspersed(false) + _ = flags.MarkHidden("details") +} + +func logs(cmd *cobra.Command, args []string) error { + if len(args) > 0 && logsOptions.Latest { + return errors.New("no containers can be specified when using 'latest'") + } + if !logsOptions.Latest && len(args) < 1 { + return errors.New("specify at least one container name or ID to log") + } + if logsOptions.SinceRaw != "" { + // parse time, error out if something is wrong + since, err := util.ParseInputTime(logsOptions.SinceRaw) + if err != nil { + return errors.Wrapf(err, "error parsing --since %q", logsOptions.SinceRaw) + } + logsOptions.Since = since + } + logsOptions.Writer = os.Stdout + return registry.ContainerEngine().ContainerLogs(registry.GetContext(), args, logsOptions.ContainerLogsOptions) +} diff --git a/pkg/api/handlers/compat/containers.go b/pkg/api/handlers/compat/containers.go index c53af0f26..3f6aca502 100644 --- a/pkg/api/handlers/compat/containers.go +++ b/pkg/api/handlers/compat/containers.go @@ -261,6 +261,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) { var until time.Time if _, found := r.URL.Query()["until"]; found { + // FIXME: until != since but the logs backend does not yet support until. since, err = util.ParseInputTime(query.Until) if err != nil { utils.BadRequest(w, "until", query.Until, err) diff --git a/pkg/domain/entities/containers.go b/pkg/domain/entities/containers.go index 5d302058b..98f9f9471 100644 --- a/pkg/domain/entities/containers.go +++ b/pkg/domain/entities/containers.go @@ -172,6 +172,26 @@ type AttachOptions struct { Stderr *os.File } +// ContainerLogsOptions describes the options to extract container logs. +type ContainerLogsOptions struct { + // Show extra details provided to the logs. + Details bool + // Follow the log output. + Follow bool + // Display logs for the latest container only. Ignored on the remote client. + Latest bool + // Show container names in the output. + Names bool + // Show logs since this timestamp. + Since time.Time + // Number of lines to display at the end of the output. + Tail int64 + // Show timestamps in the logs. + Timestamps bool + // Write the logs to Writer. + Writer io.Writer +} + // ExecOptions describes the cli values to exec into // a container type ExecOptions struct { diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go index 24b7a9acc..fec288eb8 100644 --- a/pkg/domain/entities/engine_container.go +++ b/pkg/domain/entities/engine_container.go @@ -20,6 +20,7 @@ type ContainerEngine interface { ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error) ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, error) + ContainerLogs(ctx context.Context, containers []string, options ContainerLogsOptions) error ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error) ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) diff --git a/pkg/domain/infra/abi/containers.go b/pkg/domain/infra/abi/containers.go index ccbe6d4fd..1aabb2a6b 100644 --- a/pkg/domain/infra/abi/containers.go +++ b/pkg/domain/infra/abi/containers.go @@ -4,9 +4,11 @@ package abi import ( "context" + "fmt" "io/ioutil" "strconv" "strings" + "sync" "github.com/containers/buildah" "github.com/containers/image/v5/manifest" @@ -14,6 +16,7 @@ import ( "github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/image" + "github.com/containers/libpod/libpod/logs" "github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/infra/abi/terminal" @@ -709,3 +712,48 @@ func (ic *ContainerEngine) ContainerRun(ctx context.Context, opts entities.Conta } return &report, nil } + +func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error { + if options.Writer == nil { + return errors.New("no io.Writer set for container logs") + } + + var wg sync.WaitGroup + + ctrs, err := getContainersByContext(false, options.Latest, containers, ic.Libpod) + if err != nil { + return err + } + + logOpts := &logs.LogOptions{ + Multi: len(ctrs) > 1, + Details: options.Details, + Follow: options.Follow, + Since: options.Since, + Tail: options.Tail, + Timestamps: options.Timestamps, + UseName: options.Names, + WaitGroup: &wg, + } + + chSize := len(ctrs) * int(options.Tail) + if chSize <= 0 { + chSize = 1 + } + logChannel := make(chan *logs.LogLine, chSize) + + if err := ic.Libpod.Log(ctrs, logOpts, logChannel); err != nil { + return err + } + + go func() { + wg.Wait() + close(logChannel) + }() + + for line := range logChannel { + fmt.Fprintln(options.Writer, line.String(logOpts)) + } + + return nil +} diff --git a/pkg/domain/infra/tunnel/containers.go b/pkg/domain/infra/tunnel/containers.go index b22c6e3ba..d40f0da36 100644 --- a/pkg/domain/infra/tunnel/containers.go +++ b/pkg/domain/infra/tunnel/containers.go @@ -305,6 +305,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG return &entities.ContainerCreateReport{Id: response.ID}, nil } +func (ic *ContainerEngine) ContainerLogs(ctx context.Context, containers []string, options entities.ContainerLogsOptions) error { + // The endpoint is not ready yet and requires some more work. + return errors.New("not implemented yet") +} + func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error { return errors.New("not implemented") } |