mirror of
https://github.com/k3s-io/kubernetes.git
synced 2026-01-29 21:29:24 +00:00
Add verb support for discovery client
This commit is contained in:
committed by
Dr. Stefan Schimanski
parent
4d1d98c49a
commit
458d2b2fe4
@@ -45,7 +45,7 @@ type Fake struct {
|
||||
// for every request in the order they are tried.
|
||||
ProxyReactionChain []ProxyReactor
|
||||
|
||||
Resources map[string]*metav1.APIResourceList
|
||||
Resources []*metav1.APIResourceList
|
||||
}
|
||||
|
||||
// Reactor is an interface to allow the composition of reaction functions.
|
||||
@@ -225,10 +225,16 @@ func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*me
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
}
|
||||
c.Invokes(action, nil)
|
||||
return c.Resources[groupVersion], nil
|
||||
for _, rl := range c.Resources {
|
||||
if rl.GroupVersion == groupVersion {
|
||||
return rl, nil
|
||||
}
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("GroupVersion %q not found", groupVersion)
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerResources() (map[string]*metav1.APIResourceList, error) {
|
||||
func (c *FakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
action := ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
|
||||
@@ -36,6 +36,9 @@ import (
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
)
|
||||
|
||||
// defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. ThirdPartyResources).
|
||||
const defaultRetries = 2
|
||||
|
||||
// DiscoveryInterface holds the methods that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryInterface interface {
|
||||
@@ -67,13 +70,13 @@ type ServerResourcesInterface interface {
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)
|
||||
// ServerResources returns the supported resources for all groups and versions.
|
||||
ServerResources() (map[string]*metav1.APIResourceList, error)
|
||||
ServerResources() ([]*metav1.APIResourceList, error)
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
ServerPreferredResources() ([]schema.GroupVersionResource, error)
|
||||
ServerPreferredResources() ([]*metav1.APIResourceList, error)
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error)
|
||||
ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
|
||||
}
|
||||
|
||||
// ServerVersionInterface has a method for retrieving the server's version.
|
||||
@@ -154,7 +157,9 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
|
||||
} else {
|
||||
url.Path = "/apis/" + groupVersion
|
||||
}
|
||||
resources = &metav1.APIResourceList{}
|
||||
resources = &metav1.APIResourceList{
|
||||
GroupVersion: groupVersion,
|
||||
}
|
||||
err = d.restClient.Get().AbsPath(url.String()).Do().Into(resources)
|
||||
if err != nil {
|
||||
// ignore 403 or 404 error to be compatible with an v1.0 server.
|
||||
@@ -166,22 +171,43 @@ func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (r
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// ServerResources returns the supported resources for all groups and versions.
|
||||
func (d *DiscoveryClient) ServerResources() (map[string]*metav1.APIResourceList, error) {
|
||||
// serverResources returns the supported resources for all groups and versions.
|
||||
func (d *DiscoveryClient) serverResources(failEarly bool) ([]*metav1.APIResourceList, error) {
|
||||
apiGroups, err := d.ServerGroups()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groupVersions := metav1.ExtractGroupVersions(apiGroups)
|
||||
result := map[string]*metav1.APIResourceList{}
|
||||
for _, groupVersion := range groupVersions {
|
||||
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
|
||||
result := []*metav1.APIResourceList{}
|
||||
failedGroups := make(map[schema.GroupVersion]error)
|
||||
|
||||
for _, apiGroup := range apiGroups.Groups {
|
||||
for _, version := range apiGroup.Versions {
|
||||
gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
|
||||
resources, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
|
||||
if err != nil {
|
||||
// TODO: maybe restrict this to NotFound errors
|
||||
failedGroups[gv] = err
|
||||
if failEarly {
|
||||
return nil, &ErrGroupDiscoveryFailed{Groups: failedGroups}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, resources)
|
||||
}
|
||||
result[groupVersion] = resources
|
||||
}
|
||||
return result, nil
|
||||
|
||||
if len(failedGroups) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
|
||||
}
|
||||
|
||||
// ServerResources returns the supported resources for all groups and versions.
|
||||
func (d *DiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
return withRetries(defaultRetries, d.serverResources)
|
||||
}
|
||||
|
||||
// ErrGroupDiscoveryFailed is returned if one or more API groups fail to load.
|
||||
@@ -207,78 +233,86 @@ func IsGroupDiscoveryFailedError(err error) bool {
|
||||
return err != nil && ok
|
||||
}
|
||||
|
||||
// serverPreferredResources returns the supported resources with the version preferred by the
|
||||
// server. If namespaced is true, only namespaced resources will be returned.
|
||||
func (d *DiscoveryClient) serverPreferredResources(namespaced bool) ([]schema.GroupVersionResource, error) {
|
||||
// retry in case the groups supported by the server change after ServerGroup() returns.
|
||||
const maxRetries = 2
|
||||
var failedGroups map[schema.GroupVersion]error
|
||||
var results []schema.GroupVersionResource
|
||||
var resources map[schema.GroupResource]string
|
||||
RetrieveGroups:
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
results = []schema.GroupVersionResource{}
|
||||
resources = map[schema.GroupResource]string{}
|
||||
failedGroups = make(map[schema.GroupVersion]error)
|
||||
serverGroupList, err := d.ServerGroups()
|
||||
if err != nil {
|
||||
return results, err
|
||||
}
|
||||
// serverPreferredResources returns the supported resources with the version preferred by the server.
|
||||
func (d *DiscoveryClient) serverPreferredResources(failEarly bool) ([]*metav1.APIResourceList, error) {
|
||||
serverGroupList, err := d.ServerGroups()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
for _, apiGroup := range serverGroupList.Groups {
|
||||
versions := apiGroup.Versions
|
||||
for _, version := range versions {
|
||||
groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
|
||||
apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
|
||||
if err != nil {
|
||||
if i < maxRetries-1 {
|
||||
continue RetrieveGroups
|
||||
}
|
||||
failedGroups[groupVersion] = err
|
||||
result := []*metav1.APIResourceList{}
|
||||
failedGroups := make(map[schema.GroupVersion]error)
|
||||
|
||||
grVersions := map[schema.GroupResource]string{} // selected version of a GroupResource
|
||||
grApiResources := map[schema.GroupResource]*metav1.APIResource{} // selected APIResource for a GroupResource
|
||||
gvApiResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // blueprint for a APIResourceList for later grouping
|
||||
|
||||
for _, apiGroup := range serverGroupList.Groups {
|
||||
for _, version := range apiGroup.Versions {
|
||||
groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}
|
||||
apiResourceList, err := d.ServerResourcesForGroupVersion(version.GroupVersion)
|
||||
if err != nil {
|
||||
// TODO: maybe restrict this to NotFound errors
|
||||
failedGroups[groupVersion] = err
|
||||
if failEarly {
|
||||
return nil, &ErrGroupDiscoveryFailed{Groups: failedGroups}
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
// create empty list which is filled later in another loop
|
||||
emptyApiResourceList := metav1.APIResourceList{
|
||||
GroupVersion: version.GroupVersion,
|
||||
}
|
||||
gvApiResourceLists[groupVersion] = &emptyApiResourceList
|
||||
result = append(result, &emptyApiResourceList)
|
||||
|
||||
for i := range apiResourceList.APIResources {
|
||||
apiResource := &apiResourceList.APIResources[i]
|
||||
if strings.Contains(apiResource.Name, "/") {
|
||||
continue
|
||||
}
|
||||
for _, apiResource := range apiResourceList.APIResources {
|
||||
// ignore the root scoped resources if "namespaced" is true.
|
||||
if namespaced && !apiResource.Namespaced {
|
||||
continue
|
||||
}
|
||||
if strings.Contains(apiResource.Name, "/") {
|
||||
continue
|
||||
}
|
||||
gvr := groupVersion.WithResource(apiResource.Name)
|
||||
if _, ok := resources[gvr.GroupResource()]; ok {
|
||||
if gvr.Version != apiGroup.PreferredVersion.Version {
|
||||
continue
|
||||
}
|
||||
// remove previous entry, because it will be replaced with a preferred one
|
||||
for i := range results {
|
||||
if results[i].GroupResource() == gvr.GroupResource() {
|
||||
results = append(results[:i], results[i+1:]...)
|
||||
}
|
||||
}
|
||||
}
|
||||
resources[gvr.GroupResource()] = gvr.Version
|
||||
results = append(results, gvr)
|
||||
gv := schema.GroupResource{Group: apiGroup.Name, Resource: apiResource.Name}
|
||||
if _, ok := grApiResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {
|
||||
// only override with preferred version
|
||||
continue
|
||||
}
|
||||
grVersions[gv] = version.Version
|
||||
grApiResources[gv] = apiResource
|
||||
}
|
||||
}
|
||||
if len(failedGroups) == 0 {
|
||||
return results, nil
|
||||
}
|
||||
}
|
||||
return results, &ErrGroupDiscoveryFailed{Groups: failedGroups}
|
||||
|
||||
// group selected APIResources according to GroupVersion into APIResourceLists
|
||||
for groupResource, apiResource := range grApiResources {
|
||||
version := grVersions[groupResource]
|
||||
groupVersion := schema.GroupVersion{Group: groupResource.Group, Version: version}
|
||||
apiResourceList := gvApiResourceLists[groupVersion]
|
||||
apiResourceList.APIResources = append(apiResourceList.APIResources, *apiResource)
|
||||
}
|
||||
|
||||
if len(failedGroups) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
return result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
|
||||
}
|
||||
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
func (d *DiscoveryClient) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
|
||||
return d.serverPreferredResources(false)
|
||||
func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return withRetries(defaultRetries, func(retryEarly bool) ([]*metav1.APIResourceList, error) {
|
||||
return d.serverPreferredResources(retryEarly)
|
||||
})
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
|
||||
return d.serverPreferredResources(true)
|
||||
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
all, err := d.ServerPreferredResources()
|
||||
return FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {
|
||||
return r.Namespaced
|
||||
}), all), err
|
||||
}
|
||||
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
@@ -329,6 +363,23 @@ func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.A
|
||||
return &schema, nil
|
||||
}
|
||||
|
||||
// withRetries retries the given recovery function in case the groups supported by the server change after ServerGroup() returns.
|
||||
func withRetries(maxRetries int, f func(failEarly bool) ([]*metav1.APIResourceList, error)) ([]*metav1.APIResourceList, error) {
|
||||
var result []*metav1.APIResourceList
|
||||
var err error
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
failEarly := i < maxRetries-1
|
||||
result, err = f(failEarly)
|
||||
if err == nil {
|
||||
return result, nil
|
||||
}
|
||||
if _, ok := err.(*ErrGroupDiscoveryFailed); !ok {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return result, err
|
||||
}
|
||||
|
||||
func setDiscoveryDefaults(config *restclient.Config) error {
|
||||
config.APIPath = ""
|
||||
config.GroupVersion = nil
|
||||
|
||||
@@ -29,6 +29,7 @@ import (
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
"k8s.io/kubernetes/pkg/version"
|
||||
)
|
||||
|
||||
@@ -141,14 +142,14 @@ func TestGetServerResourcesWithV1Server(t *testing.T) {
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerResources should not return an error even if server returns error at /api/v1.
|
||||
resourceMap, err := client.ServerResources()
|
||||
serverResources, err := client.ServerResources()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
if _, found := resourceMap["v1"]; !found {
|
||||
t.Errorf("missing v1 in resource map")
|
||||
gvs := groupVersions(serverResources)
|
||||
if !sets.NewString(gvs...).Has("v1") {
|
||||
t.Errorf("missing v1 in resource list: %v", serverResources)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func TestGetServerResources(t *testing.T) {
|
||||
@@ -161,7 +162,7 @@ func TestGetServerResources(t *testing.T) {
|
||||
},
|
||||
}
|
||||
beta := metav1.APIResourceList{
|
||||
GroupVersion: "extensions/v1",
|
||||
GroupVersion: "extensions/v1beta1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
|
||||
{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
|
||||
@@ -249,13 +250,14 @@ func TestGetServerResources(t *testing.T) {
|
||||
}
|
||||
}
|
||||
|
||||
resourceMap, err := client.ServerResources()
|
||||
serverResources, err := client.ServerResources()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
serverGroupVersions := sets.NewString(groupVersions(serverResources)...)
|
||||
for _, api := range []string{"v1", "extensions/v1beta1"} {
|
||||
if _, found := resourceMap[api]; !found {
|
||||
t.Errorf("missing expected api: %s", api)
|
||||
if !serverGroupVersions.Has(api) {
|
||||
t.Errorf("missing expected api %q in %v", api, serverResources)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -332,12 +334,12 @@ func TestServerPreferredResources(t *testing.T) {
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
resourcesList *metav1.APIResourceList
|
||||
resourcesList []*metav1.APIResourceList
|
||||
response func(w http.ResponseWriter, req *http.Request)
|
||||
expectErr func(err error) bool
|
||||
}{
|
||||
{
|
||||
resourcesList: &stable,
|
||||
resourcesList: []*metav1.APIResourceList{&stable},
|
||||
expectErr: IsGroupDiscoveryFailedError,
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
@@ -426,7 +428,7 @@ func TestServerPreferredResources(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
got, err := client.ServerPreferredResources()
|
||||
resources, err := client.ServerPreferredResources()
|
||||
if test.expectErr != nil {
|
||||
if err == nil {
|
||||
t.Error("unexpected non-error")
|
||||
@@ -438,7 +440,13 @@ func TestServerPreferredResources(t *testing.T) {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
if !reflect.DeepEqual(got, test.resourcesList) {
|
||||
got, err := GroupVersionResources(resources)
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
expected, _ := GroupVersionResources(test.resourcesList)
|
||||
if !reflect.DeepEqual(got, expected) {
|
||||
t.Errorf("expected:\n%v\ngot:\n%v\n", test.resourcesList, got)
|
||||
}
|
||||
server.Close()
|
||||
@@ -533,10 +541,14 @@ func TestServerPreferredResourcesRetries(t *testing.T) {
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
got, err := client.ServerPreferredResources()
|
||||
resources, err := client.ServerPreferredResources()
|
||||
if !tc.expectedError(err) {
|
||||
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||
}
|
||||
got, err := GroupVersionResources(resources)
|
||||
if err != nil {
|
||||
t.Errorf("case %d: unexpected error: %v", i, err)
|
||||
}
|
||||
if len(got) != tc.expectResources {
|
||||
t.Errorf("case %d: expect %d resources, got %#v", i, tc.expectResources, got)
|
||||
}
|
||||
@@ -575,7 +587,7 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
}
|
||||
tests := []struct {
|
||||
response func(w http.ResponseWriter, req *http.Request)
|
||||
expected []schema.GroupVersionResource
|
||||
expected map[schema.GroupVersionResource]struct{}
|
||||
}{
|
||||
{
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
@@ -603,9 +615,9 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
},
|
||||
expected: []schema.GroupVersionResource{
|
||||
{Group: "", Version: "v1", Resource: "pods"},
|
||||
{Group: "", Version: "v1", Resource: "services"},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "pods"}: {},
|
||||
schema.GroupVersionResource{Group: "", Version: "v1", Resource: "services"}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -646,9 +658,9 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
},
|
||||
expected: []schema.GroupVersionResource{
|
||||
{Group: "batch", Version: "v1", Resource: "jobs"},
|
||||
{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
schema.GroupVersionResource{Group: "batch", Version: "v1", Resource: "jobs"}: {},
|
||||
schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
@@ -689,27 +701,39 @@ func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
},
|
||||
expected: []schema.GroupVersionResource{
|
||||
{Group: "batch", Version: "v2alpha1", Resource: "jobs"},
|
||||
{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "jobs"}: {},
|
||||
schema.GroupVersionResource{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
for i, test := range tests {
|
||||
server := httptest.NewServer(http.HandlerFunc(test.response))
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
got, err := client.ServerPreferredNamespacedResources()
|
||||
resources, err := client.ServerPreferredNamespacedResources()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
// we need deterministic order and since during processing in ServerPreferredNamespacedResources
|
||||
// a map comes into play the result needs sorting
|
||||
got, err := GroupVersionResources(resources)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
t.Errorf("expected:\n%v\ngot:\n%v\n", test.expected, got)
|
||||
t.Errorf("[%d] expected:\n%v\ngot:\n%v\n", i, test.expected, got)
|
||||
}
|
||||
server.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func groupVersions(resources []*metav1.APIResourceList) []string {
|
||||
result := []string{}
|
||||
for _, resourceList := range resources {
|
||||
result = append(result, resourceList.GroupVersion)
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -17,7 +17,10 @@ limitations under the License.
|
||||
package fake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/emicklei/go-restful/swagger"
|
||||
|
||||
"k8s.io/kubernetes/pkg/api/v1"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
@@ -36,10 +39,15 @@ func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*me
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
}
|
||||
c.Invokes(action, nil)
|
||||
return c.Resources[groupVersion], nil
|
||||
for _, resourceList := range c.Resources {
|
||||
if resourceList.GroupVersion == groupVersion {
|
||||
return resourceList, nil
|
||||
}
|
||||
}
|
||||
return nil, fmt.Errorf("GroupVersion %q not found", groupVersion)
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerResources() (map[string]*metav1.APIResourceList, error) {
|
||||
func (c *FakeDiscovery) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
action := core.ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
@@ -48,11 +56,11 @@ func (c *FakeDiscovery) ServerResources() (map[string]*metav1.APIResourceList, e
|
||||
return c.Resources, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
|
||||
func (c *FakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
|
||||
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
|
||||
@@ -108,3 +108,55 @@ func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion
|
||||
return nil, fmt.Errorf("failed to negotiate an api version; server supports: %v, client supports: %v",
|
||||
serverVersions, clientVersions)
|
||||
}
|
||||
|
||||
// GroupVersionResources converts APIResourceLists to the GroupVersionResources.
|
||||
func GroupVersionResources(rls []*metav1.APIResourceList) (map[schema.GroupVersionResource]struct{}, error) {
|
||||
gvrs := map[schema.GroupVersionResource]struct{}{}
|
||||
for _, rl := range rls {
|
||||
gv, err := schema.ParseGroupVersion(rl.GroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for i := range rl.APIResources {
|
||||
gvrs[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{}
|
||||
}
|
||||
}
|
||||
return gvrs, nil
|
||||
}
|
||||
|
||||
// FilteredBy filters by the given predicate. Empty APIResourceLists are dropped.
|
||||
func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1.APIResourceList {
|
||||
result := []*metav1.APIResourceList{}
|
||||
for _, rl := range rls {
|
||||
filtered := *rl
|
||||
filtered.APIResources = nil
|
||||
for i := range rl.APIResources {
|
||||
if pred.Match(rl.GroupVersion, &rl.APIResources[i]) {
|
||||
filtered.APIResources = append(filtered.APIResources, rl.APIResources[i])
|
||||
}
|
||||
}
|
||||
if filtered.APIResources != nil {
|
||||
result = append(result, &filtered)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
type ResourcePredicate interface {
|
||||
Match(groupVersion string, r *metav1.APIResource) bool
|
||||
}
|
||||
|
||||
type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool
|
||||
|
||||
func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool {
|
||||
return fn(groupVersion, r)
|
||||
}
|
||||
|
||||
// SupportsAllVerbs is a predicate matching a resource iff all given verbs are supported.
|
||||
type SupportsAllVerbs struct {
|
||||
Verbs []string
|
||||
}
|
||||
|
||||
func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool {
|
||||
return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...)
|
||||
}
|
||||
|
||||
@@ -29,12 +29,14 @@ import (
|
||||
"k8s.io/kubernetes/pkg/api"
|
||||
"k8s.io/kubernetes/pkg/api/testapi"
|
||||
"k8s.io/kubernetes/pkg/apimachinery/registered"
|
||||
metav1 "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
uapi "k8s.io/kubernetes/pkg/apis/meta/v1"
|
||||
"k8s.io/kubernetes/pkg/client/restclient"
|
||||
"k8s.io/kubernetes/pkg/client/restclient/fake"
|
||||
"k8s.io/kubernetes/pkg/client/typed/discovery"
|
||||
"k8s.io/kubernetes/pkg/runtime"
|
||||
"k8s.io/kubernetes/pkg/runtime/schema"
|
||||
"k8s.io/kubernetes/pkg/util/sets"
|
||||
)
|
||||
|
||||
func objBody(object interface{}) io.ReadCloser {
|
||||
@@ -155,3 +157,74 @@ func TestNegotiateVersion(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestFilteredBy(t *testing.T) {
|
||||
all := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
|
||||
return true
|
||||
})
|
||||
none := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
|
||||
return false
|
||||
})
|
||||
onlyV2 := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
|
||||
return strings.HasSuffix(gv, "/v2") || gv == "v2"
|
||||
})
|
||||
onlyBar := discovery.ResourcePredicateFunc(func(gv string, r *metav1.APIResource) bool {
|
||||
return r.Kind == "Bar"
|
||||
})
|
||||
|
||||
foo := []*metav1.APIResourceList{
|
||||
{
|
||||
GroupVersion: "foo/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "bar", Kind: "Bar"},
|
||||
{Name: "test", Kind: "Test"},
|
||||
},
|
||||
},
|
||||
{
|
||||
GroupVersion: "foo/v2",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "bar", Kind: "Bar"},
|
||||
{Name: "test", Kind: "Test"},
|
||||
},
|
||||
},
|
||||
{
|
||||
GroupVersion: "foo/v3",
|
||||
APIResources: []metav1.APIResource{},
|
||||
},
|
||||
}
|
||||
|
||||
tests := []struct {
|
||||
input []*metav1.APIResourceList
|
||||
pred discovery.ResourcePredicate
|
||||
expectedResources []string
|
||||
}{
|
||||
{nil, all, []string{}},
|
||||
{[]*metav1.APIResourceList{
|
||||
{GroupVersion: "foo/v1"},
|
||||
}, all, []string{}},
|
||||
{foo, all, []string{"foo/v1.bar", "foo/v1.test", "foo/v2.bar", "foo/v2.test"}},
|
||||
{foo, onlyV2, []string{"foo/v2.bar", "foo/v2.test"}},
|
||||
{foo, onlyBar, []string{"foo/v1.bar", "foo/v2.bar"}},
|
||||
{foo, none, []string{}},
|
||||
}
|
||||
for i, test := range tests {
|
||||
filtered := discovery.FilteredBy(test.pred, test.input)
|
||||
|
||||
if expected, got := sets.NewString(test.expectedResources...), sets.NewString(stringify(filtered)...); !expected.Equal(got) {
|
||||
t.Errorf("[%d] unexpected group versions: expected=%v, got=%v", i, test.expectedResources, stringify(filtered))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func stringify(rls []*metav1.APIResourceList) []string {
|
||||
result := []string{}
|
||||
for _, rl := range rls {
|
||||
for _, r := range rl.APIResources {
|
||||
result = append(result, rl.GroupVersion+"."+r.Name)
|
||||
}
|
||||
if len(rl.APIResources) == 0 {
|
||||
result = append(result, rl.GroupVersion)
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
@@ -290,31 +290,34 @@ func (c *fakeCachedDiscoveryInterface) ServerResourcesForGroupVersion(groupVersi
|
||||
return nil, errors.NewNotFound(schema.GroupResource{}, "")
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerResources() (map[string]*metav1.APIResourceList, error) {
|
||||
func (c *fakeCachedDiscoveryInterface) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
if c.enabledA {
|
||||
av1, _ := c.ServerResourcesForGroupVersion("a/v1")
|
||||
return map[string]*metav1.APIResourceList{
|
||||
"a/v1": av1,
|
||||
}, nil
|
||||
return []*metav1.APIResourceList{av1}, nil
|
||||
}
|
||||
return map[string]*metav1.APIResourceList{}, nil
|
||||
return []*metav1.APIResourceList{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]schema.GroupVersionResource, error) {
|
||||
func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
if c.enabledA {
|
||||
return []schema.GroupVersionResource{
|
||||
return []*metav1.APIResourceList{
|
||||
{
|
||||
Group: "a",
|
||||
Version: "v1",
|
||||
Resource: "foo",
|
||||
GroupVersion: "a/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{
|
||||
Name: "foo",
|
||||
Kind: "Foo",
|
||||
Verbs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return []schema.GroupVersionResource{}, nil
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerPreferredNamespacedResources() ([]schema.GroupVersionResource, error) {
|
||||
return []schema.GroupVersionResource{}, nil
|
||||
func (c *fakeCachedDiscoveryInterface) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) {
|
||||
|
||||
Reference in New Issue
Block a user