Skip to content

Commit

Permalink
Merge pull request #11427 from flouthoc/kube-pod-logs
Browse files Browse the repository at this point in the history
kube: Add support for `podman pod logs`.
  • Loading branch information
openshift-merge-robot authored Sep 7, 2021
2 parents b07e735 + 11fc0e5 commit 30d0cc3
Show file tree
Hide file tree
Showing 9 changed files with 345 additions and 2 deletions.
140 changes: 140 additions & 0 deletions cmd/podman/pods/logs.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,140 @@
package pods

import (
"os"

"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v3/cmd/podman/common"
"github.com/containers/podman/v3/cmd/podman/registry"
"github.com/containers/podman/v3/cmd/podman/validate"
"github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/pkg/domain/entities"
"github.com/containers/podman/v3/pkg/util"
"github.com/pkg/errors"
"github.com/spf13/cobra"
)

// logsOptionsWrapper wraps entities.LogsOptions and prevents leaking
// CLI-only fields into the API types.
type logsOptionsWrapper struct {
entities.PodLogsOptions

SinceRaw string

UntilRaw string
}

var (
logsPodOptions logsOptionsWrapper
logsPodDescription = `Displays logs for pod with one or more containers.`
logsPodCommand = &cobra.Command{
Use: "logs [options] POD",
Short: "Fetch logs for pod with one or more containers",
Long: logsPodDescription,
// We dont want users to invoke latest and pod togather
Args: func(cmd *cobra.Command, args []string) error {
switch {
case registry.IsRemote() && logsPodOptions.Latest:
return errors.New(cmd.Name() + " does not support 'latest' when run remotely")
case len(args) > 1:
return errors.New("requires exactly 1 arg")
case logsPodOptions.Latest && len(args) > 0:
return errors.New("--latest and pods cannot be used together")
case !logsPodOptions.Latest && len(args) < 1:
return errors.New("specify at least one pod name or ID to log")
}
return nil
},
RunE: logs,
ValidArgsFunction: common.AutocompletePods,
Example: `podman pod logs podID
podman pod logs -c ctrname podName
podman pod logs --tail 2 mywebserver
podman pod logs --follow=true --since 10m podID
podman pod logs mywebserver`,
}

containerLogsCommand = &cobra.Command{
Use: logsPodCommand.Use,
Short: logsPodCommand.Short,
Long: logsPodCommand.Long,
Args: logsPodCommand.Args,
RunE: logsPodCommand.RunE,
ValidArgsFunction: logsPodCommand.ValidArgsFunction,
Example: `podman pod logs podId
podman pod logs -c ctrname podName
podman pod logs --tail 2 mywebserver
podman pod logs --follow=true --since 10m podID`,
}
)

func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: logsPodCommand,
})
logsFlags(logsPodCommand)
validate.AddLatestFlag(logsPodCommand, &logsPodOptions.Latest)

// container logs
registry.Commands = append(registry.Commands, registry.CliCommand{
Command: containerLogsCommand,
Parent: podCmd,
})
logsFlags(containerLogsCommand)
validate.AddLatestFlag(containerLogsCommand, &logsPodOptions.Latest)
}

