summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorAditya Rajan <arajan@redhat.com>2021-09-03 11:54:56 +0530
committerAditya Rajan <arajan@redhat.com>2021-09-05 16:24:49 +0530
commit11fc0e55401a21a88c90a2fa646f2624541711fe (patch)
tree78cef30dc1fbfc3b1af17901247a35368a17141d
parent858d3e47c26788e64083842cc6617b666f4279a1 (diff)
downloadpodman-11fc0e55401a21a88c90a2fa646f2624541711fe.tar.gz
podman-11fc0e55401a21a88c90a2fa646f2624541711fe.tar.bz2
podman-11fc0e55401a21a88c90a2fa646f2624541711fe.zip
kube: Add support for podman pod logs
Following PR adds support for `kubectl` like `pod logs` to podman. Usage `podman pod logs <podIDorName` gives a stream of logs for all the containers within the pod with **containername** as a field. Just like **`kubectl`** also supports `podman pod logs -c ctrIDorName podIDorName` to limit the log stream to any of the specificied container which belongs to pod. Signed-off-by: Aditya Rajan <arajan@redhat.com>
-rw-r--r--cmd/podman/pods/logs.go140
-rw-r--r--docs/source/markdown/podman-pod-logs.1.md88
-rw-r--r--docs/source/markdown/podman-pod.1.md5
-rw-r--r--docs/source/pod.rst2
-rw-r--r--pkg/domain/entities/engine_container.go1
-rw-r--r--pkg/domain/entities/pods.go27
-rw-r--r--pkg/domain/infra/abi/pods.go40
-rw-r--r--pkg/domain/infra/tunnel/pods.go10
-rw-r--r--test/e2e/play_kube_test.go34
9 files changed, 345 insertions, 2 deletions
diff --git a/cmd/podman/pods/logs.go b/cmd/podman/pods/logs.go
new file mode 100644
index 000000000..fe5205669
--- /dev/null
+++ b/cmd/podman/pods/logs.go
@@ -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)
+}
diff --git a/docs/source/markdown/podman-pod-logs.1.md b/docs/source/markdown/podman-pod-logs.1.md
new file mode 100644
index 000000000..8378f2eea
--- /dev/null
+++ b/docs/source/markdown/podman-pod-logs.1.md
@@ -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)
diff --git a/docs/source/markdown/podman-pod.1.md b/docs/source/markdown/podman-pod.1.md
index e5a8207e9..9de2442bd 100644
--- a/docs/source/markdown/podman-pod.1.md
+++ b/docs/source/markdown/podman-pod.1.md
@@ -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. |
diff --git a/docs/source/pod.rst b/docs/source/pod.rst
index 2df377762..d9ad07d83 100644
--- a/docs/source/pod.rst
+++ b/docs/source/pod.rst
@@ -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
diff --git a/pkg/domain/entities/engine_container.go b/pkg/domain/entities/engine_container.go
index bd011d309..93fa7a20f 100644
--- a/pkg/domain/entities/engine_container.go
+++ b/pkg/domain/entities/engine_container.go
@@ -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)
diff --git a/pkg/domain/entities/pods.go b/pkg/domain/entities/pods.go
index 10bd7e5ce..d9dd0c532 100644
--- a/pkg/domain/entities/pods.go
+++ b/pkg/domain/entities/pods.go
@@ -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
@@ -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
+}
diff --git a/pkg/domain/infra/abi/pods.go b/pkg/domain/infra/abi/pods.go
index 98233f60d..6b432c214 100644
--- a/pkg/domain/infra/abi/pods.go
+++ b/pkg/domain/infra/abi/pods.go
@@ -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)
diff --git a/pkg/domain/infra/tunnel/pods.go b/pkg/domain/infra/tunnel/pods.go
index 480adb88a..8139216b3 100644
--- a/pkg/domain/infra/tunnel/pods.go
+++ b/pkg/domain/infra/tunnel/pods.go
@@ -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 {
diff --git a/test/e2e/play_kube_test.go b/test/e2e/play_kube_test.go
index ab496f0eb..5d875effd 100644
--- a/test/e2e/play_kube_test.go
+++ b/test/e2e/play_kube_test.go
@@ -1289,6 +1289,40 @@ var _ = Describe("Podman play kube", func() {
Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
})
+ It("podman pod logs test", func() {
+ SkipIfRemote("podman-remote pod logs -c is mandatory for remote machine")
+ p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
+
+ err := generateKubeYaml("pod", p, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ logs := podmanTest.Podman([]string{"pod", "logs", p.Name})
+ logs.WaitWithDefaultTimeout()
+ Expect(logs).Should(Exit(0))
+ Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
+ })
+
+ It("podman-remote pod logs test", func() {
+ // -c or --container is required in podman-remote due to api limitation.
+ p := getPod(withCtr(getCtr(withCmd([]string{"echo", "hello"}), withArg([]string{"world"}))))
+
+ err := generateKubeYaml("pod", p, kubeYaml)
+ Expect(err).To(BeNil())
+
+ kube := podmanTest.Podman([]string{"play", "kube", kubeYaml})
+ kube.WaitWithDefaultTimeout()
+ Expect(kube).Should(Exit(0))
+
+ logs := podmanTest.Podman([]string{"pod", "logs", "-c", getCtrNameInPod(p), p.Name})
+ logs.WaitWithDefaultTimeout()
+ Expect(logs).Should(Exit(0))
+ Expect(logs.OutputToString()).To(ContainSubstring("hello world"))
+ })
+
It("podman play kube test restartPolicy", func() {
// podName, set, expect
testSli := [][]string{