From a06f0077bdaf0a5cac1a23ad1179b1c439f4003e Mon Sep 17 00:00:00 2001 From: Cao Shufeng Date: Wed, 3 May 2017 18:32:15 +0800 Subject: [PATCH] Support multi-container pod for "kubectl logs" kubectl logs -l will print logs for pods with the same label, however it doesn't support pods with multi containers. This change adds support to it with --all-containers. Ussage: $ kubectl logs my-pod --all-containers $ kubectl logs -laa=bb --all-containers $ kubectl logs my-pod my-container --all-containers (err: --all-containers=true should not be specifiled with container name my-container) --- pkg/kubectl/cmd/logs.go | 46 +++++++++++++++++++++++++++++++----- pkg/kubectl/cmd/logs_test.go | 13 +++++++++- 2 files changed, 52 insertions(+), 7 deletions(-) diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index d6d965c38bf..d4cc79ca446 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -18,6 +18,7 @@ package cmd import ( "errors" + "fmt" "io" "math" "os" @@ -41,8 +42,11 @@ 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 logs from pod nginx with multi containers + kubectl logs nginx --all-containers=true + + # Return snapshot logs from all containers in pods defined by label app=nginx + kubectl logs -lapp=nginx --all-containers=true # Return snapshot of previous terminated ruby container logs from pod web-1 kubectl logs -p -c ruby web-1 @@ -70,9 +74,10 @@ const ( ) type LogsOptions struct { - Namespace string - ResourceArg string - Options runtime.Object + Namespace string + ResourceArg string + AllContainers bool + Options runtime.Object Mapper meta.RESTMapper Typer runtime.ObjectTyper @@ -106,6 +111,7 @@ func NewCmdLogs(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { }, Aliases: []string{"log"}, } + cmd.Flags().Bool("all-containers", false, "Get all containers's logs in the pod(s).") cmd.Flags().BoolP("follow", "f", false, "Specify if the logs should be streamed.") 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.") @@ -125,6 +131,7 @@ func NewCmdLogs(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command { 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") + o.AllContainers = cmdutil.GetFlagBool(cmd, "all-containers") switch len(args) { case 0: if len(selector) == 0 { @@ -221,6 +228,9 @@ func (o LogsOptions) Validate() error { if !ok { return errors.New("unexpected logs options object") } + if o.AllContainers && len(logsOptions.Container) > 0 { + return fmt.Errorf("--all-containers=true should not be specifiled with container name %s", logsOptions.Container) + } if errs := validation.ValidatePodLogOptions(logsOptions); len(errs) > 0 { return errs.ToAggregate() } @@ -233,16 +243,40 @@ 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 { + if err := o.getPodLogs(&p); err != nil { return err } } return nil + case *api.Pod: + return o.getPodLogs(t) default: return o.getLogs(o.Object) } } +// getPodLogs checks whether o.AllContainers is set to true. +// If so, it retrives all containers' log in the pod. +func (o LogsOptions) getPodLogs(pod *api.Pod) error { + if !o.AllContainers { + return o.getLogs(pod) + } + + for _, c := range pod.Spec.InitContainers { + o.Options.(*api.PodLogOptions).Container = c.Name + if err := o.getLogs(pod); err != nil { + return err + } + } + for _, c := range pod.Spec.Containers { + o.Options.(*api.PodLogOptions).Container = c.Name + if err := o.getLogs(pod); err != nil { + return err + } + } + return nil +} + func (o LogsOptions) getLogs(obj runtime.Object) error { req, err := o.LogsForObject(obj, o.Options, o.GetPodTimeout) if err != nil { diff --git a/pkg/kubectl/cmd/logs_test.go b/pkg/kubectl/cmd/logs_test.go index 349609b4c6a..1a7e5e359e1 100644 --- a/pkg/kubectl/cmd/logs_test.go +++ b/pkg/kubectl/cmd/logs_test.go @@ -109,28 +109,39 @@ func TestValidateLogFlags(t *testing.T) { tests := []struct { name string flags map[string]string + args []string expected string }{ { name: "since & since-time", flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"}, + args: []string{"foo"}, expected: "at most one of `sinceTime` or `sinceSeconds` may be specified", }, { name: "negative since-time", flags: map[string]string{"since": "-1s"}, + args: []string{"foo"}, expected: "must be greater than 0", }, { name: "negative limit-bytes", flags: map[string]string{"limit-bytes": "-100"}, + args: []string{"foo"}, expected: "must be greater than 0", }, { name: "negative tail", flags: map[string]string{"tail": "-100"}, + args: []string{"foo"}, expected: "must be greater than or equal to 0", }, + { + name: "container name combined with --all-containers", + flags: map[string]string{"all-containers": "true"}, + args: []string{"my-pod", "my-container"}, + expected: "--all-containers=true should not be specifiled with container", + }, } for _, test := range tests { buf := bytes.NewBuffer([]byte{}) @@ -146,7 +157,7 @@ func TestValidateLogFlags(t *testing.T) { o.Complete(f, os.Stdout, cmd, args) out = o.Validate().Error() } - cmd.Run(cmd, []string{"foo"}) + cmd.Run(cmd, test.args) if !strings.Contains(out, test.expected) { t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)