From 5ba07364ee4a6c600b05833fe20c03653b3ef9f0 Mon Sep 17 00:00:00 2001 From: David Eads Date: Thu, 19 Jul 2018 16:06:50 -0400 Subject: [PATCH] fix logs command to be generic for all resources again --- pkg/kubectl/cmd/clusterinfo_dump.go | 16 ++-- pkg/kubectl/cmd/logs.go | 55 +++--------- pkg/kubectl/cmd/logs_test.go | 4 +- pkg/kubectl/cmd/run.go | 18 ++-- pkg/kubectl/polymorphichelpers/interface.go | 2 +- .../polymorphichelpers/logsforobject.go | 86 +++++++++++++++++-- .../polymorphichelpers/logsforobject_test.go | 2 +- 7 files changed, 113 insertions(+), 70 deletions(-) diff --git a/pkg/kubectl/cmd/clusterinfo_dump.go b/pkg/kubectl/cmd/clusterinfo_dump.go index 9e55ca45d7..c4200b221a 100644 --- a/pkg/kubectl/cmd/clusterinfo_dump.go +++ b/pkg/kubectl/cmd/clusterinfo_dump.go @@ -236,20 +236,22 @@ func (o *ClusterInfoDumpOptions) Run() error { writer.Write([]byte(fmt.Sprintf("==== START logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name))) defer writer.Write([]byte(fmt.Sprintf("==== END logs for container %s of pod %s/%s ====\n", container.Name, pod.Namespace, pod.Name))) - request, err := o.LogsForObject(o.RESTClientGetter, pod, &api.PodLogOptions{Container: container.Name}, timeout) + requests, err := o.LogsForObject(o.RESTClientGetter, pod, &api.PodLogOptions{Container: container.Name}, timeout, false) if err != nil { // Print error and return. writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error()))) return } - data, err := request.DoRaw() - if err != nil { - // Print error and return. - writer.Write([]byte(fmt.Sprintf("Request log error: %s\n", err.Error()))) - return + for _, request := range requests { + data, err := request.DoRaw() + if err != nil { + // Print error and return. + writer.Write([]byte(fmt.Sprintf("Request log error: %s\n", err.Error()))) + return + } + writer.Write(data) } - writer.Write(data) } for ix := range pods.Items { diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index 9b5e55cf5a..163930e209 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -27,6 +27,7 @@ import ( metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/core/validation" @@ -244,55 +245,27 @@ func (o LogsOptions) Validate() error { // RunLogs retrieves a pod log func (o LogsOptions) RunLogs() error { - switch t := o.Object.(type) { - case *api.PodList: - for _, p := range t.Items { - 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(o.RESTClientGetter, obj, o.Options, o.GetPodTimeout) + requests, err := o.LogsForObject(o.RESTClientGetter, o.Object, o.Options, o.GetPodTimeout, o.AllContainers) if err != nil { return err } - readCloser, err := req.Stream() + for _, request := range requests { + if err := consumeRequest(request, o.Out); err != nil { + return err + } + } + + return nil +} + +func consumeRequest(request *rest.Request, out io.Writer) error { + readCloser, err := request.Stream() if err != nil { return err } defer readCloser.Close() - _, err = io.Copy(o.Out, readCloser) + _, err = io.Copy(out, readCloser) return err } diff --git a/pkg/kubectl/cmd/logs_test.go b/pkg/kubectl/cmd/logs_test.go index 7b82432532..03d2dd22a2 100644 --- a/pkg/kubectl/cmd/logs_test.go +++ b/pkg/kubectl/cmd/logs_test.go @@ -244,14 +244,14 @@ type logTestMock struct { client internalclientset.Interface } -func (m logTestMock) logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) { +func (m logTestMock) logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*restclient.Request, error) { switch t := object.(type) { case *api.Pod: opts, ok := options.(*api.PodLogOptions) if !ok { return nil, errors.New("provided options object is not a PodLogOptions") } - return m.client.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil + return []*restclient.Request{m.client.Core().Pods(t.Namespace).GetLogs(t.Name, opts)}, nil default: return nil, fmt.Errorf("cannot get the logs from %T", object) } diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index ec204a928b..35c062ce3a 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -18,7 +18,6 @@ package cmd import ( "fmt" - "io" "github.com/docker/distribution/reference" "github.com/spf13/cobra" @@ -523,21 +522,16 @@ func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *api.Pod, return err } - req, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &api.PodLogOptions{Container: ctrName}, opts.GetPodTimeout) + requests, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &api.PodLogOptions{Container: ctrName}, opts.GetPodTimeout, false) if err != nil { return err } + for _, request := range requests { + if err := consumeRequest(request, opts.Out); err != nil { + return err + } + } - readCloser, err := req.Stream() - if err != nil { - return err - } - defer readCloser.Close() - - _, err = io.Copy(opts.Out, readCloser) - if err != nil { - return err - } return nil } diff --git a/pkg/kubectl/polymorphichelpers/interface.go b/pkg/kubectl/polymorphichelpers/interface.go index 06f40df60b..7904616a4f 100644 --- a/pkg/kubectl/polymorphichelpers/interface.go +++ b/pkg/kubectl/polymorphichelpers/interface.go @@ -30,7 +30,7 @@ import ( ) // LogsForObjectFunc is a function type that can tell you how to get logs for a runtime.object -type LogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration) (*rest.Request, error) +type LogsForObjectFunc func(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*rest.Request, error) // LogsForObjectFn gives a way to easily override the function for unit testing if needed. var LogsForObjectFn LogsForObjectFunc = logsForObject diff --git a/pkg/kubectl/polymorphichelpers/logsforobject.go b/pkg/kubectl/polymorphichelpers/logsforobject.go index f5a517a1b3..0cb6852651 100644 --- a/pkg/kubectl/polymorphichelpers/logsforobject.go +++ b/pkg/kubectl/polymorphichelpers/logsforobject.go @@ -33,7 +33,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) -func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration) (*rest.Request, error) { +func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*rest.Request, error) { clientConfig, err := restClientGetter.ToRESTConfig() if err != nil { return nil, err @@ -42,21 +42,94 @@ func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, if err != nil { return nil, err } - return logsForObjectWithClient(clientset, object, options, timeout) + return logsForObjectWithClient(clientset, object, options, timeout, allContainers) } // this is split for easy test-ability -func logsForObjectWithClient(clientset internalclientset.Interface, object, options runtime.Object, timeout time.Duration) (*rest.Request, error) { +func logsForObjectWithClient(clientset internalclientset.Interface, object, options runtime.Object, timeout time.Duration, allContainers bool) ([]*rest.Request, error) { opts, ok := options.(*coreinternal.PodLogOptions) if !ok { return nil, errors.New("provided options object is not a PodLogOptions") } switch t := object.(type) { + case *coreinternal.PodList: + ret := []*rest.Request{} + for i := range t.Items { + currRet, err := logsForObjectWithClient(clientset, &t.Items[i], options, timeout, allContainers) + if err != nil { + return nil, err + } + ret = append(ret, currRet...) + } + return ret, nil + + case *corev1.PodList: + ret := []*rest.Request{} + for i := range t.Items { + currRet, err := logsForObjectWithClient(clientset, &t.Items[i], options, timeout, allContainers) + if err != nil { + return nil, err + } + ret = append(ret, currRet...) + } + return ret, nil + case *coreinternal.Pod: - return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil + // if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false + if !allContainers { + return []*rest.Request{clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts)}, nil + } + + ret := []*rest.Request{} + for _, c := range t.Spec.InitContainers { + currOpts := opts.DeepCopy() + currOpts.Container = c.Name + currRet, err := logsForObjectWithClient(clientset, t, options, timeout, false) + if err != nil { + return nil, err + } + ret = append(ret, currRet...) + } + for _, c := range t.Spec.Containers { + currOpts := opts.DeepCopy() + currOpts.Container = c.Name + currRet, err := logsForObjectWithClient(clientset, t, options, timeout, false) + if err != nil { + return nil, err + } + ret = append(ret, currRet...) + } + + return ret, nil + case *corev1.Pod: - return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil + // if allContainers is true, then we're going to locate all containers and then iterate through them. At that point, "allContainers" is false + if !allContainers { + return []*rest.Request{clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts)}, nil + } + + ret := []*rest.Request{} + for _, c := range t.Spec.InitContainers { + currOpts := opts.DeepCopy() + currOpts.Container = c.Name + currRet, err := logsForObjectWithClient(clientset, t, options, timeout, false) + if err != nil { + return nil, err + } + ret = append(ret, currRet...) + } + for _, c := range t.Spec.Containers { + currOpts := opts.DeepCopy() + currOpts.Container = c.Name + currRet, err := logsForObjectWithClient(clientset, t, options, timeout, false) + if err != nil { + return nil, err + } + ret = append(ret, currRet...) + } + + return ret, nil } namespace, selector, err := SelectorsForObject(object) @@ -71,5 +144,6 @@ func logsForObjectWithClient(clientset internalclientset.Interface, object, opti if numPods > 1 { fmt.Fprintf(os.Stderr, "Found %v pods, using pod/%v\n", numPods, pod.Name) } - return clientset.Core().Pods(pod.Namespace).GetLogs(pod.Name, opts), nil + + return logsForObjectWithClient(clientset, pod, options, timeout, allContainers) } diff --git a/pkg/kubectl/polymorphichelpers/logsforobject_test.go b/pkg/kubectl/polymorphichelpers/logsforobject_test.go index 6ed87d910b..3f87f710b5 100644 --- a/pkg/kubectl/polymorphichelpers/logsforobject_test.go +++ b/pkg/kubectl/polymorphichelpers/logsforobject_test.go @@ -130,7 +130,7 @@ func TestLogsForObject(t *testing.T) { for _, test := range tests { fakeClientset := fake.NewSimpleClientset(test.pods...) - _, err := logsForObjectWithClient(fakeClientset, test.obj, test.opts, 20*time.Second) + _, err := logsForObjectWithClient(fakeClientset, test.obj, test.opts, 20*time.Second, false) if err != nil { t.Errorf("%s: unexpected error: %v", test.name, err) continue