Merge pull request #10707 from kargakis/logs-with-resource-builder

logs: Use resource builder
This commit is contained in:
Wojciech Tyczynski 2015-11-09 16:40:10 +01:00
commit d80e0e837c
10 changed files with 257 additions and 137 deletions

View File

@ -19,15 +19,15 @@ Print the logs for a container in a pod. If the pod has only one container, the
.SH OPTIONS .SH OPTIONS
.PP .PP
\fB\-c\fP, \fB\-\-container\fP="" \fB\-c\fP, \fB\-\-container\fP=""
Container name Print the logs of this container
.PP .PP
\fB\-f\fP, \fB\-\-follow\fP=false \fB\-f\fP, \fB\-\-follow\fP=false
Specify if the logs should be streamed. Specify if the logs should be streamed.
.PP .PP
\fB\-\-interactive\fP=true \fB\-\-interactive\fP=false
If true, prompt the user for input when required. Default true. If true, prompt the user for input when required.
.PP .PP
\fB\-\-limit\-bytes\fP=0 \fB\-\-limit\-bytes\fP=0

View File

@ -66,7 +66,7 @@ $ kubectl logs --since=1h nginx
### Options ### Options
``` ```
-c, --container="": Container name -c, --container="": Print the logs of this container
-f, --follow[=false]: Specify if the logs should be streamed. -f, --follow[=false]: Specify if the logs should be streamed.
--limit-bytes=0: Maximum bytes of logs to return. Defaults to no limit. --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. -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 * [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
<!-- BEGIN MUNGE: GENERATED_ANALYTICS --> <!-- BEGIN MUNGE: GENERATED_ANALYTICS -->
[![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_logs.md?pixel)]() [![Analytics](https://kubernetes-site.appspot.com/UA-36037335-10/GitHub/docs/user-guide/kubectl/kubectl_logs.md?pixel)]()

View File

@ -38,6 +38,7 @@ type PodInterface interface {
Watch(label labels.Selector, field fields.Selector, opts api.ListOptions) (watch.Interface, error) Watch(label labels.Selector, field fields.Selector, opts api.ListOptions) (watch.Interface, error)
Bind(binding *api.Binding) error Bind(binding *api.Binding) error
UpdateStatus(pod *api.Pod) (*api.Pod, error) UpdateStatus(pod *api.Pod) (*api.Pod, error)
GetLogs(name string, opts *api.PodLogOptions) *Request
} }
// pods implements PodsNamespacer interface // 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) err = c.r.Put().Namespace(c.ns).Resource("pods").Name(pod.Name).SubResource("status").Body(pod).Do().Into(result)
return 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)
}

View File

@ -187,3 +187,29 @@ func TestUpdatePod(t *testing.T) {
receivedPod, err := c.Setup(t).Pods(ns).Update(requestPod) receivedPod, err := c.Setup(t).Pods(ns).Update(requestPod)
c.Validate(t, receivedPod, err) 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])
}
}
}

View File

