diff --git a/pkg/kubectl/cmd/get.go b/pkg/kubectl/cmd/get.go index b8a95e946d6..e977bd4211b 100644 --- a/pkg/kubectl/cmd/get.go +++ b/pkg/kubectl/cmd/get.go @@ -17,6 +17,7 @@ limitations under the License. package cmd import ( + "encoding/json" "fmt" "io" "strings" @@ -26,11 +27,13 @@ import ( kapierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/apis/meta/v1/unstructured" "k8s.io/apimachinery/pkg/runtime" utilerrors "k8s.io/apimachinery/pkg/util/errors" "k8s.io/apimachinery/pkg/util/sets" "k8s.io/apimachinery/pkg/watch" + "k8s.io/kubernetes/pkg/api" "k8s.io/kubernetes/pkg/kubectl" "k8s.io/kubernetes/pkg/kubectl/cmd/templates" cmdutil "k8s.io/kubernetes/pkg/kubectl/cmd/util" @@ -354,17 +357,32 @@ func RunGet(f cmdutil.Factory, out, errOut io.Writer, cmd *cobra.Command, args [ var obj runtime.Object if !singleItemImplied || len(infos) > 1 { // we have more than one item, so coerce all items into a list - list := &unstructured.UnstructuredList{ - Object: map[string]interface{}{ - "kind": "List", - "apiVersion": "v1", - "metadata": map[string]interface{}{}, + // we have more than one item, so coerce all items into a list. + // we don't want an *unstructured.Unstructured list yet, as we + // may be dealing with non-unstructured objects. Compose all items + // into an api.List, and then decode using an unstructured scheme. + list := api.List{ + TypeMeta: metav1.TypeMeta{ + Kind: "List", + APIVersion: "v1", }, + ListMeta: metav1.ListMeta{}, } for _, info := range infos { - list.Items = append(list.Items, *info.Object.(*unstructured.Unstructured)) + list.Items = append(list.Items, info.Object) } - obj = list + + listData, err := json.Marshal(list) + if err != nil { + return err + } + + converted, err := runtime.Decode(unstructured.UnstructuredJSONScheme, listData) + if err != nil { + return err + } + + obj = converted } else { obj = infos[0].Object } diff --git a/pkg/kubectl/cmd/get_test.go b/pkg/kubectl/cmd/get_test.go index 129a0e08a78..f62369bd37c 100644 --- a/pkg/kubectl/cmd/get_test.go +++ b/pkg/kubectl/cmd/get_test.go @@ -575,6 +575,83 @@ func TestGetListComponentStatus(t *testing.T) { } } +func TestGetMixedGenericObjects(t *testing.T) { + initTestErrorHandler(t) + + // ensure that a runtime.Object without + // an ObjectMeta field is handled properly + structuredObj := &metav1.Status{ + TypeMeta: metav1.TypeMeta{ + Kind: "Status", + APIVersion: "v1", + }, + Status: "Success", + Message: "", + Reason: "", + Code: 0, + } + + f, tf, codec, _ := cmdtesting.NewAPIFactory() + tf.Printer = &testPrinter{GenericPrinter: true} + tf.UnstructuredClient = &fake.RESTClient{ + APIRegistry: api.Registry, + NegotiatedSerializer: unstructuredSerializer, + Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { + switch req.URL.Path { + case "/namespaces/test/pods": + return &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, structuredObj)}, nil + default: + t.Fatalf("request url: %#v,and request: %#v", req.URL, req) + return nil, nil + } + }), + } + tf.Namespace = "test" + tf.ClientConfig = &restclient.Config{ContentConfig: restclient.ContentConfig{GroupVersion: &api.Registry.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.Run(cmd, []string{"pods"}) + + if len(buf.String()) == 0 { + t.Error("unexpected empty output") + } + + actual := tf.Printer.(*testPrinter).Objects + fn := func(obj runtime.Object) unstructured.Unstructured { + data, err := runtime.Encode(api.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) + if err != nil { + panic(err) + } + out := &unstructured.Unstructured{Object: make(map[string]interface{})} + if err := encjson.Unmarshal(data, &out.Object); err != nil { + panic(err) + } + return *out + } + + expected := &unstructured.UnstructuredList{ + Object: map[string]interface{}{"kind": "List", "apiVersion": "v1", "metadata": map[string]interface{}{"selfLink": "", "resourceVersion": ""}}, + Items: []unstructured.Unstructured{ + fn(structuredObj), + }, + } + actualBytes, err := encjson.Marshal(actual[0]) + if err != nil { + t.Fatal(err) + } + expectedBytes, err := encjson.Marshal(expected) + if err != nil { + t.Fatal(err) + } + if string(actualBytes) != string(expectedBytes) { + t.Errorf("expectedBytes: %s,but actualBytes: %s", expectedBytes, actualBytes) + } +} + func TestGetMultipleTypeObjects(t *testing.T) { pods, svc, _ := testData() @@ -649,11 +726,11 @@ func TestGetMultipleTypeObjectsAsList(t *testing.T) { fn := func(obj runtime.Object) unstructured.Unstructured { data, err := runtime.Encode(api.Codecs.LegacyCodec(schema.GroupVersion{Version: "v1"}), obj) if err != nil { - panic(err) + t.Fatal(err) } out := &unstructured.Unstructured{Object: make(map[string]interface{})} if err := encjson.Unmarshal(data, &out.Object); err != nil { - panic(err) + t.Fatal(err) } return *out }