diff --git a/pkg/client/unversioned/client.go b/pkg/client/unversioned/client.go index ef23b69238..b1bbab16f9 100644 --- a/pkg/client/unversioned/client.go +++ b/pkg/client/unversioned/client.go @@ -52,6 +52,7 @@ type Interface interface { ComponentStatusesInterface SwaggerSchemaInterface Extensions() ExtensionsInterface + ResourcesInterface } func (c *Client) ReplicationControllers(namespace string) ReplicationControllerInterface { @@ -119,6 +120,12 @@ type VersionInterface interface { ServerAPIVersions() (*unversioned.APIVersions, error) } +// ResourcesInterface has methods for obtaining supported resources on the API server +type ResourcesInterface interface { + SupportedResourcesForGroupVersion(groupVersion string) (*api.APIResourceList, error) + SupportedResources() (map[string]*api.APIResourceList, error) +} + // APIStatus is exposed by errors that can be converted to an api.Status object // for finer grained details. type APIStatus interface { @@ -145,6 +152,42 @@ func (c *Client) ServerVersion() (*version.Info, error) { return &info, nil } +// SupportedResourcesForGroupVersion retrieves the list of resources supported by the API server for a group version. +func (c *Client) SupportedResourcesForGroupVersion(groupVersion string) (*api.APIResourceList, error) { + var prefix string + if groupVersion == "v1" { + prefix = "/api" + } else { + prefix = "/apis" + } + body, err := c.Get().AbsPath(prefix, groupVersion).Do().Raw() + if err != nil { + return nil, err + } + resources := api.APIResourceList{} + if err := json.Unmarshal(body, &resources); err != nil { + return nil, err + } + return &resources, nil +} + +// SupportedResources gets all supported resources for all group versions. The key in the map is an API groupVersion. +func (c *Client) SupportedResources() (map[string]*api.APIResourceList, error) { + apis, err := c.ServerAPIVersions() + if err != nil { + return nil, err + } + result := map[string]*api.APIResourceList{} + for _, groupVersion := range apis.Versions { + resources, err := c.SupportedResourcesForGroupVersion(groupVersion) + if err != nil { + return nil, err + } + result[groupVersion] = resources + } + return result, nil +} + // ServerAPIVersions retrieves and parses the list of API versions the server supports. func (c *Client) ServerAPIVersions() (*unversioned.APIVersions, error) { body, err := c.Get().UnversionedPath("").Do().Raw() diff --git a/pkg/client/unversioned/client_test.go b/pkg/client/unversioned/client_test.go index 843e4ee302..86d015c62d 100644 --- a/pkg/client/unversioned/client_test.go +++ b/pkg/client/unversioned/client_test.go @@ -271,6 +271,105 @@ func TestGetServerVersion(t *testing.T) { } } +func TestGetServerResources(t *testing.T) { + stable := api.APIResourceList{ + GroupVersion: "v1", + APIResources: []api.APIResource{ + {"pods", true}, + {"services", true}, + {"namespaces", false}, + }, + } + beta := api.APIResourceList{ + GroupVersion: "extensions/v1", + APIResources: []api.APIResource{ + {"deployments", true}, + {"ingress", true}, + {"jobs", true}, + }, + } + tests := []struct { + resourcesList *api.APIResourceList + path string + request string + expectErr bool + }{ + { + resourcesList: &stable, + path: "/api/v1", + request: "v1", + expectErr: false, + }, + { + resourcesList: &beta, + path: "/apis/extensions/v1beta1", + request: "extensions/v1beta1", + expectErr: false, + }, + { + resourcesList: &stable, + path: "/api/v1", + request: "foobar", + expectErr: true, + }, + } + server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) { + var list interface{} + switch req.URL.Path { + case "/api/v1": + list = &stable + case "/apis/extensions/v1beta1": + list = &beta + case "/api": + list = &api.APIVersions{ + Versions: []string{ + "v1", + "extensions/v1beta1", + }, + } + default: + t.Logf("unexpected request: %s", req.URL.Path) + w.WriteHeader(http.StatusNotFound) + return + } + output, err := json.Marshal(list) + if err != nil { + t.Errorf("unexpected encoding error: %v", err) + return + } + w.Header().Set("Content-Type", "application/json") + w.WriteHeader(http.StatusOK) + w.Write(output) + })) + client := NewOrDie(&Config{Host: server.URL}) + for _, test := range tests { + got, err := client.SupportedResourcesForGroupVersion(test.request) + if test.expectErr { + if err == nil { + t.Error("unexpected non-error") + } + continue + } + if err != nil { + t.Errorf("unexpected error: %v", err) + continue + } + if !reflect.DeepEqual(got, test.resourcesList) { + t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got) + } + } + + resourceMap, err := client.SupportedResources() + if err != nil { + t.Errorf("unexpected error: %v", err) + } + for _, api := range []string{"v1", "extensions/v1beta1"} { + if _, found := resourceMap[api]; !found { + t.Errorf("missing expected api: %s", api) + } + } +} + func TestGetServerAPIVersions(t *testing.T) { versions := []string{"v1", "v2", "v3"} expect := unversioned.APIVersions{Versions: versions} diff --git a/pkg/client/unversioned/testclient/testclient.go b/pkg/client/unversioned/testclient/testclient.go index fe0e2fdcd7..ddcf9d5ebe 100644 --- a/pkg/client/unversioned/testclient/testclient.go +++ b/pkg/client/unversioned/testclient/testclient.go @@ -60,6 +60,8 @@ type Fake struct { WatchReactionChain []WatchReactor // ProxyReactionChain is the list of proxy reactors that will be attempted for every request in the order they are tried ProxyReactionChain []ProxyReactor + + Resources []api.APIResourceList } // Reactor is an interface to allow the composition of reaction functions. @@ -272,6 +274,35 @@ func (c *Fake) Extensions() client.ExtensionsInterface { return &FakeExperimental{c} } +func (c *Fake) SupportedResources() (map[string]*api.APIResourceList, error) { + action := ActionImpl{ + Verb: "get", + Resource: "resources", + } + c.Invokes(action, nil) + result := map[string]*api.APIResourceList{} + for _, resource := range c.Resources { + result[resource.GroupVersion] = &api.APIResourceList{ + APIResources: resource.APIResources, + } + } + return result, nil +} + +func (c *Fake) SupportedResourcesForGroupVersion(version string) (*api.APIResourceList, error) { + action := ActionImpl{ + Verb: "get", + Resource: "resource", + } + c.Invokes(action, nil) + for _, resource := range c.Resources { + if resource.GroupVersion == version { + return &resource, nil + } + } + return nil, nil +} + func (c *Fake) ServerVersion() (*version.Info, error) { action := ActionImpl{} action.Verb = "get"