From 61673c4b39606fc7e1de9a3cdd4ff5aaaebc0f31 Mon Sep 17 00:00:00 2001 From: deads2k Date: Wed, 2 Nov 2016 12:29:01 -0400 Subject: [PATCH] make kubectl get generic with respect to objects --- pkg/api/meta/help.go | 16 ++ pkg/api/meta/help_test.go | 21 ++ pkg/kubectl/cmd/BUILD | 2 - pkg/kubectl/cmd/delete_test.go | 8 +- pkg/kubectl/cmd/get.go | 83 ++++---- pkg/kubectl/cmd/get_test.go | 290 +++++++++------------------ pkg/kubectl/cmd/testing/fake.go | 11 +- pkg/kubectl/cmd/util/BUILD | 2 - pkg/kubectl/cmd/util/factory.go | 68 +------ pkg/kubectl/cmd/util/helpers.go | 44 ---- pkg/kubectl/custom_column_printer.go | 11 +- pkg/kubectl/kubectl.go | 22 -- pkg/kubectl/resource_printer.go | 127 +++++++++++- pkg/kubectl/sorting_printer.go | 85 +++++++- test/e2e/kubectl.go | 12 +- test/test_owners.csv | 2 +- 16 files changed, 410 insertions(+), 394 deletions(-) diff --git a/pkg/api/meta/help.go b/pkg/api/meta/help.go index 0d733a58a09..e8d25b4fefb 100644 --- a/pkg/api/meta/help.go +++ b/pkg/api/meta/help.go @@ -26,6 +26,14 @@ import ( // IsListType returns true if the provided Object has a slice called Items func IsListType(obj runtime.Object) bool { + // if we're a runtime.Unstructured, check to see if we have an `items` key + // This is a list type for recognition, but other Items type methods will fail on it + // and give you errors. + if unstructured, ok := obj.(*runtime.Unstructured); ok { + _, ok := unstructured.Object["items"] + return ok + } + _, err := GetItemsPtr(obj) return err == nil } @@ -39,6 +47,7 @@ func GetItemsPtr(list runtime.Object) (interface{}, error) { if err != nil { return nil, err } + items := v.FieldByName("Items") if !items.IsValid() { return nil, fmt.Errorf("no Items field in %#v", list) @@ -117,6 +126,13 @@ func SetList(list runtime.Object, objects []runtime.Object) error { slice := reflect.MakeSlice(items.Type(), len(objects), len(objects)) for i := range objects { dest := slice.Index(i) + + // check to see if you're directly assignable + if reflect.TypeOf(objects[i]).AssignableTo(dest.Type()) { + dest.Set(reflect.ValueOf(objects[i])) + continue + } + src, err := conversion.EnforcePtr(objects[i]) if err != nil { return err diff --git a/pkg/api/meta/help_test.go b/pkg/api/meta/help_test.go index fe97d8b5a79..81b6e3aafb9 100644 --- a/pkg/api/meta/help_test.go +++ b/pkg/api/meta/help_test.go @@ -229,6 +229,27 @@ func TestSetListToRuntimeObjectArray(t *testing.T) { } } +func TestSetListToMatchingType(t *testing.T) { + pl := &runtime.UnstructuredList{} + list := []runtime.Object{ + &runtime.Unstructured{Object: map[string]interface{}{"foo": 1}}, + &runtime.Unstructured{Object: map[string]interface{}{"foo": 2}}, + &runtime.Unstructured{Object: map[string]interface{}{"foo": 3}}, + } + err := meta.SetList(pl, list) + if err != nil { + t.Fatalf("Unexpected error %v", err) + } + if e, a := len(list), len(pl.Items); e != a { + t.Fatalf("Expected %v, got %v", e, a) + } + for i := range list { + if e, a := list[i], pl.Items[i]; e != a { + t.Fatalf("%d: unmatched: %s", i, diff.ObjectDiff(e, a)) + } + } +} + func TestSetExtractListRoundTrip(t *testing.T) { fuzzer := fuzz.New().NilChance(0).NumElements(1, 5) for i := 0; i < 5; i++ { diff --git a/pkg/kubectl/cmd/BUILD b/pkg/kubectl/cmd/BUILD index 5a05ef69756..b186ab0dae7 100644 --- a/pkg/kubectl/cmd/BUILD +++ b/pkg/kubectl/cmd/BUILD @@ -182,11 +182,9 @@ go_test( "//pkg/kubectl/cmd/util:go_default_library", "//pkg/kubectl/resource:go_default_library", "//pkg/runtime:go_default_library", - "//pkg/runtime/serializer:go_default_library", "//pkg/runtime/serializer/json:go_default_library", "//pkg/runtime/serializer/streaming:go_default_library", "//pkg/types:go_default_library", - "//pkg/util/diff:go_default_library", "//pkg/util/intstr:go_default_library", "//pkg/util/strings:go_default_library", "//pkg/util/term:go_default_library", diff --git a/pkg/kubectl/cmd/delete_test.go b/pkg/kubectl/cmd/delete_test.go index b4a4b8c4566..5104c6b0134 100644 --- a/pkg/kubectl/cmd/delete_test.go +++ b/pkg/kubectl/cmd/delete_test.go @@ -28,16 +28,12 @@ import ( "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/client/restclient" "k8s.io/kubernetes/pkg/client/restclient/fake" + "k8s.io/kubernetes/pkg/client/typed/dynamic" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/kubectl/resource" - "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/runtime/serializer" ) -var unstructuredSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{ - MediaType: "application/json", - EncodesAsText: true, - Serializer: runtime.UnstructuredJSONScheme}) +var unstructuredSerializer = dynamic.ContentConfig().NegotiatedSerializer func TestDeleteObjectByTuple(t *testing.T) { _, _, rc := testData() diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index 20458f5748f..7fab4c5d473 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -149,7 +149,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ selector := cmdutil.GetFlagString(cmd, "selector") allNamespaces := cmdutil.GetFlagBool(cmd, "all-namespaces") showKind := cmdutil.GetFlagBool(cmd, "show-kind") - mapper, typer := f.Object() + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } printAll := false filterFuncs := f.DefaultResourceFilterFunc() filterOpts := f.DefaultResourceFilterOptions(cmd, allNamespaces) @@ -196,7 +199,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ // handle watch separately since we cannot watch multiple resource types isWatch, isWatchOnly := cmdutil.GetFlagBool(cmd, "watch"), cmdutil.GetFlagBool(cmd, "watch-only") if isWatch || isWatchOnly { - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(selector). @@ -281,7 +284,7 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ return nil } - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), runtime.UnstructuredJSONScheme). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, &options.FilenameOptions). SelectorParam(selector). @@ -302,18 +305,10 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ } if generic { - clientConfig, err := f.ClientConfig() - if err != nil { - return err - } - - // the outermost object will be converted to the output-version, but inner - // objects can use their mappings - version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) - if err != nil { - return err - } - + // we flattened the data from the builder, so we have individual items, but now we'd like to either: + // 1. if there is more than one item, combine them all into a single list + // 2. if there is a single item and that item is a list, leave it as its specific list + // 3. if there is a single item and it is not a a list, leave it as a single item var errs []error singular := false infos, err := r.IntoSingular(&singular).Infos() @@ -332,9 +327,22 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ res = infos[0].ResourceMapping().Resource } - obj, err := resource.AsVersionedObject(infos, !singular, version, f.JSONEncoder()) - if err != nil { - return err + var obj runtime.Object + if singular { + obj = infos[0].Object + } else { + // we have more than one item, so coerce all items into a list + list := &runtime.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{}, + }, + } + for _, info := range infos { + list.Items = append(list.Items, info.Object.(*runtime.Unstructured)) + } + obj = list } isList := meta.IsListType(obj) @@ -343,11 +351,24 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ if err != nil { return err } - filteredObj, err := cmdutil.ObjectListToVersionedObject(items, version) - if err != nil { - return err + + // take the filtered items and create a new list for display + list := &runtime.UnstructuredList{ + Object: map[string]interface{}{ + "kind": "List", + "apiVersion": "v1", + "metadata": map[string]interface{}{}, + }, } - if err := printer.PrintObj(filteredObj, out); err != nil { + if listMeta, err := meta.ListAccessor(obj); err == nil { + list.Object["selfLink"] = listMeta.GetSelfLink() + list.Object["resourceVersion"] = listMeta.GetResourceVersion() + } + + for _, item := range items { + list.Items = append(list.Items, item.(*runtime.Unstructured)) + } + if err := printer.PrintObj(list, out); err != nil { errs = append(errs, err) } @@ -390,24 +411,6 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ } var sorter *kubectl.RuntimeSort if len(sorting) > 0 && len(objs) > 1 { - clientConfig, err := f.ClientConfig() - if err != nil { - return err - } - - version, err := cmdutil.OutputVersion(cmd, clientConfig.GroupVersion) - if err != nil { - return err - } - - for ix := range infos { - objs[ix], err = infos[ix].Mapping.ConvertToVersion(infos[ix].Object, version) - if err != nil { - allErrs = append(allErrs, err) - continue - } - } - // TODO: questionable if sorter, err = kubectl.SortObjects(f.Decoder(true), objs, sorting); err != nil { return err diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 6b10fe50dca..c97c04c486a 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -19,7 +19,6 @@ package cmd import ( "bytes" encjson "encoding/json" - "fmt" "io" "io/ioutil" "net/http" @@ -37,10 +36,8 @@ import ( "k8s.io/kubernetes/pkg/client/restclient/fake" cmdtesting "k8s.io/kubernetes/pkg/kubectl/cmd/testing" "k8s.io/kubernetes/pkg/runtime" - "k8s.io/kubernetes/pkg/runtime/serializer" "k8s.io/kubernetes/pkg/runtime/serializer/json" "k8s.io/kubernetes/pkg/runtime/serializer/streaming" - "k8s.io/kubernetes/pkg/util/diff" "k8s.io/kubernetes/pkg/watch" "k8s.io/kubernetes/pkg/watch/versioned" ) @@ -120,10 +117,11 @@ func testComponentStatusData() *api.ComponentStatusList { // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestGetUnknownSchemaObject(t *testing.T) { - f, tf, codec, ns := cmdtesting.NewTestFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() + _, _, codec, _ := cmdtesting.NewTestFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, } tf.Namespace = "test" @@ -135,114 +133,39 @@ func TestGetUnknownSchemaObject(t *testing.T) { cmd.SetOutput(buf) cmd.Run(cmd, []string{"type", "foo"}) - expected := cmdtesting.NewInternalType("", "", "foo") - actual := tf.Printer.(*testPrinter).Objects[0] - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) + expected := []runtime.Object{cmdtesting.NewInternalType("", "", "foo")} + actual := tf.Printer.(*testPrinter).Objects + if len(actual) != len(expected) { + t.Fatal(actual) } - if buf.String() != fmt.Sprintf("%#v", expected) { - t.Errorf("unexpected output: %s", buf.String()) - } -} - -// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. -// Because api.List is part of the Kube API, resource.Builder has to perform a conversion on -// api.Scheme, which may not have access to all objects, and not all objects are at the same -// internal versioning scheme. This test verifies that two isolated schemes (Test, and api.Scheme) -// can be conjoined into a single output object. -// -// The expected behavior of the `kubectl get` command is: -// 1. objects using unrecognized schemes will always be returned using that scheme/version, "unlikelyversion" in this test; -// 2. if the specified output-version is a recognized, valid Scheme, then the list should use that scheme, and otherwise it will default to the client version, registered.GroupOrDie(api.GroupName).GroupVersion.String() in this test; -// 3a. if the specified output-version is a recognized, valid Scheme, in which the requested object (replicationcontroller) can be represented, then the object should be returned using that version; -// 3b. otherwise if the specified output-version is unrecognized, but the requested object (replicationcontroller) is recognized by the client's codec, then it will be converted to the client version, registered.GroupOrDie(api.GroupName).GroupVersion.String() in this test. -func TestGetUnknownSchemaObjectListGeneric(t *testing.T) { - testCases := map[string]struct { - outputVersion string - listVersion string - testtypeVersion string - rcVersion string - }{ - "handles specific version": { - outputVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - listVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - testtypeVersion: cmdtesting.UnlikelyGV.String(), - rcVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - }, - "handles second specific version": { - outputVersion: "unlikely.group/unlikelyversion", - listVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - testtypeVersion: cmdtesting.UnlikelyGV.String(), - rcVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), // see expected behavior 3b - }, - "handles common version": { - outputVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - listVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - testtypeVersion: cmdtesting.UnlikelyGV.String(), - rcVersion: registered.GroupOrDie(api.GroupName).GroupVersion.String(), - }, - } - for k, test := range testCases { - apiCodec := testapi.Default.Codec() - apiNegotiatedSerializer := testapi.Default.NegotiatedSerializer() - regularClient := &fake.RESTClient{ - NegotiatedSerializer: apiNegotiatedSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(apiCodec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})}, nil - }), + for i, obj := range actual { + expectedJSON := runtime.EncodeOrDie(codec, expected[i]) + expectedMap := map[string]interface{}{} + if err := encjson.Unmarshal([]byte(expectedJSON), &expectedMap); err != nil { + t.Fatal(err) } - f, tf, codec := cmdtesting.NewMixedFactory(regularClient) - negotiatedSerializer := serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec}) - tf.Printer = &testPrinter{} - tf.Client = &fake.RESTClient{ - NegotiatedSerializer: negotiatedSerializer, - Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { - return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, nil - }), + actualJSON := runtime.EncodeOrDie(api.Codecs.LegacyCodec(), obj) + actualMap := map[string]interface{}{} + if err := encjson.Unmarshal([]byte(actualJSON), &actualMap); err != nil { + t.Fatal(err) } - tf.Namespace = "test" - tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: ®istered.GroupOrDie(api.GroupName).GroupVersion}} - buf := bytes.NewBuffer([]byte{}) - errBuf := bytes.NewBuffer([]byte{}) - cmd := NewCmdGet(f, buf, errBuf) - cmd.SetOutput(buf) - cmd.Flags().Set("output", "json") - cmd.Flags().Set("output-version", test.outputVersion) - err := RunGet(f, buf, errBuf, cmd, []string{"type/foo", "replicationcontrollers/foo"}, &GetOptions{}) - if err != nil { - t.Errorf("%s: unexpected error: %v", k, err) - continue - } - out := make(map[string]interface{}) - if err := encjson.Unmarshal(buf.Bytes(), &out); err != nil { - t.Errorf("%s: unexpected error: %v\n%s", k, err, buf.String()) - continue - } - if out["apiVersion"] != test.listVersion { - t.Errorf("%s: unexpected list: %#v", k, out) - } - arr := out["items"].([]interface{}) - if arr[0].(map[string]interface{})["apiVersion"] != test.testtypeVersion { - t.Errorf("%s: unexpected list: %#v", k, out) - } - if arr[1].(map[string]interface{})["apiVersion"] != test.rcVersion { - t.Errorf("%s: unexpected list: %#v", k, out) + if !reflect.DeepEqual(expectedMap, actualMap) { + t.Errorf("unexpected object: \n%#v\n%#v", expectedMap, actualMap) } } } // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestGetSchemaObject(t *testing.T) { - f, tf, _, _ := cmdtesting.NewTestFactory() + f, tf, _, _ := cmdtesting.NewAPIFactory() tf.Mapper = testapi.Default.RESTMapper() tf.Typer = api.Scheme codec := testapi.Default.Codec() - ns := testapi.Default.NegotiatedSerializer() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &api.ReplicationController{ObjectMeta: api.ObjectMeta{Name: "foo"}})}, } tf.Namespace = "test" @@ -261,10 +184,10 @@ func TestGetSchemaObject(t *testing.T) { func TestGetObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } tf.Namespace = "test" @@ -276,10 +199,8 @@ func TestGetObjects(t *testing.T) { cmd.Run(cmd, []string{"pods", "foo"}) expected := []runtime.Object{&pods.Items[0]} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -306,10 +227,10 @@ func TestGetSortedObjects(t *testing.T) { }, } - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } tf.Namespace = "test" @@ -327,23 +248,37 @@ func TestGetSortedObjects(t *testing.T) { // expect sorted: a,b,c expected := []runtime.Object{&pods.Items[2], &pods.Items[1], &pods.Items[0]} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } +} +func verifyObjects(t *testing.T, expected, actual []runtime.Object) { + if len(actual) != len(expected) { + t.Fatal(actual) + } + for i, obj := range actual { + actualObj, err := runtime.Decode( + api.Codecs.UniversalDecoder(), + []byte(runtime.EncodeOrDie(api.Codecs.LegacyCodec(), obj))) + if err != nil { + t.Fatal(err) + } + if !api.Semantic.DeepEqual(expected[i], actualObj) { + t.Errorf("unexpected object: \n%#v\n%#v", expected[i], actualObj) + } + } } func TestGetObjectsIdentifiedByFile(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } tf.Namespace = "test" @@ -356,10 +291,8 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { cmd.Run(cmd, []string{}) expected := []runtime.Object{&pods.Items[0]} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -368,10 +301,10 @@ func TestGetObjectsIdentifiedByFile(t *testing.T) { func TestGetListObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } tf.Namespace = "test" @@ -384,12 +317,10 @@ func TestGetListObjects(t *testing.T) { expected, err := extractResourceList([]runtime.Object{pods}) if err != nil { - t.Fatalf("unexpected error: %v", err) - } - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: expected %#v, got %#v", expected, actual) + t.Fatal(err) } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -412,10 +343,10 @@ func extractResourceList(objs []runtime.Object) ([]runtime.Object, error) { func TestGetAllListObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } tf.Namespace = "test" @@ -429,12 +360,10 @@ func TestGetAllListObjects(t *testing.T) { expected, err := extractResourceList([]runtime.Object{pods}) if err != nil { - t.Fatalf("unexpected error: %v", err) - } - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v %#v", expected, actual) + t.Fatal(err) } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -443,10 +372,10 @@ func TestGetAllListObjects(t *testing.T) { func TestGetListComponentStatus(t *testing.T) { statuses := testComponentStatusData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, statuses)}, } tf.Namespace = "test" @@ -459,12 +388,10 @@ func TestGetListComponentStatus(t *testing.T) { expected, err := extractResourceList([]runtime.Object{statuses}) if err != nil { - t.Fatalf("unexpected error: %v", err) - } - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: expected %#v, got %#v", expected, actual) + t.Fatal(err) } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -473,10 +400,10 @@ func TestGetListComponentStatus(t *testing.T) { func TestGetMultipleTypeObjects(t *testing.T) { pods, svc, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods": @@ -499,12 +426,10 @@ func TestGetMultipleTypeObjects(t *testing.T) { expected, err := extractResourceList([]runtime.Object{pods, svc}) if err != nil { - t.Fatalf("unexpected error: %v", err) - } - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) + t.Fatal(err) } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -513,10 +438,10 @@ func TestGetMultipleTypeObjects(t *testing.T) { func TestGetMultipleTypeObjectsAsList(t *testing.T) { pods, svc, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods": @@ -574,10 +499,10 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { pods, svc, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { if req.URL.Query().Get(unversioned.LabelSelectorQueryParam(registered.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" { t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) @@ -605,12 +530,10 @@ func TestGetMultipleTypeObjectsWithSelector(t *testing.T) { expected, err := extractResourceList([]runtime.Object{pods, svc}) if err != nil { - t.Fatalf("unexpected error: %v", err) - } - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) + t.Fatal(err) } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -627,10 +550,10 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { }, } - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/nodes/foo": @@ -653,10 +576,8 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { cmd.Run(cmd, []string{"services/bar", "node/foo"}) expected := []runtime.Object{&svc.Items[0], node} - actual := tf.Printer.(*testPrinter).Objects - if !api.Semantic.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %s", diff.ObjectDiff(expected, actual)) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -665,10 +586,10 @@ func TestGetMultipleTypeObjectsWithDirectReference(t *testing.T) { func TestGetByNameForcesFlag(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, &pods.Items[0])}, } tf.Namespace = "test" @@ -758,7 +679,7 @@ func watchTestData() ([]api.Pod, []watch.Event) { func TestWatchSelector(t *testing.T) { pods, events := watchTestData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} podList := &api.PodList{ Items: pods, @@ -767,7 +688,7 @@ func TestWatchSelector(t *testing.T) { }, } tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { if req.URL.Query().Get(unversioned.LabelSelectorQueryParam(registered.GroupOrDie(api.GroupName).GroupVersion.String())) != "a=b" { t.Fatalf("unexpected request: %#v\n%#v", req.URL, req) @@ -795,10 +716,8 @@ func TestWatchSelector(t *testing.T) { cmd.Run(cmd, []string{"pods"}) expected := []runtime.Object{podList, events[2].Object, events[3].Object} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object:\nExpected: %#v\n\nGot: %#v\n\n", expected, actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -807,10 +726,10 @@ func TestWatchSelector(t *testing.T) { func TestWatchResource(t *testing.T) { pods, events := watchTestData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods/foo": @@ -834,10 +753,8 @@ func TestWatchResource(t *testing.T) { cmd.Run(cmd, []string{"pods", "foo"}) expected := []runtime.Object{&pods[1], events[2].Object, events[3].Object} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object:\nExpected: %#v\n\nGot: %#v\n\n", expected, actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -846,10 +763,10 @@ func TestWatchResource(t *testing.T) { func TestWatchResourceIdentifiedByFile(t *testing.T) { pods, events := watchTestData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/replicationcontrollers/cassandra": @@ -874,10 +791,7 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { cmd.Run(cmd, []string{}) expected := []runtime.Object{&pods[1], events[2].Object, events[3].Object} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("expected object: %#v unexpected object: %#v", expected, actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) if len(buf.String()) == 0 { t.Errorf("unexpected empty output") @@ -887,10 +801,10 @@ func TestWatchResourceIdentifiedByFile(t *testing.T) { func TestWatchOnlyResource(t *testing.T) { pods, events := watchTestData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods/foo": @@ -914,10 +828,8 @@ func TestWatchOnlyResource(t *testing.T) { cmd.Run(cmd, []string{"pods", "foo"}) expected := []runtime.Object{events[2].Object, events[3].Object} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } @@ -926,7 +838,7 @@ func TestWatchOnlyResource(t *testing.T) { func TestWatchOnlyList(t *testing.T) { pods, events := watchTestData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() tf.Printer = &testPrinter{} podList := &api.PodList{ Items: pods, @@ -935,7 +847,7 @@ func TestWatchOnlyList(t *testing.T) { }, } tf.Client = &fake.RESTClient{ - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch req.URL.Path { case "/namespaces/test/pods": @@ -959,10 +871,8 @@ func TestWatchOnlyList(t *testing.T) { cmd.Run(cmd, []string{"pods"}) expected := []runtime.Object{events[2].Object, events[3].Object} - actual := tf.Printer.(*testPrinter).Objects - if !reflect.DeepEqual(expected, actual) { - t.Errorf("unexpected object: %#v", actual) - } + verifyObjects(t, expected, tf.Printer.(*testPrinter).Objects) + if len(buf.String()) == 0 { t.Errorf("unexpected empty output") } diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index ae3256c8ee5..f690e87ce4a 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -182,7 +182,11 @@ func (f *FakeFactory) Object() (meta.RESTMapper, runtime.ObjectTyper) { } func (f *FakeFactory) UnstructuredObject() (meta.RESTMapper, runtime.ObjectTyper, error) { - return nil, nil, nil + groupResources := testDynamicResources() + mapper := discovery.NewRESTMapper(groupResources, meta.InterfacesForUnstructured) + typer := discovery.NewUnstructuredObjectTyper(groupResources) + + return cmdutil.NewShortcutExpander(mapper, nil), typer, nil } func (f *FakeFactory) Decoder(bool) runtime.Decoder { @@ -379,7 +383,7 @@ func (f *fakeMixedFactory) ClientForMapping(m *meta.RESTMapping) (resource.RESTC } func NewMixedFactory(apiClient resource.RESTClient) (cmdutil.Factory, *TestFactory, runtime.Codec) { - f, t, c, _ := NewTestFactory() + f, t, c, _ := NewAPIFactory() return &fakeMixedFactory{ Factory: f, tf: t, @@ -545,6 +549,9 @@ func testDynamicResources() []*discovery.APIGroupResources { {Name: "pods", Namespaced: true, Kind: "Pod"}, {Name: "services", Namespaced: true, Kind: "Service"}, {Name: "replicationcontrollers", Namespaced: true, Kind: "ReplicationController"}, + {Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"}, + {Name: "nodes", Namespaced: false, Kind: "Node"}, + {Name: "type", Namespaced: false, Kind: "Type"}, }, }, }, diff --git a/pkg/kubectl/cmd/util/BUILD b/pkg/kubectl/cmd/util/BUILD index 8cdef866a2a..96bd711a8c9 100644 --- a/pkg/kubectl/cmd/util/BUILD +++ b/pkg/kubectl/cmd/util/BUILD @@ -29,12 +29,10 @@ go_library( "//pkg/api/service:go_default_library", "//pkg/api/unversioned:go_default_library", "//pkg/api/validation:go_default_library", - "//pkg/apimachinery:go_default_library", "//pkg/apimachinery/registered:go_default_library", "//pkg/apis/apps:go_default_library", "//pkg/apis/batch:go_default_library", "//pkg/apis/extensions:go_default_library", - "//pkg/apis/extensions/v1beta1:go_default_library", "//pkg/client/clientset_generated/internalclientset:go_default_library", "//pkg/client/clientset_generated/internalclientset/typed/core/internalversion:go_default_library", "//pkg/client/restclient:go_default_library", diff --git a/pkg/kubectl/cmd/util/factory.go b/pkg/kubectl/cmd/util/factory.go index e5340ec71d0..8ee299327fa 100644 --- a/pkg/kubectl/cmd/util/factory.go +++ b/pkg/kubectl/cmd/util/factory.go @@ -33,23 +33,19 @@ import ( "time" "github.com/emicklei/go-restful/swagger" - "github.com/golang/glog" "github.com/spf13/cobra" "github.com/spf13/pflag" "k8s.io/kubernetes/federation/apis/federation" "k8s.io/kubernetes/pkg/api" - apierrors "k8s.io/kubernetes/pkg/api/errors" "k8s.io/kubernetes/pkg/api/meta" "k8s.io/kubernetes/pkg/api/service" "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/api/validation" - "k8s.io/kubernetes/pkg/apimachinery" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/apps" "k8s.io/kubernetes/pkg/apis/batch" "k8s.io/kubernetes/pkg/apis/extensions" - extensionsv1beta1 "k8s.io/kubernetes/pkg/apis/extensions/v1beta1" "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset" coreclient "k8s.io/kubernetes/pkg/client/clientset_generated/internalclientset/typed/core/internalversion" "k8s.io/kubernetes/pkg/client/restclient" @@ -339,21 +335,10 @@ func (f *factory) Object() (meta.RESTMapper, runtime.ObjectTyper) { mapper := registered.RESTMapper() discoveryClient, err := discovery.NewDiscoveryClientForConfig(cfg) if err == nil { - // register third party resources with the api machinery groups. This probably should be done, but - // its consistent with old code, so we'll start with it. - if err := registerThirdPartyResources(discoveryClient); err != nil { - glog.V(1).Infof("Unable to register third party resources: %v", err) - } - // ThirdPartyResourceData is special. It's not discoverable, but needed for thirdparty resource listing - // TODO eliminate this once we're truly generic. - thirdPartyResourceDataMapper := meta.NewDefaultRESTMapper([]unversioned.GroupVersion{extensionsv1beta1.SchemeGroupVersion}, registered.InterfacesFor) - thirdPartyResourceDataMapper.Add(extensionsv1beta1.SchemeGroupVersion.WithKind("ThirdPartyResourceData"), meta.RESTScopeNamespace) - mapper = meta.FirstHitRESTMapper{ MultiRESTMapper: meta.MultiRESTMapper{ discovery.NewDeferredDiscoveryRESTMapper(discoveryClient, registered.InterfacesFor), - thirdPartyResourceDataMapper, // needed for TPR printing - registered.RESTMapper(), // hardcoded fall back + registered.RESTMapper(), // hardcoded fall back }, } } @@ -1331,54 +1316,3 @@ func (f *factory) SuggestedPodTemplateResources() []unversioned.GroupResource { {Resource: "replicaset"}, } } - -// registerThirdPartyResources inspects the discovery endpoint to find thirdpartyresources in the discovery doc -// and then registers them with the apimachinery code. I think this is done so that scheme/codec stuff works, -// but I really don't know. Feels like this code should go away once kubectl is completely generic for generic -// CRUD -func registerThirdPartyResources(discoveryClient discovery.DiscoveryInterface) error { - var versions []unversioned.GroupVersion - var gvks []unversioned.GroupVersionKind - var err error - retries := 3 - for i := 0; i < retries; i++ { - versions, gvks, err = GetThirdPartyGroupVersions(discoveryClient) - // Retry if we got a NotFound error, because user may delete - // a thirdparty group when the GetThirdPartyGroupVersions is - // running. - if err == nil || !apierrors.IsNotFound(err) { - break - } - } - if err != nil { - return err - } - - groupsMap := map[string][]unversioned.GroupVersion{} - for _, version := range versions { - groupsMap[version.Group] = append(groupsMap[version.Group], version) - } - for group, versionList := range groupsMap { - preferredExternalVersion := versionList[0] - - thirdPartyMapper, err := kubectl.NewThirdPartyResourceMapper(versionList, getGroupVersionKinds(gvks, group)) - if err != nil { - return err - } - - accessor := meta.NewAccessor() - groupMeta := apimachinery.GroupMeta{ - GroupVersion: preferredExternalVersion, - GroupVersions: versionList, - RESTMapper: thirdPartyMapper, - SelfLinker: runtime.SelfLinker(accessor), - InterfacesFor: makeInterfacesFor(versionList), - } - if err := registered.RegisterGroup(groupMeta); err != nil { - return err - } - registered.AddThirdPartyAPIGroupVersions(versionList...) - } - - return nil -} diff --git a/pkg/kubectl/cmd/util/helpers.go b/pkg/kubectl/cmd/util/helpers.go index 5c8e972bd6b..e5130c7d4e8 100644 --- a/pkg/kubectl/cmd/util/helpers.go +++ b/pkg/kubectl/cmd/util/helpers.go @@ -34,7 +34,6 @@ import ( "k8s.io/kubernetes/pkg/api/unversioned" "k8s.io/kubernetes/pkg/apimachinery/registered" "k8s.io/kubernetes/pkg/apis/extensions" - "k8s.io/kubernetes/pkg/client/typed/discovery" "k8s.io/kubernetes/pkg/client/unversioned/clientcmd" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/resource" @@ -551,49 +550,6 @@ func ShouldRecord(cmd *cobra.Command, info *resource.Info) bool { return GetRecordFlag(cmd) || (ContainsChangeCause(info) && !cmd.Flags().Changed("record")) } -// GetThirdPartyGroupVersions returns the thirdparty "group/versions"s and -// resources supported by the server. A user may delete a thirdparty resource -// when this function is running, so this function may return a "NotFound" error -// due to the race. -func GetThirdPartyGroupVersions(discovery discovery.DiscoveryInterface) ([]unversioned.GroupVersion, []unversioned.GroupVersionKind, error) { - result := []unversioned.GroupVersion{} - gvks := []unversioned.GroupVersionKind{} - - groupList, err := discovery.ServerGroups() - if err != nil { - // On forbidden or not found, just return empty lists. - if kerrors.IsForbidden(err) || kerrors.IsNotFound(err) { - return result, gvks, nil - } - - return nil, nil, err - } - - for ix := range groupList.Groups { - group := &groupList.Groups[ix] - for jx := range group.Versions { - gv, err2 := unversioned.ParseGroupVersion(group.Versions[jx].GroupVersion) - if err2 != nil { - return nil, nil, err - } - // Skip GroupVersionKinds that have been statically registered. - if registered.IsRegisteredVersion(gv) { - continue - } - result = append(result, gv) - - resourceList, err := discovery.ServerResourcesForGroupVersion(group.Versions[jx].GroupVersion) - if err != nil { - return nil, nil, err - } - for kx := range resourceList.APIResources { - gvks = append(gvks, gv.WithKind(resourceList.APIResources[kx].Kind)) - } - } - } - return result, gvks, nil -} - func AddInclude3rdPartyFlags(cmd *cobra.Command) { cmd.Flags().Bool("include-extended-apis", true, "If true, include definitions of new APIs via calls to the API server. [default true]") cmd.Flags().MarkDeprecated("include-extended-apis", "No longer required.") diff --git a/pkg/kubectl/custom_column_printer.go b/pkg/kubectl/custom_column_printer.go index 30855d55efa..a73c99d9fd2 100644 --- a/pkg/kubectl/custom_column_printer.go +++ b/pkg/kubectl/custom_column_printer.go @@ -206,9 +206,18 @@ func (s *CustomColumnsPrinter) printOneObject(obj runtime.Object, parsers []*jso } } } + for ix := range parsers { parser := parsers[ix] - values, err := parser.FindResults(reflect.ValueOf(obj).Elem().Interface()) + + var values [][]reflect.Value + var err error + if unstructured, ok := obj.(*runtime.Unstructured); ok { + values, err = parser.FindResults(unstructured.Object) + } else { + values, err = parser.FindResults(reflect.ValueOf(obj).Elem().Interface()) + } + if err != nil { return err } diff --git a/pkg/kubectl/kubectl.go b/pkg/kubectl/kubectl.go index b8f5580d49b..80a050a91df 100644 --- a/pkg/kubectl/kubectl.go +++ b/pkg/kubectl/kubectl.go @@ -48,28 +48,6 @@ func makeImageList(spec *api.PodSpec) string { return strings.Join(listOfImages(spec), ",") } -func NewThirdPartyResourceMapper(gvs []unversioned.GroupVersion, gvks []unversioned.GroupVersionKind) (meta.RESTMapper, error) { - mapper := meta.NewDefaultRESTMapper(gvs, func(gv unversioned.GroupVersion) (*meta.VersionInterfaces, error) { - for ix := range gvs { - if gvs[ix].Group == gv.Group && gvs[ix].Version == gv.Version { - return &meta.VersionInterfaces{ - ObjectConvertor: api.Scheme, - MetadataAccessor: meta.NewAccessor(), - }, nil - } - } - groupVersions := make([]string, 0, len(gvs)) - for ix := range gvs { - groupVersions = append(groupVersions, gvs[ix].String()) - } - return nil, fmt.Errorf("unsupported storage version: %s (valid: %s)", gv.String(), strings.Join(groupVersions, ", ")) - }) - for ix := range gvks { - mapper.Add(gvks[ix], meta.RESTScopeNamespace) - } - return mapper, nil -} - // OutputVersionMapper is a RESTMapper that will prefer mappings that // correspond to a preferred output version (if feasible) type OutputVersionMapper struct { diff --git a/pkg/kubectl/resource_printer.go b/pkg/kubectl/resource_printer.go index afb40ab31c6..da9fcd23a83 100644 --- a/pkg/kubectl/resource_printer.go +++ b/pkg/kubectl/resource_printer.go @@ -241,8 +241,6 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { return nil } - // TODO: this is wrong, runtime.Unknown and runtime.Unstructured are not handled properly here. - name := "" if acc, err := meta.Accessor(obj); err == nil { if n := acc.GetName(); len(n) > 0 { @@ -250,12 +248,22 @@ func (p *NamePrinter) PrintObj(obj runtime.Object, w io.Writer) error { } } - if gvks, _, err := p.Typer.ObjectKinds(obj); err == nil { - // TODO: this is wrong, it assumes that meta knows about all Kinds - should take a RESTMapper - _, resource := meta.KindToResource(gvks[0]) - fmt.Fprintf(w, "%s/%s\n", resource.Resource, name) + if kind := obj.GetObjectKind().GroupVersionKind(); len(kind.Kind) == 0 { + // this is the old code. It's unnecessary on decoded external objects, but on internal objects + // you may have to do it. Tests are definitely calling it with internals and I'm not sure who else + // is + if gvks, _, err := p.Typer.ObjectKinds(obj); err == nil { + // TODO: this is wrong, it assumes that meta knows about all Kinds - should take a RESTMapper + _, resource := meta.KindToResource(gvks[0]) + fmt.Fprintf(w, "%s/%s\n", resource.Resource, name) + } else { + fmt.Fprintf(w, "/%s\n", name) + } + } else { - fmt.Fprintf(w, "/%s\n", name) + // TODO: this is wrong, it assumes that meta knows about all Kinds - should take a RESTMapper + _, resource := meta.KindToResource(kind) + fmt.Fprintf(w, "%s/%s\n", resource.Resource, name) } return nil @@ -2251,6 +2259,18 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er w = GetNewTabWriter(output) defer w.Flush() } + + // check if the object is unstructured. If so, let's attempt to convert it to a type we can understand before + // trying to print, since the printers are keyed by type. This is extremely expensive. + switch obj.(type) { + case *runtime.Unstructured, *runtime.Unknown: + if objBytes, err := runtime.Encode(api.Codecs.LegacyCodec(), obj); err == nil { + if decodedObj, err := runtime.Decode(api.Codecs.UniversalDecoder(), objBytes); err == nil { + obj = decodedObj + } + } + } + t := reflect.TypeOf(obj) if handler := h.handlerMap[t]; handler != nil { if !h.options.NoHeaders && t != h.lastType { @@ -2271,9 +2291,78 @@ func (h *HumanReadablePrinter) PrintObj(obj runtime.Object, output io.Writer) er } return resultValue.Interface().(error) } + + // we don't recognize this type, but we can still attempt to print some reasonable information about. + unstructured, ok := obj.(*runtime.Unstructured) + if !ok { + return fmt.Errorf("error: unknown type %#v", obj) + } + + if _, err := meta.Accessor(obj); err == nil { + if !h.options.NoHeaders && t != h.lastType { + headers := []string{"NAME", "KIND"} + headers = append(headers, formatLabelHeaders(h.options.ColumnLabels)...) + // LABELS is always the last column. + headers = append(headers, formatShowLabelsHeader(h.options.ShowLabels, t)...) + if h.options.WithNamespace { + headers = append(withNamespacePrefixColumns, headers...) + } + h.printHeader(headers, w) + h.lastType = t + } + // if the error isn't nil, report the "I don't recognize this" error + if err := printUnstructured(unstructured, w, h.options); err != nil { + return err + } + return nil + } + + // we failed all reasonable printing efforts, report failure return fmt.Errorf("error: unknown type %#v", obj) } +func printUnstructured(unstructured *runtime.Unstructured, w io.Writer, options PrintOptions) error { + metadata, err := meta.Accessor(unstructured) + if err != nil { + return err + } + + if options.WithNamespace { + if _, err := fmt.Fprintf(w, "%s\t", metadata.GetNamespace()); err != nil { + return err + } + } + + kind := "" + if objKind, ok := unstructured.Object["kind"]; ok { + if str, ok := objKind.(string); ok { + kind = str + } + } + if objAPIVersion, ok := unstructured.Object["apiVersion"]; ok { + if str, ok := objAPIVersion.(string); ok { + version, err := unversioned.ParseGroupVersion(str) + if err != nil { + return err + } + kind = kind + "." + version.Version + "." + version.Group + } + } + name := formatResourceName(options.Kind, metadata.GetName(), options.WithKind) + + if _, err := fmt.Fprintf(w, "%s\t%s", name, kind); err != nil { + return err + } + if _, err := fmt.Fprint(w, AppendLabels(metadata.GetLabels(), options.ColumnLabels)); err != nil { + return err + } + if _, err := fmt.Fprint(w, AppendAllLabels(options.ShowLabels, metadata.GetLabels())); err != nil { + return err + } + + return nil +} + // TemplatePrinter is an implementation of ResourcePrinter which formats data with a Go Template. type TemplatePrinter struct { rawTemplate string @@ -2299,10 +2388,18 @@ func (p *TemplatePrinter) AfterPrint(w io.Writer, res string) error { // PrintObj formats the obj with the Go Template. func (p *TemplatePrinter) PrintObj(obj runtime.Object, w io.Writer) error { - data, err := json.Marshal(obj) + var data []byte + var err error + if unstructured, ok := obj.(*runtime.Unstructured); ok { + data, err = json.Marshal(unstructured.Object) + } else { + data, err = json.Marshal(obj) + + } if err != nil { return err } + out := map[string]interface{}{} if err := json.Unmarshal(data, &out); err != nil { return err @@ -2462,6 +2559,20 @@ func (j *JSONPathPrinter) PrintObj(obj runtime.Object, w io.Writer) error { } } + if unknown, ok := obj.(*runtime.Unknown); ok { + data, err := json.Marshal(unknown) + if err != nil { + return err + } + queryObj = map[string]interface{}{} + if err := json.Unmarshal(data, &queryObj); err != nil { + return err + } + } + if unstructured, ok := obj.(*runtime.Unstructured); ok { + queryObj = unstructured.Object + } + if err := j.JSONPath.Execute(w, queryObj); err != nil { fmt.Fprintf(w, "Error executing template: %v. Printing more information for debugging the template:\n", err) fmt.Fprintf(w, "\ttemplate was:\n\t\t%v\n", j.rawTemplate) diff --git a/pkg/kubectl/sorting_printer.go b/pkg/kubectl/sorting_printer.go index 8e25f272471..d2fcb451f1d 100644 --- a/pkg/kubectl/sorting_printer.go +++ b/pkg/kubectl/sorting_printer.go @@ -109,7 +109,13 @@ func SortObjects(decoder runtime.Decoder, objs []runtime.Object, fieldInput stri } } - values, err := parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface()) + var values [][]reflect.Value + if unstructured, ok := objs[0].(*runtime.Unstructured); ok { + values, err = parser.FindResults(unstructured.Object) + } else { + values, err = parser.FindResults(reflect.ValueOf(objs[0]).Elem().Interface()) + } + if err != nil { return nil, err } @@ -182,6 +188,66 @@ func isLess(i, j reflect.Value) (bool, error) { } } return true, nil + + case reflect.Interface: + switch itype := i.Interface().(type) { + case uint8: + if jtype, ok := j.Interface().(uint8); ok { + return itype < jtype, nil + } + case uint16: + if jtype, ok := j.Interface().(uint16); ok { + return itype < jtype, nil + } + case uint32: + if jtype, ok := j.Interface().(uint32); ok { + return itype < jtype, nil + } + case uint64: + if jtype, ok := j.Interface().(uint64); ok { + return itype < jtype, nil + } + case int8: + if jtype, ok := j.Interface().(int8); ok { + return itype < jtype, nil + } + case int16: + if jtype, ok := j.Interface().(int16); ok { + return itype < jtype, nil + } + case int32: + if jtype, ok := j.Interface().(int32); ok { + return itype < jtype, nil + } + case int64: + if jtype, ok := j.Interface().(int64); ok { + return itype < jtype, nil + } + case uint: + if jtype, ok := j.Interface().(uint); ok { + return itype < jtype, nil + } + case int: + if jtype, ok := j.Interface().(int); ok { + return itype < jtype, nil + } + case float32: + if jtype, ok := j.Interface().(float32); ok { + return itype < jtype, nil + } + case float64: + if jtype, ok := j.Interface().(float64); ok { + return itype < jtype, nil + } + case string: + if jtype, ok := j.Interface().(string); ok { + return itype < jtype, nil + } + default: + return false, fmt.Errorf("unsortable type: %T", itype) + } + return false, fmt.Errorf("unsortable interface: %v", i.Kind()) + default: return false, fmt.Errorf("unsortable type: %v", i.Kind()) } @@ -194,11 +260,24 @@ func (r *RuntimeSort) Less(i, j int) bool { parser := jsonpath.New("sorting") parser.Parse(r.field) - iValues, err := parser.FindResults(reflect.ValueOf(iObj).Elem().Interface()) + var iValues [][]reflect.Value + var jValues [][]reflect.Value + var err error + + if unstructured, ok := iObj.(*runtime.Unstructured); ok { + iValues, err = parser.FindResults(unstructured.Object) + } else { + iValues, err = parser.FindResults(reflect.ValueOf(iObj).Elem().Interface()) + } if err != nil { glog.Fatalf("Failed to get i values for %#v using %s (%#v)", iObj, r.field, err) } - jValues, err := parser.FindResults(reflect.ValueOf(jObj).Elem().Interface()) + + if unstructured, ok := jObj.(*runtime.Unstructured); ok { + jValues, err = parser.FindResults(unstructured.Object) + } else { + jValues, err = parser.FindResults(reflect.ValueOf(jObj).Elem().Interface()) + } if err != nil { glog.Fatalf("Failed to get j values for %#v using %s (%v)", jObj, r.field, err) } diff --git a/test/e2e/kubectl.go b/test/e2e/kubectl.go index ce04e54d4a6..b0b8ba0683b 100644 --- a/test/e2e/kubectl.go +++ b/test/e2e/kubectl.go @@ -551,25 +551,25 @@ var _ = framework.KubeDescribe("Kubectl client", func() { By("checking the result") forEachReplicationController(c, ns, "app", "redis", validateReplicationControllerConfiguration) }) - It("should reuse nodePort when apply to an existing SVC", func() { + It("should reuse port when apply to an existing SVC", func() { serviceJson := readTestFileOrDie(redisServiceFilename) nsFlag := fmt.Sprintf("--namespace=%v", ns) By("creating Redis SVC") framework.RunKubectlOrDieInput(string(serviceJson[:]), "create", "-f", "-", nsFlag) - By("getting the original nodePort") - originalNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].nodePort}") + By("getting the original port") + originalNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].port}") By("applying the same configuration") framework.RunKubectlOrDieInput(string(serviceJson[:]), "apply", "-f", "-", nsFlag) - By("getting the nodePort after applying configuration") - currentNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].nodePort}") + By("getting the port after applying configuration") + currentNodePort := framework.RunKubectlOrDie("get", "service", "redis-master", nsFlag, "-o", "jsonpath={.spec.ports[0].port}") By("checking the result") if originalNodePort != currentNodePort { - framework.Failf("nodePort should keep the same") + framework.Failf("port should keep the same") } }) }) diff --git a/test/test_owners.csv b/test/test_owners.csv index b6be9f1ce2b..760225e2f23 100644 --- a/test/test_owners.csv +++ b/test/test_owners.csv @@ -181,7 +181,7 @@ Kubectl alpha client Kubectl run ScheduledJob should create a ScheduledJob,karga Kubectl client Guestbook application should create and stop a working application,pwittrock,0 Kubectl client Kubectl api-versions should check if v1 is in available api versions,pwittrock,0 Kubectl client Kubectl apply should apply a new configuration to an existing RC,pwittrock,0 -Kubectl client Kubectl apply should reuse nodePort when apply to an existing SVC,pwittrock,0 +Kubectl client Kubectl apply should reuse port when apply to an existing SVC,deads2k,0 Kubectl client Kubectl cluster-info should check if Kubernetes master services is included in cluster-info,pwittrock,0 Kubectl client Kubectl create quota should create a quota with scopes,jdef,1 Kubectl client Kubectl create quota should create a quota without scopes,xiang90,1