diff --git a/pkg/kubectl/cmd/describe.go b/pkg/kubectl/cmd/describe.go index 08cfd5de0e1..5c398a4dc76 100644 --- a/pkg/kubectl/cmd/describe.go +++ b/pkg/kubectl/cmd/describe.go @@ -25,6 +25,7 @@ import ( apierrors "k8s.io/apimachinery/pkg/api/errors" "k8s.io/apimachinery/pkg/api/meta" + "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" @@ -111,8 +112,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a return cmdutil.UsageError(cmd, "Required resource not specified.") } - mapper, typer := f.Object() - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). ContinueOnError(). NamespaceParam(cmdNamespace).DefaultNamespace().AllNamespaces(allNamespaces). FilenameParam(enforceNamespace, options). @@ -168,7 +172,11 @@ func RunDescribe(f cmdutil.Factory, out, cmdErr io.Writer, cmd *cobra.Command, a } func DescribeMatchingResources(mapper meta.RESTMapper, typer runtime.ObjectTyper, f cmdutil.Factory, namespace, rsrc, prefix string, describerSettings *kubectl.DescriberSettings, out io.Writer, originalError error) error { - r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.ClientForMapping), f.Decoder(true)). + mapper, typer, err := f.UnstructuredObject() + if err != nil { + return err + } + r := resource.NewBuilder(mapper, typer, resource.ClientMapperFunc(f.UnstructuredClientForMapping), unstructured.UnstructuredJSONScheme). NamespaceParam(namespace).DefaultNamespace(). ResourceTypeOrNameArgs(true, rsrc). SingleResourceType(). diff --git a/pkg/kubectl/cmd/describe_test.go b/pkg/kubectl/cmd/describe_test.go index e36673308d0..1064d981f32 100644 --- a/pkg/kubectl/cmd/describe_test.go +++ b/pkg/kubectl/cmd/describe_test.go @@ -30,11 +30,11 @@ import ( // Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. func TestDescribeUnknownSchemaObject(t *testing.T) { d := &testDescriber{Output: "test output"} - f, tf, codec, ns := cmdtesting.NewTestFactory() + f, tf, codec, _ := cmdtesting.NewTestFactory() tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalType("", "", "foo"))}, } tf.Namespace = "non-default" @@ -43,6 +43,31 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { cmd := NewCmdDescribe(f, buf, buferr) cmd.Run(cmd, []string{"type", "foo"}) + if d.Name != "foo" || d.Namespace != "" { + t.Errorf("unexpected describer: %#v", d) + } + + if buf.String() != fmt.Sprintf("%s", d.Output) { + t.Errorf("unexpected output: %s", buf.String()) + } +} + +// Verifies that schemas that are not in the master tree of Kubernetes can be retrieved via Get. +func TestDescribeUnknownNamespacedSchemaObject(t *testing.T) { + d := &testDescriber{Output: "test output"} + f, tf, codec, _ := cmdtesting.NewTestFactory() + tf.Describer = d + tf.UnstructuredClient = &fake.RESTClient{ + APIRegistry: api.Registry, + NegotiatedSerializer: unstructuredSerializer, + Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, cmdtesting.NewInternalNamespacedType("", "", "foo", "non-default"))}, + } + tf.Namespace = "non-default" + buf := bytes.NewBuffer([]byte{}) + buferr := bytes.NewBuffer([]byte{}) + cmd := NewCmdDescribe(f, buf, buferr) + cmd.Run(cmd, []string{"namespacedtype", "foo"}) + if d.Name != "foo" || d.Namespace != "non-default" { t.Errorf("unexpected describer: %#v", d) } @@ -54,12 +79,12 @@ func TestDescribeUnknownSchemaObject(t *testing.T) { func TestDescribeObject(t *testing.T) { _, _, rc := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) { switch p, m := req.URL.Path, req.Method; { case p == "/namespaces/test/replicationcontrollers/redis-master" && m == "GET": @@ -88,12 +113,12 @@ func TestDescribeObject(t *testing.T) { func TestDescribeListObjects(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -109,12 +134,12 @@ func TestDescribeListObjects(t *testing.T) { func TestDescribeObjectShowEvents(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } @@ -131,12 +156,12 @@ func TestDescribeObjectShowEvents(t *testing.T) { func TestDescribeObjectSkipEvents(t *testing.T) { pods, _, _ := testData() - f, tf, codec, ns := cmdtesting.NewAPIFactory() + f, tf, codec, _ := cmdtesting.NewAPIFactory() d := &testDescriber{Output: "test output"} tf.Describer = d - tf.Client = &fake.RESTClient{ + tf.UnstructuredClient = &fake.RESTClient{ APIRegistry: api.Registry, - NegotiatedSerializer: ns, + NegotiatedSerializer: unstructuredSerializer, Resp: &http.Response{StatusCode: 200, Header: defaultHeader(), Body: objBody(codec, pods)}, } diff --git a/pkg/kubectl/cmd/testing/fake.go b/pkg/kubectl/cmd/testing/fake.go index 035b9276fe2..58e2088ac4d 100644 --- a/pkg/kubectl/cmd/testing/fake.go +++ b/pkg/kubectl/cmd/testing/fake.go @@ -93,6 +93,60 @@ func NewInternalType(kind, apiversion, name string) *InternalType { return &item } +type InternalNamespacedType struct { + Kind string + APIVersion string + + Name string + Namespace string +} + +type ExternalNamespacedType struct { + Kind string `json:"kind"` + APIVersion string `json:"apiVersion"` + + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +type ExternalNamespacedType2 struct { + Kind string `json:"kind"` + APIVersion string `json:"apiVersion"` + + Name string `json:"name"` + Namespace string `json:"namespace"` +} + +func (obj *InternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj } +func (obj *InternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *InternalNamespacedType) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} +func (obj *ExternalNamespacedType) GetObjectKind() schema.ObjectKind { return obj } +func (obj *ExternalNamespacedType) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *ExternalNamespacedType) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} +func (obj *ExternalNamespacedType2) GetObjectKind() schema.ObjectKind { return obj } +func (obj *ExternalNamespacedType2) SetGroupVersionKind(gvk schema.GroupVersionKind) { + obj.APIVersion, obj.Kind = gvk.ToAPIVersionAndKind() +} +func (obj *ExternalNamespacedType2) GroupVersionKind() schema.GroupVersionKind { + return schema.FromAPIVersionAndKind(obj.APIVersion, obj.Kind) +} + +func NewInternalNamespacedType(kind, apiversion, name, namespace string) *InternalNamespacedType { + item := InternalNamespacedType{Kind: kind, + APIVersion: apiversion, + Name: name, + Namespace: namespace} + return &item +} + var versionErr = errors.New("not a version") func versionErrIfFalse(b bool) error { @@ -109,11 +163,17 @@ var ValidVersionGV = schema.GroupVersion{Group: "apitest", Version: ValidVersion func newExternalScheme() (*runtime.Scheme, meta.RESTMapper, runtime.Codec) { scheme := runtime.NewScheme() + scheme.AddKnownTypeWithName(InternalGV.WithKind("Type"), &InternalType{}) scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("Type"), &ExternalType{}) //This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name. scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("Type"), &ExternalType2{}) + scheme.AddKnownTypeWithName(InternalGV.WithKind("NamespacedType"), &InternalNamespacedType{}) + scheme.AddKnownTypeWithName(UnlikelyGV.WithKind("NamespacedType"), &ExternalNamespacedType{}) + //This tests that kubectl will not confuse the external scheme with the internal scheme, even when they accidentally have versions of the same name. + scheme.AddKnownTypeWithName(ValidVersionGV.WithKind("NamespacedType"), &ExternalNamespacedType2{}) + codecs := serializer.NewCodecFactory(scheme) codec := codecs.LegacyCodec(UnlikelyGV) mapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{UnlikelyGV, ValidVersionGV}, func(version schema.GroupVersion) (*meta.VersionInterfaces, error) { @@ -603,6 +663,7 @@ func testDynamicResources() []*discovery.APIGroupResources { {Name: "componentstatuses", Namespaced: false, Kind: "ComponentStatus"}, {Name: "nodes", Namespaced: false, Kind: "Node"}, {Name: "type", Namespaced: false, Kind: "Type"}, + {Name: "namespacedtype", Namespaced: true, Kind: "NamespacedType"}, }, }, },