Repository: kubernetes/dashboard
Pull request #4585 (2019-11-21 19:14:13)

Bump karma-coverage-istanbul-reporter from 2.1.0 to 2.1.1
Bumps [karma-coverage-istanbul-reporter](https://github.com/mattlewis92/karma-coverage-istanbul-reporter) from 2.1.0 to 2.1.1.
<details>
<summary>Changelog</summary>

*Sourced from [karma-coverage-istanbul-reporter's changelog](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/blob/master/CHANGELOG.md).*

> ## [2.1.1](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/compare/v2.1.0...v2.1.1) (2019-11-21)
> 
> 
> ### Bug Fixes
> 
> * add npm funding link ([ee9fb51](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/ee9fb51))
> 
> 
> 
> <a name="2.1.0"></a>
</details>
<details>
<summary>Commits</summary>

- [`30ce860`](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/30ce860848105bf69864402d1381e2d3e3894a68) chore(release): 2.1.1
- [`e97218c`](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/e97218cca1d1272211e836427466dbf3bbf1a88f) build: upgrade dependencies
- [`ee9fb51`](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/ee9fb51e770b82695683016f17373eecf10dc79d) fix: add npm funding link
- [`213056c`](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/213056c2849562d016bf08729ee95db29bcdacfa) build(deps): bump lodash.template from 4.4.0 to 4.5.0 ([#75](https://github-redirect.dependabot.com/mattlewis92/karma-coverage-istanbul-reporter/issues/75))
- [`2aefe78`](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/2aefe78b6116ad362057c7c6051be5e8904c5142) build(deps): bump eslint-utils from 1.3.1 to 1.4.3 ([#76](https://github-redirect.dependabot.com/mattlewis92/karma-coverage-istanbul-reporter/issues/76))
- [`3eac3fe`](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/commit/3eac3fe779b3689099ab1e227bd93127fe4468b6) chore: move to github sponsors
- See full diff in [compare view](https://github.com/mattlewis92/karma-coverage-istanbul-reporter/compare/v2.1.0...v2.1.1)
</details>
<br />

[![Dependabot compatibility score](https://api.dependabot.com/badges/compatibility_score?dependency-name=karma-coverage-istanbul-reporter&package-manager=npm_and_yarn&previous-version=2.1.0&new-version=2.1.1)](https://dependabot.com/compatibility-score.html?dependency-name=karma-coverage-istanbul-reporter&package-manager=npm_and_yarn&previous-version=2.1.0&new-version=2.1.1)

Dependabot will resolve any conflicts with this PR as long as you don't alter it yourself. You can also trigger a rebase manually by commenting `@dependabot rebase`.

[//]: # (dependabot-automerge-start)
[//]: # (dependabot-automerge-end)

---

<details>
<summary>Dependabot commands and options</summary>
<br />

You can trigger Dependabot actions by commenting on this PR:
- `@dependabot rebase` will rebase this PR
- `@dependabot recreate` will recreate this PR, overwriting any edits that have been made to it
- `@dependabot merge` will merge this PR after your CI passes on it
- `@dependabot squash and merge` will squash and merge this PR after your CI passes on it
- `@dependabot cancel merge` will cancel a previously requested merge and block automerging
- `@dependabot reopen` will reopen this PR if it is closed
- `@dependabot close` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually
- `@dependabot ignore this major version` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this minor version` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)
- `@dependabot ignore this dependency` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)
- `@dependabot use these labels` will set the current labels as the default for future PRs for this repo and language
- `@dependabot use these reviewers` will set the current reviewers as the default for future PRs for this repo and language
- `@dependabot use these assignees` will set the current assignees as the default for future PRs for this repo and language
- `@dependabot use this milestone` will set the current milestone as the default for future PRs for this repo and language
- `@dependabot badge me` will comment on this PR with code to add a "Dependabot enabled" badge to your readme

Additionally, you can set the following in your Dependabot [dashboard](https://app.dependabot.com):
- Update frequency (including time of day and day of week)
- Pull request limits (per update run and/or open at any time)
- Out-of-range updates (receive only lockfile updates, if desired)
- Security updates (receive only security updates, if desired)



</details>

k8s-ci-robot: Thanks for your pull request. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA).

:memo: **Please follow instructions at <https://git.k8s.io/community/CLA.md#the-contributor-license-agreement> to sign the CLA.**

It may take a couple minutes for the CLA signature to be fully registered; after that, please reply here with a new comment and we'll verify.  Thanks.

---

- If you've already signed a CLA, it's possible we don't have your GitHub username or you're using a different email address.  Check your existing CLA data and verify that your [email is set on your git commits](https://help.github.com/articles/setting-your-email-in-git/).
- If you signed the CLA as a corporation, please sign in with your organization's credentials at <https://identity.linuxfoundation.org/projects/cncf> to be authorized.
- If you have done the above and are still having issues with the CLA being reported as unsigned, please log a ticket with the Linux Foundation Helpdesk: <https://support.linuxfoundation.org/>
- Should you encounter any issues with the Linux Foundation Helpdesk, send a message to the backup e-mail support address at: login-issues@jira.linuxfoundation.org

<!-- need_sender_cla -->

<details>

Instructions for interacting with me using PR comments are available [here](https://git.k8s.io/community/contributors/guide/pull-requests.md).  If you have questions or suggestions related to my behavior, please file an issue against the [kubernetes/test-infra](https://github.com/kubernetes/test-infra/issues/new?title=Prow%20issue:) repository. I understand the commands that are listed [here](https://go.k8s.io/bot-commands).
</details>
	
k8s-ci-robot: [APPROVALNOTIFIER] This PR is **NOT APPROVED**

This pull-request has been approved by: *<a href="https://github.com/kubernetes/dashboard/pull/4585#" title="Author self-approved">dependabot-preview[bot]</a>*
To complete the [pull request process](https://git.k8s.io/community/contributors/guide/owners.md#the-code-review-process), please assign **maciaszczykm**
You can assign the PR to them by writing `/assign @maciaszczykm` in a comment when ready.

The full list of commands accepted by this bot can be found [here](https://go.k8s.io/bot-commands?repo=kubernetes%2Fdashboard).

<details open>
Needs approval from an approver in each of these files:

- **[OWNERS](https://github.com/kubernetes/dashboard/blob/master/OWNERS)**

Approvers can indicate their approval by writing `/approve` in a comment
Approvers can cancel approval by writing `/approve cancel` in a comment
</details>
<!-- META={"approvers":["maciaszczykm"]} -->

Patch:
diff --git a/package-lock.json b/package-lock.json
index 8e45fee59..0d4dd03f5 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -7975,9 +7975,9 @@
       }
     },
     "core-js": {
-      "version": "3.4.1",
-      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.1.tgz",
-      "integrity": "sha512-KX/dnuY/J8FtEwbnrzmAjUYgLqtk+cxM86hfG60LGiW3MmltIc2yAmDgBgEkfm0blZhUrdr1Zd84J2Y14mLxzg=="
+      "version": "3.4.2",
+      "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.4.2.tgz",
+      "integrity": "sha512-bUTfqFWtNKWp73oNIfRkqwYZJeNT3lstzZcAkhhiuvDraRSgOH1/+F9ZklbpR4zpdKuo4cpXN8tKP7s61yjX+g=="
     },
     "core-js-compat": {
       "version": "3.4.1",
@@ -12215,9 +12215,9 @@
       "dev": true
     },
     "handlebars": {
-      "version": "4.2.0",
-      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.2.0.tgz",
-      "integrity": "sha512-Kb4xn5Qh1cxAKvQnzNWZ512DhABzyFNmsaJf3OAkWNa4NkaqWcNI8Tao8Tasi0/F4JD9oyG0YxuFyvyR57d+Gw==",
+      "version": "4.5.3",
+      "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.5.3.tgz",
+      "integrity": "sha512-3yPecJoJHK/4c6aZhSvxOyG4vJKDshV36VHp0iVCDVh7o9w2vwi3NSnL2MMPj3YdduqaBcu7cGbggJQM0br9xA==",
       "dev": true,
       "requires": {
         "neo-async": "^2.6.0",
@@ -13829,9 +13829,9 @@
       }
     },
     "karma-coverage-istanbul-reporter": {
-      "version": "2.1.0",
-      "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.0.tgz",
-      "integrity": "sha512-UH0mXPJFJyK5uiK7EkwGtQ8f30lCBAfqRResnZ4pzLJ04SOp4SPlYkmwbbZ6iVJ6sQFVzlDUXlntBEsLRdgZpg==",
+      "version": "2.1.1",
+      "resolved": "https://registry.npmjs.org/karma-coverage-istanbul-reporter/-/karma-coverage-istanbul-reporter-2.1.1.tgz",
+      "integrity": "sha512-CH8lTi8+kKXGvrhy94+EkEMldLCiUA0xMOiL31vvli9qK0T+qcXJAwWBRVJWnVWxYkTmyWar8lPz63dxX6/z1A==",
       "dev": true,
       "requires": {
         "istanbul-api": "^2.1.6",
@@ -21705,16 +21705,23 @@
       "dev": true
     },
     "uglify-js": {
-      "version": "3.6.0",
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.0.tgz",
-      "integrity": "sha512-W+jrUHJr3DXKhrsS7NUVxn3zqMOFn0hL/Ei6v0anCIMoKC93TjcflTagwIHLW7SfMFfiQuktQyFVCFHGUE0+yg==",
+      "version": "3.6.9",
+      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.6.9.tgz",
+      "integrity": "sha512-pcnnhaoG6RtrvHJ1dFncAe8Od6Nuy30oaJ82ts6//sGSXOP5UjBMEthiProjXmMNHOfd93sqlkztifFMcb+4yw==",
       "dev": true,
       "optional": true,
       "requires": {
-        "commander": "~2.20.0",
+        "commander": "~2.20.3",
         "source-map": "~0.6.1"
       },
       "dependencies": {
+        "commander": {
+          "version": "2.20.3",
+          "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz",
+          "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==",
+          "dev": true,
+          "optional": true
+        },
         "source-map": {
           "version": "0.6.1",
           "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz",
diff --git a/package.json b/package.json
index 076344952..41a030be7 100644
--- a/package.json
+++ b/package.json
@@ -111,7 +111,7 @@
     "@types/highlight.js": "^9.12.3",
     "ace-builds": "1.4.7",
     "c3": "0.7.11",
-    "core-js": "3.4.1",
+    "core-js": "3.4.2",
     "d3": "5.14.2",
     "file-saver": "2.0.2",
     "hammerjs": "2.0.8",
@@ -162,7 +162,7 @@
     "karma": "4.4.1",
     "karma-chrome-launcher": "3.1.0",
     "karma-cli": "2.0.0",
-    "karma-coverage-istanbul-reporter": "2.1.0",
+    "karma-coverage-istanbul-reporter": "2.1.1",
     "karma-firefox-launcher": "1.2.0",
     "karma-jasmine": "2.0.1",
     "karma-jasmine-html-reporter": "1.4.2",
diff --git a/src/app/backend/client/api/types.go b/src/app/backend/client/api/types.go
index 89a277145..862709aa7 100644
--- a/src/app/backend/client/api/types.go
+++ b/src/app/backend/client/api/types.go
@@ -15,9 +15,7 @@
 package api
 
 import (
-	restful "github.com/emicklei/go-restful"
-	authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api"
-	pluginclientset "github.com/kubernetes/dashboard/src/app/backend/plugin/client/clientset/versioned"
+	"github.com/emicklei/go-restful"
 	v1 "k8s.io/api/authorization/v1"
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/apimachinery/pkg/runtime"
@@ -25,6 +23,9 @@ import (
 	"k8s.io/client-go/rest"
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/clientcmd/api"
+
+	authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api"
+	pluginclientset "github.com/kubernetes/dashboard/src/app/backend/plugin/client/clientset/versioned"
 )
 
 const (
diff --git a/src/app/backend/client/manager.go b/src/app/backend/client/manager.go
index d4384dbad..63e71d6df 100644
--- a/src/app/backend/client/manager.go
+++ b/src/app/backend/client/manager.go
@@ -18,8 +18,7 @@ import (
 	"log"
 	"strings"
 
-	restful "github.com/emicklei/go-restful"
-	pluginclientset "github.com/kubernetes/dashboard/src/app/backend/plugin/client/clientset/versioned"
+	"github.com/emicklei/go-restful"
 	v1 "k8s.io/api/authorization/v1"
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	"k8s.io/client-go/kubernetes"
@@ -27,6 +26,9 @@ import (
 	"k8s.io/client-go/tools/clientcmd"
 	"k8s.io/client-go/tools/clientcmd/api"
 
+	pluginclientset "github.com/kubernetes/dashboard/src/app/backend/plugin/client/clientset/versioned"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition"
+
 	"github.com/kubernetes/dashboard/src/app/backend/args"
 	authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api"
 	clientapi "github.com/kubernetes/dashboard/src/app/backend/client/api"
@@ -257,11 +259,16 @@ func (self *clientManager) VerberClient(req *restful.Request, config *rest.Confi
 		return nil, err
 	}
 
+	apiextensionsRestClient, err := customresourcedefinition.GetExtensionsAPIRestClient(apiextensionsclient)
+	if err != nil {
+		return nil, err
+	}
+
 	return NewResourceVerber(k8sClient.CoreV1().RESTClient(),
 		k8sClient.ExtensionsV1beta1().RESTClient(), k8sClient.AppsV1().RESTClient(),
 		k8sClient.BatchV1().RESTClient(), k8sClient.BatchV1beta1().RESTClient(), k8sClient.AutoscalingV1().RESTClient(),
 		k8sClient.StorageV1().RESTClient(), k8sClient.RbacV1().RESTClient(),
-		apiextensionsclient.ApiextensionsV1().RESTClient(),
+		apiextensionsRestClient,
 		pluginsclient.DashboardV1alpha1().RESTClient(),
 		config), nil
 }
diff --git a/src/app/backend/client/verber.go b/src/app/backend/client/verber.go
index 4390439b9..858a50773 100644
--- a/src/app/backend/client/verber.go
+++ b/src/app/backend/client/verber.go
@@ -17,14 +17,16 @@ package client
 import (
 	"fmt"
 
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	restclient "k8s.io/client-go/rest"
+
 	"github.com/kubernetes/dashboard/src/app/backend/api"
 	clientapi "github.com/kubernetes/dashboard/src/app/backend/client/api"
 	"github.com/kubernetes/dashboard/src/app/backend/errors"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition"
-	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-	v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/apimachinery/pkg/runtime"
-	restclient "k8s.io/client-go/rest"
 )
 
 // resourceVerber is a struct responsible for doing common verb operations on resources, like
@@ -43,6 +45,13 @@ type resourceVerber struct {
 	config              *restclient.Config
 }
 
+type crdInfo struct {
+	version    string
+	group      string
+	pluralName string
+	namespaced bool
+}
+
 func (verber *resourceVerber) getRESTClientByType(clientType api.ClientType) RESTClient {
 	switch clientType {
 	case api.ClientTypeExtensionClient:
@@ -71,24 +80,22 @@ func (verber *resourceVerber) getRESTClientByType(clientType api.ClientType) RES
 func (verber *resourceVerber) getResourceSpecFromKind(kind string, namespaceSet bool) (client RESTClient, resourceSpec api.APIMapping, err error) {
 	resourceSpec, ok := api.KindToAPIMapping[kind]
 	if !ok {
+		var crdInfo crdInfo
+
 		// check if kind is CRD
-		var crd apiextensions.CustomResourceDefinition
-		err = verber.apiExtensionsClient.Get().Resource("customresourcedefinitions").Name(kind).Do().Into(&crd)
+		crdInfo, err = verber.getCRDGroupAndVersion(kind)
 		if err != nil {
-			if errors.IsNotFoundError(err) {
-				err = errors.NewInvalid(fmt.Sprintf("Unknown resource kind: %s", kind))
-			}
 			return
 		}
 
-		client, err = customresourcedefinition.NewRESTClient(verber.config, &crd)
+		client, err = customresourcedefinition.NewRESTClient(verber.config, crdInfo.group, crdInfo.version)
 		if err != nil {
 			return
 		}
 
 		resourceSpec = api.APIMapping{
-			Resource:   crd.Status.AcceptedNames.Plural,
-			Namespaced: crd.Spec.Scope == apiextensions.NamespaceScoped,
+			Resource:   crdInfo.pluralName,
+			Namespaced: crdInfo.namespaced,
 		}
 	}
 
@@ -107,6 +114,47 @@ func (verber *resourceVerber) getResourceSpecFromKind(kind string, namespaceSet
 	return
 }
 
+func (verber *resourceVerber) getCRDGroupAndVersion(kind string) (info crdInfo, err error) {
+	var crdv1 apiextensionsv1.CustomResourceDefinition
+	var crdv1beta1 apiextensionsv1beta1.CustomResourceDefinition
+
+	err = verber.apiExtensionsClient.Get().Resource("customresourcedefinitions").Name(kind).Do().Into(&crdv1)
+	if err != nil {
+		if errors.IsNotFoundError(err) {
+			return info, errors.NewInvalid(fmt.Sprintf("Unknown resource kind: %s", kind))
+		}
+
+		return
+	}
+
+	if len(crdv1.Spec.Versions) > 0 {
+		info.group = crdv1.Spec.Group
+		info.version = crdv1.Spec.Versions[0].Name
+		info.pluralName = crdv1.Status.AcceptedNames.Plural
+		info.namespaced = crdv1.Spec.Scope == apiextensionsv1.NamespaceScoped
+
+		return
+	}
+
+	err = verber.apiExtensionsClient.Get().Resource("customresourcedefinitions").Name(kind).Do().Into(&crdv1beta1)
+	if err != nil {
+		if errors.IsNotFoundError(err) {
+			return info, errors.NewInvalid(fmt.Sprintf("Unknown resource kind: %s", kind))
+		}
+
+		return
+	}
+
+	if len(crdv1beta1.Spec.Versions) > 0 {
+		info.group = crdv1beta1.Spec.Group
+		info.version = crdv1beta1.Spec.Versions[0].Name
+		info.pluralName = crdv1beta1.Status.AcceptedNames.Plural
+		info.namespaced = crdv1beta1.Spec.Scope == apiextensionsv1beta1.NamespaceScoped
+	}
+
+	return
+}
+
 // RESTClient is an interface for REST operations used in this file.
 type RESTClient interface {
 	Delete() *restclient.Request
diff --git a/src/app/backend/handler/apihandler.go b/src/app/backend/handler/apihandler.go
index aa71cc679..de0bd807b 100644
--- a/src/app/backend/handler/apihandler.go
+++ b/src/app/backend/handler/apihandler.go
@@ -21,10 +21,15 @@ import (
 	"strings"
 
 	"github.com/kubernetes/dashboard/src/app/backend/handler/parser"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
 
 	"github.com/kubernetes/dashboard/src/app/backend/plugin"
 
-	restful "github.com/emicklei/go-restful"
+	"github.com/emicklei/go-restful"
+	"golang.org/x/net/xsrftoken"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/client-go/tools/remotecommand"
+
 	"github.com/kubernetes/dashboard/src/app/backend/api"
 	"github.com/kubernetes/dashboard/src/app/backend/auth"
 	authApi "github.com/kubernetes/dashboard/src/app/backend/auth/api"
@@ -65,9 +70,6 @@ import (
 	settingsApi "github.com/kubernetes/dashboard/src/app/backend/settings/api"
 	"github.com/kubernetes/dashboard/src/app/backend/systembanner"
 	"github.com/kubernetes/dashboard/src/app/backend/validation"
-	"golang.org/x/net/xsrftoken"
-	"k8s.io/apimachinery/pkg/runtime"
-	"k8s.io/client-go/tools/remotecommand"
 )
 
 const (
@@ -575,22 +577,22 @@ func CreateHTTPAPIHandler(iManager integration.IntegrationManager, cManager clie
 	apiV1Ws.Route(
 		apiV1Ws.GET("/crd").
 			To(apiHandler.handleGetCustomResourceDefinitionList).
-			Writes(customresourcedefinition.CustomResourceDefinitionList{}))
+			Writes(types.CustomResourceDefinitionList{}))
 
 	apiV1Ws.Route(
 		apiV1Ws.GET("/crd/{crd}").
 			To(apiHandler.handleGetCustomResourceDefinitionDetail).
-			Writes(customresourcedefinition.CustomResourceDefinitionDetail{}))
+			Writes(types.CustomResourceDefinitionDetail{}))
 
 	apiV1Ws.Route(
 		apiV1Ws.GET("/crd/{namespace}/{crd}/object").
 			To(apiHandler.handleGetCustomResourceObjectList).
-			Writes(customresourcedefinition.CustomResourceObjectList{}))
+			Writes(types.CustomResourceObjectList{}))
 
 	apiV1Ws.Route(
 		apiV1Ws.GET("/crd/{namespace}/{crd}/{object}").
 			To(apiHandler.handleGetCustomResourceObjectDetail).
-			Writes(customresourcedefinition.CustomResourceObjectDetail{}))
+			Writes(types.CustomResourceObjectDetail{}))
 
 	apiV1Ws.Route(
 		apiV1Ws.GET("/crd/{namespace}/{crd}/{object}/event").
diff --git a/src/app/backend/resource/common/resourcechannels.go b/src/app/backend/resource/common/resourcechannels.go
index 78bc5781b..44b757a05 100644
--- a/src/app/backend/resource/common/resourcechannels.go
+++ b/src/app/backend/resource/common/resourcechannels.go
@@ -15,7 +15,6 @@
 package common
 
 import (
-	"github.com/kubernetes/dashboard/src/app/backend/api"
 	apps "k8s.io/api/apps/v1"
 	autoscaling "k8s.io/api/autoscaling/v1"
 	batch "k8s.io/api/batch/v1"
@@ -25,9 +24,12 @@ import (
 	rbac "k8s.io/api/rbac/v1"
 	storage "k8s.io/api/storage/v1"
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	client "k8s.io/client-go/kubernetes"
+
+	"github.com/kubernetes/dashboard/src/app/backend/api"
 )
 
 // ResourceChannels struct holds channels to resource lists. Each list channel is paired with
@@ -837,15 +839,15 @@ func GetPersistentVolumeClaimListChannel(client client.Interface, nsQuery *Names
 }
 
 // CustomResourceDefinitionChannel is a list and error channels to CustomResourceDefinition.
-type CustomResourceDefinitionChannel struct {
+type CustomResourceDefinitionChannelV1 struct {
 	List  chan *apiextensions.CustomResourceDefinitionList
 	Error chan error
 }
 
-// GetCustomResourceDefinitionChannel returns a pair of channels to a CustomResourceDefinition list and errors
+// GetCustomResourceDefinitionChannelV1 returns a pair of channels to a CustomResourceDefinition list and errors
 // that both must be read numReads times.
-func GetCustomResourceDefinitionChannel(client apiextensionsclientset.Interface, numReads int) CustomResourceDefinitionChannel {
-	channel := CustomResourceDefinitionChannel{
+func GetCustomResourceDefinitionChannelV1(client apiextensionsclientset.Interface, numReads int) CustomResourceDefinitionChannelV1 {
+	channel := CustomResourceDefinitionChannelV1{
 		List:  make(chan *apiextensions.CustomResourceDefinitionList, numReads),
 		Error: make(chan error, numReads),
 	}
@@ -861,6 +863,31 @@ func GetCustomResourceDefinitionChannel(client apiextensionsclientset.Interface,
 	return channel
 }
 
+// CustomResourceDefinitionChannel is a list and error channels to CustomResourceDefinition.
+type CustomResourceDefinitionChannelV1beta1 struct {
+	List  chan *apiextensionsv1beta1.CustomResourceDefinitionList
+	Error chan error
+}
+
+// GetCustomResourceDefinitionChannelV1beta1 returns a pair of channels to a CustomResourceDefinition list and errors
+// that both must be read numReads times.
+func GetCustomResourceDefinitionChannelV1beta1(client apiextensionsclientset.Interface, numReads int) CustomResourceDefinitionChannelV1beta1 {
+	channel := CustomResourceDefinitionChannelV1beta1{
+		List:  make(chan *apiextensionsv1beta1.CustomResourceDefinitionList, numReads),
+		Error: make(chan error, numReads),
+	}
+
+	go func() {
+		list, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().List(api.ListEverything)
+		for i := 0; i < numReads; i++ {
+			channel.List <- list
+			channel.Error <- err
+		}
+	}()
+
+	return channel
+}
+
 // ResourceQuotaListChannel is a list and error channels to ResourceQuotas.
 type ResourceQuotaListChannel struct {
 	List  chan *v1.ResourceQuotaList
diff --git a/src/app/backend/resource/customresourcedefinition/common.go b/src/app/backend/resource/customresourcedefinition/common.go
index 50e4d210d..d96ab7314 100644
--- a/src/app/backend/resource/customresourcedefinition/common.go
+++ b/src/app/backend/resource/customresourcedefinition/common.go
@@ -15,97 +15,152 @@
 package customresourcedefinition
 
 import (
+	"fmt"
+
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apiextensionsv1beta "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/client-go/rest"
+
+	"github.com/kubernetes/dashboard/src/app/backend/errors"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	crdv1 "github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/v1"
+	crdv1beta1 "github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/v1beta1"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
-	api "k8s.io/api/core/v1"
-	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-	"k8s.io/apimachinery/pkg/runtime/schema"
 )
 
-type CustomResourceDefinitionCell apiextensions.CustomResourceDefinition
-
-func (self CustomResourceDefinitionCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
-	switch name {
-	case dataselect.NameProperty:
-		return dataselect.StdComparableString(self.ObjectMeta.Name)
-	case dataselect.CreationTimestampProperty:
-		return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
-	default:
-		// if name is not supported then just return a constant dummy value, sort will have no effect.
-		return nil
+var (
+	groupName = apiextensionsv1.GroupName
+	v1        = apiextensionsv1.SchemeGroupVersion.Version
+	v1beta1   = apiextensionsv1beta.SchemeGroupVersion.Version
+)
+
+func GetExtensionsAPIVersion(client clientset.Interface) (string, error) {
+	list, err := client.Discovery().ServerGroups()
+	if err != nil {
+		return "", err
 	}
-}
 
-func toCells(std []apiextensions.CustomResourceDefinition) []dataselect.DataCell {
-	cells := make([]dataselect.DataCell, len(std))
-	for i := range std {
-		cells[i] = CustomResourceDefinitionCell(std[i])
+	for _, group := range list.Groups {
+		if group.Name == groupName {
+			return group.PreferredVersion.Version, nil
+		}
 	}
 
-	return cells
+	return "", errors.NewNotFound("supported version for extensions api not found")
 }
 
-func fromCells(cells []dataselect.DataCell) []apiextensions.CustomResourceDefinition {
-	std := make([]apiextensions.CustomResourceDefinition, len(cells))
-	for i := range std {
-		std[i] = apiextensions.CustomResourceDefinition(cells[i].(CustomResourceDefinitionCell))
+func GetExtensionsAPIRestClient(client clientset.Interface) (rest.Interface, error) {
+	version, err := GetExtensionsAPIVersion(client)
+	if err != nil {
+		return nil, err
 	}
 
-	return std
+	switch version {
+	case v1:
+		return client.ApiextensionsV1().RESTClient(), nil
+	case v1beta1:
+		return client.ApiextensionsV1beta1().RESTClient(), nil
+	}
+
+	return nil, errors.NewNotFound(fmt.Sprintf("unsupported extensions api version: %s", version))
 }
 
-// The code below allows to perform complex data section on CustomResourceObject.
-type CustomResourceObjectCell CustomResourceObject
-
-func (self CustomResourceObjectCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
-	switch name {
-	case dataselect.NameProperty:
-		return dataselect.StdComparableString(self.ObjectMeta.Name)
-	case dataselect.CreationTimestampProperty:
-		return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
-	case dataselect.NamespaceProperty:
-		return dataselect.StdComparableString(self.ObjectMeta.Namespace)
-	default:
-		// if name is not supported then just return a constant dummy value, sort will have no effect.
-		return nil
+func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, dsQuery *dataselect.DataSelectQuery) (*types.CustomResourceDefinitionList, error) {
+	version, err := GetExtensionsAPIVersion(client)
+	if err != nil {
+		return nil, err
+	}
+
+	switch version {
+	case v1:
+		return crdv1.GetCustomResourceDefinitionList(client, dsQuery)
+	case v1beta1:
+		return crdv1beta1.GetCustomResourceDefinitionList(client, dsQuery)
 	}
+
+	return nil, errors.NewNotFound(fmt.Sprintf("unsupported extensions api version: %s", version))
 }
 
-func toObjectCells(std []CustomResourceObject) []dataselect.DataCell {
-	cells := make([]dataselect.DataCell, len(std))
-	for i := range std {
-		cells[i] = CustomResourceObjectCell(std[i])
+func GetCustomResourceDefinitionDetail(client apiextensionsclientset.Interface, config *rest.Config, name string) (*types.CustomResourceDefinitionDetail, error) {
+	version, err := GetExtensionsAPIVersion(client)
+	if err != nil {
+		return nil, err
 	}
-	return cells
+
+	switch version {
+	case v1:
+		return crdv1.GetCustomResourceDefinitionDetail(client, config, name)
+	case v1beta1:
+		return crdv1beta1.GetCustomResourceDefinitionDetail(client, config, name)
+	}
+
+	return nil, errors.NewNotFound(fmt.Sprintf("unsupported extensions api versions: %s", version))
 }
 
-func fromObjectCells(cells []dataselect.DataCell) []CustomResourceObject {
-	std := make([]CustomResourceObject, len(cells))
-	for i := range std {
-		std[i] = CustomResourceObject(cells[i].(CustomResourceObjectCell))
+func GetCustomResourceObjectList(client apiextensionsclientset.Interface, config *rest.Config, namespace *common.NamespaceQuery,
+	dsQuery *dataselect.DataSelectQuery, crdName string) (*types.CustomResourceObjectList, error) {
+	version, err := GetExtensionsAPIVersion(client)
+	if err != nil {
+		return nil, err
+	}
+
+	switch version {
+	case v1:
+		return crdv1.GetCustomResourceObjectList(client, config, namespace, dsQuery, crdName)
+	case v1beta1:
+		return crdv1beta1.GetCustomResourceObjectList(client, config, namespace, dsQuery, crdName)
 	}
-	return std
+
+	return nil, errors.NewNotFound(fmt.Sprintf("unsupported extensions api versions: %s", version))
 }
 
-// getCustomResourceDefinitionGroupVersion returns first group version of custom resource definition.
-// It's also known as preferredVersion.
-func getCustomResourceDefinitionGroupVersion(crd *apiextensions.CustomResourceDefinition) schema.GroupVersion {
-	return schema.GroupVersion{
-		Group:   crd.Spec.Group,
-		Version: crd.Spec.Versions[0].Name,
+func GetCustomResourceObjectDetail(client apiextensionsclientset.Interface, namespace *common.NamespaceQuery, config *rest.Config, crdName string, name string) (*types.CustomResourceObjectDetail, error) {
+	version, err := GetExtensionsAPIVersion(client)
+	if err != nil {
+		return nil, err
+	}
+
+	switch version {
+	case v1:
+		return crdv1.GetCustomResourceObjectDetail(client, namespace, config, crdName, name)
+	case v1beta1:
+		return crdv1beta1.GetCustomResourceObjectDetail(client, namespace, config, crdName, name)
 	}
+
+	return nil, errors.NewNotFound(fmt.Sprintf("unsupported extensions api versions: %s", version))
 }
 
-func getCRDConditions(crd *apiextensions.CustomResourceDefinition) []common.Condition {
-	var conditions []common.Condition
-	for _, condition := range crd.Status.Conditions {
-		conditions = append(conditions, common.Condition{
-			Type:               string(condition.Type),
-			Status:             api.ConditionStatus(condition.Status),
-			LastTransitionTime: condition.LastTransitionTime,
-			Reason:             condition.Reason,
-			Message:            condition.Message,
+func NewRESTClient(config *rest.Config, group, version string) (*rest.RESTClient, error) {
+	groupVersion := schema.GroupVersion{
+		Group:   group,
+		Version: version,
+	}
+
+	scheme := runtime.NewScheme()
+	schemeBuilder := runtime.NewSchemeBuilder(
+		func(scheme *runtime.Scheme) error {
+			scheme.AddKnownTypes(
+				groupVersion,
+				&metav1.ListOptions{},
+				&metav1.DeleteOptions{},
+			)
+			return nil
 		})
+	if err := schemeBuilder.AddToScheme(scheme); err != nil {
+		return nil, err
 	}
-	return conditions
+
+	config.GroupVersion = &groupVersion
+	config.APIPath = "/apis"
+	config.ContentType = runtime.ContentTypeJSON
+	config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
+
+	return rest.RESTClientFor(config)
 }
diff --git a/src/app/backend/resource/customresourcedefinition/events.go b/src/app/backend/resource/customresourcedefinition/events.go
index c307b40dd..e51348eba 100644
--- a/src/app/backend/resource/customresourcedefinition/events.go
+++ b/src/app/backend/resource/customresourcedefinition/events.go
@@ -15,10 +15,11 @@
 package customresourcedefinition
 
 import (
+	client "k8s.io/client-go/kubernetes"
+
 	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/event"
-	client "k8s.io/client-go/kubernetes"
 )
 
 // GetEventsForCustomResourceObject gets events that are associated with this CR object.
diff --git a/src/app/backend/resource/customresourcedefinition/types/types.go b/src/app/backend/resource/customresourcedefinition/types/types.go
new file mode 100644
index 000000000..015e94519
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/types/types.go
@@ -0,0 +1,150 @@
+// Copyright 2017 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 types
+
+import (
+	"encoding/json"
+
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+
+	"github.com/kubernetes/dashboard/src/app/backend/api"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+)
+
+// CustomResourceDefinitionList contains a list of Custom Resource Definitions in the cluster.
+type CustomResourceDefinitionList struct {
+	ListMeta api.ListMeta `json:"listMeta"`
+
+	// Unordered list of custom resource definitions
+	Items []CustomResourceDefinition `json:"items"`
+
+	// List of non-critical errors, that occurred during resource retrieval.
+	Errors []error `json:"errors"`
+}
+
+// CustomResourceDefinition represents a custom resource definition.
+type CustomResourceDefinition struct {
+	ObjectMeta  api.ObjectMeta                `json:"objectMeta"`
+	TypeMeta    api.TypeMeta                  `json:"typeMeta"`
+	Version     string                        `json:"version,omitempty"`
+	Group       string                        `json:"group"`
+	Scope       apiextensions.ResourceScope   `json:"scope"`
+	Names       CustomResourceDefinitionNames `json:"names"`
+	Established apiextensions.ConditionStatus `json:"established"`
+}
+
+type CustomResourceDefinitionDetail struct {
+	CustomResourceDefinition `json:",inline"`
+
+	Versions     []CustomResourceDefinitionVersion `json:"versions,omitempty"`
+	Conditions   []common.Condition                `json:"conditions"`
+	Objects      CustomResourceObjectList          `json:"objects"`
+	Subresources []string                          `json:"subresources"`
+
+	// List of non-critical errors, that occurred during resource retrieval.
+	Errors []error `json:"errors"`
+}
+
+type CustomResourceDefinitionVersion struct {
+	Name    string `json:"name"`
+	Served  bool   `json:"served"`
+	Storage bool   `json:"storage"`
+}
+
+// CustomResourceObject represents a custom resource object.
+type CustomResourceObject struct {
+	TypeMeta   api.TypeMeta   `json:"typeMeta"`
+	ObjectMeta api.ObjectMeta `json:"objectMeta"`
+}
+
+func (r *CustomResourceObject) UnmarshalJSON(data []byte) error {
+	tempStruct := &struct {
+		metav1.TypeMeta `json:",inline"`
+		ObjectMeta      metav1.ObjectMeta `json:"metadata,omitempty"`
+	}{}
+
+	err := json.Unmarshal(data, &tempStruct)
+	if err != nil {
+		return err
+	}
+
+	r.TypeMeta = api.NewTypeMeta(api.ResourceKind(tempStruct.TypeMeta.Kind))
+	r.ObjectMeta = api.NewObjectMeta(tempStruct.ObjectMeta)
+	return nil
+}
+
+type CustomResourceObjectDetail struct {
+	CustomResourceObject `json:",inline"`
+
+	// List of non-critical errors, that occurred during resource retrieval.
+	Errors []error `json:"errors"`
+}
+
+// CustomResourceObjectList represents crd objects in a namespace.
+type CustomResourceObjectList struct {
+	TypeMeta metav1.TypeMeta `json:"typeMeta"`
+	ListMeta api.ListMeta    `json:"listMeta"`
+
+	// Unordered list of custom resource definitions
+	Items []CustomResourceObject `json:"items"`
+
+	// List of non-critical errors, that occurred during resource retrieval.
+	Errors []error `json:"errors"`
+}
+
+func (r *CustomResourceObjectList) UnmarshalJSON(data []byte) error {
+	tempStruct := &struct {
+		metav1.TypeMeta `json:",inline"`
+		Items           []CustomResourceObject `json:"items"`
+	}{}
+
+	err := json.Unmarshal(data, &tempStruct)
+	if err != nil {
+		return err
+	}
+
+	r.TypeMeta = tempStruct.TypeMeta
+	r.Items = tempStruct.Items
+	return nil
+}
+
+type CustomResourceDefinitionNames struct {
+	// plural is the plural name of the resource to serve.
+	// The custom resources are served under `/apis/<group>/<version>/.../<plural>`.
+	// Must match the name of the CustomResourceDefinition (in the form `<names.plural>.<group>`).
+	// Must be all lowercase.
+	Plural string `json:"plural"`
+	// singular is the singular name of the resource. It must be all lowercase. Defaults to lowercased `kind`.
+	// +optional
+	Singular string `json:"singular,omitempty"`
+	// shortNames are short names for the resource, exposed in API discovery documents,
+	// and used by clients to support invocations like `kubectl get <shortname>`.
+	// It must be all lowercase.
+	// +optional
+	ShortNames []string `json:"shortNames,omitempty"`
+	// kind is the serialized kind of the resource. It is normally CamelCase and singular.
+	// Custom resource instances will use this value as the `kind` attribute in API calls.
+	Kind string `json:"kind"`
+	// listKind is the serialized kind of the list for this resource. Defaults to "`kind`List".
+	// +optional
+	ListKind string `json:"listKind,omitempty"`
+	// categories is a list of grouped resources this custom resource belongs to (e.g. 'all').
+	// This is published in API discovery documents, and used by clients to support invocations like
+	// `kubectl get all`.
+	// +optional
+	Categories []string `json:"categories,omitempty"`
+}
diff --git a/src/app/backend/resource/customresourcedefinition/client.go b/src/app/backend/resource/customresourcedefinition/v1/client.go
similarity index 97%
rename from src/app/backend/resource/customresourcedefinition/client.go
rename to src/app/backend/resource/customresourcedefinition/v1/client.go
index 490dd4f2b..bee54730e 100644
--- a/src/app/backend/resource/customresourcedefinition/client.go
+++ b/src/app/backend/resource/customresourcedefinition/v1/client.go
@@ -12,7 +12,7 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package customresourcedefinition
+package v1
 
 import (
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
diff --git a/src/app/backend/resource/customresourcedefinition/v1/common.go b/src/app/backend/resource/customresourcedefinition/v1/common.go
new file mode 100644
index 000000000..99e1c3517
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1/common.go
@@ -0,0 +1,113 @@
+// Copyright 2017 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 v1
+
+import (
+	api "k8s.io/api/core/v1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
+
+type CustomResourceDefinitionCell apiextensions.CustomResourceDefinition
+
+func (self CustomResourceDefinitionCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
+	switch name {
+	case dataselect.NameProperty:
+		return dataselect.StdComparableString(self.ObjectMeta.Name)
+	case dataselect.CreationTimestampProperty:
+		return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
+	default:
+		// if name is not supported then just return a constant dummy value, sort will have no effect.
+		return nil
+	}
+}
+
+func toCells(std []apiextensions.CustomResourceDefinition) []dataselect.DataCell {
+	cells := make([]dataselect.DataCell, len(std))
+	for i := range std {
+		cells[i] = CustomResourceDefinitionCell(std[i])
+	}
+
+	return cells
+}
+
+func fromCells(cells []dataselect.DataCell) []apiextensions.CustomResourceDefinition {
+	std := make([]apiextensions.CustomResourceDefinition, len(cells))
+	for i := range std {
+		std[i] = apiextensions.CustomResourceDefinition(cells[i].(CustomResourceDefinitionCell))
+	}
+
+	return std
+}
+
+// The code below allows to perform complex data section on CustomResourceObject.
+type CustomResourceObjectCell types.CustomResourceObject
+
+func (self CustomResourceObjectCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
+	switch name {
+	case dataselect.NameProperty:
+		return dataselect.StdComparableString(self.ObjectMeta.Name)
+	case dataselect.CreationTimestampProperty:
+		return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
+	case dataselect.NamespaceProperty:
+		return dataselect.StdComparableString(self.ObjectMeta.Namespace)
+	default:
+		// if name is not supported then just return a constant dummy value, sort will have no effect.
+		return nil
+	}
+}
+
+func toObjectCells(std []types.CustomResourceObject) []dataselect.DataCell {
+	cells := make([]dataselect.DataCell, len(std))
+	for i := range std {
+		cells[i] = CustomResourceObjectCell(std[i])
+	}
+	return cells
+}
+
+func fromObjectCells(cells []dataselect.DataCell) []types.CustomResourceObject {
+	std := make([]types.CustomResourceObject, len(cells))
+	for i := range std {
+		std[i] = types.CustomResourceObject(cells[i].(CustomResourceObjectCell))
+	}
+	return std
+}
+
+// getCustomResourceDefinitionGroupVersion returns first group version of custom resource definition.
+// It's also known as preferredVersion.
+func getCustomResourceDefinitionGroupVersion(crd *apiextensions.CustomResourceDefinition) schema.GroupVersion {
+	return schema.GroupVersion{
+		Group:   crd.Spec.Group,
+		Version: crd.Spec.Versions[0].Name,
+	}
+}
+
+func getCRDConditions(crd *apiextensions.CustomResourceDefinition) []common.Condition {
+	var conditions []common.Condition
+	for _, condition := range crd.Status.Conditions {
+		conditions = append(conditions, common.Condition{
+			Type:               string(condition.Type),
+			Status:             api.ConditionStatus(condition.Status),
+			LastTransitionTime: condition.LastTransitionTime,
+			Reason:             condition.Reason,
+			Message:            condition.Message,
+		})
+	}
+	return conditions
+}
diff --git a/src/app/backend/resource/customresourcedefinition/detail.go b/src/app/backend/resource/customresourcedefinition/v1/detail.go
similarity index 68%
rename from src/app/backend/resource/customresourcedefinition/detail.go
rename to src/app/backend/resource/customresourcedefinition/v1/detail.go
index 01927cb64..9dcfaf6cb 100644
--- a/src/app/backend/resource/customresourcedefinition/detail.go
+++ b/src/app/backend/resource/customresourcedefinition/v1/detail.go
@@ -12,41 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package customresourcedefinition
+package v1
 
 import (
-	"github.com/kubernetes/dashboard/src/app/backend/errors"
-	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
-	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
 	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/client-go/rest"
-)
 
-type CustomResourceDefinitionDetail struct {
-	CustomResourceDefinition `json:",inline"`
-
-	Versions     []CustomResourceDefinitionVersion `json:"versions,omitempty"`
-	Conditions   []common.Condition                `json:"conditions"`
-	Objects      CustomResourceObjectList          `json:"objects"`
-	Subresources []string                          `json:"subresources"`
-
-	// List of non-critical errors, that occurred during resource retrieval.
-	Errors []error `json:"errors"`
-}
-
-type CustomResourceDefinitionVersion struct {
-	Name    string `json:"name"`
-	Served  bool   `json:"served"`
-	Storage bool   `json:"storage"`
-}
+	"github.com/kubernetes/dashboard/src/app/backend/errors"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
 
 // GetCustomResourceDefinitionDetail returns detailed information about a custom resource definition.
-func GetCustomResourceDefinitionDetail(client apiextensionsclientset.Interface, config *rest.Config, name string) (*CustomResourceDefinitionDetail, error) {
-	customResourceDefinition, err := client.ApiextensionsV1().
-		CustomResourceDefinitions().
-		Get(name, metav1.GetOptions{})
+func GetCustomResourceDefinitionDetail(client apiextensionsclientset.Interface, config *rest.Config, name string) (*types.CustomResourceDefinitionDetail, error) {
+	customResourceDefinition, err := client.ApiextensionsV1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
 	nonCriticalErrors, criticalError := errors.HandleError(err)
 	if criticalError != nil {
 		return nil, criticalError
@@ -61,7 +43,7 @@ func GetCustomResourceDefinitionDetail(client apiextensionsclientset.Interface,
 	return toCustomResourceDefinitionDetail(customResourceDefinition, *objects, nonCriticalErrors), nil
 }
 
-func toCustomResourceDefinitionDetail(crd *apiextensions.CustomResourceDefinition, objects CustomResourceObjectList, nonCriticalErrors []error) *CustomResourceDefinitionDetail {
+func toCustomResourceDefinitionDetail(crd *apiextensions.CustomResourceDefinition, objects types.CustomResourceObjectList, nonCriticalErrors []error) *types.CustomResourceDefinitionDetail {
 	subresources := []string{}
 	crdSubresources := crd.Spec.Versions[0].Subresources
 	if crdSubresources != nil {
@@ -73,7 +55,7 @@ func toCustomResourceDefinitionDetail(crd *apiextensions.CustomResourceDefinitio
 		}
 	}
 
-	return &CustomResourceDefinitionDetail{
+	return &types.CustomResourceDefinitionDetail{
 		CustomResourceDefinition: toCustomResourceDefinition(crd),
 		Versions:                 getCRDVersions(crd),
 		Conditions:               getCRDConditions(crd),
@@ -83,11 +65,11 @@ func toCustomResourceDefinitionDetail(crd *apiextensions.CustomResourceDefinitio
 	}
 }
 
-func getCRDVersions(crd *apiextensions.CustomResourceDefinition) []CustomResourceDefinitionVersion {
-	crdVersions := make([]CustomResourceDefinitionVersion, 0, len(crd.Spec.Versions))
+func getCRDVersions(crd *apiextensions.CustomResourceDefinition) []types.CustomResourceDefinitionVersion {
+	crdVersions := make([]types.CustomResourceDefinitionVersion, 0, len(crd.Spec.Versions))
 	if len(crd.Spec.Versions) > 0 {
 		for _, version := range crd.Spec.Versions {
-			crdVersions = append(crdVersions, CustomResourceDefinitionVersion{
+			crdVersions = append(crdVersions, types.CustomResourceDefinitionVersion{
 				Name:    version.Name,
 				Served:  version.Served,
 				Storage: version.Storage,
diff --git a/src/app/backend/resource/customresourcedefinition/list.go b/src/app/backend/resource/customresourcedefinition/v1/list.go
similarity index 50%
rename from src/app/backend/resource/customresourcedefinition/list.go
rename to src/app/backend/resource/customresourcedefinition/v1/list.go
index da42f5ff5..c17f3644b 100644
--- a/src/app/backend/resource/customresourcedefinition/list.go
+++ b/src/app/backend/resource/customresourcedefinition/v1/list.go
@@ -12,42 +12,23 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package customresourcedefinition
+package v1
 
 import (
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+
 	"github.com/kubernetes/dashboard/src/app/backend/api"
 	"github.com/kubernetes/dashboard/src/app/backend/errors"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
-	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
 )
 
-// CustomResourceDefinitionList contains a list of Custom Resource Definitions in the cluster.
-type CustomResourceDefinitionList struct {
-	ListMeta api.ListMeta `json:"listMeta"`
-
-	// Unordered list of custom resource definitions
-	Items []CustomResourceDefinition `json:"items"`
-
-	// List of non-critical errors, that occurred during resource retrieval.
-	Errors []error `json:"errors"`
-}
-
-// CustomResourceDefinition represents a custom resource definition.
-type CustomResourceDefinition struct {
-	ObjectMeta  api.ObjectMeta                              `json:"objectMeta"`
-	TypeMeta    api.TypeMeta                                `json:"typeMeta"`
-	Version     string                                      `json:"version,omitempty"`
-	Group       string                                      `json:"group"`
-	Scope       apiextensions.ResourceScope                 `json:"scope"`
-	Names       apiextensions.CustomResourceDefinitionNames `json:"names"`
-	Established apiextensions.ConditionStatus               `json:"established"`
-}
-
 // GetCustomResourceDefinitionList returns all the custom resource definitions in the cluster.
-func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, dsQuery *dataselect.DataSelectQuery) (*CustomResourceDefinitionList, error) {
-	channel := common.GetCustomResourceDefinitionChannel(client, 1)
+func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, dsQuery *dataselect.DataSelectQuery) (*types.CustomResourceDefinitionList, error) {
+	channel := common.GetCustomResourceDefinitionChannelV1(client, 1)
 	crdList := <-channel.List
 	err := <-channel.Error
 
@@ -59,9 +40,9 @@ func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, ds
 	return toCustomResourceDefinitionList(crdList.Items, nonCriticalErrors, dsQuery), nil
 }
 
-func toCustomResourceDefinitionList(crds []apiextensions.CustomResourceDefinition, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *CustomResourceDefinitionList {
-	crdList := &CustomResourceDefinitionList{
-		Items:    make([]CustomResourceDefinition, 0),
+func toCustomResourceDefinitionList(crds []apiextensionsv1.CustomResourceDefinition, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *types.CustomResourceDefinitionList {
+	crdList := &types.CustomResourceDefinitionList{
+		Items:    make([]types.CustomResourceDefinition, 0),
 		ListMeta: api.ListMeta{TotalItems: len(crds)},
 		Errors:   nonCriticalErrors,
 	}
@@ -77,22 +58,37 @@ func toCustomResourceDefinitionList(crds []apiextensions.CustomResourceDefinitio
 	return crdList
 }
 
-func toCustomResourceDefinition(crd *apiextensions.CustomResourceDefinition) CustomResourceDefinition {
-	return CustomResourceDefinition{
+func toCustomResourceDefinition(crd *apiextensionsv1.CustomResourceDefinition) types.CustomResourceDefinition {
+	return types.CustomResourceDefinition{
 		ObjectMeta:  api.NewObjectMeta(crd.ObjectMeta),
 		TypeMeta:    api.NewTypeMeta(api.ResourceKindCustomResourceDefinition),
 		Version:     crd.Spec.Versions[0].Name,
 		Group:       crd.Spec.Group,
-		Scope:       crd.Spec.Scope,
-		Names:       crd.Status.AcceptedNames,
-		Established: getCRDConditionStatus(crd, apiextensions.Established),
+		Scope:       toCustomResourceDefinitionScope(crd.Spec.Scope),
+		Names:       toCustomResourceDefinitionAcceptedNames(crd.Status.AcceptedNames),
+		Established: getCRDConditionStatus(crd, apiextensionsv1.Established),
+	}
+}
+
+func toCustomResourceDefinitionScope(scope apiextensionsv1.ResourceScope) apiextensions.ResourceScope {
+	return apiextensions.ResourceScope(scope)
+}
+
+func toCustomResourceDefinitionAcceptedNames(names apiextensionsv1.CustomResourceDefinitionNames) types.CustomResourceDefinitionNames {
+	return types.CustomResourceDefinitionNames{
+		Plural:     names.Plural,
+		Singular:   names.Singular,
+		ShortNames: names.ShortNames,
+		Kind:       names.Kind,
+		ListKind:   names.ListKind,
+		Categories: names.Categories,
 	}
 }
 
-func getCRDConditionStatus(node *apiextensions.CustomResourceDefinition, conditionType apiextensions.CustomResourceDefinitionConditionType) apiextensions.ConditionStatus {
+func getCRDConditionStatus(node *apiextensionsv1.CustomResourceDefinition, conditionType apiextensionsv1.CustomResourceDefinitionConditionType) apiextensions.ConditionStatus {
 	for _, condition := range node.Status.Conditions {
 		if condition.Type == conditionType {
-			return condition.Status
+			return apiextensions.ConditionStatus(condition.Status)
 		}
 	}
 	return apiextensions.ConditionUnknown
diff --git a/src/app/backend/resource/customresourcedefinition/v1/objects.go b/src/app/backend/resource/customresourcedefinition/v1/objects.go
new file mode 100644
index 000000000..3ffe92728
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1/objects.go
@@ -0,0 +1,123 @@
+// Copyright 2017 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 v1
+
+import (
+	"encoding/json"
+
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/rest"
+
+	"github.com/kubernetes/dashboard/src/app/backend/api"
+	"github.com/kubernetes/dashboard/src/app/backend/errors"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
+
+// GetCustomResourceObjectList gets objects for a CR.
+func GetCustomResourceObjectList(client apiextensionsclientset.Interface, config *rest.Config, namespace *common.NamespaceQuery,
+	dsQuery *dataselect.DataSelectQuery, crdName string) (*types.CustomResourceObjectList, error) {
+	var list *types.CustomResourceObjectList
+
+	customResourceDefinition, err := client.ApiextensionsV1().
+		CustomResourceDefinitions().
+		Get(crdName, metav1.GetOptions{})
+	nonCriticalErrors, criticalError := errors.HandleError(err)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	restClient, err := NewRESTClient(config, customResourceDefinition)
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	raw, err := restClient.Get().
+		NamespaceIfScoped(namespace.ToRequestParam(), customResourceDefinition.Spec.Scope == apiextensionsv1.NamespaceScoped).
+		Resource(customResourceDefinition.Spec.Names.Plural).
+		Do().Raw()
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	err = json.Unmarshal(raw, &list)
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+	list.Errors = nonCriticalErrors
+
+	// Return only slice of data, pagination is done here.
+	crdObjectCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toObjectCells(list.Items), dsQuery)
+	list.Items = fromObjectCells(crdObjectCells)
+	list.ListMeta = api.ListMeta{TotalItems: filteredTotal}
+
+	for i := range list.Items {
+		toCRDObject(&list.Items[i], customResourceDefinition)
+	}
+
+	return list, nil
+}
+
+// GetCustomResourceObjectDetail returns details of a single object in a CR.
+func GetCustomResourceObjectDetail(client apiextensionsclientset.Interface, namespace *common.NamespaceQuery, config *rest.Config, crdName string, name string) (*types.CustomResourceObjectDetail, error) {
+	var detail *types.CustomResourceObjectDetail
+
+	customResourceDefinition, err := client.ApiextensionsV1().
+		CustomResourceDefinitions().
+		Get(crdName, metav1.GetOptions{})
+	nonCriticalErrors, criticalError := errors.HandleError(err)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	restClient, err := NewRESTClient(config, customResourceDefinition)
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	raw, err := restClient.Get().
+		NamespaceIfScoped(namespace.ToRequestParam(), customResourceDefinition.Spec.Scope == apiextensionsv1.NamespaceScoped).
+		Resource(customResourceDefinition.Spec.Names.Plural).
+		Name(name).Do().Raw()
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	err = json.Unmarshal(raw, &detail)
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+	detail.Errors = nonCriticalErrors
+
+	toCRDObject(&detail.CustomResourceObject, customResourceDefinition)
+	return detail, nil
+}
+
+// toCRDObject sets the object kind to the full name of the CRD.
+// E.g. changes "Foo" to "foos.samplecontroller.k8s.io"
+func toCRDObject(object *types.CustomResourceObject, crd *apiextensionsv1.CustomResourceDefinition) {
+	object.TypeMeta.Kind = api.ResourceKind(crd.Name)
+	crdSubresources := crd.Spec.Versions[0].Subresources
+	object.TypeMeta.Scalable = crdSubresources != nil && crdSubresources.Scale != nil
+}
diff --git a/src/app/backend/resource/customresourcedefinition/v1beta1/client.go b/src/app/backend/resource/customresourcedefinition/v1beta1/client.go
new file mode 100644
index 000000000..b0b9ac24d
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1beta1/client.go
@@ -0,0 +1,47 @@
+// Copyright 2017 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 v1beta1
+
+import (
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/apimachinery/pkg/runtime"
+	"k8s.io/apimachinery/pkg/runtime/serializer"
+	"k8s.io/client-go/rest"
+)
+
+func NewRESTClient(config *rest.Config, crd *apiextensions.CustomResourceDefinition) (*rest.RESTClient, error) {
+	groupVersion := getCustomResourceDefinitionGroupVersion(crd)
+	scheme := runtime.NewScheme()
+	schemeBuilder := runtime.NewSchemeBuilder(
+		func(scheme *runtime.Scheme) error {
+			scheme.AddKnownTypes(
+				groupVersion,
+				&metav1.ListOptions{},
+				&metav1.DeleteOptions{},
+			)
+			return nil
+		})
+	if err := schemeBuilder.AddToScheme(scheme); err != nil {
+		return nil, err
+	}
+
+	config.GroupVersion = &groupVersion
+	config.APIPath = "/apis"
+	config.ContentType = runtime.ContentTypeJSON
+	config.NegotiatedSerializer = serializer.WithoutConversionCodecFactory{CodecFactory: serializer.NewCodecFactory(scheme)}
+
+	return rest.RESTClientFor(config)
+}
diff --git a/src/app/backend/resource/customresourcedefinition/v1beta1/common.go b/src/app/backend/resource/customresourcedefinition/v1beta1/common.go
new file mode 100644
index 000000000..fea156f1d
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1beta1/common.go
@@ -0,0 +1,113 @@
+// Copyright 2017 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 v1beta1
+
+import (
+	api "k8s.io/api/core/v1"
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	"k8s.io/apimachinery/pkg/runtime/schema"
+
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
+
+type CustomResourceDefinitionCell apiextensions.CustomResourceDefinition
+
+func (self CustomResourceDefinitionCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
+	switch name {
+	case dataselect.NameProperty:
+		return dataselect.StdComparableString(self.ObjectMeta.Name)
+	case dataselect.CreationTimestampProperty:
+		return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
+	default:
+		// if name is not supported then just return a constant dummy value, sort will have no effect.
+		return nil
+	}
+}
+
+func toCells(std []apiextensions.CustomResourceDefinition) []dataselect.DataCell {
+	cells := make([]dataselect.DataCell, len(std))
+	for i := range std {
+		cells[i] = CustomResourceDefinitionCell(std[i])
+	}
+
+	return cells
+}
+
+func fromCells(cells []dataselect.DataCell) []apiextensions.CustomResourceDefinition {
+	std := make([]apiextensions.CustomResourceDefinition, len(cells))
+	for i := range std {
+		std[i] = apiextensions.CustomResourceDefinition(cells[i].(CustomResourceDefinitionCell))
+	}
+
+	return std
+}
+
+// The code below allows to perform complex data section on CustomResourceObject.
+type CustomResourceObjectCell types.CustomResourceObject
+
+func (self CustomResourceObjectCell) GetProperty(name dataselect.PropertyName) dataselect.ComparableValue {
+	switch name {
+	case dataselect.NameProperty:
+		return dataselect.StdComparableString(self.ObjectMeta.Name)
+	case dataselect.CreationTimestampProperty:
+		return dataselect.StdComparableTime(self.ObjectMeta.CreationTimestamp.Time)
+	case dataselect.NamespaceProperty:
+		return dataselect.StdComparableString(self.ObjectMeta.Namespace)
+	default:
+		// if name is not supported then just return a constant dummy value, sort will have no effect.
+		return nil
+	}
+}
+
+func toObjectCells(std []types.CustomResourceObject) []dataselect.DataCell {
+	cells := make([]dataselect.DataCell, len(std))
+	for i := range std {
+		cells[i] = CustomResourceObjectCell(std[i])
+	}
+	return cells
+}
+
+func fromObjectCells(cells []dataselect.DataCell) []types.CustomResourceObject {
+	std := make([]types.CustomResourceObject, len(cells))
+	for i := range std {
+		std[i] = types.CustomResourceObject(cells[i].(CustomResourceObjectCell))
+	}
+	return std
+}
+
+// getCustomResourceDefinitionGroupVersion returns first group version of custom resource definition.
+// It's also known as preferredVersion.
+func getCustomResourceDefinitionGroupVersion(crd *apiextensions.CustomResourceDefinition) schema.GroupVersion {
+	return schema.GroupVersion{
+		Group:   crd.Spec.Group,
+		Version: crd.Spec.Versions[0].Name,
+	}
+}
+
+func getCRDConditions(crd *apiextensions.CustomResourceDefinition) []common.Condition {
+	var conditions []common.Condition
+	for _, condition := range crd.Status.Conditions {
+		conditions = append(conditions, common.Condition{
+			Type:               string(condition.Type),
+			Status:             api.ConditionStatus(condition.Status),
+			LastTransitionTime: condition.LastTransitionTime,
+			Reason:             condition.Reason,
+			Message:            condition.Message,
+		})
+	}
+	return conditions
+}
diff --git a/src/app/backend/resource/customresourcedefinition/v1beta1/detail.go b/src/app/backend/resource/customresourcedefinition/v1beta1/detail.go
new file mode 100644
index 000000000..8ddb96d9f
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1beta1/detail.go
@@ -0,0 +1,81 @@
+// Copyright 2017 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 v1beta1
+
+import (
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/rest"
+
+	"github.com/kubernetes/dashboard/src/app/backend/errors"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
+
+// GetCustomResourceDefinitionDetail returns detailed information about a custom resource definition.
+func GetCustomResourceDefinitionDetail(client apiextensionsclientset.Interface, config *rest.Config, name string) (*types.CustomResourceDefinitionDetail, error) {
+	customResourceDefinition, err := client.ApiextensionsV1beta1().CustomResourceDefinitions().Get(name, metav1.GetOptions{})
+	nonCriticalErrors, criticalError := errors.HandleError(err)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	objects, err := GetCustomResourceObjectList(client, config, &common.NamespaceQuery{}, dataselect.DefaultDataSelect, name)
+	nonCriticalErrors, criticalError = errors.AppendError(err, nonCriticalErrors)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	return toCustomResourceDefinitionDetail(customResourceDefinition, *objects, nonCriticalErrors), nil
+}
+
+func toCustomResourceDefinitionDetail(crd *apiextensions.CustomResourceDefinition, objects types.CustomResourceObjectList, nonCriticalErrors []error) *types.CustomResourceDefinitionDetail {
+	subresources := []string{}
+	crdSubresources := crd.Spec.Versions[0].Subresources
+	if crdSubresources != nil {
+		if crdSubresources.Scale != nil {
+			subresources = append(subresources, "Scale")
+		}
+		if crdSubresources.Status != nil {
+			subresources = append(subresources, "Status")
+		}
+	}
+
+	return &types.CustomResourceDefinitionDetail{
+		CustomResourceDefinition: toCustomResourceDefinition(crd),
+		Versions:                 getCRDVersions(crd),
+		Conditions:               getCRDConditions(crd),
+		Objects:                  objects,
+		Subresources:             subresources,
+		Errors:                   nonCriticalErrors,
+	}
+}
+
+func getCRDVersions(crd *apiextensions.CustomResourceDefinition) []types.CustomResourceDefinitionVersion {
+	crdVersions := make([]types.CustomResourceDefinitionVersion, 0, len(crd.Spec.Versions))
+	if len(crd.Spec.Versions) > 0 {
+		for _, version := range crd.Spec.Versions {
+			crdVersions = append(crdVersions, types.CustomResourceDefinitionVersion{
+				Name:    version.Name,
+				Served:  version.Served,
+				Storage: version.Storage,
+			})
+		}
+	}
+
+	return crdVersions
+}
diff --git a/src/app/backend/resource/customresourcedefinition/v1beta1/list.go b/src/app/backend/resource/customresourcedefinition/v1beta1/list.go
new file mode 100644
index 000000000..32db7c2d8
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1beta1/list.go
@@ -0,0 +1,95 @@
+// Copyright 2017 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 v1beta1
+
+import (
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+
+	"github.com/kubernetes/dashboard/src/app/backend/api"
+	"github.com/kubernetes/dashboard/src/app/backend/errors"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
+
+// GetCustomResourceDefinitionList returns all the custom resource definitions in the cluster.
+func GetCustomResourceDefinitionList(client apiextensionsclientset.Interface, dsQuery *dataselect.DataSelectQuery) (*types.CustomResourceDefinitionList, error) {
+	channel := common.GetCustomResourceDefinitionChannelV1beta1(client, 1)
+	crdList := <-channel.List
+	err := <-channel.Error
+
+	nonCriticalErrors, criticalError := errors.HandleError(err)
+	if criticalError != nil {
+		return nil, criticalError
+	}
+
+	return toCustomResourceDefinitionList(crdList.Items, nonCriticalErrors, dsQuery), nil
+}
+
+func toCustomResourceDefinitionList(crds []apiextensionsv1beta1.CustomResourceDefinition, nonCriticalErrors []error, dsQuery *dataselect.DataSelectQuery) *types.CustomResourceDefinitionList {
+	crdList := &types.CustomResourceDefinitionList{
+		Items:    make([]types.CustomResourceDefinition, 0),
+		ListMeta: api.ListMeta{TotalItems: len(crds)},
+		Errors:   nonCriticalErrors,
+	}
+
+	crdCells, filteredTotal := dataselect.GenericDataSelectWithFilter(toCells(crds), dsQuery)
+	crds = fromCells(crdCells)
+	crdList.ListMeta = api.ListMeta{TotalItems: filteredTotal}
+
+	for _, crd := range crds {
+		crdList.Items = append(crdList.Items, toCustomResourceDefinition(&crd))
+	}
+
+	return crdList
+}
+
+func toCustomResourceDefinition(crd *apiextensionsv1beta1.CustomResourceDefinition) types.CustomResourceDefinition {
+	return types.CustomResourceDefinition{
+		ObjectMeta:  api.NewObjectMeta(crd.ObjectMeta),
+		TypeMeta:    api.NewTypeMeta(api.ResourceKindCustomResourceDefinition),
+		Version:     crd.Spec.Versions[0].Name,
+		Group:       crd.Spec.Group,
+		Scope:       toCustomResourceDefinitionScope(crd.Spec.Scope),
+		Names:       toCustomResourceDefinitionAcceptedNames(crd.Status.AcceptedNames),
+		Established: getCRDConditionStatus(crd, apiextensionsv1beta1.Established),
+	}
+}
+
+func toCustomResourceDefinitionScope(scope apiextensionsv1beta1.ResourceScope) apiextensions.ResourceScope {
+	return apiextensions.ResourceScope(scope)
+}
+
+func toCustomResourceDefinitionAcceptedNames(names apiextensionsv1beta1.CustomResourceDefinitionNames) types.CustomResourceDefinitionNames {
+	return types.CustomResourceDefinitionNames{
+		Plural:     names.Plural,
+		Singular:   names.Singular,
+		ShortNames: names.ShortNames,
+		Kind:       names.Kind,
+		ListKind:   names.ListKind,
+		Categories: names.Categories,
+	}
+}
+
+func getCRDConditionStatus(node *apiextensionsv1beta1.CustomResourceDefinition, conditionType apiextensionsv1beta1.CustomResourceDefinitionConditionType) apiextensions.ConditionStatus {
+	for _, condition := range node.Status.Conditions {
+		if condition.Type == conditionType {
+			return apiextensions.ConditionStatus(condition.Status)
+		}
+	}
+	return apiextensions.ConditionUnknown
+}
diff --git a/src/app/backend/resource/customresourcedefinition/objects.go b/src/app/backend/resource/customresourcedefinition/v1beta1/objects.go
similarity index 67%
rename from src/app/backend/resource/customresourcedefinition/objects.go
rename to src/app/backend/resource/customresourcedefinition/v1beta1/objects.go
index dfa56b021..750d7dce6 100644
--- a/src/app/backend/resource/customresourcedefinition/objects.go
+++ b/src/app/backend/resource/customresourcedefinition/v1beta1/objects.go
@@ -12,84 +12,29 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package customresourcedefinition
+package v1beta1
 
 import (
 	"encoding/json"
 
+	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
+	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+	"k8s.io/client-go/rest"
+
 	"github.com/kubernetes/dashboard/src/app/backend/api"
 	"github.com/kubernetes/dashboard/src/app/backend/errors"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
-	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
-	apiextensionsclientset "k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset"
-	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
-	"k8s.io/client-go/rest"
 )
 
-// CustomResourceObject represents a custom resource object.
-type CustomResourceObject struct {
-	TypeMeta   api.TypeMeta   `json:"typeMeta"`
-	ObjectMeta api.ObjectMeta `json:"objectMeta"`
-}
-
-func (r *CustomResourceObject) UnmarshalJSON(data []byte) error {
-	tempStruct := &struct {
-		metav1.TypeMeta `json:",inline"`
-		ObjectMeta      metav1.ObjectMeta `json:"metadata,omitempty"`
-	}{}
-
-	err := json.Unmarshal(data, &tempStruct)
-	if err != nil {
-		return err
-	}
-
-	r.TypeMeta = api.NewTypeMeta(api.ResourceKind(tempStruct.TypeMeta.Kind))
-	r.ObjectMeta = api.NewObjectMeta(tempStruct.ObjectMeta)
-	return nil
-}
-
-type CustomResourceObjectDetail struct {
-	CustomResourceObject `json:",inline"`
-
-	// List of non-critical errors, that occurred during resource retrieval.
-	Errors []error `json:"errors"`
-}
-
-// CustomResourceObjectList represents crd objects in a namespace.
-type CustomResourceObjectList struct {
-	TypeMeta metav1.TypeMeta `json:"typeMeta"`
-	ListMeta api.ListMeta    `json:"listMeta"`
-
-	// Unordered list of custom resource definitions
-	Items []CustomResourceObject `json:"items"`
-
-	// List of non-critical errors, that occurred during resource retrieval.
-	Errors []error `json:"errors"`
-}
-
-func (r *CustomResourceObjectList) UnmarshalJSON(data []byte) error {
-	tempStruct := &struct {
-		metav1.TypeMeta `json:",inline"`
-		Items           []CustomResourceObject `json:"items"`
-	}{}
-
-	err := json.Unmarshal(data, &tempStruct)
-	if err != nil {
-		return err
-	}
-
-	r.TypeMeta = tempStruct.TypeMeta
-	r.Items = tempStruct.Items
-	return nil
-}
-
 // GetCustomResourceObjectList gets objects for a CR.
 func GetCustomResourceObjectList(client apiextensionsclientset.Interface, config *rest.Config, namespace *common.NamespaceQuery,
-	dsQuery *dataselect.DataSelectQuery, crdName string) (*CustomResourceObjectList, error) {
-	var list *CustomResourceObjectList
+	dsQuery *dataselect.DataSelectQuery, crdName string) (*types.CustomResourceObjectList, error) {
+	var list *types.CustomResourceObjectList
 
-	customResourceDefinition, err := client.ApiextensionsV1().
+	customResourceDefinition, err := client.ApiextensionsV1beta1().
 		CustomResourceDefinitions().
 		Get(crdName, metav1.GetOptions{})
 	nonCriticalErrors, criticalError := errors.HandleError(err)
@@ -132,10 +77,10 @@ func GetCustomResourceObjectList(client apiextensionsclientset.Interface, config
 }
 
 // GetCustomResourceObjectDetail returns details of a single object in a CR.
-func GetCustomResourceObjectDetail(client apiextensionsclientset.Interface, namespace *common.NamespaceQuery, config *rest.Config, crdName string, name string) (*CustomResourceObjectDetail, error) {
-	var detail *CustomResourceObjectDetail
+func GetCustomResourceObjectDetail(client apiextensionsclientset.Interface, namespace *common.NamespaceQuery, config *rest.Config, crdName string, name string) (*types.CustomResourceObjectDetail, error) {
+	var detail *types.CustomResourceObjectDetail
 
-	customResourceDefinition, err := client.ApiextensionsV1().
+	customResourceDefinition, err := client.ApiextensionsV1beta1().
 		CustomResourceDefinitions().
 		Get(crdName, metav1.GetOptions{})
 	nonCriticalErrors, criticalError := errors.HandleError(err)
@@ -171,7 +116,7 @@ func GetCustomResourceObjectDetail(client apiextensionsclientset.Interface, name
 
 // toCRDObject sets the object kind to the full name of the CRD.
 // E.g. changes "Foo" to "foos.samplecontroller.k8s.io"
-func toCRDObject(object *CustomResourceObject, crd *apiextensions.CustomResourceDefinition) {
+func toCRDObject(object *types.CustomResourceObject, crd *apiextensions.CustomResourceDefinition) {
 	object.TypeMeta.Kind = api.ResourceKind(crd.Name)
 	crdSubresources := crd.Spec.Versions[0].Subresources
 	object.TypeMeta.Scalable = crdSubresources != nil && crdSubresources.Scale != nil
diff --git a/src/app/frontend/common/services/global/pinner.ts b/src/app/frontend/common/services/global/pinner.ts
index 97c475b5b..1778f5b3e 100644
--- a/src/app/frontend/common/services/global/pinner.ts
+++ b/src/app/frontend/common/services/global/pinner.ts
@@ -24,7 +24,7 @@ import {VerberService} from './verber';
 export class PinnerService {
   onPinUpdate = new Subject();
   private isInitialized_ = false;
-  private pinnedResources_: PinnedResource[];
+  private pinnedResources_: PinnedResource[] = [];
   private readonly endpoint_ = `api/v1/settings/pinner`;
 
   constructor(


Test patch:
diff --git a/src/app/backend/client/manager_test.go b/src/app/backend/client/manager_test.go
index 6ad1b15ac..07b3f0fcd 100644
--- a/src/app/backend/client/manager_test.go
+++ b/src/app/backend/client/manager_test.go
@@ -284,7 +284,10 @@ func TestClientCmdConfig(t *testing.T) {
 }
 
 func TestVerberClient(t *testing.T) {
-	manager := NewClientManager("", "http://localhost:8080")
+	// TODO: client manager needs a way of mocking created client to be able to test the logic inside
+	t.Skip("Skipping verber client test.")
+
+	manager := NewClientManager("", "")
 	_, err := manager.VerberClient(&restful.Request{Request: &http.Request{TLS: &tls.ConnectionState{}}}, &rest.Config{})
 
 	if err != nil {
@@ -332,7 +335,6 @@ func TestImpersonationUserClient(t *testing.T) {
 	for _, c := range cases {
 		manager := NewClientManager("", "https://localhost:8080")
 		cfg, err := manager.Config(c.request)
-		//authInfo := manager.extractAuthInfo(c.request)
 		if err != nil {
 			t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
 				" %s",
@@ -370,7 +372,6 @@ func TestNoImpersonationUserWithNoBearerClient(t *testing.T) {
 	for _, c := range cases {
 		manager := NewClientManager("", "https://localhost:8080")
 		cfg, err := manager.Config(c.request)
-		//authInfo := manager.extractAuthInfo(c.request)
 		if err != nil {
 			t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
 				" %s",
@@ -418,7 +419,6 @@ func TestImpersonationOneGroupClient(t *testing.T) {
 	for _, c := range cases {
 		manager := NewClientManager("", "https://localhost:8080")
 		cfg, err := manager.Config(c.request)
-		//authInfo := manager.extractAuthInfo(c.request)
 		if err != nil {
 			t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
 				" %s",
@@ -475,7 +475,6 @@ func TestImpersonationTwoGroupClient(t *testing.T) {
 	for _, c := range cases {
 		manager := NewClientManager("", "https://localhost:8080")
 		cfg, err := manager.Config(c.request)
-		//authInfo := manager.extractAuthInfo(c.request)
 		if err != nil {
 			t.Fatalf("Config(%v): Expected config to be created but error was thrown:"+
 				" %s",
diff --git a/src/app/backend/resource/customresourcedefinition/events_test.go b/src/app/backend/resource/customresourcedefinition/events_test.go
index a266888cc..b07f3bf1d 100644
--- a/src/app/backend/resource/customresourcedefinition/events_test.go
+++ b/src/app/backend/resource/customresourcedefinition/events_test.go
@@ -18,10 +18,12 @@ import (
 	"reflect"
 	"testing"
 
+	coreV1 "k8s.io/api/core/v1"
+
 	"github.com/kubernetes/dashboard/src/app/backend/api"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/common"
 	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
-	v1 "k8s.io/api/core/v1"
+
 	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
 	"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
 	"k8s.io/client-go/kubernetes/fake"
@@ -30,20 +32,20 @@ import (
 func TestGetEventsForCustomResourceObject(t *testing.T) {
 	cases := []struct {
 		namespace, objectName string
-		eventList             *v1.EventList
+		eventList             *coreV1.EventList
 		objectList            *unstructured.UnstructuredList
 		expected              *common.EventList
 	}{
 		{
 			"ns-1", "example-foo",
-			&v1.EventList{Items: []v1.Event{
+			&coreV1.EventList{Items: []coreV1.Event{
 				{
 					Message: "test-message",
 					ObjectMeta: metaV1.ObjectMeta{
 						Name: "ev-1", Namespace: "ns-1",
 						Labels: map[string]string{"app": "test"},
 					},
-					InvolvedObject: v1.ObjectReference{UID: "test-uid"}},
+					InvolvedObject: coreV1.ObjectReference{UID: "test-uid"}},
 			}},
 			&unstructured.UnstructuredList{Items: []unstructured.Unstructured{
 				{
@@ -65,7 +67,7 @@ func TestGetEventsForCustomResourceObject(t *testing.T) {
 					ObjectMeta: api.ObjectMeta{Name: "ev-1", Namespace: "ns-1",
 						Labels: map[string]string{"app": "test"}},
 					Message: "test-message",
-					Type:    v1.EventTypeNormal,
+					Type:    coreV1.EventTypeNormal,
 				}},
 				Errors: []error{},
 			},
diff --git a/src/app/backend/resource/customresourcedefinition/list_test.go b/src/app/backend/resource/customresourcedefinition/v1/list_test.go
similarity index 75%
rename from src/app/backend/resource/customresourcedefinition/list_test.go
rename to src/app/backend/resource/customresourcedefinition/v1/list_test.go
index 89e371990..e8bad5d89 100644
--- a/src/app/backend/resource/customresourcedefinition/list_test.go
+++ b/src/app/backend/resource/customresourcedefinition/v1/list_test.go
@@ -12,37 +12,40 @@
 // See the License for the specific language governing permissions and
 // limitations under the License.
 
-package customresourcedefinition
+package v1
 
 import (
 	"reflect"
 	"testing"
 
-	"github.com/kubernetes/dashboard/src/app/backend/api"
-	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
-	apiextensions "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+	apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
 	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
 	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/kubernetes/dashboard/src/app/backend/api"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
 )
 
 func TestGetCustomResourceDefinition(t *testing.T) {
 	cases := []struct {
 		expectedActions []string
-		crdList         *apiextensions.CustomResourceDefinitionList
-		expected        *CustomResourceDefinitionList
+		crdList         *apiextensionsv1.CustomResourceDefinitionList
+		expected        *types.CustomResourceDefinitionList
 	}{
 		{
 			[]string{"list"},
-			&apiextensions.CustomResourceDefinitionList{
-				Items: []apiextensions.CustomResourceDefinition{
+			&apiextensionsv1.CustomResourceDefinitionList{
+				Items: []apiextensionsv1.CustomResourceDefinition{
 					{
 						ObjectMeta: metaV1.ObjectMeta{Name: "foos.samplecontroller.k8s.io"},
-						Spec: apiextensions.CustomResourceDefinitionSpec{
-							Names: apiextensions.CustomResourceDefinitionNames{
+						Spec: apiextensionsv1.CustomResourceDefinitionSpec{
+							Names: apiextensionsv1.CustomResourceDefinitionNames{
 								Kind:   "Foo",
 								Plural: "foos",
 							},
-							Versions: []apiextensions.CustomResourceDefinitionVersion{
+							Versions: []apiextensionsv1.CustomResourceDefinitionVersion{
 								{
 									Name: "v1alpha1",
 								},
@@ -51,9 +54,9 @@ func TestGetCustomResourceDefinition(t *testing.T) {
 					},
 				},
 			},
-			&CustomResourceDefinitionList{
+			&types.CustomResourceDefinitionList{
 				ListMeta: api.ListMeta{TotalItems: 1},
-				Items: []CustomResourceDefinition{
+				Items: []types.CustomResourceDefinition{
 					{
 						ObjectMeta:  api.ObjectMeta{Name: "foos.samplecontroller.k8s.io"},
 						TypeMeta:    api.TypeMeta{Kind: api.ResourceKindCustomResourceDefinition},
diff --git a/src/app/backend/resource/customresourcedefinition/v1beta1/list_test.go b/src/app/backend/resource/customresourcedefinition/v1beta1/list_test.go
new file mode 100644
index 000000000..f3c4e673f
--- /dev/null
+++ b/src/app/backend/resource/customresourcedefinition/v1beta1/list_test.go
@@ -0,0 +1,96 @@
+// Copyright 2017 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 v1beta1
+
+import (
+	"reflect"
+	"testing"
+
+	"k8s.io/apiextensions-apiserver/pkg/apis/apiextensions"
+	apiextensionsv1beta1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1beta1"
+	"k8s.io/apiextensions-apiserver/pkg/client/clientset/clientset/fake"
+	metaV1 "k8s.io/apimachinery/pkg/apis/meta/v1"
+
+	"github.com/kubernetes/dashboard/src/app/backend/api"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/customresourcedefinition/types"
+	"github.com/kubernetes/dashboard/src/app/backend/resource/dataselect"
+)
+
+func TestGetCustomResourceDefinition(t *testing.T) {
+	cases := []struct {
+		expectedActions []string
+		crdList         *apiextensionsv1beta1.CustomResourceDefinitionList
+		expected        *types.CustomResourceDefinitionList
+	}{
+		{
+			[]string{"list"},
+			&apiextensionsv1beta1.CustomResourceDefinitionList{
+				Items: []apiextensionsv1beta1.CustomResourceDefinition{
+					{
+						ObjectMeta: metaV1.ObjectMeta{Name: "foos.samplecontroller.k8s.io"},
+						Spec: apiextensionsv1beta1.CustomResourceDefinitionSpec{
+							Names: apiextensionsv1beta1.CustomResourceDefinitionNames{
+								Kind:   "Foo",
+								Plural: "foos",
+							},
+							Versions: []apiextensionsv1beta1.CustomResourceDefinitionVersion{
+								{
+									Name: "v1alpha1",
+								},
+							},
+						},
+					},
+				},
+			},
+			&types.CustomResourceDefinitionList{
+				ListMeta: api.ListMeta{TotalItems: 1},
+				Items: []types.CustomResourceDefinition{
+					{
+						ObjectMeta:  api.ObjectMeta{Name: "foos.samplecontroller.k8s.io"},
+						TypeMeta:    api.TypeMeta{Kind: api.ResourceKindCustomResourceDefinition},
+						Version:     "v1alpha1",
+						Established: apiextensions.ConditionUnknown,
+					},
+				},
+				Errors: []error{},
+			},
+		},
+	}
+
+	for _, c := range cases {
+		fakeClient := fake.NewSimpleClientset(c.crdList)
+
+		actual, _ := GetCustomResourceDefinitionList(fakeClient, dataselect.DefaultDataSelect)
+
+		actions := fakeClient.Actions()
+		if len(actions) != len(c.expectedActions) {
+			t.Errorf("Unexpected actions: %v, expected %d actions got %d", actions,
+				len(c.expectedActions), len(actions))
+			continue
+		}
+
+		for i, verb := range c.expectedActions {
+			if actions[i].GetVerb() != verb {
+				t.Errorf("Unexpected action: %+v, expected %s",
+					actions[i], verb)
+			}
+		}
+
+		if !reflect.DeepEqual(actual, c.expected) {
+			t.Errorf("GetCustomResourceDefinitionList(client, nil) == \ngot: %#v, \nexpected %#v",
+				actual, c.expected)
+		}
+	}
+}