func logsFlags(cmd *cobra.Command) {
flags := cmd.Flags()

flags.BoolVar(&logsPodOptions.Details, "details", false, "Show extra details provided to the logs")
flags.BoolVarP(&logsPodOptions.Follow, "follow", "f", false, "Follow log output.")

containerNameFlag := "container"
flags.StringVarP(&logsPodOptions.ContainerName, containerNameFlag, "c", "", "Filter logs by container name or id which belongs to pod")
_ = cmd.RegisterFlagCompletionFunc(containerNameFlag, common.AutocompleteContainers)

sinceFlagName := "since"
flags.StringVar(&logsPodOptions.SinceRaw, sinceFlagName, "", "Show logs since TIMESTAMP")
_ = cmd.RegisterFlagCompletionFunc(sinceFlagName, completion.AutocompleteNone)

untilFlagName := "until"
flags.StringVar(&logsPodOptions.UntilRaw, untilFlagName, "", "Show logs until TIMESTAMP")
_ = cmd.RegisterFlagCompletionFunc(untilFlagName, completion.AutocompleteNone)

tailFlagName := "tail"
flags.Int64Var(&logsPodOptions.Tail, tailFlagName, -1, "Output the specified number of LINES at the end of the logs.")
_ = cmd.RegisterFlagCompletionFunc(tailFlagName, completion.AutocompleteNone)

flags.BoolVarP(&logsPodOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
flags.SetInterspersed(false)
_ = flags.MarkHidden("details")
}

func logs(_ *cobra.Command, args []string) error {
if logsPodOptions.SinceRaw != "" {
// parse time, error out if something is wrong
since, err := util.ParseInputTime(logsPodOptions.SinceRaw, true)
if err != nil {
return errors.Wrapf(err, "error parsing --since %q", logsPodOptions.SinceRaw)
}
logsPodOptions.Since = since
}
if logsPodOptions.UntilRaw != "" {
// parse time, error out if something is wrong
until, err := util.ParseInputTime(logsPodOptions.UntilRaw, false)
if err != nil {
return errors.Wrapf(err, "error parsing --until %q", logsPodOptions.UntilRaw)
}
logsPodOptions.Until = until
}

// Remote can only process one container at a time
if registry.IsRemote() && logsPodOptions.ContainerName == "" {
return errors.Wrapf(define.ErrInvalidArg, "-c or --container cannot be empty")
}

logsPodOptions.StdoutWriter = os.Stdout
logsPodOptions.StderrWriter = os.Stderr
return registry.ContainerEngine().PodLogs(registry.GetContext(), args[0], logsPodOptions.PodLogsOptions)
}
88 changes: 88 additions & 0 deletions docs/source/markdown/podman-pod-logs.1.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
% podman-pod-logs(1)

## NAME
podman\-pod\-logs - Displays logs for pod with one or more containers

## SYNOPSIS
**podman pod logs** [*options*] *pod*

## DESCRIPTION
The podman pod logs command batch-retrieves whatever logs are present with all the containers of a pod. Pod logs can be filtered by container name or id using flag **-c** or **--container** if needed.

Note: Long running command of `podman pod log` with a `-f` or `--follow` needs to be reinvoked if new container is added to the pod dynamically otherwise logs of newly added containers would not be visible in log stream.

## OPTIONS

#### **--container**, **-c**

By default `podman pod logs` retrives logs for all the containers available within the pod differentiate by field `container`. However there are use-cases where user would want to limit the log stream only to a particular container of a pod for such cases `-c` can be used like `podman pod logs -c ctrNameorID podname`.

#### **--follow**, **-f**

Follow log output. Default is false.

Note: If you are following a pod which is removed `podman pod rm`, then there is a
chance the the log file will be removed before `podman pod logs` reads the final content.

#### **--latest**, **-l**

Instead of providing the pod name or id, get logs of the last created pod. (This option is not available with the remote Podman client)

#### **--since**=*TIMESTAMP*

Show logs since TIMESTAMP. The --since option can be Unix timestamps, date formatted timestamps, or Go duration
strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
and 2006-01-02.

#### **--until**=*TIMESTAMP*

Show logs until TIMESTAMP. The --until option can be Unix timestamps, date formatted timestamps, or Go duration
strings (e.g. 10m, 1h30m) computed relative to the client machine's time. Supported formats for date formatted
time stamps include RFC3339Nano, RFC3339, 2006-01-02T15:04:05, 2006-01-02T15:04:05.999999999, 2006-01-02Z07:00,
and 2006-01-02.


#### **--tail**=*LINES*

Output the specified number of LINES at the end of the logs. LINES must be an integer. Defaults to -1,
which prints all lines

#### **--timestamps**, **-t**

Show timestamps in the log outputs. The default is false

## EXAMPLE

To view a pod's logs:
```
podman pod logs -t podIdorName
```

To view logs of a specific container on the pod
```
podman pod logs -c ctrIdOrName podIdOrName
```

To view all pod logs:
```
podman pod logs -t --since 0 myserver-pod-1
```

To view a pod's logs since a certain time:
```
podman pod logs -t --since 2017-08-07T10:10:09.055837383-04:00 myserver-pod-1
```

To view a pod's logs generated in the last 10 minutes:
```
podman pod logs --since 10m myserver-pod-1
```

To view a pod's logs until 30 minutes ago:
```
podman pod logs --until 30m myserver-pod-1
```

## SEE ALSO
podman(1), podman-pod-start(1), podman-pod-rm(1), podman-logs(1)
5 changes: 3 additions & 2 deletions docs/source/markdown/podman-pod.1.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,12 @@ podman pod is a set of subcommands that manage pods, or groups of containers.
| exists | [podman-pod-exists(1)](podman-pod-exists.1.md) | Check if a pod exists in local storage. |
| inspect | [podman-pod-inspect(1)](podman-pod-inspect.1.md) | Displays information describing a pod. |
| kill | [podman-pod-kill(1)](podman-pod-kill.1.md) | Kill the main process of each container in one or more pods. |
| logs | [podman-pod-logs(1)](podman-pod-logs.1.md) | Displays logs for pod with one or more containers. |
| pause | [podman-pod-pause(1)](podman-pod-pause.1.md) | Pause one or more pods. |
| prune | [podman-pod-prune(1)](podman-pod-prune.1.md) | Remove all stopped pods and their containers. |
| prune | [podman-pod-prune(1)](podman-pod-prune.1.md) | Remove all stopped pods and their containers. |
| ps | [podman-pod-ps(1)](podman-pod-ps.1.md) | Prints out information about pods. |
| restart | [podman-pod-restart(1)](podman-pod-restart.1.md) | Restart one or more pods. |
| rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more stopped pods and containers. |
| rm | [podman-pod-rm(1)](podman-pod-rm.1.md) | Remove one or more stopped pods and containers. |
| start | [podman-pod-start(1)](podman-pod-start.1.md) | Start one or more pods. |
| stats | [podman-pod-stats(1)](podman-pod-stats.1.md) | Display a live stream of resource usage stats for containers in one or more pods. |
| stop | [podman-pod-stop(1)](podman-pod-stop.1.md) | Stop one or more pods. |
Expand Down
2 changes: 2 additions & 0 deletions docs/source/pod.rst
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,8 @@ Pod

:doc:`kill <markdown/podman-pod-kill.1>` Send the specified signal or SIGKILL to containers in pod

:doc:`logs <markdown/podman-pod-logs.1>` Displays logs for pod with one or more containers

:doc:`pause <markdown/podman-pause.1>` Pause one or more pods

:doc:`prune <markdown/podman-pod-prune.1>` Remove all stopped pods and their containers
Expand Down
1 change: 1 addition & 0 deletions pkg/domain/entities/engine_container.go
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ type ContainerEngine interface {
PodExists(ctx context.Context, nameOrID string) (*BoolReport, error)
PodInspect(ctx context.Context, options PodInspectOptions) (*PodInspectReport, error)
PodKill(ctx context.Context, namesOrIds []string, options PodKillOptions) ([]*PodKillReport, error)
PodLogs(ctx context.Context, pod string, options PodLogsOptions) error
PodPause(ctx context.Context, namesOrIds []string, options PodPauseOptions) ([]*PodPauseReport, error)
PodPrune(ctx context.Context, options PodPruneOptions) ([]*PodPruneReport, error)
PodPs(ctx context.Context, options PodPSOptions) ([]*ListPodsReport, error)
Expand Down
27 changes: 27 additions & 0 deletions pkg/domain/entities/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,14 @@ type PodCreateOptions struct {
Userns specgen.Namespace
}

// PodLogsOptions describes the options to extract pod logs.
type PodLogsOptions struct {
// Other fields are exactly same as ContainerLogOpts
ContainerLogsOptions
// If specified will only fetch the logs of specified container
ContainerName string
}

type ContainerCreateOptions struct {
Annotation []string
Attach []string
Expand Down Expand Up @@ -426,3 +434,22 @@ func ValidatePodStatsOptions(args []string, options *PodStatsOptions) error {
return errors.New("--all, --latest and arguments cannot be used together")
}
}

// Converts PodLogOptions to ContainerLogOptions
func PodLogsOptionsToContainerLogsOptions(options PodLogsOptions) ContainerLogsOptions {
// PodLogsOptions are similar but contains few extra fields like ctrName
// So cast other values as is so we can re-use the code
containerLogsOpts := ContainerLogsOptions{
Details: options.Details,
Latest: options.Latest,
Follow: options.Follow,
Names: options.Names,
Since: options.Since,
Until: options.Until,
Tail: options.Tail,
Timestamps: options.Timestamps,
StdoutWriter: options.StdoutWriter,
StderrWriter: options.StderrWriter,
}
return containerLogsOpts
}
40 changes: 40 additions & 0 deletions pkg/domain/infra/abi/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,46 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt
return reports, nil
}

func (ic *ContainerEngine) PodLogs(ctx context.Context, nameOrID string, options entities.PodLogsOptions) error {
// Implementation accepts slice
podName := []string{nameOrID}
pod, err := getPodsByContext(false, options.Latest, podName, ic.Libpod)
if err != nil {
return err
}
// Get pod containers
podCtrs, err := pod[0].AllContainers()
if err != nil {
return err
}

ctrNames := []string{}
// Check if `kubectl pod logs -c ctrname <podname>` alike command is used
if options.ContainerName != "" {
ctrFound := false
for _, ctr := range podCtrs {
if ctr.ID() == options.ContainerName || ctr.Name() == options.ContainerName {
ctrNames = append(ctrNames, options.ContainerName)
ctrFound = true
}
}
if !ctrFound {
return errors.Wrapf(define.ErrNoSuchCtr, "container %s is not in pod %s", options.ContainerName, nameOrID)
}
} else {
// No container name specified select all containers
for _, ctr := range podCtrs {
ctrNames = append(ctrNames, ctr.Name())
}
}

// PodLogsOptions are similar but contains few extra fields like ctrName
// So cast other values as is so we can re-use the code
containerLogsOpts := entities.PodLogsOptionsToContainerLogsOptions(options)

return ic.ContainerLogs(ctx, ctrNames, containerLogsOpts)
}

func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) {
reports := []*entities.PodPauseReport{}
pods, err := getPodsByContext(options.All, options.Latest, namesOrIds, ic.Libpod)
Expand Down
10 changes: 10 additions & 0 deletions pkg/domain/infra/tunnel/pods.go
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ func (ic *ContainerEngine) PodKill(ctx context.Context, namesOrIds []string, opt
return reports, nil
}

func (ic *ContainerEngine) PodLogs(_ context.Context, nameOrIDs string, options entities.PodLogsOptions) error {
// PodLogsOptions are similar but contains few extra fields like ctrName
// So cast other values as is so we can re-use the code
containerLogsOpts := entities.PodLogsOptionsToContainerLogsOptions(options)

// interface only accepts slice, keep everything consistent
name := []string{options.ContainerName}
return ic.ContainerLogs(nil, name, containerLogsOpts)
}

func (ic *ContainerEngine) PodPause(ctx context.Context, namesOrIds []string, options entities.PodPauseOptions) ([]*entities.PodPauseReport, error) {
foundPods, err := getPodsByContext(ic.ClientCtx, options.All, namesOrIds)
if err != nil {
Expand Down
Loading

0 comments on commit 30d0cc3

Please sign in to comment.