Merge pull request #45275 from CaoShuFeng/log-l

Automatic merge from submit-queue (batch tested with PRs 60990, 60947, 45275, 60565, 61091). If you want to cherry-pick this change to another branch, please follow the instructions <a href="https://github.com/kubernetes/community/blob/master/contributors/devel/cherry-picks.md">here</a>.

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: container should not combined with --all-containers)

**Release note**:

```
add --all-containers option to "kubectl log"
```

Fixes:
https://github.com/kubernetes/kubectl/issues/371
This commit is contained in:
Kubernetes Submit Queue 2018-03-30 11:53:10 -07:00 committed by GitHub
commit cea4284677
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 52 additions and 7 deletions

View File

@ -18,6 +18,7 @@ package cmd
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"math" "math"
"os" "os"
@ -41,8 +42,11 @@ var (
# Return snapshot logs from pod nginx with only one container # Return snapshot logs from pod nginx with only one container
kubectl logs nginx kubectl logs nginx
# Return snapshot logs for the pods defined by label app=nginx # Return snapshot logs from pod nginx with multi containers
kubectl logs -lapp=nginx 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 # Return snapshot of previous terminated ruby container logs from pod web-1
kubectl logs -p -c ruby web-1 kubectl logs -p -c ruby web-1
@ -70,9 +74,10 @@ const (
) )
type LogsOptions struct { type LogsOptions struct {
Namespace string Namespace string
ResourceArg string ResourceArg string
Options runtime.Object AllContainers bool
Options runtime.Object
Mapper meta.RESTMapper Mapper meta.RESTMapper
Typer runtime.ObjectTyper Typer runtime.ObjectTyper
@ -106,6 +111,7 @@ func NewCmdLogs(f cmdutil.Factory, out, errOut io.Writer) *cobra.Command {
}, },
Aliases: []string{"log"}, 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().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().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().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 { func (o *LogsOptions) Complete(f cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error {
containerName := cmdutil.GetFlagString(cmd, "container") containerName := cmdutil.GetFlagString(cmd, "container")
selector := cmdutil.GetFlagString(cmd, "selector") selector := cmdutil.GetFlagString(cmd, "selector")
o.AllContainers = cmdutil.GetFlagBool(cmd, "all-containers")
switch len(args) { switch len(args) {
case 0: case 0:
if len(selector) == 0 { if len(selector) == 0 {
@ -221,6 +228,9 @@ func (o LogsOptions) Validate() error {
if !ok { if !ok {
return errors.New("unexpected logs options object") 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 { if errs := validation.ValidatePodLogOptions(logsOptions); len(errs) > 0 {
return errs.ToAggregate() return errs.ToAggregate()
} }
@ -233,16 +243,40 @@ func (o LogsOptions) RunLogs() error {
switch t := o.Object.(type) { switch t := o.Object.(type) {
case *api.PodList: case *api.PodList:
for _, p := range t.Items { for _, p := range t.Items {
if err := o.getLogs(&p); err != nil { if err := o.getPodLogs(&p); err != nil {
return err return err
} }
} }
return nil return nil
case *api.Pod:
return o.getPodLogs(t)
default: default:
return o.getLogs(o.Object) 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 { func (o LogsOptions) getLogs(obj runtime.Object) error {
req, err := o.LogsForObject(obj, o.Options, o.GetPodTimeout) req, err := o.LogsForObject(obj, o.Options, o.GetPodTimeout)
if err != nil { if err != nil {

View File

@ -109,28 +109,39 @@ func TestValidateLogFlags(t *testing.T) {
tests := []struct { tests := []struct {
name string name string
flags map[string]string flags map[string]string
args []string
expected string expected string
}{ }{
{ {
name: "since & since-time", name: "since & since-time",
flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"}, 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", expected: "at most one of `sinceTime` or `sinceSeconds` may be specified",
}, },
{ {
name: "negative since-time", name: "negative since-time",
flags: map[string]string{"since": "-1s"}, flags: map[string]string{"since": "-1s"},
args: []string{"foo"},
expected: "must be greater than 0", expected: "must be greater than 0",
}, },
{ {
name: "negative limit-bytes", name: "negative limit-bytes",
flags: map[string]string{"limit-bytes": "-100"}, flags: map[string]string{"limit-bytes": "-100"},
args: []string{"foo"},
expected: "must be greater than 0", expected: "must be greater than 0",
}, },
{ {
name: "negative tail", name: "negative tail",
flags: map[string]string{"tail": "-100"}, flags: map[string]string{"tail": "-100"},
args: []string{"foo"},
expected: "must be greater than or equal to 0", 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 { for _, test := range tests {
buf := bytes.NewBuffer([]byte{}) buf := bytes.NewBuffer([]byte{})
@ -146,7 +157,7 @@ func TestValidateLogFlags(t *testing.T) {
o.Complete(f, os.Stdout, cmd, args) o.Complete(f, os.Stdout, cmd, args)
out = o.Validate().Error() out = o.Validate().Error()
} }
cmd.Run(cmd, []string{"foo"}) cmd.Run(cmd, test.args)
if !strings.Contains(out, test.expected) { 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) t.Errorf("%s: expected to find:\n\t%s\nfound:\n\t%s\n", test.name, test.expected, out)