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)
This commit is contained in:
Cao Shufeng 2017-05-03 18:32:15 +08:00
parent 7d4bf1c338
commit a06f0077bd
2 changed files with 52 additions and 7 deletions

View File

@ -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
@ -72,6 +76,7 @@ const (
type LogsOptions struct {
Namespace string
ResourceArg string
AllContainers bool
Options runtime.Object
Mapper meta.RESTMapper
@ -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 {

View File

@ -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)