From 4fdb6d1331998011d30f8a9765653f6594992c9e Mon Sep 17 00:00:00 2001 From: kargakis Date: Sun, 18 Oct 2015 16:20:28 +0200 Subject: [PATCH] logs: Use resource builder --- docs/man/man1/kubectl-logs.1 | 6 +- docs/user-guide/kubectl/kubectl_logs.md | 4 +- pkg/client/unversioned/pods.go | 6 + pkg/client/unversioned/pods_test.go | 26 +++ .../unversioned/testclient/fake_pods.go | 13 ++ pkg/kubectl/cmd/cmd_test.go | 20 ++ pkg/kubectl/cmd/log.go | 216 ++++++++---------- pkg/kubectl/cmd/log_test.go | 52 ++++- pkg/kubectl/cmd/run.go | 28 ++- pkg/kubectl/cmd/util/factory.go | 23 ++ 10 files changed, 257 insertions(+), 137 deletions(-) diff --git a/docs/man/man1/kubectl-logs.1 b/docs/man/man1/kubectl-logs.1 index 69ff0b39225..d74c7917e32 100644 --- a/docs/man/man1/kubectl-logs.1 +++ b/docs/man/man1/kubectl-logs.1 @@ -19,15 +19,15 @@ Print the logs for a container in a pod. If the pod has only one container, the .SH OPTIONS .PP \fB\-c\fP, \fB\-\-container\fP="" - Container name + Print the logs of this container .PP \fB\-f\fP, \fB\-\-follow\fP=false Specify if the logs should be streamed. .PP -\fB\-\-interactive\fP=true - If true, prompt the user for input when required. Default true. +\fB\-\-interactive\fP=false + If true, prompt the user for input when required. .PP \fB\-\-limit\-bytes\fP=0 diff --git a/docs/user-guide/kubectl/kubectl_logs.md b/docs/user-guide/kubectl/kubectl_logs.md index 60a27e2676d..8f2b5c52b28 100644 --- a/docs/user-guide/kubectl/kubectl_logs.md +++ b/docs/user-guide/kubectl/kubectl_logs.md @@ -66,7 +66,7 @@ $ kubectl logs --since=1h nginx ### Options ``` - -c, --container="": Container name + -c, --container="": Print the logs of this container -f, --follow[=false]: Specify if the logs should be streamed. --limit-bytes=0: Maximum bytes of logs to return. Defaults to no limit. -p, --previous[=false]: If true, print the logs for the previous instance of the container in a pod if it exists. @@ -108,7 +108,7 @@ $ kubectl logs --since=1h nginx * [kubectl](kubectl.md) - kubectl controls the Kubernetes cluster manager -###### Auto generated by spf13/cobra on 14-Oct-2015 +###### Auto generated by spf13/cobra on 28-Oct-2015 [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_logs.md?pixel)]() diff --git a/pkg/client/unversioned/pods.go b/pkg/client/unversioned/pods.go index cd32566c3c4..e22970d1a7a 100644 --- a/pkg/client/unversioned/pods.go +++ b/pkg/client/unversioned/pods.go @@ -38,6 +38,7 @@ type PodInterface interface { Watch(label labels.Selector, field fields.Selector, opts api.ListOptions) (watch.Interface, error) Bind(binding *api.Binding) error UpdateStatus(pod *api.Pod) (*api.Pod, error) + GetLogs(name string, opts *api.PodLogOptions) *Request } // pods implements PodsNamespacer interface @@ -118,3 +119,8 @@ func (c *pods) UpdateStatus(pod *api.Pod) (result *api.Pod, err error) { err = c.r.Put().Namespace(c.ns).Resource("pods").Name(pod.Name).SubResource("status").Body(pod).Do().Into(result) return } + +// Get constructs a request for getting the logs for a pod +func (c *pods) GetLogs(name string, opts *api.PodLogOptions) *Request { + return c.r.Get().Namespace(c.ns).Name(name).Resource("pods").SubResource("log").VersionedParams(opts, api.Scheme) +} diff --git a/pkg/client/unversioned/pods_test.go b/pkg/client/unversioned/pods_test.go index 2e2a41725c9..ea22b35faff 100644 --- a/pkg/client/unversioned/pods_test.go +++ b/pkg/client/unversioned/pods_test.go @@ -187,3 +187,29 @@ func TestUpdatePod(t *testing.T) { receivedPod, err := c.Setup(t).Pods(ns).Update(requestPod) c.Validate(t, receivedPod, err) } + +func TestPodGetLogs(t *testing.T) { + ns := api.NamespaceDefault + opts := &api.PodLogOptions{ + Follow: true, + Timestamps: true, + } + c := &testClient{} + + request := c.Setup(t).Pods(ns).GetLogs("podName", opts) + if request.verb != "GET" { + t.Fatalf("unexpected verb %q, expected %q", request.verb, "GET") + } + if request.resource != "pods" { + t.Fatalf("unexpected resource %q, expected %q", request.subresource, "pods") + } + if request.subresource != "log" { + t.Fatalf("unexpected subresource %q, expected %q", request.subresource, "log") + } + expected := map[string]string{"container": "", "follow": "true", "previous": "false", "timestamps": "true"} + for gotKey, gotValue := range request.params { + if gotValue[0] != expected[gotKey] { + t.Fatalf("unexpected key-value %s=%s, expected %s=%s", gotKey, gotValue[0], gotKey, expected[gotKey]) + } + } +} diff --git a/pkg/client/unversioned/testclient/fake_pods.go b/pkg/client/unversioned/testclient/fake_pods.go index 424595ad174..c725652c564 100644 --- a/pkg/client/unversioned/testclient/fake_pods.go +++ b/pkg/client/unversioned/testclient/fake_pods.go @@ -18,6 +18,7 @@ package testclient import ( "k8s.io/kubernetes/pkg/api" + client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/watch" @@ -99,3 +100,15 @@ func (c *FakePods) UpdateStatus(pod *api.Pod) (*api.Pod, error) { return obj.(*api.Pod), err } + +func (c *FakePods) GetLogs(name string, opts *api.PodLogOptions) *client.Request { + action := GenericActionImpl{} + action.Verb = "get" + action.Namespace = c.Namespace + action.Resource = "pod" + action.Subresource = "logs" + action.Value = opts + + _, _ = c.Fake.Invokes(action, &api.Pod{}) + return &client.Request{} +} diff --git a/pkg/kubectl/cmd/cmd_test.go b/pkg/kubectl/cmd/cmd_test.go index 4a3075c734f..dc69efd551e 100644 --- a/pkg/kubectl/cmd/cmd_test.go +++ b/pkg/kubectl/cmd/cmd_test.go @@ -232,6 +232,26 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) { generator, ok := generators[name] return generator, ok }, + LogsForObject: func(object, options runtime.Object) (*client.Request, error) { + fakeClient := t.Client.(*fake.RESTClient) + c := client.NewOrDie(t.ClientConfig) + c.Client = fakeClient.Client + + 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 c.Pods(t.Namespace).GetLogs(t.Name, opts), nil + default: + _, kind, err := api.Scheme.ObjectVersionAndKind(object) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("cannot get the logs from %s", kind) + } + }, } rf := cmdutil.NewFactory(nil) f.PodSelectorForObject = rf.PodSelectorForObject diff --git a/pkg/kubectl/cmd/log.go b/pkg/kubectl/cmd/log.go index e6f150ec750..722b1f10166 100644 --- a/pkg/kubectl/cmd/log.go +++ b/pkg/kubectl/cmd/log.go @@ -18,19 +18,21 @@ package cmd import ( "errors" - "fmt" "io" "math" "os" - "strconv" - "strings" "time" "github.com/spf13/cobra" "k8s.io/kubernetes/pkg/api" + "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/unversioned" + "k8s.io/kubernetes/pkg/api/validation" client "k8s.io/kubernetes/pkg/client/unversioned" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" + "k8s.io/kubernetes/pkg/kubectl/resource" + "k8s.io/kubernetes/pkg/runtime" + kerrors "k8s.io/kubernetes/pkg/util/errors" ) const ( @@ -51,193 +53,153 @@ $ kubectl logs --since=1h nginx` ) type LogsOptions struct { - Client *client.Client + Namespace string + ResourceArg string + Options runtime.Object - PodNamespace string - PodName string - ContainerName string - Follow bool - Timestamps bool - Previous bool - LimitBytes int - Tail int - SinceTime *unversioned.Time - SinceSeconds time.Duration + Mapper meta.RESTMapper + Typer runtime.ObjectTyper + ClientMapper resource.ClientMapper + + LogsForObject func(object, options runtime.Object) (*client.Request, error) Out io.Writer } // NewCmdLog creates a new pod log command func NewCmdLog(f *cmdutil.Factory, out io.Writer) *cobra.Command { - o := &LogsOptions{ - Out: out, - Tail: -1, - } - + o := &LogsOptions{} cmd := &cobra.Command{ Use: "logs [-f] [-p] POD [-c CONTAINER]", Short: "Print the logs for a container in a pod.", Long: "Print the logs for a container in a pod. If the pod has only one container, the container name is optional.", Example: log_example, - Run: func(cmd *cobra.Command, args []string) { + PreRun: func(cmd *cobra.Command, args []string) { if len(os.Args) > 1 && os.Args[1] == "log" { printDeprecationWarning("logs", "log") } - + }, + Run: func(cmd *cobra.Command, args []string) { cmdutil.CheckErr(o.Complete(f, out, cmd, args)) if err := o.Validate(); err != nil { cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error())) } - cmdutil.CheckErr(o.RunLog()) + _, err := o.RunLog() + cmdutil.CheckErr(err) }, Aliases: []string{"log"}, } - cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.") - cmd.Flags().BoolVar(&o.Timestamps, "timestamps", o.Timestamps, "Include timestamps on each line in the log output") - cmd.Flags().Bool("interactive", true, "If true, prompt the user for input when required. Default true.") - cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.") - cmd.Flags().IntVar(&o.LimitBytes, "limit-bytes", o.LimitBytes, "Maximum bytes of logs to return. Defaults to no limit.") - cmd.Flags().BoolVarP(&o.Previous, "previous", "p", o.Previous, "If true, print the logs for the previous instance of the container in a pod if it exists.") - cmd.Flags().IntVar(&o.Tail, "tail", o.Tail, "Lines of recent log file to display. Defaults to -1, showing all log lines.") + 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.") + 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().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().DurationVar(&o.SinceSeconds, "since", o.SinceSeconds, "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().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name") + 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") + + 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.") return cmd } func (o *LogsOptions) Complete(f *cmdutil.Factory, out io.Writer, cmd *cobra.Command, args []string) error { + containerName := cmdutil.GetFlagString(cmd, "container") switch len(args) { case 0: - return cmdutil.UsageError(cmd, "POD is required for log") - + return cmdutil.UsageError(cmd, "POD is required for logs") case 1: - o.PodName = args[0] + o.ResourceArg = args[0] case 2: if cmd.Flag("container").Changed { return cmdutil.UsageError(cmd, "only one of -c, [CONTAINER] arg is allowed") } - o.PodName = args[0] - o.ContainerName = args[1] - + o.ResourceArg = args[0] + containerName = args[1] default: - return cmdutil.UsageError(cmd, "log POD [-c CONTAINER]") + return cmdutil.UsageError(cmd, "logs POD [-c CONTAINER]") } - var err error - o.PodNamespace, _, err = f.DefaultNamespace() - if err != nil { - return err - } - o.Client, err = f.Client() + o.Namespace, _, err = f.DefaultNamespace() if err != nil { return err } - sinceTime := cmdutil.GetFlagString(cmd, "since-time") - if len(sinceTime) > 0 { + logOptions := &api.PodLogOptions{ + Container: containerName, + Follow: cmdutil.GetFlagBool(cmd, "follow"), + Previous: cmdutil.GetFlagBool(cmd, "previous"), + Timestamps: cmdutil.GetFlagBool(cmd, "timestamps"), + } + if sinceTime := cmdutil.GetFlagString(cmd, "since-time"); len(sinceTime) > 0 { t, err := api.ParseRFC3339(sinceTime, unversioned.Now) if err != nil { return err } - o.SinceTime = &t + logOptions.SinceTime = &t } + if limit := cmdutil.GetFlagInt64(cmd, "limit-bytes"); limit != 0 { + logOptions.LimitBytes = &limit + } + if tail := cmdutil.GetFlagInt64(cmd, "tail"); tail != -1 { + logOptions.TailLines = &tail + } + if sinceSeconds := cmdutil.GetFlagDuration(cmd, "since"); sinceSeconds != 0 { + // round up to the nearest second + sec := int64(math.Ceil(float64(sinceSeconds) / float64(time.Second))) + logOptions.SinceSeconds = &sec + } + o.Options = logOptions + + o.Mapper, o.Typer = f.Object() + o.ClientMapper = f.ClientMapperForCommand() + o.LogsForObject = f.LogsForObject + + o.Out = out return nil } -func (o *LogsOptions) Validate() error { - if len(o.PodName) == 0 { - return errors.New("POD must be specified") +func (o LogsOptions) Validate() error { + if len(o.ResourceArg) == 0 { + return errors.New("a pod must be specified") } - if o.LimitBytes < 0 { - return errors.New("--limit-bytes must be greater than or equal to zero") + logOptions, ok := o.Options.(*api.PodLogOptions) + if !ok { + return errors.New("unexpected log options object") } - if o.Tail < -1 { - return errors.New("--tail must be greater than or equal to -1") - } - if o.SinceTime != nil && o.SinceSeconds > 0 { - return errors.New("only one of --since, --since-time may be specified") + if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 { + return kerrors.NewAggregate(errs) } return nil } // RunLog retrieves a pod log -func (o *LogsOptions) RunLog() error { - pod, err := o.Client.Pods(o.PodNamespace).Get(o.PodName) +func (o LogsOptions) RunLog() (int64, error) { + infos, err := resource.NewBuilder(o.Mapper, o.Typer, o.ClientMapper). + NamespaceParam(o.Namespace).DefaultNamespace(). + ResourceNames("pods", o.ResourceArg). + SingleResourceType(). + Do().Infos() if err != nil { - return err + return 0, err + } + if len(infos) != 1 { + return 0, errors.New("expected a resource") + } + info := infos[0] + + req, err := o.LogsForObject(info.Object, o.Options) + if err != nil { + return 0, err } - // [-c CONTAINER] - container := o.ContainerName - if len(container) == 0 { - // [CONTAINER] (container as arg not flag) is supported as legacy behavior. See PR #10519 for more details. - if len(pod.Spec.Containers) != 1 { - podContainersNames := []string{} - for _, container := range pod.Spec.Containers { - podContainersNames = append(podContainersNames, container.Name) - } - - return fmt.Errorf("Pod %s has the following containers: %s; please specify the container to print logs for with -c", pod.ObjectMeta.Name, strings.Join(podContainersNames, ", ")) - } - container = pod.Spec.Containers[0].Name - } - - logOptions := &api.PodLogOptions{ - Container: container, - Follow: o.Follow, - Previous: o.Previous, - Timestamps: o.Timestamps, - } - if o.SinceSeconds > 0 { - // round up to the nearest second - sec := int64(math.Ceil(float64(o.SinceSeconds) / float64(time.Second))) - logOptions.SinceSeconds = &sec - } - logOptions.SinceTime = o.SinceTime - if o.LimitBytes != 0 { - i := int64(o.LimitBytes) - logOptions.LimitBytes = &i - } - if o.Tail >= 0 { - i := int64(o.Tail) - logOptions.TailLines = &i - } - - return handleLog(o.Client, o.PodNamespace, o.PodName, logOptions, o.Out) -} - -func handleLog(client *client.Client, namespace, podID string, logOptions *api.PodLogOptions, out io.Writer) error { - // TODO: transform this into a PodLogOptions call - req := client.RESTClient.Get(). - Namespace(namespace). - Name(podID). - Resource("pods"). - SubResource("log"). - Param("follow", strconv.FormatBool(logOptions.Follow)). - Param("container", logOptions.Container). - Param("previous", strconv.FormatBool(logOptions.Previous)). - Param("timestamps", strconv.FormatBool(logOptions.Timestamps)) - - if logOptions.SinceSeconds != nil { - req.Param("sinceSeconds", strconv.FormatInt(*logOptions.SinceSeconds, 10)) - } - if logOptions.SinceTime != nil { - req.Param("sinceTime", logOptions.SinceTime.Format(time.RFC3339)) - } - if logOptions.LimitBytes != nil { - req.Param("limitBytes", strconv.FormatInt(*logOptions.LimitBytes, 10)) - } - if logOptions.TailLines != nil { - req.Param("tailLines", strconv.FormatInt(*logOptions.TailLines, 10)) - } readCloser, err := req.Stream() if err != nil { - return err + return 0, err } - defer readCloser.Close() - _, err = io.Copy(out, readCloser) - return err + + return io.Copy(o.Out, readCloser) } diff --git a/pkg/kubectl/cmd/log_test.go b/pkg/kubectl/cmd/log_test.go index 013b4ee5faf..e04f9def5e9 100644 --- a/pkg/kubectl/cmd/log_test.go +++ b/pkg/kubectl/cmd/log_test.go @@ -20,8 +20,12 @@ import ( "bytes" "io/ioutil" "net/http" + "os" + "strings" "testing" + "github.com/spf13/cobra" + "k8s.io/kubernetes/pkg/api" client "k8s.io/kubernetes/pkg/client/unversioned" "k8s.io/kubernetes/pkg/client/unversioned/fake" @@ -35,7 +39,7 @@ func TestLog(t *testing.T) { { name: "v1 - pod log", version: "v1", - podPath: "/api/v1/namespaces/test/pods/foo", + podPath: "/namespaces/test/pods/foo", logPath: "/api/v1/namespaces/test/pods/foo/log", pod: testPod(), }, @@ -88,3 +92,49 @@ func testPod() *api.Pod { }, } } + +func TestValidateLogFlags(t *testing.T) { + f, _, _ := NewAPIFactory() + + tests := []struct { + name string + flags map[string]string + expected string + }{ + { + name: "since & since-time", + flags: map[string]string{"since": "1h", "since-time": "2006-01-02T15:04:05Z"}, + expected: "only one of sinceTime or sinceSeconds can be provided", + }, + { + name: "negative limit-bytes", + flags: map[string]string{"limit-bytes": "-100"}, + expected: "limitBytes must be a positive integer or nil", + }, + { + name: "negative tail", + flags: map[string]string{"tail": "-100"}, + expected: "tailLines must be a non-negative integer or nil", + }, + } + for _, test := range tests { + cmd := NewCmdLog(f, bytes.NewBuffer([]byte{})) + out := "" + for flag, value := range test.flags { + cmd.Flags().Set(flag, value) + } + // checkErr breaks tests in case of errors, plus we just + // need to check errors returned by the command validation + o := &LogsOptions{} + cmd.Run = func(cmd *cobra.Command, args []string) { + o.Complete(f, os.Stdout, cmd, args) + out = o.Validate().Error() + o.RunLog() + } + cmd.Run(cmd, []string{"foo"}) + + 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) + } + } +} diff --git a/pkg/kubectl/cmd/run.go b/pkg/kubectl/cmd/run.go index 3b71f231282..5110feb7ffe 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -205,7 +205,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob if err != nil { return err } - return handleAttachPod(client, attachablePod, opts) + return handleAttachPod(f, client, attachablePod, opts) } outputFormat := cmdutil.GetFlagString(cmd, "output") @@ -244,20 +244,40 @@ func waitForPodRunning(c *client.Client, pod *api.Pod, out io.Writer) (status ap } } -func handleAttachPod(c *client.Client, pod *api.Pod, opts *AttachOptions) error { +func handleAttachPod(f *cmdutil.Factory, c *client.Client, pod *api.Pod, opts *AttachOptions) error { status, err := waitForPodRunning(c, pod, opts.Out) if err != nil { return err } if status == api.PodSucceeded || status == api.PodFailed { - return handleLog(c, pod.Namespace, pod.Name, &api.PodLogOptions{Container: opts.GetContainerName(pod)}, opts.Out) + req, err := f.LogsForObject(pod, &api.PodLogOptions{Container: opts.GetContainerName(pod)}) + if err != nil { + return err + } + readCloser, err := req.Stream() + if err != nil { + return err + } + defer readCloser.Close() + _, err = io.Copy(opts.Out, readCloser) + return err } opts.Client = c opts.PodName = pod.Name opts.Namespace = pod.Namespace if err := opts.Run(); err != nil { fmt.Fprintf(opts.Out, "Error attaching, falling back to logs: %v\n", err) - return handleLog(c, pod.Namespace, pod.Name, &api.PodLogOptions{Container: opts.GetContainerName(pod)}, opts.Out) + req, err := f.LogsForObject(pod, &api.PodLogOptions{Container: opts.GetContainerName(pod)}) + if err != nil { + return err + } + readCloser, err := req.Stream() + if err != nil { + return err + } + defer readCloser.Close() + _, err = io.Copy(opts.Out, readCloser) + return err } return nil } diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index 317d1178f0b..ea98c0f341a 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -83,6 +83,8 @@ type Factory struct { PortsForObject func(object runtime.Object) ([]string, error) // LabelsForObject returns the labels associated with the provided object LabelsForObject func(object runtime.Object) (map[string]string, error) + // LogsForObject returns a request for the logs associated with the provided object + LogsForObject func(object, options runtime.Object) (*client.Request, error) // Returns a schema that can validate objects stored on disk. Validator func(validate bool, cacheDir string) (validation.Schema, error) // Returns the default namespace to use in cases where no @@ -218,6 +220,27 @@ func NewFactory(optionalClientConfig clientcmd.ClientConfig) *Factory { LabelsForObject: func(object runtime.Object) (map[string]string, error) { return meta.NewAccessor().Labels(object) }, + LogsForObject: func(object, options runtime.Object) (*client.Request, error) { + c, err := clients.ClientForVersion("") + if err != nil { + return nil, err + } + + 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 c.Pods(t.Namespace).GetLogs(t.Name, opts), nil + default: + _, kind, err := api.Scheme.ObjectVersionAndKind(object) + if err != nil { + return nil, err + } + return nil, fmt.Errorf("cannot get the logs from %s", kind) + } + }, Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) { client, err := clients.ClientForVersion(mapping.APIVersion) if err != nil {