@ -18,6 +18,7 @@ package testclient
import ( import (
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/fields" "k8s.io/kubernetes/pkg/fields"
"k8s.io/kubernetes/pkg/labels" "k8s.io/kubernetes/pkg/labels"
"k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch"
@ -99,3 +100,15 @@ func (c *FakePods) UpdateStatus(pod *api.Pod) (*api.Pod, error) {
return obj.(*api.Pod), err 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{}
}

View File

@ -232,6 +232,26 @@ func NewAPIFactory() (*cmdutil.Factory, *testFactory, runtime.Codec) {
generator, ok := generators[name] generator, ok := generators[name]
return generator, ok 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) rf := cmdutil.NewFactory(nil)
f.PodSelectorForObject = rf.PodSelectorForObject f.PodSelectorForObject = rf.PodSelectorForObject

View File

@ -18,19 +18,21 @@ package cmd
import ( import (
"errors" "errors"
"fmt"
"io" "io"
"math" "math"
"os" "os"
"strconv"
"strings"
"time" "time"
"github.com/spf13/cobra" "github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/api/meta"
"k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/unversioned"
"k8s.io/kubernetes/pkg/api/validation"
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" 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 ( const (
@ -51,193 +53,153 @@ $ kubectl logs --since=1h nginx`
) )
type LogsOptions struct { type LogsOptions struct {
Client *client.Client Namespace string
ResourceArg string
Options runtime.Object
PodNamespace string Mapper meta.RESTMapper
PodName string Typer runtime.ObjectTyper
ContainerName string ClientMapper resource.ClientMapper
Follow bool
Timestamps bool LogsForObject func(object, options runtime.Object) (*client.Request, error)
Previous bool
LimitBytes int
Tail int
SinceTime *unversioned.Time
SinceSeconds time.Duration
Out io.Writer Out io.Writer
} }
// NewCmdLog creates a new pod log command // NewCmdLog creates a new pod log command
func NewCmdLog(f *cmdutil.Factory, out io.Writer) *cobra.Command { func NewCmdLog(f *cmdutil.Factory, out io.Writer) *cobra.Command {
o := &LogsOptions{ o := &LogsOptions{}
Out: out,
Tail: -1,
}
cmd := &cobra.Command{ cmd := &cobra.Command{
Use: "logs [-f] [-p] POD [-c CONTAINER]", Use: "logs [-f] [-p] POD [-c CONTAINER]",
Short: "Print the logs for a container in a pod.", 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.", 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, 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" { if len(os.Args) > 1 && os.Args[1] == "log" {
printDeprecationWarning("logs", "log") printDeprecationWarning("logs", "log")
} }
},
Run: func(cmd *cobra.Command, args []string) {
cmdutil.CheckErr(o.Complete(f, out, cmd, args)) cmdutil.CheckErr(o.Complete(f, out, cmd, args))
if err := o.Validate(); err != nil { if err := o.Validate(); err != nil {
cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error())) cmdutil.CheckErr(cmdutil.UsageError(cmd, err.Error()))
} }
cmdutil.CheckErr(o.RunLog()) _, err := o.RunLog()
cmdutil.CheckErr(err)
}, },
Aliases: []string{"log"}, Aliases: []string{"log"},
} }
cmd.Flags().BoolVarP(&o.Follow, "follow", "f", o.Follow, "Specify if the logs should be streamed.") cmd.Flags().BoolP("follow", "f", false, "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("timestamps", false, "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().Int64("limit-bytes", 0, "Maximum bytes of logs to return. Defaults to no limit.")
cmd.Flags().MarkDeprecated("interactive", "This flag is no longer respected and there is no replacement.") 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().IntVar(&o.LimitBytes, "limit-bytes", o.LimitBytes, "Maximum bytes of logs to return. Defaults to no limit.") cmd.Flags().Int64("tail", -1, "Lines of recent log file to display. Defaults to -1, showing all log lines.")
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().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().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().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().StringVarP(&o.ContainerName, "container", "c", o.ContainerName, "Container name") 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 return cmd
} }
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")
switch len(args) { switch len(args) {
case 0: case 0:
return cmdutil.UsageError(cmd, "POD is required for log") return cmdutil.UsageError(cmd, "POD is required for logs")
case 1: case 1:
o.PodName = args[0] o.ResourceArg = args[0]
case 2: case 2:
if cmd.Flag("container").Changed { if cmd.Flag("container").Changed {
return cmdutil.UsageError(cmd, "only one of -c, [CONTAINER] arg is allowed") return cmdutil.UsageError(cmd, "only one of -c, [CONTAINER] arg is allowed")
} }
o.PodName = args[0] o.ResourceArg = args[0]
o.ContainerName = args[1] containerName = args[1]
default: default:
return cmdutil.UsageError(cmd, "log POD [-c CONTAINER]") return cmdutil.UsageError(cmd, "logs POD [-c CONTAINER]")
} }
var err error var err error
o.PodNamespace, _, err = f.DefaultNamespace() o.Namespace, _, err = f.DefaultNamespace()
if err != nil {
return err
}
o.Client, err = f.Client()
if err != nil { if err != nil {
return err return err
} }
sinceTime := cmdutil.GetFlagString(cmd, "since-time") logOptions := &api.PodLogOptions{
if len(sinceTime) > 0 { 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) t, err := api.ParseRFC3339(sinceTime, unversioned.Now)
if err != nil { if err != nil {
return err 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 return nil
} }
func (o *LogsOptions) Validate() error { func (o LogsOptions) Validate() error {
if len(o.PodName) == 0 { if len(o.ResourceArg) == 0 {
return errors.New("POD must be specified") return errors.New("a pod must be specified")
} }
if o.LimitBytes < 0 { logOptions, ok := o.Options.(*api.PodLogOptions)
return errors.New("--limit-bytes must be greater than or equal to zero") if !ok {
return errors.New("unexpected log options object")
} }
if o.Tail < -1 { if errs := validation.ValidatePodLogOptions(logOptions); len(errs) > 0 {
return errors.New("--tail must be greater than or equal to -1") return kerrors.NewAggregate(errs)
}
if o.SinceTime != nil && o.SinceSeconds > 0 {
return errors.New("only one of --since, --since-time may be specified")
} }
return nil return nil
} }
// RunLog retrieves a pod log // RunLog retrieves a pod log
func (o *LogsOptions) RunLog() error { func (o LogsOptions) RunLog() (int64, error) {
pod, err := o.Client.Pods(o.PodNamespace).Get(o.PodName) infos, err := resource.NewBuilder(o.Mapper, o.Typer, o.ClientMapper).
NamespaceParam(o.Namespace).DefaultNamespace().
ResourceNames("pods", o.ResourceArg).
SingleResourceType().
Do().Infos()
if err != nil { 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() readCloser, err := req.Stream()
if err != nil { if err != nil {
return err return 0, err
} }
defer readCloser.Close() defer readCloser.Close()
_, err = io.Copy(out, readCloser)
return err return io.Copy(o.Out, readCloser)
} }

View File

@ -20,8 +20,12 @@ import (
"bytes" "bytes"
"io/ioutil" "io/ioutil"
"net/http" "net/http"
"os"
"strings"
"testing" "testing"
"github.com/spf13/cobra"
"k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/api"
client "k8s.io/kubernetes/pkg/client/unversioned" client "k8s.io/kubernetes/pkg/client/unversioned"
"k8s.io/kubernetes/pkg/client/unversioned/fake" "k8s.io/kubernetes/pkg/client/unversioned/fake"
@ -35,7 +39,7 @@ func TestLog(t *testing.T) {
{ {
name: "v1 - pod log", name: "v1 - pod log",
version: "v1", version: "v1",
podPath: "/api/v1/namespaces/test/pods/foo", podPath: "/namespaces/test/pods/foo",
logPath: "/api/v1/namespaces/test/pods/foo/log", logPath: "/api/v1/namespaces/test/pods/foo/log",
pod: testPod(), 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)
}
}
}

View File

@ -206,7 +206,7 @@ func Run(f *cmdutil.Factory, cmdIn io.Reader, cmdOut, cmdErr io.Writer, cmd *cob
if err != nil { if err != nil {
return err return err
} }
return handleAttachPod(client, attachablePod, opts) return handleAttachPod(f, client, attachablePod, opts)
} }
outputFormat := cmdutil.GetFlagString(cmd, "output") outputFormat := cmdutil.GetFlagString(cmd, "output")
@ -245,20 +245,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) status, err := waitForPodRunning(c, pod, opts.Out)
if err != nil { if err != nil {
return err return err
} }
if status == api.PodSucceeded || status == api.PodFailed { 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.Client = c
opts.PodName = pod.Name opts.PodName = pod.Name
opts.Namespace = pod.Namespace opts.Namespace = pod.Namespace
if err := opts.Run(); err != nil { if err := opts.Run(); err != nil {
fmt.Fprintf(opts.Out, "Error attaching, falling back to logs: %v\n", err) 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 return nil
} }

View File

@ -83,6 +83,8 @@ type Factory struct {
PortsForObject func(object runtime.Object) ([]string, error) PortsForObject func(object runtime.Object) ([]string, error)
// LabelsForObject returns the labels associated with the provided object // LabelsForObject returns the labels associated with the provided object
LabelsForObject func(object runtime.Object) (map[string]string, error) 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. // Returns a schema that can validate objects stored on disk.
Validator func(validate bool, cacheDir string) (validation.Schema, error) Validator func(validate bool, cacheDir string) (validation.Schema, error)
// Returns the default namespace to use in cases where no // 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) { LabelsForObject: func(object runtime.Object) (map[string]string, error) {
return meta.NewAccessor().Labels(object) 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) { Scaler: func(mapping *meta.RESTMapping) (kubectl.Scaler, error) {
client, err := clients.ClientForVersion(mapping.APIVersion) client, err := clients.ClientForVersion(mapping.APIVersion)
if err != nil { if err != nil {