diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index fc030727336..80f20ca6201 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -40,6 +40,9 @@ var ( # Return snapshot logs from pod nginx with only one container kubectl logs nginx + # Return snapshot logs for the pods defined by label app=nginx + kubectl logs -lapp=nginx + # Return snapshot of previous terminated ruby container logs from pod web-1 kubectl logs -p -c ruby web-1 @@ -51,6 +54,8 @@ var ( # Show all logs from pod nginx written in the last hour kubectl logs --since=1h nginx`) + + selectorTail int64 = 10 ) const ( @@ -91,8 +96,7 @@ func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command { if err := o.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error())) } - _, err := o.RunLogs() - cmdutil.CheckErr(err) + cmdutil.CheckErr(o.RunLogs()) }, Aliases: []string{"log"}, } @@ -100,7 +104,7 @@ func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("timestamps", false, "Include timestamps on each line in the log output") cmd.Flags().Int64("limit-bytes", 0, "Maximum bytes of logs to return. Defaults to no limit.") cmd.Flags().BoolP("previous", "p", false, "If true, print the logs for the previous instance of the container in a pod if it exists.") - cmd.Flags().Int64("tail", -1, "Lines of recent log file to display. Defaults to -1, showing all log lines.") + cmd.Flags().Int64("tail", -1, "Lines of recent log file to display. Defaults to -1 with no selector, showing all log lines otherwise 10, if a selector is provided.") cmd.Flags().String("since-time", "", "Only return logs after a specific date (RFC3339). Defaults to all logs. Only one of since-time / since may be used.") cmd.Flags().Duration("since", 0, "Only return logs newer than a relative duration like 5s, 2m, or 3h. Defaults to all logs. Only one of since-time / since may be used.") cmd.Flags().StringP("container", "c", "", "Print the logs of this container") @@ -108,16 +112,23 @@ func NewCmdLogs(f cmdutil.Factory, out io.Writer) *cobra.Command { cmd.Flags().Bool("interactive", false, "If true, prompt the user for input when required.") cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.") cmdutil.AddInclude3rdPartyFlags(cmd) + cmd.Flags().StringP("selector", "l", "", "Selector (label query) to filter on.") return cmd } func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { containerName := cmdutil.GetFlagString(cmd, "container") + selector := cmdutil.GetFlagString(cmd, "selector") switch len(args) { case 0: - return cmdutil.UsageError(cmd, logsUsageStr) + if len(selector) == 0 { + return cmdutil.UsageError(cmd, logsUsageStr) + } case 1: o.ResourceArg = args[0] + if len(selector) != 0 { + return cmdutil.UsageError(cmd, "only a selector (-l) or a POD name is allowed") + } case 2: if cmd.Flag("container").Changed { return cmdutil.UsageError(cmd, "only one of -c or an inline [CONTAINER] arg is allowed") @@ -162,18 +173,35 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Comm o.ClientMapper = resource.ClientMapperFunc(f.ClientForMapping) o.Out = out + if len(selector) != 0 { + if logOptions.Follow { + return cmdutil.UsageError(cmd, "only one of follow (-f) or selector (-l) is allowed") + } + if len(logOptions.Container) != 0 { + return cmdutil.UsageError(cmd, "a container cannot be specified when using a selector (-l)") + } + if logOptions.TailLines == nil { + logOptions.TailLines = &selectorTail + } + } + mapper, typer := f.Object() decoder := f.Decoder(true) if o.Object == nil { - infos, err := resource.NewBuilder(mapper, typer, o.ClientMapper, decoder). + builder := resource.NewBuilder(mapper, typer, o.ClientMapper, decoder). NamespaceParam(o.Namespace).DefaultNamespace(). - ResourceNames("pods", o.ResourceArg). - SingleResourceType(). - Do().Infos() + SingleResourceType() + if o.ResourceArg != "" { + builder.ResourceNames("pods", o.ResourceArg) + } + if selector != "" { + builder.ResourceTypes("pods").SelectorParam(selector) + } + infos, err := builder.Do().Infos() if err != nil { return err } - if len(infos) != 1 { + if selector == "" && len(infos) != 1 { return errors.New("expected a resource") } o.Object = infos[0].Object @@ -183,9 +211,6 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Comm } func (o LogsOptions) Validate() error { - if len(o.ResourceArg) == 0 { - return errors.New("a pod must be specified") - } logsOptions, ok := o.Options.(*api.PodLogOptions) if !ok { return errors.New("unexpected logs options object") @@ -198,17 +223,32 @@ func (o LogsOptions) Validate() error { } // RunLogs retrieves a pod log -func (o LogsOptions) RunLogs() (int64, error) { - req, err := o.LogsForObject(o.Object, o.Options) +func (o LogsOptions) RunLogs() error { + switch t := o.Object.(type) { + case *api.PodList: + for _, p := range t.Items { + if err := o.getLogs(&p); err != nil { + return err + } + } + return nil + default: + return o.getLogs(o.Object) + } +} + +func (o LogsOptions) getLogs(obj runtime.Object) error { + req, err := o.LogsForObject(obj, o.Options) if err != nil { - return 0, err + return err } readCloser, err := req.Stream() if err != nil { - return 0, err + return err } defer readCloser.Close() - return io.Copy(o.Out, readCloser) + _, err = io.Copy(o.Out, readCloser) + return err }