diff --git a/pkg/kubectl/cmd/util/factory_object_mapping.go b/pkg/kubectl/cmd/util/factory_object_mapping.go index abf162f6683..9bdb07b0a63 100644 --- a/pkg/kubectl/cmd/util/factory_object_mapping.go +++ b/pkg/kubectl/cmd/util/factory_object_mapping.go @@ -158,14 +158,55 @@ func (f *ring1Factory) Describer(mapping *meta.RESTMapping) (kubectl.Describer, return &kubectl.ClusterDescriber{Interface: fedClientSet}, nil } } + clientset, err := f.clientAccessFactory.ClientSetForVersion(&mappingVersion) if err != nil { + // if we can't make a client for this group/version, go generic if possible + if genericDescriber, genericErr := genericDescriber(f.clientAccessFactory, mapping); genericErr == nil { + return genericDescriber, nil + } + // otherwise return the original error return nil, err } + + // try to get a describer if describer, ok := kubectl.DescriberFor(mapping.GroupVersionKind.GroupKind(), clientset); ok { return describer, nil } - return nil, fmt.Errorf("no description has been implemented for %q", mapping.GroupVersionKind.Kind) + // if this is a kind we don't have a describer for yet, go generic if possible + if genericDescriber, genericErr := genericDescriber(f.clientAccessFactory, mapping); genericErr == nil { + return genericDescriber, nil + } + // otherwise return an unregistered error + return nil, fmt.Errorf("no description has been implemented for %s", mapping.GroupVersionKind.String()) +} + +// helper function to make a generic describer, or return an error +func genericDescriber(clientAccessFactory ClientAccessFactory, mapping *meta.RESTMapping) (kubectl.Describer, error) { + clientConfig, err := clientAccessFactory.ClientConfig() + if err != nil { + return nil, err + } + + clientConfigCopy := *clientConfig + clientConfigCopy.APIPath = dynamic.LegacyAPIPathResolverFunc(mapping.GroupVersionKind) + gv := mapping.GroupVersionKind.GroupVersion() + clientConfigCopy.GroupVersion = &gv + + // used to fetch the resource + dynamicClient, err := dynamic.NewClient(&clientConfigCopy) + if err != nil { + return nil, err + } + + // used to get events for the resource + clientSet, err := clientAccessFactory.ClientSet() + if err != nil { + return nil, err + } + eventsClient := clientSet.Core() + + return kubectl.GenericDescriberFor(mapping, dynamicClient, eventsClient), nil } func (f *ring1Factory) LogsForObject(object, options runtime.Object) (*restclient.Request, error) { diff --git a/pkg/kubectl/describe.go b/pkg/kubectl/describe.go index 01dea417b55..f6a11414a77 100644 --- a/pkg/kubectl/describe.go +++ b/pkg/kubectl/describe.go @@ -29,12 +29,14 @@ import ( "time" "k8s.io/apimachinery/pkg/api/errors" + "k8s.io/apimachinery/pkg/api/meta" metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" "k8s.io/apimachinery/pkg/fields" "k8s.io/apimachinery/pkg/labels" "k8s.io/apimachinery/pkg/runtime/schema" "k8s.io/apimachinery/pkg/types" "k8s.io/apimachinery/pkg/util/sets" + "k8s.io/client-go/dynamic" "k8s.io/kubernetes/federation/apis/federation" fedclientset "k8s.io/kubernetes/federation/client/clientset_generated/federation_internalclientset" "k8s.io/kubernetes/pkg/api" @@ -171,6 +173,46 @@ func DescriberFor(kind schema.GroupKind, c clientset.Interface) (Describer, bool return f, ok } +// GenericDescriberFor returns a generic describer for the specified mapping +// that uses only information available from runtime.Unstructured +func GenericDescriberFor(mapping *meta.RESTMapping, dynamic *dynamic.Client, events coreclient.EventsGetter) Describer { + return &genericDescriber{mapping, dynamic, events} +} + +type genericDescriber struct { + mapping *meta.RESTMapping + dynamic *dynamic.Client + events coreclient.EventsGetter +} + +func (g *genericDescriber) Describe(namespace, name string, describerSettings DescriberSettings) (output string, err error) { + apiResource := &metav1.APIResource{ + Name: g.mapping.Resource, + Namespaced: g.mapping.Scope.Name() == meta.RESTScopeNameNamespace, + Kind: g.mapping.GroupVersionKind.Kind, + } + obj, err := g.dynamic.Resource(apiResource, namespace).Get(name) + if err != nil { + return "", err + } + + var events *api.EventList + if describerSettings.ShowEvents { + events, _ = g.events.Events(namespace).Search(obj) + } + + return tabbedString(func(out io.Writer) error { + w := &PrefixWriter{out} + w.Write(LEVEL_0, "Name:\t%s\n", obj.GetName()) + w.Write(LEVEL_0, "Namespace:\t%s\n", obj.GetNamespace()) + printLabelsMultiline(w, "Labels", obj.GetLabels()) + if events != nil { + DescribeEvents(events, w) + } + return nil + }) +} + // DefaultObjectDescriber can describe the default Kubernetes objects. var DefaultObjectDescriber ObjectDescriber