From eabfcfaa2b739f53667602927680e312634c4fd6 Mon Sep 17 00:00:00 2001 From: David Eads Date: Wed, 16 May 2018 10:47:29 -0400 Subject: [PATCH] start splitting polymorphic functions out of the factory --- pkg/kubectl/BUILD | 1 + pkg/kubectl/cmd/BUILD | 3 + pkg/kubectl/cmd/clusterinfo_dump.go | 44 ++-- pkg/kubectl/cmd/diff.go | 2 +- pkg/kubectl/cmd/logs.go | 19 +- pkg/kubectl/cmd/logs_test.go | 35 +++ pkg/kubectl/cmd/run.go | 5 +- pkg/kubectl/cmd/testing/BUILD | 1 - pkg/kubectl/cmd/testing/fake.go | 19 -- pkg/kubectl/cmd/util/BUILD | 17 +- pkg/kubectl/cmd/util/factory.go | 65 +---- pkg/kubectl/cmd/util/factory_client_access.go | 12 +- .../cmd/util/factory_object_mapping.go | 195 +-------------- pkg/kubectl/cmd/util/factory_test.go | 205 --------------- pkg/kubectl/cmd/util/kubectl_match_version.go | 7 +- pkg/kubectl/genericclioptions/config_flags.go | 16 ++ pkg/kubectl/polymorphichelpers/BUILD | 74 ++++++ pkg/kubectl/polymorphichelpers/helpers.go | 235 ++++++++++++++++++ .../polymorphichelpers/helpers_test.go | 229 +++++++++++++++++ pkg/kubectl/polymorphichelpers/interface.go | 31 +++ .../polymorphichelpers/logsforobject.go | 75 ++++++ .../logsforobject_test.go} | 30 +-- test/e2e/kubectl/BUILD | 2 +- test/e2e/kubectl/kubectl.go | 4 +- 24 files changed, 758 insertions(+), 568 deletions(-) create mode 100644 pkg/kubectl/polymorphichelpers/BUILD create mode 100644 pkg/kubectl/polymorphichelpers/helpers.go create mode 100644 pkg/kubectl/polymorphichelpers/helpers_test.go create mode 100644 pkg/kubectl/polymorphichelpers/interface.go create mode 100644 pkg/kubectl/polymorphichelpers/logsforobject.go rename pkg/kubectl/{cmd/util/factory_object_mapping_test.go => polymorphichelpers/logsforobject_test.go} (86%) diff --git a/pkg/kubectl/BUILD b/pkg/kubectl/BUILD index 3ce1e6d876..0cae034682 100644 --- a/pkg/kubectl/BUILD +++ b/pkg/kubectl/BUILD @@ -205,6 +205,7 @@ filegroup( "//pkg/kubectl/genericclioptions:all-srcs", "//pkg/kubectl/metricsutil:all-srcs", "//pkg/kubectl/plugins:all-srcs", + "//pkg/kubectl/polymorphichelpers:all-srcs", "//pkg/kubectl/proxy:all-srcs", "//pkg/kubectl/scheme:all-srcs", "//pkg/kubectl/util:all-srcs", diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index cadfd43353..fb3a23883c 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -82,6 +82,7 @@ go_library( "//pkg/kubectl/genericclioptions/resource:go_default_library", "//pkg/kubectl/metricsutil:go_default_library", "//pkg/kubectl/plugins:go_default_library", + "//pkg/kubectl/polymorphichelpers:go_default_library", "//pkg/kubectl/proxy:go_default_library", "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util:go_default_library", @@ -193,6 +194,7 @@ go_test( "//pkg/apis/batch:go_default_library", "//pkg/apis/core:go_default_library", "//pkg/apis/extensions:go_default_library", + "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/create:go_default_library", "//pkg/kubectl/cmd/testing:go_default_library", @@ -201,6 +203,7 @@ go_test( "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/genericclioptions/resource:go_default_library", "//pkg/kubectl/plugins:go_default_library", + "//pkg/kubectl/polymorphichelpers:go_default_library", "//pkg/kubectl/scheme:go_default_library", "//pkg/kubectl/util/i18n:go_default_library", "//pkg/kubectl/util/term:go_default_library", diff --git a/pkg/kubectl/cmd/clusterinfo_dump.go b/pkg/kubectl/cmd/clusterinfo_dump.go index 5d23af31a8..e263c3ec14 100644 --- a/pkg/kubectl/cmd/clusterinfo_dump.go +++ b/pkg/kubectl/cmd/clusterinfo_dump.go @@ -26,14 +26,13 @@ import ( "github.com/spf13/cobra" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/runtime" - restclient "k8s.io/client-go/rest" "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" + "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/printers" ) @@ -46,10 +45,11 @@ type ClusterInfoDumpOptions struct { AllNamespaces bool Namespaces []string - timeout time.Duration - clientset internalclientset.Interface - namespace string - logsForObject func(object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) + Timeout time.Duration + Clientset internalclientset.Interface + Namespace string + RESTClientGetter genericclioptions.RESTClientGetter + LogsForObject polymorphichelpers.LogsForObjectFunc genericclioptions.IOStreams } @@ -126,25 +126,27 @@ func (o *ClusterInfoDumpOptions) Complete(f cmdutil.Factory, cmd *cobra.Command) o.PrintFlags.OutputFormat = &jsonOutputFmt o.PrintObj = printer.PrintObj - o.timeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd) + o.Timeout, err = cmdutil.GetPodRunningTimeoutFlag(cmd) if err != nil { return err } - o.clientset, err = f.ClientSet() + o.Clientset, err = f.ClientSet() if err != nil { return err } - o.namespace, _, err = f.DefaultNamespace() + o.Namespace, _, err = f.DefaultNamespace() if err != nil { return err } - o.logsForObject = f.LogsForObject + // TODO this should eventually just be the completed kubeconfigflag struct + o.RESTClientGetter = f + o.LogsForObject = polymorphichelpers.LogsForObjectFn return nil } func (o *ClusterInfoDumpOptions) Run() error { - nodes, err := o.clientset.Core().Nodes().List(metav1.ListOptions{}) + nodes, err := o.Clientset.Core().Nodes().List(metav1.ListOptions{}) if err != nil { return err } @@ -155,7 +157,7 @@ func (o *ClusterInfoDumpOptions) Run() error { var namespaces []string if o.AllNamespaces { - namespaceList, err := o.clientset.Core().Namespaces().List(metav1.ListOptions{}) + namespaceList, err := o.Clientset.Core().Namespaces().List(metav1.ListOptions{}) if err != nil { return err } @@ -166,14 +168,14 @@ func (o *ClusterInfoDumpOptions) Run() error { if len(o.Namespaces) == 0 { namespaces = []string{ metav1.NamespaceSystem, - o.namespace, + o.Namespace, } } } for _, namespace := range namespaces { // TODO: this is repetitive in the extreme. Use reflection or // something to make this a for loop. - events, err := o.clientset.Core().Events(namespace).List(metav1.ListOptions{}) + events, err := o.Clientset.Core().Events(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -181,7 +183,7 @@ func (o *ClusterInfoDumpOptions) Run() error { return err } - rcs, err := o.clientset.Core().ReplicationControllers(namespace).List(metav1.ListOptions{}) + rcs, err := o.Clientset.Core().ReplicationControllers(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -189,7 +191,7 @@ func (o *ClusterInfoDumpOptions) Run() error { return err } - svcs, err := o.clientset.Core().Services(namespace).List(metav1.ListOptions{}) + svcs, err := o.Clientset.Core().Services(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -197,7 +199,7 @@ func (o *ClusterInfoDumpOptions) Run() error { return err } - sets, err := o.clientset.Extensions().DaemonSets(namespace).List(metav1.ListOptions{}) + sets, err := o.Clientset.Extensions().DaemonSets(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -205,7 +207,7 @@ func (o *ClusterInfoDumpOptions) Run() error { return err } - deps, err := o.clientset.Extensions().Deployments(namespace).List(metav1.ListOptions{}) + deps, err := o.Clientset.Extensions().Deployments(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -213,7 +215,7 @@ func (o *ClusterInfoDumpOptions) Run() error { return err } - rps, err := o.clientset.Extensions().ReplicaSets(namespace).List(metav1.ListOptions{}) + rps, err := o.Clientset.Extensions().ReplicaSets(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -221,7 +223,7 @@ func (o *ClusterInfoDumpOptions) Run() error { return err } - pods, err := o.clientset.Core().Pods(namespace).List(metav1.ListOptions{}) + pods, err := o.Clientset.Core().Pods(namespace).List(metav1.ListOptions{}) if err != nil { return err } @@ -234,7 +236,7 @@ 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(pod, &api.PodLogOptions{Container: container.Name}, timeout) + request, err := o.LogsForObject(o.RESTClientGetter, pod, &api.PodLogOptions{Container: container.Name}, timeout) if err != nil { // Print error and return. writer.Write([]byte(fmt.Sprintf("Create log request error: %s\n", err.Error()))) diff --git a/pkg/kubectl/cmd/diff.go b/pkg/kubectl/cmd/diff.go index 9105d4b953..d513c7ab76 100644 --- a/pkg/kubectl/cmd/diff.go +++ b/pkg/kubectl/cmd/diff.go @@ -399,7 +399,7 @@ func NewDownloader(f cmdutil.Factory) (*Downloader, error) { var err error var d Downloader - d.mapper, err = f.RESTMapper() + d.mapper, err = f.ToRESTMapper() if err != nil { return nil, err } diff --git a/pkg/kubectl/cmd/logs.go b/pkg/kubectl/cmd/logs.go index e4fbe2fb9f..e81db051c3 100644 --- a/pkg/kubectl/cmd/logs.go +++ b/pkg/kubectl/cmd/logs.go @@ -26,16 +26,15 @@ import ( "github.com/spf13/cobra" - "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" - restclient "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" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" + "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers" "k8s.io/kubernetes/pkg/kubectl/util" "k8s.io/kubernetes/pkg/kubectl/util/i18n" ) @@ -82,13 +81,10 @@ type LogsOptions struct { AllContainers bool Options runtime.Object - Mapper meta.RESTMapper - Typer runtime.ObjectTyper - Decoder runtime.Decoder - - Object runtime.Object - GetPodTimeout time.Duration - LogsForObject func(object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) + Object runtime.Object + GetPodTimeout time.Duration + RESTClientGetter genericclioptions.RESTClientGetter + LogsForObject polymorphichelpers.LogsForObjectFunc genericclioptions.IOStreams } @@ -196,7 +192,8 @@ func (o *LogsOptions) Complete(f cmdutil.Factory, cmd *cobra.Command, args []str return err } o.Options = logOptions - o.LogsForObject = f.LogsForObject + o.RESTClientGetter = f + o.LogsForObject = polymorphichelpers.LogsForObjectFn if len(selector) != 0 { if logOptions.Follow { @@ -286,7 +283,7 @@ func (o LogsOptions) getPodLogs(pod *api.Pod) error { } func (o LogsOptions) getLogs(obj runtime.Object) error { - req, err := o.LogsForObject(obj, o.Options, o.GetPodTimeout) + req, err := o.LogsForObject(o.RESTClientGetter, obj, o.Options, o.GetPodTimeout) if err != nil { return err } diff --git a/pkg/kubectl/cmd/logs_test.go b/pkg/kubectl/cmd/logs_test.go index 67e2b19b16..e95e67a01f 100644 --- a/pkg/kubectl/cmd/logs_test.go +++ b/pkg/kubectl/cmd/logs_test.go @@ -18,19 +18,27 @@ package cmd import ( "bytes" + "errors" "io/ioutil" "net/http" "strings" "testing" + "time" "github.com/spf13/cobra" + "fmt" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/runtime" + restclient "k8s.io/client-go/rest" "k8s.io/client-go/rest/fake" "k8s.io/kubernetes/pkg/api/legacyscheme" api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" + "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers" "k8s.io/kubernetes/pkg/kubectl/scheme" ) @@ -74,6 +82,16 @@ func TestLog(t *testing.T) { } tf.Namespace = "test" tf.ClientConfigVal = defaultClientConfig() + oldLogFn := polymorphichelpers.LogsForObjectFn + defer func() { + polymorphichelpers.LogsForObjectFn = oldLogFn + }() + clientset, err := tf.ClientSet() + if err != nil { + t.Fatal(err) + } + polymorphichelpers.LogsForObjectFn = logTestMock{client: clientset}.logsForObject + streams, _, buf, _ := genericclioptions.NewTestIOStreams() cmd := NewCmdLogs(tf, streams) @@ -221,3 +239,20 @@ func TestLogComplete(t *testing.T) { } } } + +type logTestMock struct { + client internalclientset.Interface +} + +func (m logTestMock) logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration) (*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 + 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 1bd4352599..8a3f2a33b6 100644 --- a/pkg/kubectl/cmd/run.go +++ b/pkg/kubectl/cmd/run.go @@ -41,6 +41,7 @@ import ( cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers" "k8s.io/kubernetes/pkg/kubectl/scheme" "k8s.io/kubernetes/pkg/kubectl/util/i18n" "k8s.io/kubernetes/pkg/util/interrupt" @@ -525,13 +526,13 @@ func handleAttachPod(f cmdutil.Factory, podClient coreclient.PodsGetter, ns, nam } // logOpts logs output from opts to the pods log. -func logOpts(f cmdutil.Factory, pod *api.Pod, opts *AttachOptions) error { +func logOpts(restClientGetter genericclioptions.RESTClientGetter, pod *api.Pod, opts *AttachOptions) error { ctrName, err := opts.GetContainerName(pod) if err != nil { return err } - req, err := f.LogsForObject(pod, &api.PodLogOptions{Container: ctrName}, opts.GetPodTimeout) + req, err := polymorphichelpers.LogsForObjectFn(restClientGetter, pod, &api.PodLogOptions{Container: ctrName}, opts.GetPodTimeout) if err != nil { return err } diff --git a/pkg/kubectl/cmd/testing/BUILD b/pkg/kubectl/cmd/testing/BUILD index 2f11fe58e4..673c74c85e 100644 --- a/pkg/kubectl/cmd/testing/BUILD +++ b/pkg/kubectl/cmd/testing/BUILD @@ -10,7 +10,6 @@ go_library( visibility = ["//build/visible_to:pkg_kubectl_cmd_testing_CONSUMERS"], deps = [ "//pkg/api/legacyscheme:go_default_library", - "//pkg/apis/core:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/util:go_default_library", diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index 5204395961..ce678c3166 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -44,7 +44,6 @@ import ( "k8s.io/client-go/tools/clientcmd" clientcmdapi "k8s.io/client-go/tools/clientcmd/api" "k8s.io/kubernetes/pkg/api/legacyscheme" - api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -446,24 +445,6 @@ func testRESTMapper() meta.RESTMapper { return expander } -func (f *TestFactory) LogsForObject(object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) { - c, err := f.ClientSet() - if err != nil { - panic(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.Core().Pods(f.Namespace).GetLogs(t.Name, opts), nil - default: - return nil, fmt.Errorf("cannot get the logs from %T", object) - } -} - func (f *TestFactory) ScaleClient() (scaleclient.ScalesGetter, error) { return f.ScaleGetter, nil } diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index 2de6e2d002..694c9589e4 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -22,13 +22,14 @@ go_library( "//pkg/apis/core/v1:go_default_library", "//pkg/apis/extensions:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/controller:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/cmd/templates:go_default_library", "//pkg/kubectl/cmd/util/openapi:go_default_library", "//pkg/kubectl/cmd/util/openapi/validation:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", "//pkg/kubectl/genericclioptions/resource:go_default_library", + "//pkg/kubectl/polymorphichelpers:go_default_library", "//pkg/kubectl/validation:go_default_library", "//pkg/printers:go_default_library", "//pkg/printers/internalversion:go_default_library", @@ -49,13 +50,11 @@ go_library( "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1/unstructured:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/yaml:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", "//vendor/k8s.io/client-go/discovery:go_default_library", "//vendor/k8s.io/client-go/dynamic:go_default_library", "//vendor/k8s.io/client-go/kubernetes:go_default_library", @@ -69,7 +68,6 @@ go_library( go_test( name = "go_default_test", srcs = [ - "factory_object_mapping_test.go", "factory_test.go", "helpers_test.go", ], @@ -77,28 +75,17 @@ go_test( deps = [ "//pkg/api/testapi:go_default_library", "//pkg/api/testing:go_default_library", - "//pkg/apis/apps:go_default_library", - "//pkg/apis/batch:go_default_library", "//pkg/apis/core:go_default_library", - "//pkg/apis/extensions:go_default_library", - "//pkg/client/clientset_generated/internalclientset:go_default_library", - "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", - "//pkg/controller:go_default_library", "//pkg/kubectl:go_default_library", "//pkg/kubectl/genericclioptions:go_default_library", - "//vendor/k8s.io/api/core/v1:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library", "//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library", "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library", "//vendor/k8s.io/apimachinery/pkg/util/validation/field:go_default_library", - "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", - "//vendor/k8s.io/client-go/testing:go_default_library", "//vendor/k8s.io/utils/exec:go_default_library", ], ) diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index cd53bfc94a..8d655dae53 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -18,7 +18,6 @@ package util import ( "fmt" - "sort" "strconv" "strings" "time" @@ -27,21 +26,17 @@ import ( "k8s.io/api/core/v1" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" - "k8s.io/apimachinery/pkg/watch" - "k8s.io/client-go/discovery" "k8s.io/client-go/dynamic" "k8s.io/client-go/kubernetes" restclient "k8s.io/client-go/rest" scaleclient "k8s.io/client-go/scale" api "k8s.io/kubernetes/pkg/apis/core" - apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" - coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" "k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/printers" @@ -67,14 +62,7 @@ type Factory interface { // Generally provides discovery, negotiation, and no-dep calls. // TODO The polymorphic calls probably deserve their own interface. type ClientAccessFactory interface { - // Returns a client.Config for accessing the Kubernetes server. - ToRESTConfig() (*restclient.Config, error) - // Returns interfaces for dealing with arbitrary runtime.Objects. - ToRESTMapper() (meta.RESTMapper, error) - // Returns a discovery client - ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) - // Returns kubeconfig loader - ToRawKubeConfigLoader() clientcmd.ClientConfig + genericclioptions.RESTClientGetter // ClientSet gives you back an internal, generated clientset ClientSet() (internalclientset.Interface, error) @@ -151,8 +139,6 @@ type ObjectMappingFactory interface { // Returns a Describer for displaying the specified RESTMapping type or an error. Describer(mapping *meta.RESTMapping) (printers.Describer, error) - // LogsForObject returns a request for the logs associated with the provided object - LogsForObject(object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) // Returns a HistoryViewer for viewing change history HistoryViewer(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) // Returns a Rollbacker for changing the rollback version of the specified RESTMapping type or an error @@ -191,7 +177,7 @@ type factory struct { // NewFactory creates a factory with the default Kubernetes resources defined // Receives a clientGetter capable of providing a discovery client and a REST client configuration. -func NewFactory(clientGetter RESTClientGetter) Factory { +func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory { clientAccessFactory := NewClientAccessFactory(clientGetter) objectMappingFactory := NewObjectMappingFactory(clientAccessFactory) builderFactory := NewBuilderFactory(clientAccessFactory, objectMappingFactory) @@ -203,51 +189,6 @@ func NewFactory(clientGetter RESTClientGetter) Factory { } } -// GetFirstPod returns a pod matching the namespace and label selector -// and the number of all pods that match the label selector. -func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*v1.Pod) sort.Interface) (*api.Pod, int, error) { - options := metav1.ListOptions{LabelSelector: selector} - - podList, err := client.Pods(namespace).List(options) - if err != nil { - return nil, 0, err - } - pods := []*v1.Pod{} - for i := range podList.Items { - pod := podList.Items[i] - externalPod := &v1.Pod{} - apiv1.Convert_core_Pod_To_v1_Pod(&pod, externalPod, nil) - pods = append(pods, externalPod) - } - if len(pods) > 0 { - sort.Sort(sortBy(pods)) - internalPod := &api.Pod{} - apiv1.Convert_v1_Pod_To_core_Pod(pods[0], internalPod, nil) - return internalPod, len(podList.Items), nil - } - - // Watch until we observe a pod - options.ResourceVersion = podList.ResourceVersion - w, err := client.Pods(namespace).Watch(options) - if err != nil { - return nil, 0, err - } - defer w.Stop() - - condition := func(event watch.Event) (bool, error) { - return event.Type == watch.Added || event.Type == watch.Modified, nil - } - event, err := watch.Until(timeout, w, condition) - if err != nil { - return nil, 0, err - } - pod, ok := event.Object.(*api.Pod) - if !ok { - return nil, 0, fmt.Errorf("%#v is not a pod event", event) - } - return pod, 1, nil -} - func makePortsString(ports []api.ServicePort, useNodePort bool) string { pieces := make([]string, len(ports)) for ix := range ports { diff --git a/pkg/kubectl/cmd/util/factory_client_access.go b/pkg/kubectl/cmd/util/factory_client_access.go index ef6506899c..55ffc2bb3c 100644 --- a/pkg/kubectl/cmd/util/factory_client_access.go +++ b/pkg/kubectl/cmd/util/factory_client_access.go @@ -55,21 +55,15 @@ import ( "k8s.io/kubernetes/pkg/apis/extensions" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/kubectl" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" ) -type RESTClientGetter interface { - ToRESTConfig() (*restclient.Config, error) - ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) - ToRESTMapper() (meta.RESTMapper, error) - ToRawKubeConfigLoader() clientcmd.ClientConfig -} - type ring0Factory struct { - clientGetter RESTClientGetter + clientGetter genericclioptions.RESTClientGetter } -func NewClientAccessFactory(clientGetter RESTClientGetter) ClientAccessFactory { +func NewClientAccessFactory(clientGetter genericclioptions.RESTClientGetter) ClientAccessFactory { if clientGetter == nil { panic("attempt to instantiate client_access_factory with nil clientGetter") } diff --git a/pkg/kubectl/cmd/util/factory_object_mapping.go b/pkg/kubectl/cmd/util/factory_object_mapping.go index 177a80dc22..802353fed3 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping.go @@ -19,28 +19,18 @@ limitations under the License. package util import ( - "errors" "fmt" - "os" "reflect" "sort" "sync" "time" - appsv1 "k8s.io/api/apps/v1" - appsv1beta1 "k8s.io/api/apps/v1beta1" - appsv1beta2 "k8s.io/api/apps/v1beta2" - batchv1 "k8s.io/api/batch/v1" "k8s.io/api/core/v1" corev1 "k8s.io/api/core/v1" - extensionsv1beta1 "k8s.io/api/extensions/v1beta1" "k8s.io/apimachinery/pkg/api/meta" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/client-go/dynamic" restclient "k8s.io/client-go/rest" - "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" @@ -50,6 +40,7 @@ import ( "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi" openapivalidation "k8s.io/kubernetes/pkg/kubectl/cmd/util/openapi/validation" "k8s.io/kubernetes/pkg/kubectl/genericclioptions/resource" + "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers" "k8s.io/kubernetes/pkg/kubectl/validation" "k8s.io/kubernetes/pkg/printers" printersinternal "k8s.io/kubernetes/pkg/printers/internalversion" @@ -152,186 +143,6 @@ func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RES return printersinternal.GenericDescriberFor(mapping, dynamicClient, eventsClient), nil } -func (f *ring1Factory) LogsForObject(object, options runtime.Object, timeout time.Duration) (*restclient.Request, error) { - clientset, err := f.clientAccessFactory.ClientSet() - if err != nil { - return nil, err - } - opts, ok := options.(*api.PodLogOptions) - if !ok { - return nil, errors.New("provided options object is not a PodLogOptions") - } - - switch t := object.(type) { - case *api.Pod: - return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil - case *corev1.Pod: - return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil - } - - namespace, selector, err := selectorsForObject(object) - if err != nil { - return nil, fmt.Errorf("cannot get the logs from %T: %v", object, err) - } - sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) } - pod, numPods, err := GetFirstPod(clientset.Core(), namespace, selector.String(), timeout, sortBy) - if err != nil { - return nil, err - } - 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 -} - -func selectorsForObject(object runtime.Object) (namespace string, selector labels.Selector, err error) { - switch t := object.(type) { - case *extensions.ReplicaSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *extensionsv1beta1.ReplicaSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1.ReplicaSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1beta2.ReplicaSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - - case *api.ReplicationController: - namespace = t.Namespace - selector = labels.SelectorFromSet(t.Spec.Selector) - case *corev1.ReplicationController: - namespace = t.Namespace - selector = labels.SelectorFromSet(t.Spec.Selector) - - case *apps.StatefulSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1.StatefulSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1beta1.StatefulSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1beta2.StatefulSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - - case *extensions.DaemonSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *extensionsv1beta1.DaemonSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1.DaemonSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1beta2.DaemonSet: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - - case *extensions.Deployment: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *extensionsv1beta1.Deployment: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1.Deployment: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1beta1.Deployment: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *appsv1beta2.Deployment: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - - case *batch.Job: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - case *batchv1.Job: - namespace = t.Namespace - selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) - if err != nil { - return "", nil, fmt.Errorf("invalid label selector: %v", err) - } - - case *api.Service: - namespace = t.Namespace - if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { - return "", nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) - } - selector = labels.SelectorFromSet(t.Spec.Selector) - case *corev1.Service: - namespace = t.Namespace - if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { - return "", nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) - } - selector = labels.SelectorFromSet(t.Spec.Selector) - - default: - return "", nil, fmt.Errorf("selector for %T not implemented", object) - } - - return namespace, selector, nil -} - func (f *ring1Factory) HistoryViewer(mapping *meta.RESTMapping) (kubectl.HistoryViewer, error) { external, err := f.clientAccessFactory.KubernetesClientSet() if err != nil { @@ -394,12 +205,12 @@ func (f *ring1Factory) AttachablePodForObject(object runtime.Object, timeout tim } - namespace, selector, err := selectorsForObject(object) + namespace, selector, err := polymorphichelpers.SelectorsForObject(object) if err != nil { return nil, fmt.Errorf("cannot attach to %T: %v", object, err) } sortBy := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) } - pod, _, err := GetFirstPod(clientset.Core(), namespace, selector.String(), timeout, sortBy) + pod, _, err := polymorphichelpers.GetFirstPod(clientset.Core(), namespace, selector.String(), timeout, sortBy) return pod, err } diff --git a/pkg/kubectl/cmd/util/factory_test.go b/pkg/kubectl/cmd/util/factory_test.go index 7303540473..ba43f58a37 100644 --- a/pkg/kubectl/cmd/util/factory_test.go +++ b/pkg/kubectl/cmd/util/factory_test.go @@ -17,25 +17,14 @@ limitations under the License. package util import ( - "fmt" - "sort" "strings" "testing" - "time" - "k8s.io/api/core/v1" - - apiequality "k8s.io/apimachinery/pkg/api/equality" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/util/sets" - "k8s.io/apimachinery/pkg/watch" - testcore "k8s.io/client-go/testing" api "k8s.io/kubernetes/pkg/apis/core" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" - "k8s.io/kubernetes/pkg/controller" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/genericclioptions" ) @@ -187,200 +176,6 @@ func TestCanBeExposed(t *testing.T) { } } -func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *api.PodList { - pods := []api.Pod{} - for i := 0; i < count; i++ { - newPod := api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: fmt.Sprintf("pod-%d", i+1), - Namespace: metav1.NamespaceDefault, - CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, i, 0, time.UTC), - Labels: labels, - }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Status: api.ConditionTrue, - Type: api.PodReady, - }, - }, - }, - } - pods = append(pods, newPod) - } - if isUnready > -1 && isUnready < count { - pods[isUnready].Status.Conditions[0].Status = api.ConditionFalse - } - if isUnhealthy > -1 && isUnhealthy < count { - pods[isUnhealthy].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 5}} - } - return &api.PodList{ - Items: pods, - } -} - -func TestGetFirstPod(t *testing.T) { - labelSet := map[string]string{"test": "selector"} - tests := []struct { - name string - - podList *api.PodList - watching []watch.Event - sortBy func([]*v1.Pod) sort.Interface - - expected *api.Pod - expectedNum int - expectedErr bool - }{ - { - name: "kubectl logs - two ready pods", - podList: newPodList(2, -1, -1, labelSet), - sortBy: func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }, - expected: &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-1", - Namespace: metav1.NamespaceDefault, - CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), - Labels: map[string]string{"test": "selector"}, - }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Status: api.ConditionTrue, - Type: api.PodReady, - }, - }, - }, - }, - expectedNum: 2, - }, - { - name: "kubectl logs - one unhealthy, one healthy", - podList: newPodList(2, -1, 1, labelSet), - sortBy: func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }, - expected: &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-2", - Namespace: metav1.NamespaceDefault, - CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 1, 0, time.UTC), - Labels: map[string]string{"test": "selector"}, - }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Status: api.ConditionTrue, - Type: api.PodReady, - }, - }, - ContainerStatuses: []api.ContainerStatus{{RestartCount: 5}}, - }, - }, - expectedNum: 2, - }, - { - name: "kubectl attach - two ready pods", - podList: newPodList(2, -1, -1, labelSet), - sortBy: func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }, - expected: &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-1", - Namespace: metav1.NamespaceDefault, - CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), - Labels: map[string]string{"test": "selector"}, - }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Status: api.ConditionTrue, - Type: api.PodReady, - }, - }, - }, - }, - expectedNum: 2, - }, - { - name: "kubectl attach - wait for ready pod", - podList: newPodList(1, 1, -1, labelSet), - watching: []watch.Event{ - { - Type: watch.Modified, - Object: &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-1", - Namespace: metav1.NamespaceDefault, - CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), - Labels: map[string]string{"test": "selector"}, - }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Status: api.ConditionTrue, - Type: api.PodReady, - }, - }, - }, - }, - }, - }, - sortBy: func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }, - expected: &api.Pod{ - ObjectMeta: metav1.ObjectMeta{ - Name: "pod-1", - Namespace: metav1.NamespaceDefault, - CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), - Labels: map[string]string{"test": "selector"}, - }, - Status: api.PodStatus{ - Conditions: []api.PodCondition{ - { - Status: api.ConditionTrue, - Type: api.PodReady, - }, - }, - }, - }, - expectedNum: 1, - }, - } - - for i := range tests { - test := tests[i] - fake := fake.NewSimpleClientset(test.podList) - if len(test.watching) > 0 { - watcher := watch.NewFake() - for _, event := range test.watching { - switch event.Type { - case watch.Added: - go watcher.Add(event.Object) - case watch.Modified: - go watcher.Modify(event.Object) - } - } - fake.PrependWatchReactor("pods", testcore.DefaultWatchReactor(watcher, nil)) - } - selector := labels.Set(labelSet).AsSelector() - - pod, numPods, err := GetFirstPod(fake.Core(), metav1.NamespaceDefault, selector.String(), 1*time.Minute, test.sortBy) - pod.Spec.SecurityContext = nil - if !test.expectedErr && err != nil { - t.Errorf("%s: unexpected error: %v", test.name, err) - continue - } - if test.expectedErr && err == nil { - t.Errorf("%s: expected an error", test.name) - continue - } - if test.expectedNum != numPods { - t.Errorf("%s: expected %d pods, got %d", test.name, test.expectedNum, numPods) - continue - } - if !apiequality.Semantic.DeepEqual(test.expected, pod) { - t.Errorf("%s:\nexpected pod:\n%#v\ngot:\n%#v\n\n", test.name, test.expected, pod) - } - } -} - func TestMakePortsString(t *testing.T) { tests := []struct { ports []api.ServicePort diff --git a/pkg/kubectl/cmd/util/kubectl_match_version.go b/pkg/kubectl/cmd/util/kubectl_match_version.go index 954216dc4f..c5c121a79c 100644 --- a/pkg/kubectl/cmd/util/kubectl_match_version.go +++ b/pkg/kubectl/cmd/util/kubectl_match_version.go @@ -28,6 +28,7 @@ import ( "k8s.io/client-go/tools/clientcmd" "k8s.io/kubernetes/pkg/api/legacyscheme" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" "k8s.io/kubernetes/pkg/version" ) @@ -37,14 +38,14 @@ const ( // MatchVersionFlags is for setting the "match server version" function. type MatchVersionFlags struct { - Delegate RESTClientGetter + Delegate genericclioptions.RESTClientGetter RequireMatchedServerVersion bool checkServerVersion sync.Once matchesServerVersionErr error } -var _ RESTClientGetter = &MatchVersionFlags{} +var _ genericclioptions.RESTClientGetter = &MatchVersionFlags{} func (f *MatchVersionFlags) checkMatchingServerVersion() error { f.checkServerVersion.Do(func() { @@ -102,7 +103,7 @@ func (f *MatchVersionFlags) AddFlags(flags *pflag.FlagSet) { flags.BoolVar(&f.RequireMatchedServerVersion, flagMatchBinaryVersion, f.RequireMatchedServerVersion, "Require server version to match client version") } -func NewMatchVersionFlags(delegate RESTClientGetter) *MatchVersionFlags { +func NewMatchVersionFlags(delegate genericclioptions.RESTClientGetter) *MatchVersionFlags { return &MatchVersionFlags{ Delegate: delegate, } diff --git a/pkg/kubectl/genericclioptions/config_flags.go b/pkg/kubectl/genericclioptions/config_flags.go index 0e0e7a276b..e32e1eee77 100644 --- a/pkg/kubectl/genericclioptions/config_flags.go +++ b/pkg/kubectl/genericclioptions/config_flags.go @@ -54,6 +54,22 @@ const ( var defaultCacheDir = filepath.Join(homedir.HomeDir(), ".kube", "http-cache") +// RESTClientGetter is an interface that the ConfigFlags describe to provide an easier way to mock for commands +// and eliminate the direct coupling to a struct type. Users may wish to duplicate this type in their own packages +// as per the golang type overlapping. +type RESTClientGetter interface { + // ToRESTConfig returns restconfig + ToRESTConfig() (*rest.Config, error) + // ToDiscoveryClient returns discovery client + ToDiscoveryClient() (discovery.CachedDiscoveryInterface, error) + // ToRESTMapper returns a restmapper + ToRESTMapper() (meta.RESTMapper, error) + // ToRawKubeConfigLoader return kubeconfig loader as-is + ToRawKubeConfigLoader() clientcmd.ClientConfig +} + +var _ RESTClientGetter = &ConfigFlags{} + // ConfigFlags composes the set of values necessary // for obtaining a REST client config type ConfigFlags struct { diff --git a/pkg/kubectl/polymorphichelpers/BUILD b/pkg/kubectl/polymorphichelpers/BUILD new file mode 100644 index 0000000000..1b8fad5e89 --- /dev/null +++ b/pkg/kubectl/polymorphichelpers/BUILD @@ -0,0 +1,74 @@ +load("@io_bazel_rules_go//go:def.bzl", "go_library", "go_test") + +go_library( + name = "go_default_library", + srcs = [ + "helpers.go", + "interface.go", + "logsforobject.go", + ], + importpath = "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers", + visibility = ["//visibility:public"], + deps = [ + "//pkg/apis/apps:go_default_library", + "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/core/v1:go_default_library", + "//pkg/apis/extensions:go_default_library", + "//pkg/client/clientset_generated/internalclientset:go_default_library", + "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", + "//pkg/controller:go_default_library", + "//pkg/kubectl/genericclioptions:go_default_library", + "//vendor/k8s.io/api/apps/v1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta1:go_default_library", + "//vendor/k8s.io/api/apps/v1beta2:go_default_library", + "//vendor/k8s.io/api/batch/v1:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/api/extensions/v1beta1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/rest:go_default_library", + ], +) + +go_test( + name = "go_default_test", + srcs = [ + "helpers_test.go", + "logsforobject_test.go", + ], + embed = [":go_default_library"], + deps = [ + "//pkg/apis/apps:go_default_library", + "//pkg/apis/batch:go_default_library", + "//pkg/apis/core:go_default_library", + "//pkg/apis/extensions:go_default_library", + "//pkg/client/clientset_generated/internalclientset/fake:go_default_library", + "//pkg/controller:go_default_library", + "//vendor/k8s.io/api/core/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/api/equality:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/labels:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/util/diff:go_default_library", + "//vendor/k8s.io/apimachinery/pkg/watch:go_default_library", + "//vendor/k8s.io/client-go/testing:go_default_library", + ], +) + +filegroup( + name = "package-srcs", + srcs = glob(["**"]), + tags = ["automanaged"], + visibility = ["//visibility:private"], +) + +filegroup( + name = "all-srcs", + srcs = [":package-srcs"], + tags = ["automanaged"], + visibility = ["//visibility:public"], +) diff --git a/pkg/kubectl/polymorphichelpers/helpers.go b/pkg/kubectl/polymorphichelpers/helpers.go new file mode 100644 index 0000000000..5d9f0c3336 --- /dev/null +++ b/pkg/kubectl/polymorphichelpers/helpers.go @@ -0,0 +1,235 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package polymorphichelpers + +import ( + "fmt" + "sort" + "time" + + appsv1 "k8s.io/api/apps/v1" + appsv1beta1 "k8s.io/api/apps/v1beta1" + appsv1beta2 "k8s.io/api/apps/v1beta2" + batchv1 "k8s.io/api/batch/v1" + "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + extensionsv1beta1 "k8s.io/api/extensions/v1beta1" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/watch" + "k8s.io/kubernetes/pkg/apis/apps" + "k8s.io/kubernetes/pkg/apis/batch" + api "k8s.io/kubernetes/pkg/apis/core" + apiv1 "k8s.io/kubernetes/pkg/apis/core/v1" + "k8s.io/kubernetes/pkg/apis/extensions" + coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" +) + +// GetFirstPod returns a pod matching the namespace and label selector +// and the number of all pods that match the label selector. +func GetFirstPod(client coreclient.PodsGetter, namespace string, selector string, timeout time.Duration, sortBy func([]*v1.Pod) sort.Interface) (*api.Pod, int, error) { + options := metav1.ListOptions{LabelSelector: selector} + + podList, err := client.Pods(namespace).List(options) + if err != nil { + return nil, 0, err + } + pods := []*v1.Pod{} + for i := range podList.Items { + pod := podList.Items[i] + externalPod := &v1.Pod{} + apiv1.Convert_core_Pod_To_v1_Pod(&pod, externalPod, nil) + pods = append(pods, externalPod) + } + if len(pods) > 0 { + sort.Sort(sortBy(pods)) + internalPod := &api.Pod{} + apiv1.Convert_v1_Pod_To_core_Pod(pods[0], internalPod, nil) + return internalPod, len(podList.Items), nil + } + + // Watch until we observe a pod + options.ResourceVersion = podList.ResourceVersion + w, err := client.Pods(namespace).Watch(options) + if err != nil { + return nil, 0, err + } + defer w.Stop() + + condition := func(event watch.Event) (bool, error) { + return event.Type == watch.Added || event.Type == watch.Modified, nil + } + event, err := watch.Until(timeout, w, condition) + if err != nil { + return nil, 0, err + } + pod, ok := event.Object.(*api.Pod) + if !ok { + return nil, 0, fmt.Errorf("%#v is not a pod event", event) + } + return pod, 1, nil +} + +// SelectorsForObject returns the pod label selector for a given object +func SelectorsForObject(object runtime.Object) (namespace string, selector labels.Selector, err error) { + switch t := object.(type) { + case *extensions.ReplicaSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *extensionsv1beta1.ReplicaSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1.ReplicaSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1beta2.ReplicaSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + + case *api.ReplicationController: + namespace = t.Namespace + selector = labels.SelectorFromSet(t.Spec.Selector) + case *corev1.ReplicationController: + namespace = t.Namespace + selector = labels.SelectorFromSet(t.Spec.Selector) + + case *apps.StatefulSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1.StatefulSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1beta1.StatefulSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1beta2.StatefulSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + + case *extensions.DaemonSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *extensionsv1beta1.DaemonSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1.DaemonSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1beta2.DaemonSet: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + + case *extensions.Deployment: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *extensionsv1beta1.Deployment: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1.Deployment: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1beta1.Deployment: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *appsv1beta2.Deployment: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + + case *batch.Job: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + case *batchv1.Job: + namespace = t.Namespace + selector, err = metav1.LabelSelectorAsSelector(t.Spec.Selector) + if err != nil { + return "", nil, fmt.Errorf("invalid label selector: %v", err) + } + + case *api.Service: + namespace = t.Namespace + if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { + return "", nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) + } + selector = labels.SelectorFromSet(t.Spec.Selector) + case *corev1.Service: + namespace = t.Namespace + if t.Spec.Selector == nil || len(t.Spec.Selector) == 0 { + return "", nil, fmt.Errorf("invalid service '%s': Service is defined without a selector", t.Name) + } + selector = labels.SelectorFromSet(t.Spec.Selector) + + default: + return "", nil, fmt.Errorf("selector for %T not implemented", object) + } + + return namespace, selector, nil +} diff --git a/pkg/kubectl/polymorphichelpers/helpers_test.go b/pkg/kubectl/polymorphichelpers/helpers_test.go new file mode 100644 index 0000000000..98a06d5f46 --- /dev/null +++ b/pkg/kubectl/polymorphichelpers/helpers_test.go @@ -0,0 +1,229 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package polymorphichelpers + +import ( + "fmt" + "sort" + "testing" + "time" + + "k8s.io/api/core/v1" + + apiequality "k8s.io/apimachinery/pkg/api/equality" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "k8s.io/apimachinery/pkg/labels" + "k8s.io/apimachinery/pkg/watch" + testcore "k8s.io/client-go/testing" + api "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" + "k8s.io/kubernetes/pkg/controller" +) + +func TestGetFirstPod(t *testing.T) { + labelSet := map[string]string{"test": "selector"} + tests := []struct { + name string + + podList *api.PodList + watching []watch.Event + sortBy func([]*v1.Pod) sort.Interface + + expected *api.Pod + expectedNum int + expectedErr bool + }{ + { + name: "kubectl logs - two ready pods", + podList: newPodList(2, -1, -1, labelSet), + sortBy: func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }, + expected: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: metav1.NamespaceDefault, + CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), + Labels: map[string]string{"test": "selector"}, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Status: api.ConditionTrue, + Type: api.PodReady, + }, + }, + }, + }, + expectedNum: 2, + }, + { + name: "kubectl logs - one unhealthy, one healthy", + podList: newPodList(2, -1, 1, labelSet), + sortBy: func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) }, + expected: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-2", + Namespace: metav1.NamespaceDefault, + CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 1, 0, time.UTC), + Labels: map[string]string{"test": "selector"}, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Status: api.ConditionTrue, + Type: api.PodReady, + }, + }, + ContainerStatuses: []api.ContainerStatus{{RestartCount: 5}}, + }, + }, + expectedNum: 2, + }, + { + name: "kubectl attach - two ready pods", + podList: newPodList(2, -1, -1, labelSet), + sortBy: func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }, + expected: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: metav1.NamespaceDefault, + CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), + Labels: map[string]string{"test": "selector"}, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Status: api.ConditionTrue, + Type: api.PodReady, + }, + }, + }, + }, + expectedNum: 2, + }, + { + name: "kubectl attach - wait for ready pod", + podList: newPodList(1, 1, -1, labelSet), + watching: []watch.Event{ + { + Type: watch.Modified, + Object: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: metav1.NamespaceDefault, + CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), + Labels: map[string]string{"test": "selector"}, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Status: api.ConditionTrue, + Type: api.PodReady, + }, + }, + }, + }, + }, + }, + sortBy: func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) }, + expected: &api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: "pod-1", + Namespace: metav1.NamespaceDefault, + CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, 0, 0, time.UTC), + Labels: map[string]string{"test": "selector"}, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Status: api.ConditionTrue, + Type: api.PodReady, + }, + }, + }, + }, + expectedNum: 1, + }, + } + + for i := range tests { + test := tests[i] + fake := fake.NewSimpleClientset(test.podList) + if len(test.watching) > 0 { + watcher := watch.NewFake() + for _, event := range test.watching { + switch event.Type { + case watch.Added: + go watcher.Add(event.Object) + case watch.Modified: + go watcher.Modify(event.Object) + } + } + fake.PrependWatchReactor("pods", testcore.DefaultWatchReactor(watcher, nil)) + } + selector := labels.Set(labelSet).AsSelector() + + pod, numPods, err := GetFirstPod(fake.Core(), metav1.NamespaceDefault, selector.String(), 1*time.Minute, test.sortBy) + pod.Spec.SecurityContext = nil + if !test.expectedErr && err != nil { + t.Errorf("%s: unexpected error: %v", test.name, err) + continue + } + if test.expectedErr && err == nil { + t.Errorf("%s: expected an error", test.name) + continue + } + if test.expectedNum != numPods { + t.Errorf("%s: expected %d pods, got %d", test.name, test.expectedNum, numPods) + continue + } + if !apiequality.Semantic.DeepEqual(test.expected, pod) { + t.Errorf("%s:\nexpected pod:\n%#v\ngot:\n%#v\n\n", test.name, test.expected, pod) + } + } +} + +func newPodList(count, isUnready, isUnhealthy int, labels map[string]string) *api.PodList { + pods := []api.Pod{} + for i := 0; i < count; i++ { + newPod := api.Pod{ + ObjectMeta: metav1.ObjectMeta{ + Name: fmt.Sprintf("pod-%d", i+1), + Namespace: metav1.NamespaceDefault, + CreationTimestamp: metav1.Date(2016, time.April, 1, 1, 0, i, 0, time.UTC), + Labels: labels, + }, + Status: api.PodStatus{ + Conditions: []api.PodCondition{ + { + Status: api.ConditionTrue, + Type: api.PodReady, + }, + }, + }, + } + pods = append(pods, newPod) + } + if isUnready > -1 && isUnready < count { + pods[isUnready].Status.Conditions[0].Status = api.ConditionFalse + } + if isUnhealthy > -1 && isUnhealthy < count { + pods[isUnhealthy].Status.ContainerStatuses = []api.ContainerStatus{{RestartCount: 5}} + } + return &api.PodList{ + Items: pods, + } +} diff --git a/pkg/kubectl/polymorphichelpers/interface.go b/pkg/kubectl/polymorphichelpers/interface.go new file mode 100644 index 0000000000..88da4ae9e6 --- /dev/null +++ b/pkg/kubectl/polymorphichelpers/interface.go @@ -0,0 +1,31 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package polymorphichelpers + +import ( + "time" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" +) + +// 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) + +// 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 new file mode 100644 index 0000000000..f5a517a1b3 --- /dev/null +++ b/pkg/kubectl/polymorphichelpers/logsforobject.go @@ -0,0 +1,75 @@ +/* +Copyright 2018 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package polymorphichelpers + +import ( + "errors" + "fmt" + "os" + "sort" + "time" + + "k8s.io/api/core/v1" + corev1 "k8s.io/api/core/v1" + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/client-go/rest" + coreinternal "k8s.io/kubernetes/pkg/apis/core" + "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" + "k8s.io/kubernetes/pkg/controller" + "k8s.io/kubernetes/pkg/kubectl/genericclioptions" +) + +func logsForObject(restClientGetter genericclioptions.RESTClientGetter, object, options runtime.Object, timeout time.Duration) (*rest.Request, error) { + clientConfig, err := restClientGetter.ToRESTConfig() + if err != nil { + return nil, err + } + clientset, err := internalclientset.NewForConfig(clientConfig) + if err != nil { + return nil, err + } + return logsForObjectWithClient(clientset, object, options, timeout) +} + +// this is split for easy test-ability +func logsForObjectWithClient(clientset internalclientset.Interface, object, options runtime.Object, timeout time.Duration) (*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.Pod: + return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil + case *corev1.Pod: + return clientset.Core().Pods(t.Namespace).GetLogs(t.Name, opts), nil + } + + namespace, selector, err := SelectorsForObject(object) + if err != nil { + return nil, fmt.Errorf("cannot get the logs from %T: %v", object, err) + } + sortBy := func(pods []*v1.Pod) sort.Interface { return controller.ByLogging(pods) } + pod, numPods, err := GetFirstPod(clientset.Core(), namespace, selector.String(), timeout, sortBy) + if err != nil { + return nil, err + } + 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 +} diff --git a/pkg/kubectl/cmd/util/factory_object_mapping_test.go b/pkg/kubectl/polymorphichelpers/logsforobject_test.go similarity index 86% rename from pkg/kubectl/cmd/util/factory_object_mapping_test.go rename to pkg/kubectl/polymorphichelpers/logsforobject_test.go index c2e763f169..6ed87d910b 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping_test.go +++ b/pkg/kubectl/polymorphichelpers/logsforobject_test.go @@ -14,7 +14,7 @@ See the License for the specific language governing permissions and limitations under the License. */ -package util +package polymorphichelpers import ( "reflect" @@ -30,26 +30,9 @@ import ( "k8s.io/kubernetes/pkg/apis/batch" api "k8s.io/kubernetes/pkg/apis/core" "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/fake" ) -type fakeClientAccessFactory struct { - ClientAccessFactory - - fakeClientset *fake.Clientset -} - -func (f *fakeClientAccessFactory) ClientSet() (internalclientset.Interface, error) { - return f.fakeClientset, nil -} - -func newFakeClientAccessFactory(objs []runtime.Object) *fakeClientAccessFactory { - return &fakeClientAccessFactory{ - fakeClientset: fake.NewSimpleClientset(objs...), - } -} - var ( podsResource = schema.GroupVersionResource{Resource: "pods"} podsKind = schema.GroupVersionKind{Kind: "Pod"} @@ -146,20 +129,19 @@ func TestLogsForObject(t *testing.T) { } for _, test := range tests { - caf := newFakeClientAccessFactory(test.pods) - omf := NewObjectMappingFactory(caf) - _, err := omf.LogsForObject(test.obj, test.opts, 20*time.Second) + fakeClientset := fake.NewSimpleClientset(test.pods...) + _, err := logsForObjectWithClient(fakeClientset, test.obj, test.opts, 20*time.Second) if err != nil { t.Errorf("%s: unexpected error: %v", test.name, err) continue } for i := range test.actions { - if len(caf.fakeClientset.Actions()) < i { + if len(fakeClientset.Actions()) < i { t.Errorf("%s: action %d does not exists in actual actions: %#v", - test.name, i, caf.fakeClientset.Actions()) + test.name, i, fakeClientset.Actions()) continue } - got := caf.fakeClientset.Actions()[i] + got := fakeClientset.Actions()[i] want := test.actions[i] if !reflect.DeepEqual(got, want) { t.Errorf("%s: unexpected action: %s", test.name, diff.ObjectDiff(got, want)) diff --git a/test/e2e/kubectl/BUILD b/test/e2e/kubectl/BUILD index 4f80ab3b42..e17d288d6c 100644 --- a/test/e2e/kubectl/BUILD +++ b/test/e2e/kubectl/BUILD @@ -15,7 +15,7 @@ go_library( importpath = "k8s.io/kubernetes/test/e2e/kubectl", deps = [ "//pkg/controller:go_default_library", - "//pkg/kubectl/cmd/util:go_default_library", + "//pkg/kubectl/polymorphichelpers:go_default_library", "//test/e2e/framework:go_default_library", "//test/e2e/generated:go_default_library", "//test/e2e/scheduling:go_default_library", diff --git a/test/e2e/kubectl/kubectl.go b/test/e2e/kubectl/kubectl.go index 0cd2ac1f59..7088050847 100644 --- a/test/e2e/kubectl/kubectl.go +++ b/test/e2e/kubectl/kubectl.go @@ -58,7 +58,6 @@ import ( genericregistry "k8s.io/apiserver/pkg/registry/generic/registry" clientset "k8s.io/client-go/kubernetes" "k8s.io/kubernetes/pkg/controller" - "k8s.io/kubernetes/pkg/kubectl/cmd/util" "k8s.io/kubernetes/test/e2e/framework" "k8s.io/kubernetes/test/e2e/generated" "k8s.io/kubernetes/test/e2e/scheduling" @@ -67,6 +66,7 @@ import ( . "github.com/onsi/ginkgo" . "github.com/onsi/gomega" + "k8s.io/kubernetes/pkg/kubectl/polymorphichelpers" imageutils "k8s.io/kubernetes/test/utils/image" ) @@ -529,7 +529,7 @@ var _ = SIGDescribe("Kubectl client", func() { ExecOrDie() Expect(runOutput).ToNot(ContainSubstring("stdin closed")) g := func(pods []*v1.Pod) sort.Interface { return sort.Reverse(controller.ActivePods(pods)) } - runTestPod, _, err := util.GetFirstPod(f.InternalClientset.Core(), ns, "run=run-test-3", 1*time.Minute, g) + runTestPod, _, err := polymorphichelpers.GetFirstPod(f.InternalClientset.Core(), ns, "run=run-test-3", 1*time.Minute, g) if err != nil { os.Exit(1) }