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>
This commit is contained in:
Valentin Rothberg
2020-04-07 16:54:32 +02:00
parent 46227e0b03
commit 7a3bfbf076
6 changed files with 183 additions and 0 deletions

View File

@ -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)
}

View File

@ -261,6 +261,7 @@ func LogsFromContainer(w http.ResponseWriter, r *http.Request) {
var until time.Time var until time.Time
if _, found := r.URL.Query()["until"]; found { 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) since, err = util.ParseInputTime(query.Until)
if err != nil { if err != nil {
utils.BadRequest(w, "until", query.Until, err) utils.BadRequest(w, "until", query.Until, err)

View File

@ -172,6 +172,26 @@ type AttachOptions struct {
Stderr *os.File 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 // ExecOptions describes the cli values to exec into
// a container // a container
type ExecOptions struct { type ExecOptions struct {

View File

@ -20,6 +20,7 @@ type ContainerEngine interface {
ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error) ContainerKill(ctx context.Context, namesOrIds []string, options KillOptions) ([]*KillReport, error)
ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error) ContainerList(ctx context.Context, options ContainerListOptions) ([]ListContainer, error)
ContainerPause(ctx context.Context, namesOrIds []string, options PauseUnPauseOptions) ([]*PauseUnpauseReport, 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) ContainerRestart(ctx context.Context, namesOrIds []string, options RestartOptions) ([]*RestartReport, error)
ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error) ContainerRestore(ctx context.Context, namesOrIds []string, options RestoreOptions) ([]*RestoreReport, error)
ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error) ContainerRm(ctx context.Context, namesOrIds []string, options RmOptions) ([]*RmReport, error)

View File

@ -4,9 +4,11 @@ package abi
import ( import (
"context" "context"
"fmt"
"io/ioutil" "io/ioutil"
"strconv" "strconv"
"strings" "strings"
"sync"
"github.com/containers/buildah" "github.com/containers/buildah"
"github.com/containers/image/v5/manifest" "github.com/containers/image/v5/manifest"
@ -14,6 +16,7 @@ import (
"github.com/containers/libpod/libpod/define" "github.com/containers/libpod/libpod/define"
"github.com/containers/libpod/libpod/events" "github.com/containers/libpod/libpod/events"
"github.com/containers/libpod/libpod/image" "github.com/containers/libpod/libpod/image"
"github.com/containers/libpod/libpod/logs"
"github.com/containers/libpod/pkg/checkpoint" "github.com/containers/libpod/pkg/checkpoint"
"github.com/containers/libpod/pkg/domain/entities" "github.com/containers/libpod/pkg/domain/entities"
"github.com/containers/libpod/pkg/domain/infra/abi/terminal" "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 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
}

View File

@ -305,6 +305,11 @@ func (ic *ContainerEngine) ContainerCreate(ctx context.Context, s *specgen.SpecG
return &entities.ContainerCreateReport{Id: response.ID}, nil 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 { func (ic *ContainerEngine) ContainerAttach(ctx context.Context, nameOrId string, options entities.AttachOptions) error {
return errors.New("not implemented") return errors.New("not implemented")
} }