summaryrefslogtreecommitdiff
path: root/cmd/podman/pods/logs.go
blob: 0102d4b71fb081ced28af9b1c687b60c18143b87 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
package pods

import (
	"errors"
	"fmt"
	"os"

	"github.com/containers/common/pkg/completion"
	"github.com/containers/podman/v4/cmd/podman/common"
	"github.com/containers/podman/v4/cmd/podman/registry"
	"github.com/containers/podman/v4/cmd/podman/validate"
	"github.com/containers/podman/v4/libpod/define"
	"github.com/containers/podman/v4/pkg/domain/entities"
	"github.com/containers/podman/v4/pkg/util"
	"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.`
	podLogsCommand     = &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 together
		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`,
	}
)

func init() {
	// pod logs
	registry.Commands = append(registry.Commands, registry.CliCommand{
		Command: podLogsCommand,
		Parent:  podCmd,
	})
	logsFlags(podLogsCommand)
	validate.AddLatestFlag(podLogsCommand, &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.Names, "names", "n", false, "Output container names instead of container IDs in the log")
	flags.BoolVarP(&logsPodOptions.Timestamps, "timestamps", "t", false, "Output the timestamps in the log")
	flags.BoolVarP(&logsPodOptions.Colors, "color", "", false, "Output the containers within a pod with different colors 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 fmt.Errorf("error parsing --since %q: %w", logsPodOptions.SinceRaw, err)
		}
		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 fmt.Errorf("error parsing --until %q: %w", logsPodOptions.UntilRaw, err)
		}
		logsPodOptions.Until = until
	}

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

	logsPodOptions.StdoutWriter = os.Stdout
	logsPodOptions.StderrWriter = os.Stderr
	return registry.ContainerEngine().PodLogs(registry.GetContext(), args[0], logsPodOptions.PodLogsOptions)
}