mirror of
https://github.com/ceph/ceph-csi.git
synced 2025-06-13 10:33:35 +00:00
added vendors
This commit is contained in:
75
vendor/k8s.io/client-go/discovery/BUILD
generated
vendored
Normal file
75
vendor/k8s.io/client-go/discovery/BUILD
generated
vendored
Normal file
@ -0,0 +1,75 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = [
|
||||
"discovery_client.go",
|
||||
"helper.go",
|
||||
"restmapper.go",
|
||||
"unstructured.go",
|
||||
],
|
||||
importpath = "k8s.io/client-go/discovery",
|
||||
deps = [
|
||||
"//vendor/github.com/golang/glog:go_default_library",
|
||||
"//vendor/github.com/golang/protobuf/proto:go_default_library",
|
||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/meta:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/serializer:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = [
|
||||
"discovery_client_test.go",
|
||||
"helper_blackbox_test.go",
|
||||
"restmapper_test.go",
|
||||
],
|
||||
deps = [
|
||||
"//vendor/github.com/gogo/protobuf/proto:go_default_library",
|
||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||
"//vendor/github.com/stretchr/testify/assert:go_default_library",
|
||||
"//vendor/k8s.io/api/core/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/api/errors:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/sets:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/scheme:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [
|
||||
":package-srcs",
|
||||
"//staging/src/k8s.io/client-go/discovery/cached:all-srcs",
|
||||
"//staging/src/k8s.io/client-go/discovery/fake:all-srcs",
|
||||
],
|
||||
tags = ["automanaged"],
|
||||
)
|
44
vendor/k8s.io/client-go/discovery/cached/BUILD
generated
vendored
Normal file
44
vendor/k8s.io/client-go/discovery/cached/BUILD
generated
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_test",
|
||||
srcs = ["memcache_test.go"],
|
||||
embed = [":go_default_library"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery/fake:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["memcache.go"],
|
||||
importpath = "k8s.io/client-go/discovery/cached",
|
||||
deps = [
|
||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/util/runtime:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
179
vendor/k8s.io/client-go/discovery/cached/memcache.go
generated
vendored
Normal file
179
vendor/k8s.io/client-go/discovery/cached/memcache.go
generated
vendored
Normal file
@ -0,0 +1,179 @@
|
||||
/*
|
||||
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 cached
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
utilruntime "k8s.io/apimachinery/pkg/util/runtime"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/discovery"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
// memCacheClient can Invalidate() to stay up-to-date with discovery
|
||||
// information.
|
||||
//
|
||||
// TODO: Switch to a watch interface. Right now it will poll anytime
|
||||
// Invalidate() is called.
|
||||
type memCacheClient struct {
|
||||
delegate discovery.DiscoveryInterface
|
||||
|
||||
lock sync.RWMutex
|
||||
groupToServerResources map[string]*metav1.APIResourceList
|
||||
groupList *metav1.APIGroupList
|
||||
cacheValid bool
|
||||
}
|
||||
|
||||
var (
|
||||
ErrCacheEmpty = errors.New("the cache has not been filled yet")
|
||||
ErrCacheNotFound = errors.New("not found")
|
||||
)
|
||||
|
||||
var _ discovery.CachedDiscoveryInterface = &memCacheClient{}
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
func (d *memCacheClient) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
if !d.cacheValid {
|
||||
return nil, ErrCacheEmpty
|
||||
}
|
||||
cachedVal, ok := d.groupToServerResources[groupVersion]
|
||||
if !ok {
|
||||
return nil, ErrCacheNotFound
|
||||
}
|
||||
return cachedVal, nil
|
||||
}
|
||||
|
||||
// ServerResources returns the supported resources for all groups and versions.
|
||||
func (d *memCacheClient) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
apiGroups, err := d.ServerGroups()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
groupVersions := metav1.ExtractGroupVersions(apiGroups)
|
||||
result := []*metav1.APIResourceList{}
|
||||
for _, groupVersion := range groupVersions {
|
||||
resources, err := d.ServerResourcesForGroupVersion(groupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
result = append(result, resources)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
if d.groupList == nil {
|
||||
return nil, ErrCacheEmpty
|
||||
}
|
||||
return d.groupList, nil
|
||||
}
|
||||
|
||||
func (d *memCacheClient) RESTClient() restclient.Interface {
|
||||
return d.delegate.RESTClient()
|
||||
}
|
||||
|
||||
// TODO: Should this also be cached? The results seem more likely to be
|
||||
// inconsistent with ServerGroups and ServerResources given the requirement to
|
||||
// actively Invalidate.
|
||||
func (d *memCacheClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return d.delegate.ServerPreferredResources()
|
||||
}
|
||||
|
||||
// TODO: Should this also be cached? The results seem more likely to be
|
||||
// inconsistent with ServerGroups and ServerResources given the requirement to
|
||||
// actively Invalidate.
|
||||
func (d *memCacheClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return d.delegate.ServerPreferredNamespacedResources()
|
||||
}
|
||||
|
||||
func (d *memCacheClient) ServerVersion() (*version.Info, error) {
|
||||
return d.delegate.ServerVersion()
|
||||
}
|
||||
|
||||
func (d *memCacheClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return d.delegate.OpenAPISchema()
|
||||
}
|
||||
|
||||
func (d *memCacheClient) Fresh() bool {
|
||||
d.lock.RLock()
|
||||
defer d.lock.RUnlock()
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something. The idea here is that Invalidate will be called
|
||||
// periodically and therefore we'll always be returning the latest data. (And
|
||||
// in the future we can watch and stay even more up-to-date.) So we only
|
||||
// return false if the cache has never been filled.
|
||||
return d.cacheValid
|
||||
}
|
||||
|
||||
// Invalidate refreshes the cache, blocking calls until the cache has been
|
||||
// refreshed. It would be trivial to make a version that does this in the
|
||||
// background while continuing to respond to requests if needed.
|
||||
func (d *memCacheClient) Invalidate() {
|
||||
d.lock.Lock()
|
||||
defer d.lock.Unlock()
|
||||
|
||||
// TODO: Could this multiplicative set of calls be replaced by a single call
|
||||
// to ServerResources? If it's possible for more than one resulting
|
||||
// APIResourceList to have the same GroupVersion, the lists would need merged.
|
||||
gl, err := d.delegate.ServerGroups()
|
||||
if err != nil || len(gl.Groups) == 0 {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get current server API group list; will keep using cached value. (%v)", err))
|
||||
return
|
||||
}
|
||||
|
||||
rl := map[string]*metav1.APIResourceList{}
|
||||
for _, g := range gl.Groups {
|
||||
for _, v := range g.Versions {
|
||||
r, err := d.delegate.ServerResourcesForGroupVersion(v.GroupVersion)
|
||||
if err != nil || len(r.APIResources) == 0 {
|
||||
utilruntime.HandleError(fmt.Errorf("couldn't get resource list for %v: %v", v.GroupVersion, err))
|
||||
if cur, ok := d.groupToServerResources[v.GroupVersion]; ok {
|
||||
// retain the existing list, if we had it.
|
||||
r = cur
|
||||
} else {
|
||||
continue
|
||||
}
|
||||
}
|
||||
rl[v.GroupVersion] = r
|
||||
}
|
||||
}
|
||||
|
||||
d.groupToServerResources, d.groupList = rl, gl
|
||||
d.cacheValid = true
|
||||
}
|
||||
|
||||
// NewMemCacheClient creates a new CachedDiscoveryInterface which caches
|
||||
// discovery information in memory and will stay up-to-date if Invalidate is
|
||||
// called with regularity.
|
||||
//
|
||||
// NOTE: The client will NOT resort to live lookups on cache misses.
|
||||
func NewMemCacheClient(delegate discovery.DiscoveryInterface) discovery.CachedDiscoveryInterface {
|
||||
return &memCacheClient{
|
||||
delegate: delegate,
|
||||
groupToServerResources: map[string]*metav1.APIResourceList{},
|
||||
}
|
||||
}
|
132
vendor/k8s.io/client-go/discovery/cached/memcache_test.go
generated
vendored
Normal file
132
vendor/k8s.io/client-go/discovery/cached/memcache_test.go
generated
vendored
Normal file
@ -0,0 +1,132 @@
|
||||
/*
|
||||
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 cached
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"reflect"
|
||||
"sync"
|
||||
"testing"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/client-go/discovery/fake"
|
||||
)
|
||||
|
||||
type fakeDiscovery struct {
|
||||
*fake.FakeDiscovery
|
||||
|
||||
lock sync.Mutex
|
||||
groupList *metav1.APIGroupList
|
||||
resourceMap map[string]*metav1.APIResourceList
|
||||
}
|
||||
|
||||
func (c *fakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if rl, ok := c.resourceMap[groupVersion]; ok {
|
||||
return rl, nil
|
||||
}
|
||||
return nil, errors.New("doesn't exist")
|
||||
}
|
||||
|
||||
func (c *fakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
c.lock.Lock()
|
||||
defer c.lock.Unlock()
|
||||
if c.groupList == nil {
|
||||
return nil, errors.New("doesn't exist")
|
||||
}
|
||||
return c.groupList, nil
|
||||
}
|
||||
|
||||
func TestClient(t *testing.T) {
|
||||
fake := &fakeDiscovery{
|
||||
groupList: &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{{
|
||||
Name: "astronomy",
|
||||
Versions: []metav1.GroupVersionForDiscovery{{
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
Version: "v8beta1",
|
||||
}},
|
||||
}},
|
||||
},
|
||||
resourceMap: map[string]*metav1.APIResourceList{
|
||||
"astronomy/v8beta1": {
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "dwarfplanets",
|
||||
SingularName: "dwarfplanet",
|
||||
Namespaced: true,
|
||||
Kind: "DwarfPlanet",
|
||||
ShortNames: []string{"dp"},
|
||||
}},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
c := NewMemCacheClient(fake)
|
||||
g, err := c.ServerGroups()
|
||||
if err == nil {
|
||||
t.Errorf("Unexpected non-error.")
|
||||
}
|
||||
if c.Fresh() {
|
||||
t.Errorf("Expected not fresh.")
|
||||
}
|
||||
|
||||
c.Invalidate()
|
||||
if !c.Fresh() {
|
||||
t.Errorf("Expected fresh.")
|
||||
}
|
||||
|
||||
g, err = c.ServerGroups()
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.groupList, g; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
r, err := c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"], r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
|
||||
fake.lock.Lock()
|
||||
fake.resourceMap = map[string]*metav1.APIResourceList{
|
||||
"astronomy/v8beta1": {
|
||||
GroupVersion: "astronomy/v8beta1",
|
||||
APIResources: []metav1.APIResource{{
|
||||
Name: "stars",
|
||||
SingularName: "star",
|
||||
Namespaced: true,
|
||||
Kind: "Star",
|
||||
ShortNames: []string{"s"},
|
||||
}},
|
||||
},
|
||||
}
|
||||
fake.lock.Unlock()
|
||||
|
||||
c.Invalidate()
|
||||
r, err = c.ServerResourcesForGroupVersion("astronomy/v8beta1")
|
||||
if err != nil {
|
||||
t.Errorf("Unexpected error: %v", err)
|
||||
}
|
||||
if e, a := fake.resourceMap["astronomy/v8beta1"], r; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("Expected %#v, got %#v", e, a)
|
||||
}
|
||||
}
|
418
vendor/k8s.io/client-go/discovery/discovery_client.go
generated
vendored
Normal file
418
vendor/k8s.io/client-go/discovery/discovery_client.go
generated
vendored
Normal file
@ -0,0 +1,418 @@
|
||||
/*
|
||||
Copyright 2015 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 discovery
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"net/url"
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
"github.com/golang/protobuf/proto"
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
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/apimachinery/pkg/version"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
const (
|
||||
// defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. ThirdPartyResources).
|
||||
defaultRetries = 2
|
||||
// protobuf mime type
|
||||
mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"
|
||||
)
|
||||
|
||||
// DiscoveryInterface holds the methods that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryInterface interface {
|
||||
RESTClient() restclient.Interface
|
||||
ServerGroupsInterface
|
||||
ServerResourcesInterface
|
||||
ServerVersionInterface
|
||||
OpenAPISchemaInterface
|
||||
}
|
||||
|
||||
// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness.
|
||||
type CachedDiscoveryInterface interface {
|
||||
DiscoveryInterface
|
||||
// Fresh is supposed to tell the caller whether or not to retry if the cache
|
||||
// fails to find something (false = retry, true = no need to retry).
|
||||
//
|
||||
// TODO: this needs to be revisited, this interface can't be locked properly
|
||||
// and doesn't make a lot of sense.
|
||||
Fresh() bool
|
||||
// Invalidate enforces that no cached data is used in the future that is older than the current time.
|
||||
Invalidate()
|
||||
}
|
||||
|
||||
// ServerGroupsInterface has methods for obtaining supported groups on the API server
|
||||
type ServerGroupsInterface interface {
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
ServerGroups() (*metav1.APIGroupList, error)
|
||||
}
|
||||
|
||||
// ServerResourcesInterface has methods for obtaining supported resources on the API server
|
||||
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() ([]*metav1.APIResourceList, error)
|
||||
// ServerPreferredResources returns the supported resources with the version preferred by the
|
||||
// server.
|
||||
ServerPreferredResources() ([]*metav1.APIResourceList, error)
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
|
||||
}
|
||||
|
||||
// ServerVersionInterface has a method for retrieving the server's version.
|
||||
type ServerVersionInterface interface {
|
||||
// ServerVersion retrieves and parses the server's version (git version).
|
||||
ServerVersion() (*version.Info, error)
|
||||
}
|
||||
|
||||
// OpenAPISchemaInterface has a method to retrieve the open API schema.
|
||||
type OpenAPISchemaInterface interface {
|
||||
// OpenAPISchema retrieves and parses the swagger API schema the server supports.
|
||||
OpenAPISchema() (*openapi_v2.Document, error)
|
||||
}
|
||||
|
||||
// DiscoveryClient implements the functions that discover server-supported API groups,
|
||||
// versions and resources.
|
||||
type DiscoveryClient struct {
|
||||
restClient restclient.Interface
|
||||
|
||||
LegacyPrefix string
|
||||
}
|
||||
|
||||
// Convert metav1.APIVersions to metav1.APIGroup. APIVersions is used by legacy v1, so
|
||||
// group would be "".
|
||||
func apiVersionsToAPIGroup(apiVersions *metav1.APIVersions) (apiGroup metav1.APIGroup) {
|
||||
groupVersions := []metav1.GroupVersionForDiscovery{}
|
||||
for _, version := range apiVersions.Versions {
|
||||
groupVersion := metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: version,
|
||||
Version: version,
|
||||
}
|
||||
groupVersions = append(groupVersions, groupVersion)
|
||||
}
|
||||
apiGroup.Versions = groupVersions
|
||||
// There should be only one groupVersion returned at /api
|
||||
apiGroup.PreferredVersion = groupVersions[0]
|
||||
return
|
||||
}
|
||||
|
||||
// ServerGroups returns the supported groups, with information like supported versions and the
|
||||
// preferred version.
|
||||
func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) {
|
||||
// Get the groupVersions exposed at /api
|
||||
v := &metav1.APIVersions{}
|
||||
err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do().Into(v)
|
||||
apiGroup := metav1.APIGroup{}
|
||||
if err == nil && len(v.Versions) != 0 {
|
||||
apiGroup = apiVersionsToAPIGroup(v)
|
||||
}
|
||||
if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// Get the groupVersions exposed at /apis
|
||||
apiGroupList = &metav1.APIGroupList{}
|
||||
err = d.restClient.Get().AbsPath("/apis").Do().Into(apiGroupList)
|
||||
if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {
|
||||
return nil, err
|
||||
}
|
||||
// to be compatible with a v1.0 server, if it's a 403 or 404, ignore and return whatever we got from /api
|
||||
if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
|
||||
apiGroupList = &metav1.APIGroupList{}
|
||||
}
|
||||
|
||||
// prepend the group retrieved from /api to the list if not empty
|
||||
if len(v.Versions) != 0 {
|
||||
apiGroupList.Groups = append([]metav1.APIGroup{apiGroup}, apiGroupList.Groups...)
|
||||
}
|
||||
return apiGroupList, nil
|
||||
}
|
||||
|
||||
// ServerResourcesForGroupVersion returns the supported resources for a group and version.
|
||||
func (d *DiscoveryClient) ServerResourcesForGroupVersion(groupVersion string) (resources *metav1.APIResourceList, err error) {
|
||||
url := url.URL{}
|
||||
if len(groupVersion) == 0 {
|
||||
return nil, fmt.Errorf("groupVersion shouldn't be empty")
|
||||
}
|
||||
if len(d.LegacyPrefix) > 0 && groupVersion == "v1" {
|
||||
url.Path = d.LegacyPrefix + "/" + groupVersion
|
||||
} else {
|
||||
url.Path = "/apis/" + groupVersion
|
||||
}
|
||||
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.
|
||||
if groupVersion == "v1" && (errors.IsNotFound(err) || errors.IsForbidden(err)) {
|
||||
return resources, nil
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return resources, nil
|
||||
}
|
||||
|
||||
// serverResources returns the supported resources for all groups and versions.
|
||||
func (d *DiscoveryClient) serverResources() ([]*metav1.APIResourceList, error) {
|
||||
apiGroups, err := d.ServerGroups()
|
||||
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
|
||||
continue
|
||||
}
|
||||
|
||||
result = append(result, resources)
|
||||
}
|
||||
}
|
||||
|
||||
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.
|
||||
type ErrGroupDiscoveryFailed struct {
|
||||
// Groups is a list of the groups that failed to load and the error cause
|
||||
Groups map[schema.GroupVersion]error
|
||||
}
|
||||
|
||||
// Error implements the error interface
|
||||
func (e *ErrGroupDiscoveryFailed) Error() string {
|
||||
var groups []string
|
||||
for k, v := range e.Groups {
|
||||
groups = append(groups, fmt.Sprintf("%s: %v", k, v))
|
||||
}
|
||||
sort.Strings(groups)
|
||||
return fmt.Sprintf("unable to retrieve the complete list of server APIs: %s", strings.Join(groups, ", "))
|
||||
}
|
||||
|
||||
// IsGroupDiscoveryFailedError returns true if the provided error indicates the server was unable to discover
|
||||
// a complete list of APIs for the client to use.
|
||||
func IsGroupDiscoveryFailedError(err error) bool {
|
||||
_, ok := err.(*ErrGroupDiscoveryFailed)
|
||||
return err != nil && ok
|
||||
}
|
||||
|
||||
// serverPreferredResources returns the supported resources with the version preferred by the server.
|
||||
func (d *DiscoveryClient) serverPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
serverGroupList, err := d.ServerGroups()
|
||||
if err != nil {
|
||||
return nil, 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
|
||||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 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() ([]*metav1.APIResourceList, error) {
|
||||
return withRetries(defaultRetries, d.serverPreferredResources)
|
||||
}
|
||||
|
||||
// ServerPreferredNamespacedResources returns the supported namespaced resources with the
|
||||
// version preferred by the server.
|
||||
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).
|
||||
func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {
|
||||
body, err := d.restClient.Get().AbsPath("/version").Do().Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var info version.Info
|
||||
err = json.Unmarshal(body, &info)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("got '%s': %v", string(body), err)
|
||||
}
|
||||
return &info, nil
|
||||
}
|
||||
|
||||
// OpenAPISchema fetches the open api schema using a rest client and parses the proto.
|
||||
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", mimePb).Do().Raw()
|
||||
if err != nil {
|
||||
if errors.IsForbidden(err) || errors.IsNotFound(err) {
|
||||
// single endpoint not found/registered in old server, try to fetch old endpoint
|
||||
// TODO(roycaihw): remove this in 1.11
|
||||
data, err = d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do().Raw()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
} else {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
document := &openapi_v2.Document{}
|
||||
err = proto.Unmarshal(data, document)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return document, 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() ([]*metav1.APIResourceList, error)) ([]*metav1.APIResourceList, error) {
|
||||
var result []*metav1.APIResourceList
|
||||
var err error
|
||||
for i := 0; i < maxRetries; i++ {
|
||||
result, err = f()
|
||||
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
|
||||
codec := runtime.NoopEncoder{Decoder: scheme.Codecs.UniversalDecoder()}
|
||||
config.NegotiatedSerializer = serializer.NegotiatedSerializerWrapper(runtime.SerializerInfo{Serializer: codec})
|
||||
if len(config.UserAgent) == 0 {
|
||||
config.UserAgent = restclient.DefaultKubernetesUserAgent()
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// NewDiscoveryClientForConfig creates a new DiscoveryClient for the given config. This client
|
||||
// can be used to discover supported resources in the API server.
|
||||
func NewDiscoveryClientForConfig(c *restclient.Config) (*DiscoveryClient, error) {
|
||||
config := *c
|
||||
if err := setDiscoveryDefaults(&config); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
client, err := restclient.UnversionedRESTClientFor(&config)
|
||||
return &DiscoveryClient{restClient: client, LegacyPrefix: "/api"}, err
|
||||
}
|
||||
|
||||
// NewDiscoveryClientForConfigOrDie creates a new DiscoveryClient for the given config. If
|
||||
// there is an error, it panics.
|
||||
func NewDiscoveryClientForConfigOrDie(c *restclient.Config) *DiscoveryClient {
|
||||
client, err := NewDiscoveryClientForConfig(c)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return client
|
||||
|
||||
}
|
||||
|
||||
// NewDiscoveryClient returns a new DiscoveryClient for the given RESTClient.
|
||||
func NewDiscoveryClient(c restclient.Interface) *DiscoveryClient {
|
||||
return &DiscoveryClient{restClient: c, LegacyPrefix: "/api"}
|
||||
}
|
||||
|
||||
// RESTClient returns a RESTClient that is used to communicate
|
||||
// with API server by this client implementation.
|
||||
func (c *DiscoveryClient) RESTClient() restclient.Interface {
|
||||
if c == nil {
|
||||
return nil
|
||||
}
|
||||
return c.restClient
|
||||
}
|
830
vendor/k8s.io/client-go/discovery/discovery_client_test.go
generated
vendored
Normal file
830
vendor/k8s.io/client-go/discovery/discovery_client_test.go
generated
vendored
Normal file
@ -0,0 +1,830 @@
|
||||
/*
|
||||
Copyright 2014 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 discovery_test
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
"mime"
|
||||
"net/http"
|
||||
"net/http/httptest"
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"github.com/gogo/protobuf/proto"
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
. "k8s.io/client-go/discovery"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
)
|
||||
|
||||
func TestGetServerVersion(t *testing.T) {
|
||||
expect := version.Info{
|
||||
Major: "foo",
|
||||
Minor: "bar",
|
||||
GitCommit: "baz",
|
||||
}
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
output, err := json.Marshal(expect)
|
||||
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)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
|
||||
got, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
}
|
||||
if e, a := expect, *got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerGroupsWithV1Server(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var obj interface{}
|
||||
switch req.URL.Path {
|
||||
case "/api":
|
||||
obj = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
case "/apis":
|
||||
obj = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "extensions",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "extensions/v1beta1"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected encoding error: %v", err)
|
||||
return
|
||||
}
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerGroups should not return an error even if server returns error at /api and /apis
|
||||
apiGroupList, err := client.ServerGroups()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
groupVersions := metav1.ExtractGroupVersions(apiGroupList)
|
||||
if !reflect.DeepEqual(groupVersions, []string{"v1", "extensions/v1beta1"}) {
|
||||
t.Errorf("expected: %q, got: %q", []string{"v1", "extensions/v1beta1"}, groupVersions)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerGroupsWithBrokenServer(t *testing.T) {
|
||||
for _, statusCode := range []int{http.StatusNotFound, http.StatusForbidden} {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
w.WriteHeader(statusCode)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerGroups should not return an error even if server returns Not Found or Forbidden error at all end points
|
||||
apiGroupList, err := client.ServerGroups()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
groupVersions := metav1.ExtractGroupVersions(apiGroupList)
|
||||
if len(groupVersions) != 0 {
|
||||
t.Errorf("expected empty list, got: %q", groupVersions)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerResourcesWithV1Server(t *testing.T) {
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
var obj interface{}
|
||||
switch req.URL.Path {
|
||||
case "/api":
|
||||
obj = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
default:
|
||||
w.WriteHeader(http.StatusNotFound)
|
||||
return
|
||||
}
|
||||
output, err := json.Marshal(obj)
|
||||
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)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
// ServerResources should not return an error even if server returns error at /api/v1.
|
||||
serverResources, err := client.ServerResources()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
}
|
||||
gvs := groupVersions(serverResources)
|
||||
if !sets.NewString(gvs...).Has("v1") {
|
||||
t.Errorf("missing v1 in resource list: %v", serverResources)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetServerResources(t *testing.T) {
|
||||
stable := metav1.APIResourceList{
|
||||
GroupVersion: "v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||
{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
|
||||
},
|
||||
}
|
||||
beta := metav1.APIResourceList{
|
||||
GroupVersion: "extensions/v1beta1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
|
||||
{Name: "ingresses", Namespaced: true, Kind: "Ingress"},
|
||||
{Name: "jobs", Namespaced: true, Kind: "Job"},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
resourcesList *metav1.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 = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "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)
|
||||
}))
|
||||
defer server.Close()
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
for _, test := range tests {
|
||||
got, err := client.ServerResourcesForGroupVersion(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)
|
||||
}
|
||||
}
|
||||
|
||||
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 !serverGroupVersions.Has(api) {
|
||||
t.Errorf("missing expected api %q in %v", api, serverResources)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var returnedOpenAPI = openapi_v2.Document{
|
||||
Definitions: &openapi_v2.Definitions{
|
||||
AdditionalProperties: []*openapi_v2.NamedSchema{
|
||||
{
|
||||
Name: "fake.type.1",
|
||||
Value: &openapi_v2.Schema{
|
||||
Properties: &openapi_v2.Properties{
|
||||
AdditionalProperties: []*openapi_v2.NamedSchema{
|
||||
{
|
||||
Name: "count",
|
||||
Value: &openapi_v2.Schema{
|
||||
Type: &openapi_v2.TypeItem{
|
||||
Value: []string{"integer"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Name: "fake.type.2",
|
||||
Value: &openapi_v2.Schema{
|
||||
Properties: &openapi_v2.Properties{
|
||||
AdditionalProperties: []*openapi_v2.NamedSchema{
|
||||
{
|
||||
Name: "count",
|
||||
Value: &openapi_v2.Schema{
|
||||
Type: &openapi_v2.TypeItem{
|
||||
Value: []string{"array"},
|
||||
},
|
||||
Items: &openapi_v2.ItemsItem{
|
||||
Schema: []*openapi_v2.Schema{
|
||||
{
|
||||
Type: &openapi_v2.TypeItem{
|
||||
Value: []string{"string"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
func openapiSchemaDeprecatedFakeServer() (*httptest.Server, error) {
|
||||
var sErr error
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
// old server returns 403 on new endpoint request
|
||||
if req.URL.Path == "/openapi/v2" {
|
||||
w.WriteHeader(http.StatusForbidden)
|
||||
return
|
||||
}
|
||||
if req.URL.Path != "/swagger-2.0.0.pb-v1" {
|
||||
sErr = fmt.Errorf("Unexpected url %v", req.URL)
|
||||
}
|
||||
if req.Method != "GET" {
|
||||
sErr = fmt.Errorf("Unexpected method %v", req.Method)
|
||||
}
|
||||
|
||||
mime.AddExtensionType(".pb-v1", "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf")
|
||||
|
||||
output, err := proto.Marshal(&returnedOpenAPI)
|
||||
if err != nil {
|
||||
sErr = err
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
return server, sErr
|
||||
}
|
||||
|
||||
func openapiSchemaFakeServer() (*httptest.Server, error) {
|
||||
var sErr error
|
||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
|
||||
if req.URL.Path != "/openapi/v2" {
|
||||
sErr = fmt.Errorf("Unexpected url %v", req.URL)
|
||||
}
|
||||
if req.Method != "GET" {
|
||||
sErr = fmt.Errorf("Unexpected method %v", req.Method)
|
||||
}
|
||||
decipherableFormat := req.Header.Get("Accept")
|
||||
if decipherableFormat != "application/com.github.proto-openapi.spec.v2@v1.0+protobuf" {
|
||||
sErr = fmt.Errorf("Unexpected accept mime type %v", decipherableFormat)
|
||||
}
|
||||
|
||||
mime.AddExtensionType(".pb-v1", "application/com.github.googleapis.gnostic.OpenAPIv2@68f4ded+protobuf")
|
||||
|
||||
output, err := proto.Marshal(&returnedOpenAPI)
|
||||
if err != nil {
|
||||
sErr = err
|
||||
return
|
||||
}
|
||||
w.WriteHeader(http.StatusOK)
|
||||
w.Write(output)
|
||||
}))
|
||||
return server, sErr
|
||||
}
|
||||
|
||||
func TestGetOpenAPISchema(t *testing.T) {
|
||||
server, err := openapiSchemaFakeServer()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error starting fake server: %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
got, err := client.OpenAPISchema()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting openapi: %v", err)
|
||||
}
|
||||
if e, a := returnedOpenAPI, *got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestGetOpenAPISchemaFallback(t *testing.T) {
|
||||
server, err := openapiSchemaDeprecatedFakeServer()
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error starting fake server: %v", err)
|
||||
}
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
got, err := client.OpenAPISchema()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error getting openapi: %v", err)
|
||||
}
|
||||
if e, a := returnedOpenAPI, *got; !reflect.DeepEqual(e, a) {
|
||||
t.Errorf("expected %v, got %v", e, a)
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPreferredResources(t *testing.T) {
|
||||
stable := metav1.APIResourceList{
|
||||
GroupVersion: "v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||
{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
resourcesList []*metav1.APIResourceList
|
||||
response func(w http.ResponseWriter, req *http.Request)
|
||||
expectErr func(err error) bool
|
||||
}{
|
||||
{
|
||||
resourcesList: []*metav1.APIResourceList{&stable},
|
||||
expectErr: IsGroupDiscoveryFailedError,
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/apis/extensions/v1beta1":
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
case "/api/v1":
|
||||
list = &stable
|
||||
case "/api":
|
||||
list = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "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)
|
||||
},
|
||||
},
|
||||
{
|
||||
resourcesList: nil,
|
||||
expectErr: IsGroupDiscoveryFailedError,
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/apis/extensions/v1beta1":
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
case "/api/v1":
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
case "/api":
|
||||
list = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "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)
|
||||
},
|
||||
},
|
||||
}
|
||||
for _, test := range tests {
|
||||
server := httptest.NewServer(http.HandlerFunc(test.response))
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
resources, err := client.ServerPreferredResources()
|
||||
if test.expectErr != nil {
|
||||
if err == nil {
|
||||
t.Error("unexpected non-error")
|
||||
}
|
||||
|
||||
continue
|
||||
}
|
||||
if err != nil {
|
||||
t.Errorf("unexpected error: %v", err)
|
||||
continue
|
||||
}
|
||||
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()
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPreferredResourcesRetries(t *testing.T) {
|
||||
stable := metav1.APIResourceList{
|
||||
GroupVersion: "v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
},
|
||||
}
|
||||
beta := metav1.APIResourceList{
|
||||
GroupVersion: "extensions/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "deployments", Namespaced: true, Kind: "Deployment"},
|
||||
},
|
||||
}
|
||||
|
||||
response := func(numErrors int) http.HandlerFunc {
|
||||
var i = 0
|
||||
return func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/apis/extensions/v1beta1":
|
||||
if i < numErrors {
|
||||
i++
|
||||
w.WriteHeader(http.StatusInternalServerError)
|
||||
return
|
||||
}
|
||||
list = &beta
|
||||
case "/api/v1":
|
||||
list = &stable
|
||||
case "/api":
|
||||
list = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "extensions",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "extensions/v1beta1"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: "extensions/v1beta1",
|
||||
Version: "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)
|
||||
}
|
||||
}
|
||||
tests := []struct {
|
||||
responseErrors int
|
||||
expectResources int
|
||||
expectedError func(err error) bool
|
||||
}{
|
||||
{
|
||||
responseErrors: 1,
|
||||
expectResources: 2,
|
||||
expectedError: func(err error) bool {
|
||||
return err == nil
|
||||
},
|
||||
},
|
||||
{
|
||||
responseErrors: 2,
|
||||
expectResources: 1,
|
||||
expectedError: IsGroupDiscoveryFailedError,
|
||||
},
|
||||
}
|
||||
|
||||
for i, tc := range tests {
|
||||
server := httptest.NewServer(http.HandlerFunc(response(tc.responseErrors)))
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
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)
|
||||
}
|
||||
server.Close()
|
||||
}
|
||||
}
|
||||
|
||||
func TestServerPreferredNamespacedResources(t *testing.T) {
|
||||
stable := metav1.APIResourceList{
|
||||
GroupVersion: "v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
{Name: "services", Namespaced: true, Kind: "Service"},
|
||||
{Name: "namespaces", Namespaced: false, Kind: "Namespace"},
|
||||
},
|
||||
}
|
||||
batchv1 := metav1.APIResourceList{
|
||||
GroupVersion: "batch/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "jobs", Namespaced: true, Kind: "Job"},
|
||||
},
|
||||
}
|
||||
batchv2alpha1 := metav1.APIResourceList{
|
||||
GroupVersion: "batch/v2alpha1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "jobs", Namespaced: true, Kind: "Job"},
|
||||
{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
|
||||
},
|
||||
}
|
||||
batchv3alpha1 := metav1.APIResourceList{
|
||||
GroupVersion: "batch/v3alpha1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{Name: "jobs", Namespaced: true, Kind: "Job"},
|
||||
{Name: "cronjobs", Namespaced: true, Kind: "CronJob"},
|
||||
},
|
||||
}
|
||||
tests := []struct {
|
||||
response func(w http.ResponseWriter, req *http.Request)
|
||||
expected map[schema.GroupVersionResource]struct{}
|
||||
}{
|
||||
{
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/api/v1":
|
||||
list = &stable
|
||||
case "/api":
|
||||
list = &metav1.APIVersions{
|
||||
Versions: []string{
|
||||
"v1",
|
||||
},
|
||||
}
|
||||
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)
|
||||
},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "", Version: "v1", Resource: "pods"}: {},
|
||||
{Group: "", Version: "v1", Resource: "services"}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "batch",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "batch/v1", Version: "v1"},
|
||||
{GroupVersion: "batch/v2alpha1", Version: "v2alpha1"},
|
||||
{GroupVersion: "batch/v3alpha1", Version: "v3alpha1"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v1", Version: "v1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
case "/apis/batch/v1":
|
||||
list = &batchv1
|
||||
case "/apis/batch/v2alpha1":
|
||||
list = &batchv2alpha1
|
||||
case "/apis/batch/v3alpha1":
|
||||
list = &batchv3alpha1
|
||||
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)
|
||||
},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "batch", Version: "v1", Resource: "jobs"}: {},
|
||||
{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
|
||||
},
|
||||
},
|
||||
{
|
||||
response: func(w http.ResponseWriter, req *http.Request) {
|
||||
var list interface{}
|
||||
switch req.URL.Path {
|
||||
case "/apis":
|
||||
list = &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "batch",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{GroupVersion: "batch/v1", Version: "v1"},
|
||||
{GroupVersion: "batch/v2alpha1", Version: "v2alpha1"},
|
||||
{GroupVersion: "batch/v3alpha1", Version: "v3alpha1"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{GroupVersion: "batch/v2alpha", Version: "v2alpha1"},
|
||||
},
|
||||
},
|
||||
}
|
||||
case "/apis/batch/v1":
|
||||
list = &batchv1
|
||||
case "/apis/batch/v2alpha1":
|
||||
list = &batchv2alpha1
|
||||
case "/apis/batch/v3alpha1":
|
||||
list = &batchv3alpha1
|
||||
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)
|
||||
},
|
||||
expected: map[schema.GroupVersionResource]struct{}{
|
||||
{Group: "batch", Version: "v2alpha1", Resource: "jobs"}: {},
|
||||
{Group: "batch", Version: "v2alpha1", Resource: "cronjobs"}: {},
|
||||
},
|
||||
},
|
||||
}
|
||||
for i, test := range tests {
|
||||
server := httptest.NewServer(http.HandlerFunc(test.response))
|
||||
defer server.Close()
|
||||
|
||||
client := NewDiscoveryClientForConfigOrDie(&restclient.Config{Host: server.URL})
|
||||
resources, err := client.ServerPreferredNamespacedResources()
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
got, err := GroupVersionResources(resources)
|
||||
if err != nil {
|
||||
t.Errorf("[%d] unexpected error: %v", i, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, test.expected) {
|
||||
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
|
||||
}
|
45
vendor/k8s.io/client-go/discovery/fake/BUILD
generated
vendored
Normal file
45
vendor/k8s.io/client-go/discovery/fake/BUILD
generated
vendored
Normal file
@ -0,0 +1,45 @@
|
||||
package(default_visibility = ["//visibility:public"])
|
||||
|
||||
load(
|
||||
"@io_bazel_rules_go//go:def.bzl",
|
||||
"go_library",
|
||||
"go_test",
|
||||
)
|
||||
|
||||
go_library(
|
||||
name = "go_default_library",
|
||||
srcs = ["discovery.go"],
|
||||
importpath = "k8s.io/client-go/discovery/fake",
|
||||
deps = [
|
||||
"//vendor/github.com/googleapis/gnostic/OpenAPIv2:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/apis/meta/v1:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/runtime/schema:go_default_library",
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/rest:go_default_library",
|
||||
"//vendor/k8s.io/client-go/testing:go_default_library",
|
||||
],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "package-srcs",
|
||||
srcs = glob(["**"]),
|
||||
tags = ["automanaged"],
|
||||
visibility = ["//visibility:private"],
|
||||
)
|
||||
|
||||
filegroup(
|
||||
name = "all-srcs",
|
||||
srcs = [":package-srcs"],
|
||||
tags = ["automanaged"],
|
||||
)
|
||||
|
||||
go_test(
|
||||
name = "go_default_xtest",
|
||||
srcs = ["discovery_test.go"],
|
||||
deps = [
|
||||
"//vendor/k8s.io/apimachinery/pkg/version:go_default_library",
|
||||
"//vendor/k8s.io/client-go/discovery/fake:go_default_library",
|
||||
"//vendor/k8s.io/client-go/kubernetes/fake:go_default_library",
|
||||
],
|
||||
)
|
131
vendor/k8s.io/client-go/discovery/fake/discovery.go
generated
vendored
Normal file
131
vendor/k8s.io/client-go/discovery/fake/discovery.go
generated
vendored
Normal file
@ -0,0 +1,131 @@
|
||||
/*
|
||||
Copyright 2016 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 fake
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
kubeversion "k8s.io/client-go/pkg/version"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/testing"
|
||||
)
|
||||
|
||||
// FakeDiscovery implements discovery.DiscoveryInterface and sometimes calls testing.Fake.Invoke with an action,
|
||||
// but doesn't respect the return value if any. There is a way to fake static values like ServerVersion by using the Faked... fields on the struct.
|
||||
type FakeDiscovery struct {
|
||||
*testing.Fake
|
||||
FakedServerVersion *version.Info
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
action := testing.ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
}
|
||||
c.Invokes(action, 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() ([]*metav1.APIResourceList, error) {
|
||||
action := testing.ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "resource"},
|
||||
}
|
||||
c.Invokes(action, nil)
|
||||
return c.Resources, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
action := testing.ActionImpl{
|
||||
Verb: "get",
|
||||
Resource: schema.GroupVersionResource{Resource: "group"},
|
||||
}
|
||||
c.Invokes(action, nil)
|
||||
|
||||
groups := map[string]*metav1.APIGroup{}
|
||||
|
||||
for _, res := range c.Resources {
|
||||
gv, err := schema.ParseGroupVersion(res.GroupVersion)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
group := groups[gv.Group]
|
||||
if group == nil {
|
||||
group = &metav1.APIGroup{
|
||||
Name: gv.Group,
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: res.GroupVersion,
|
||||
Version: gv.Version,
|
||||
},
|
||||
}
|
||||
groups[gv.Group] = group
|
||||
}
|
||||
|
||||
group.Versions = append(group.Versions, metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: res.GroupVersion,
|
||||
Version: gv.Version,
|
||||
})
|
||||
}
|
||||
|
||||
list := &metav1.APIGroupList{}
|
||||
for _, apiGroup := range groups {
|
||||
list.Groups = append(list.Groups, *apiGroup)
|
||||
}
|
||||
|
||||
return list, nil
|
||||
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) ServerVersion() (*version.Info, error) {
|
||||
action := testing.ActionImpl{}
|
||||
action.Verb = "get"
|
||||
action.Resource = schema.GroupVersionResource{Resource: "version"}
|
||||
c.Invokes(action, nil)
|
||||
|
||||
if c.FakedServerVersion != nil {
|
||||
return c.FakedServerVersion, nil
|
||||
}
|
||||
|
||||
versionInfo := kubeversion.Get()
|
||||
return &versionInfo, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
||||
|
||||
func (c *FakeDiscovery) RESTClient() restclient.Interface {
|
||||
return nil
|
||||
}
|
46
vendor/k8s.io/client-go/discovery/fake/discovery_test.go
generated
vendored
Normal file
46
vendor/k8s.io/client-go/discovery/fake/discovery_test.go
generated
vendored
Normal file
@ -0,0 +1,46 @@
|
||||
/*
|
||||
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 fake_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
fakediscovery "k8s.io/client-go/discovery/fake"
|
||||
fakeclientset "k8s.io/client-go/kubernetes/fake"
|
||||
)
|
||||
|
||||
func TestFakingServerVersion(t *testing.T) {
|
||||
client := fakeclientset.NewSimpleClientset()
|
||||
fakeDiscovery, ok := client.Discovery().(*fakediscovery.FakeDiscovery)
|
||||
if !ok {
|
||||
t.Fatalf("couldn't convert Discovery() to *FakeDiscovery")
|
||||
}
|
||||
|
||||
testGitCommit := "v1.0.0"
|
||||
fakeDiscovery.FakedServerVersion = &version.Info{
|
||||
GitCommit: testGitCommit,
|
||||
}
|
||||
|
||||
sv, err := client.Discovery().ServerVersion()
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
if sv.GitCommit != testGitCommit {
|
||||
t.Fatalf("unexpected faked discovery return value: %q", sv.GitCommit)
|
||||
}
|
||||
}
|
121
vendor/k8s.io/client-go/discovery/helper.go
generated
vendored
Normal file
121
vendor/k8s.io/client-go/discovery/helper.go
generated
vendored
Normal file
@ -0,0 +1,121 @@
|
||||
/*
|
||||
Copyright 2016 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 discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
apimachineryversion "k8s.io/apimachinery/pkg/version"
|
||||
)
|
||||
|
||||
// MatchesServerVersion queries the server to compares the build version
|
||||
// (git hash) of the client with the server's build version. It returns an error
|
||||
// if it failed to contact the server or if the versions are not an exact match.
|
||||
func MatchesServerVersion(clientVersion apimachineryversion.Info, client DiscoveryInterface) error {
|
||||
sVer, err := client.ServerVersion()
|
||||
if err != nil {
|
||||
return fmt.Errorf("couldn't read version from server: %v\n", err)
|
||||
}
|
||||
// GitVersion includes GitCommit and GitTreeState, but best to be safe?
|
||||
if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit || clientVersion.GitTreeState != sVer.GitTreeState {
|
||||
return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", sVer, clientVersion)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
// ServerSupportsVersion returns an error if the server doesn't have the required version
|
||||
func ServerSupportsVersion(client DiscoveryInterface, requiredGV schema.GroupVersion) error {
|
||||
groups, err := client.ServerGroups()
|
||||
if err != nil {
|
||||
// This is almost always a connection error, and higher level code should treat this as a generic error,
|
||||
// not a negotiation specific error.
|
||||
return err
|
||||
}
|
||||
versions := metav1.ExtractGroupVersions(groups)
|
||||
serverVersions := sets.String{}
|
||||
for _, v := range versions {
|
||||
serverVersions.Insert(v)
|
||||
}
|
||||
|
||||
if serverVersions.Has(requiredGV.String()) {
|
||||
return nil
|
||||
}
|
||||
|
||||
// If the server supports no versions, then we should pretend it has the version because of old servers.
|
||||
// This can happen because discovery fails due to 403 Forbidden errors
|
||||
if len(serverVersions) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
return fmt.Errorf("server does not support API version %q", requiredGV)
|
||||
}
|
||||
|
||||
// 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...)
|
||||
}
|
184
vendor/k8s.io/client-go/discovery/helper_blackbox_test.go
generated
vendored
Normal file
184
vendor/k8s.io/client-go/discovery/helper_blackbox_test.go
generated
vendored
Normal file
@ -0,0 +1,184 @@
|
||||
/*
|
||||
Copyright 2015 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 discovery_test
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"io"
|
||||
"io/ioutil"
|
||||
"net/http"
|
||||
"strings"
|
||||
"testing"
|
||||
|
||||
"k8s.io/api/core/v1"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/util/sets"
|
||||
"k8s.io/client-go/discovery"
|
||||
"k8s.io/client-go/kubernetes/scheme"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
)
|
||||
|
||||
func objBody(object interface{}) io.ReadCloser {
|
||||
output, err := json.MarshalIndent(object, "", "")
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
return ioutil.NopCloser(bytes.NewReader([]byte(output)))
|
||||
}
|
||||
|
||||
func TestServerSupportsVersion(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
requiredVersion schema.GroupVersion
|
||||
serverVersions []string
|
||||
expectErr func(err error) bool
|
||||
sendErr error
|
||||
statusCode int
|
||||
}{
|
||||
{
|
||||
name: "explicit version supported",
|
||||
requiredVersion: schema.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"/version1", v1.SchemeGroupVersion.String()},
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "explicit version not supported on server",
|
||||
requiredVersion: schema.GroupVersion{Version: "v1"},
|
||||
serverVersions: []string{"version1"},
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), `server does not support API version "v1"`) },
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "connection refused error",
|
||||
serverVersions: []string{"version1"},
|
||||
sendErr: errors.New("connection refused"),
|
||||
expectErr: func(err error) bool { return strings.Contains(err.Error(), "connection refused") },
|
||||
statusCode: http.StatusOK,
|
||||
},
|
||||
{
|
||||
name: "discovery fails due to 404 Not Found errors and thus serverVersions is empty, use requested GroupVersion",
|
||||
requiredVersion: schema.GroupVersion{Version: "version1"},
|
||||
statusCode: http.StatusNotFound,
|
||||
},
|
||||
}
|
||||
|
||||
for _, test := range tests {
|
||||
fakeClient := &fake.RESTClient{
|
||||
NegotiatedSerializer: scheme.Codecs,
|
||||
Resp: &http.Response{
|
||||
StatusCode: test.statusCode,
|
||||
Body: objBody(&metav1.APIVersions{Versions: test.serverVersions}),
|
||||
},
|
||||
Client: fake.CreateHTTPClient(func(req *http.Request) (*http.Response, error) {
|
||||
if test.sendErr != nil {
|
||||
return nil, test.sendErr
|
||||
}
|
||||
header := http.Header{}
|
||||
header.Set("Content-Type", runtime.ContentTypeJSON)
|
||||
return &http.Response{StatusCode: test.statusCode, Header: header, Body: objBody(&metav1.APIVersions{Versions: test.serverVersions})}, nil
|
||||
}),
|
||||
}
|
||||
c := discovery.NewDiscoveryClientForConfigOrDie(&restclient.Config{})
|
||||
c.RESTClient().(*restclient.RESTClient).Client = fakeClient.Client
|
||||
err := discovery.ServerSupportsVersion(c, test.requiredVersion)
|
||||
if err == nil && test.expectErr != nil {
|
||||
t.Errorf("expected error, got nil for [%s].", test.name)
|
||||
}
|
||||
if err != nil {
|
||||
if test.expectErr == nil || !test.expectErr(err) {
|
||||
t.Errorf("unexpected error for [%s]: %v.", test.name, err)
|
||||
}
|
||||
continue
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
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
|
||||
}
|
333
vendor/k8s.io/client-go/discovery/restmapper.go
generated
vendored
Normal file
333
vendor/k8s.io/client-go/discovery/restmapper.go
generated
vendored
Normal file
@ -0,0 +1,333 @@
|
||||
/*
|
||||
Copyright 2016 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 discovery
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/meta"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
|
||||
"github.com/golang/glog"
|
||||
)
|
||||
|
||||
// APIGroupResources is an API group with a mapping of versions to
|
||||
// resources.
|
||||
type APIGroupResources struct {
|
||||
Group metav1.APIGroup
|
||||
// A mapping of version string to a slice of APIResources for
|
||||
// that version.
|
||||
VersionedResources map[string][]metav1.APIResource
|
||||
}
|
||||
|
||||
// NewRESTMapper returns a PriorityRESTMapper based on the discovered
|
||||
// groups and resources passed in.
|
||||
func NewRESTMapper(groupResources []*APIGroupResources, versionInterfaces meta.VersionInterfacesFunc) meta.RESTMapper {
|
||||
unionMapper := meta.MultiRESTMapper{}
|
||||
|
||||
var groupPriority []string
|
||||
// /v1 is special. It should always come first
|
||||
resourcePriority := []schema.GroupVersionResource{{Group: "", Version: "v1", Resource: meta.AnyResource}}
|
||||
kindPriority := []schema.GroupVersionKind{{Group: "", Version: "v1", Kind: meta.AnyKind}}
|
||||
|
||||
for _, group := range groupResources {
|
||||
groupPriority = append(groupPriority, group.Group.Name)
|
||||
|
||||
// Make sure the preferred version comes first
|
||||
if len(group.Group.PreferredVersion.Version) != 0 {
|
||||
preferred := group.Group.PreferredVersion.Version
|
||||
if _, ok := group.VersionedResources[preferred]; ok {
|
||||
resourcePriority = append(resourcePriority, schema.GroupVersionResource{
|
||||
Group: group.Group.Name,
|
||||
Version: group.Group.PreferredVersion.Version,
|
||||
Resource: meta.AnyResource,
|
||||
})
|
||||
|
||||
kindPriority = append(kindPriority, schema.GroupVersionKind{
|
||||
Group: group.Group.Name,
|
||||
Version: group.Group.PreferredVersion.Version,
|
||||
Kind: meta.AnyKind,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
for _, discoveryVersion := range group.Group.Versions {
|
||||
resources, ok := group.VersionedResources[discoveryVersion.Version]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
// Add non-preferred versions after the preferred version, in case there are resources that only exist in those versions
|
||||
if discoveryVersion.Version != group.Group.PreferredVersion.Version {
|
||||
resourcePriority = append(resourcePriority, schema.GroupVersionResource{
|
||||
Group: group.Group.Name,
|
||||
Version: discoveryVersion.Version,
|
||||
Resource: meta.AnyResource,
|
||||
})
|
||||
|
||||
kindPriority = append(kindPriority, schema.GroupVersionKind{
|
||||
Group: group.Group.Name,
|
||||
Version: discoveryVersion.Version,
|
||||
Kind: meta.AnyKind,
|
||||
})
|
||||
}
|
||||
|
||||
gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
|
||||
versionMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gv}, versionInterfaces)
|
||||
|
||||
for _, resource := range resources {
|
||||
scope := meta.RESTScopeNamespace
|
||||
if !resource.Namespaced {
|
||||
scope = meta.RESTScopeRoot
|
||||
}
|
||||
|
||||
// this is for legacy resources and servers which don't list singular forms. For those we must still guess.
|
||||
if len(resource.SingularName) == 0 {
|
||||
versionMapper.Add(gv.WithKind(resource.Kind), scope)
|
||||
// TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior
|
||||
versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
|
||||
continue
|
||||
}
|
||||
|
||||
plural := gv.WithResource(resource.Name)
|
||||
singular := gv.WithResource(resource.SingularName)
|
||||
versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope)
|
||||
versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope)
|
||||
// TODO this is producing unsafe guesses that don't actually work, but it matches previous behavior
|
||||
versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
|
||||
}
|
||||
// TODO why is this type not in discovery (at least for "v1")
|
||||
versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot)
|
||||
unionMapper = append(unionMapper, versionMapper)
|
||||
}
|
||||
}
|
||||
|
||||
for _, group := range groupPriority {
|
||||
resourcePriority = append(resourcePriority, schema.GroupVersionResource{
|
||||
Group: group,
|
||||
Version: meta.AnyVersion,
|
||||
Resource: meta.AnyResource,
|
||||
})
|
||||
kindPriority = append(kindPriority, schema.GroupVersionKind{
|
||||
Group: group,
|
||||
Version: meta.AnyVersion,
|
||||
Kind: meta.AnyKind,
|
||||
})
|
||||
}
|
||||
|
||||
return meta.PriorityRESTMapper{
|
||||
Delegate: unionMapper,
|
||||
ResourcePriority: resourcePriority,
|
||||
KindPriority: kindPriority,
|
||||
}
|
||||
}
|
||||
|
||||
// GetAPIGroupResources uses the provided discovery client to gather
|
||||
// discovery information and populate a slice of APIGroupResources.
|
||||
func GetAPIGroupResources(cl DiscoveryInterface) ([]*APIGroupResources, error) {
|
||||
apiGroups, err := cl.ServerGroups()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var result []*APIGroupResources
|
||||
for _, group := range apiGroups.Groups {
|
||||
groupResources := &APIGroupResources{
|
||||
Group: group,
|
||||
VersionedResources: make(map[string][]metav1.APIResource),
|
||||
}
|
||||
for _, version := range group.Versions {
|
||||
resources, err := cl.ServerResourcesForGroupVersion(version.GroupVersion)
|
||||
if err != nil {
|
||||
// continue as best we can
|
||||
// TODO track the errors and update callers to handle partial errors.
|
||||
continue
|
||||
}
|
||||
groupResources.VersionedResources[version.Version] = resources.APIResources
|
||||
}
|
||||
result = append(result, groupResources)
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
// DeferredDiscoveryRESTMapper is a RESTMapper that will defer
|
||||
// initialization of the RESTMapper until the first mapping is
|
||||
// requested.
|
||||
type DeferredDiscoveryRESTMapper struct {
|
||||
initMu sync.Mutex
|
||||
delegate meta.RESTMapper
|
||||
cl CachedDiscoveryInterface
|
||||
versionInterface meta.VersionInterfacesFunc
|
||||
}
|
||||
|
||||
// NewDeferredDiscoveryRESTMapper returns a
|
||||
// DeferredDiscoveryRESTMapper that will lazily query the provided
|
||||
// client for discovery information to do REST mappings.
|
||||
func NewDeferredDiscoveryRESTMapper(cl CachedDiscoveryInterface, versionInterface meta.VersionInterfacesFunc) *DeferredDiscoveryRESTMapper {
|
||||
return &DeferredDiscoveryRESTMapper{
|
||||
cl: cl,
|
||||
versionInterface: versionInterface,
|
||||
}
|
||||
}
|
||||
|
||||
func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
|
||||
d.initMu.Lock()
|
||||
defer d.initMu.Unlock()
|
||||
|
||||
if d.delegate != nil {
|
||||
return d.delegate, nil
|
||||
}
|
||||
|
||||
groupResources, err := GetAPIGroupResources(d.cl)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
d.delegate = NewRESTMapper(groupResources, d.versionInterface)
|
||||
return d.delegate, err
|
||||
}
|
||||
|
||||
// Reset resets the internally cached Discovery information and will
|
||||
// cause the next mapping request to re-discover.
|
||||
func (d *DeferredDiscoveryRESTMapper) Reset() {
|
||||
glog.V(5).Info("Invalidating discovery information")
|
||||
|
||||
d.initMu.Lock()
|
||||
defer d.initMu.Unlock()
|
||||
|
||||
d.cl.Invalidate()
|
||||
d.delegate = nil
|
||||
}
|
||||
|
||||
// KindFor takes a partial resource and returns back the single match.
|
||||
// It returns an error if there are multiple matches.
|
||||
func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return schema.GroupVersionKind{}, err
|
||||
}
|
||||
gvk, err = del.KindFor(resource)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvk, err = d.KindFor(resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// KindsFor takes a partial resource and returns back the list of
|
||||
// potential kinds in priority order.
|
||||
func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvks, err = del.KindsFor(resource)
|
||||
if len(gvks) == 0 && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvks, err = d.KindsFor(resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResourceFor takes a partial resource and returns back the single
|
||||
// match. It returns an error if there are multiple matches.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return schema.GroupVersionResource{}, err
|
||||
}
|
||||
gvr, err = del.ResourceFor(input)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvr, err = d.ResourceFor(input)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResourcesFor takes a partial resource and returns back the list of
|
||||
// potential resource in priority order.
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
gvrs, err = del.ResourcesFor(input)
|
||||
if len(gvrs) == 0 && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
gvrs, err = d.ResourcesFor(input)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RESTMapping identifies a preferred resource mapping for the
|
||||
// provided group kind.
|
||||
func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
m, err = del.RESTMapping(gk, versions...)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
m, err = d.RESTMapping(gk, versions...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// RESTMappings returns the RESTMappings for the provided group kind
|
||||
// in a rough internal preferred order. If no kind is found, it will
|
||||
// return a NoResourceMatchError.
|
||||
func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ms, err = del.RESTMappings(gk, versions...)
|
||||
if len(ms) == 0 && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
ms, err = d.RESTMappings(gk, versions...)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
// ResourceSingularizer converts a resource name from plural to
|
||||
// singular (e.g., from pods to pod).
|
||||
func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return resource, err
|
||||
}
|
||||
singular, err = del.ResourceSingularizer(resource)
|
||||
if err != nil && !d.cl.Fresh() {
|
||||
d.Reset()
|
||||
singular, err = d.ResourceSingularizer(resource)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
func (d *DeferredDiscoveryRESTMapper) String() string {
|
||||
del, err := d.getDelegate()
|
||||
if err != nil {
|
||||
return fmt.Sprintf("DeferredDiscoveryRESTMapper{%v}", err)
|
||||
}
|
||||
return fmt.Sprintf("DeferredDiscoveryRESTMapper{\n\t%v\n}", del)
|
||||
}
|
||||
|
||||
// Make sure it satisfies the interface
|
||||
var _ meta.RESTMapper = &DeferredDiscoveryRESTMapper{}
|
384
vendor/k8s.io/client-go/discovery/restmapper_test.go
generated
vendored
Normal file
384
vendor/k8s.io/client-go/discovery/restmapper_test.go
generated
vendored
Normal file
@ -0,0 +1,384 @@
|
||||
/*
|
||||
Copyright 2016 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 discovery_test
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"testing"
|
||||
|
||||
"k8s.io/apimachinery/pkg/api/errors"
|
||||
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
"k8s.io/apimachinery/pkg/version"
|
||||
. "k8s.io/client-go/discovery"
|
||||
restclient "k8s.io/client-go/rest"
|
||||
"k8s.io/client-go/rest/fake"
|
||||
|
||||
"github.com/googleapis/gnostic/OpenAPIv2"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestRESTMapper(t *testing.T) {
|
||||
resources := []*APIGroupResources{
|
||||
{
|
||||
Group: metav1.APIGroup{
|
||||
Name: "extensions",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{Version: "v1beta"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1beta"},
|
||||
},
|
||||
VersionedResources: map[string][]metav1.APIResource{
|
||||
"v1beta": {
|
||||
{Name: "jobs", Namespaced: true, Kind: "Job"},
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
Group: metav1.APIGroup{
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{Version: "v1"},
|
||||
{Version: "v2"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
|
||||
},
|
||||
VersionedResources: map[string][]metav1.APIResource{
|
||||
"v1": {
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
},
|
||||
"v2": {
|
||||
{Name: "pods", Namespaced: true, Kind: "Pod"},
|
||||
},
|
||||
},
|
||||
},
|
||||
|
||||
// This group tests finding and prioritizing resources that only exist in non-preferred versions
|
||||
{
|
||||
Group: metav1.APIGroup{
|
||||
Name: "unpreferred",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{Version: "v1"},
|
||||
{Version: "v2beta1"},
|
||||
{Version: "v2alpha1"},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{Version: "v1"},
|
||||
},
|
||||
VersionedResources: map[string][]metav1.APIResource{
|
||||
"v1": {
|
||||
{Name: "broccoli", Namespaced: true, Kind: "Broccoli"},
|
||||
},
|
||||
"v2beta1": {
|
||||
{Name: "broccoli", Namespaced: true, Kind: "Broccoli"},
|
||||
{Name: "peas", Namespaced: true, Kind: "Pea"},
|
||||
},
|
||||
"v2alpha1": {
|
||||
{Name: "broccoli", Namespaced: true, Kind: "Broccoli"},
|
||||
{Name: "peas", Namespaced: true, Kind: "Pea"},
|
||||
},
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
restMapper := NewRESTMapper(resources, nil)
|
||||
|
||||
kindTCs := []struct {
|
||||
input schema.GroupVersionResource
|
||||
want schema.GroupVersionKind
|
||||
}{
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionKind{
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionKind{
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Version: "v2",
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionKind{
|
||||
Version: "v2",
|
||||
Kind: "Pod",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionKind{
|
||||
Version: "v1",
|
||||
Kind: "Pod",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "jobs",
|
||||
},
|
||||
want: schema.GroupVersionKind{
|
||||
Group: "extensions",
|
||||
Version: "v1beta",
|
||||
Kind: "Job",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "peas",
|
||||
},
|
||||
want: schema.GroupVersionKind{
|
||||
Group: "unpreferred",
|
||||
Version: "v2beta1",
|
||||
Kind: "Pea",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range kindTCs {
|
||||
got, err := restMapper.KindFor(tc.input)
|
||||
if err != nil {
|
||||
t.Errorf("KindFor(%#v) unexpected error: %v", tc.input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("KindFor(%#v) = %#v, want %#v", tc.input, got, tc.want)
|
||||
}
|
||||
}
|
||||
|
||||
resourceTCs := []struct {
|
||||
input schema.GroupVersionResource
|
||||
want schema.GroupVersionResource
|
||||
}{
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionResource{
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionResource{
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Version: "v2",
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionResource{
|
||||
Version: "v2",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "pods",
|
||||
},
|
||||
want: schema.GroupVersionResource{
|
||||
Version: "v1",
|
||||
Resource: "pods",
|
||||
},
|
||||
},
|
||||
{
|
||||
input: schema.GroupVersionResource{
|
||||
Resource: "jobs",
|
||||
},
|
||||
want: schema.GroupVersionResource{
|
||||
Group: "extensions",
|
||||
Version: "v1beta",
|
||||
Resource: "jobs",
|
||||
},
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range resourceTCs {
|
||||
got, err := restMapper.ResourceFor(tc.input)
|
||||
if err != nil {
|
||||
t.Errorf("ResourceFor(%#v) unexpected error: %v", tc.input, err)
|
||||
continue
|
||||
}
|
||||
|
||||
if !reflect.DeepEqual(got, tc.want) {
|
||||
t.Errorf("ResourceFor(%#v) = %#v, want %#v", tc.input, got, tc.want)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func TestDeferredDiscoveryRESTMapper_CacheMiss(t *testing.T) {
|
||||
assert := assert.New(t)
|
||||
|
||||
cdc := fakeCachedDiscoveryInterface{fresh: false}
|
||||
m := NewDeferredDiscoveryRESTMapper(&cdc, nil)
|
||||
assert.False(cdc.fresh, "should NOT be fresh after instantiation")
|
||||
assert.Zero(cdc.invalidateCalls, "should not have called Invalidate()")
|
||||
|
||||
gvk, err := m.KindFor(schema.GroupVersionResource{
|
||||
Group: "a",
|
||||
Version: "v1",
|
||||
Resource: "foo",
|
||||
})
|
||||
assert.NoError(err)
|
||||
assert.True(cdc.fresh, "should be fresh after a cache-miss")
|
||||
assert.Equal(cdc.invalidateCalls, 1, "should have called Invalidate() once")
|
||||
assert.Equal(gvk.Kind, "Foo")
|
||||
|
||||
gvk, err = m.KindFor(schema.GroupVersionResource{
|
||||
Group: "a",
|
||||
Version: "v1",
|
||||
Resource: "foo",
|
||||
})
|
||||
assert.NoError(err)
|
||||
assert.Equal(cdc.invalidateCalls, 1, "should NOT have called Invalidate() again")
|
||||
|
||||
gvk, err = m.KindFor(schema.GroupVersionResource{
|
||||
Group: "a",
|
||||
Version: "v1",
|
||||
Resource: "bar",
|
||||
})
|
||||
assert.Error(err)
|
||||
assert.Equal(cdc.invalidateCalls, 1, "should NOT have called Invalidate() again after another cache-miss, but with fresh==true")
|
||||
|
||||
cdc.fresh = false
|
||||
gvk, err = m.KindFor(schema.GroupVersionResource{
|
||||
Group: "a",
|
||||
Version: "v1",
|
||||
Resource: "bar",
|
||||
})
|
||||
assert.Error(err)
|
||||
assert.Equal(cdc.invalidateCalls, 2, "should HAVE called Invalidate() again after another cache-miss, but with fresh==false")
|
||||
}
|
||||
|
||||
type fakeCachedDiscoveryInterface struct {
|
||||
invalidateCalls int
|
||||
fresh bool
|
||||
enabledA bool
|
||||
}
|
||||
|
||||
var _ CachedDiscoveryInterface = &fakeCachedDiscoveryInterface{}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) Fresh() bool {
|
||||
return c.fresh
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) Invalidate() {
|
||||
c.invalidateCalls = c.invalidateCalls + 1
|
||||
c.fresh = true
|
||||
c.enabledA = true
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) RESTClient() restclient.Interface {
|
||||
return &fake.RESTClient{}
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerGroups() (*metav1.APIGroupList, error) {
|
||||
if c.enabledA {
|
||||
return &metav1.APIGroupList{
|
||||
Groups: []metav1.APIGroup{
|
||||
{
|
||||
Name: "a",
|
||||
Versions: []metav1.GroupVersionForDiscovery{
|
||||
{
|
||||
GroupVersion: "a/v1",
|
||||
Version: "v1",
|
||||
},
|
||||
},
|
||||
PreferredVersion: metav1.GroupVersionForDiscovery{
|
||||
GroupVersion: "a/v1",
|
||||
Version: "v1",
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return &metav1.APIGroupList{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error) {
|
||||
if c.enabledA && groupVersion == "a/v1" {
|
||||
return &metav1.APIResourceList{
|
||||
GroupVersion: "a/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{
|
||||
Name: "foo",
|
||||
Kind: "Foo",
|
||||
Namespaced: false,
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
|
||||
return nil, errors.NewNotFound(schema.GroupResource{}, "")
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerResources() ([]*metav1.APIResourceList, error) {
|
||||
if c.enabledA {
|
||||
av1, _ := c.ServerResourcesForGroupVersion("a/v1")
|
||||
return []*metav1.APIResourceList{av1}, nil
|
||||
}
|
||||
return []*metav1.APIResourceList{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerPreferredResources() ([]*metav1.APIResourceList, error) {
|
||||
if c.enabledA {
|
||||
return []*metav1.APIResourceList{
|
||||
{
|
||||
GroupVersion: "a/v1",
|
||||
APIResources: []metav1.APIResource{
|
||||
{
|
||||
Name: "foo",
|
||||
Kind: "Foo",
|
||||
Verbs: []string{},
|
||||
},
|
||||
},
|
||||
},
|
||||
}, nil
|
||||
}
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {
|
||||
return nil, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) ServerVersion() (*version.Info, error) {
|
||||
return &version.Info{}, nil
|
||||
}
|
||||
|
||||
func (c *fakeCachedDiscoveryInterface) OpenAPISchema() (*openapi_v2.Document, error) {
|
||||
return &openapi_v2.Document{}, nil
|
||||
}
|
95
vendor/k8s.io/client-go/discovery/unstructured.go
generated
vendored
Normal file
95
vendor/k8s.io/client-go/discovery/unstructured.go
generated
vendored
Normal file
@ -0,0 +1,95 @@
|
||||
/*
|
||||
Copyright 2016 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 discovery
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
||||
"k8s.io/apimachinery/pkg/runtime"
|
||||
"k8s.io/apimachinery/pkg/runtime/schema"
|
||||
)
|
||||
|
||||
// UnstructuredObjectTyper provides a runtime.ObjectTyper implementation for
|
||||
// runtime.Unstructured object based on discovery information.
|
||||
type UnstructuredObjectTyper struct {
|
||||
registered map[schema.GroupVersionKind]bool
|
||||
typers []runtime.ObjectTyper
|
||||
}
|
||||
|
||||
// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for
|
||||
// unstructured objects based on discovery information. It accepts a list of fallback typers
|
||||
// for handling objects that are not runtime.Unstructured. It does not delegate the Recognizes
|
||||
// check, only ObjectKinds.
|
||||
func NewUnstructuredObjectTyper(groupResources []*APIGroupResources, typers ...runtime.ObjectTyper) *UnstructuredObjectTyper {
|
||||
dot := &UnstructuredObjectTyper{
|
||||
registered: make(map[schema.GroupVersionKind]bool),
|
||||
typers: typers,
|
||||
}
|
||||
for _, group := range groupResources {
|
||||
for _, discoveryVersion := range group.Group.Versions {
|
||||
resources, ok := group.VersionedResources[discoveryVersion.Version]
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
|
||||
gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
|
||||
for _, resource := range resources {
|
||||
dot.registered[gv.WithKind(resource.Kind)] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
return dot
|
||||
}
|
||||
|
||||
// ObjectKinds returns a slice of one element with the group,version,kind of the
|
||||
// provided object, or an error if the object is not runtime.Unstructured or
|
||||
// has no group,version,kind information. unversionedType will always be false
|
||||
// because runtime.Unstructured object should always have group,version,kind
|
||||
// information set.
|
||||
func (d *UnstructuredObjectTyper) ObjectKinds(obj runtime.Object) (gvks []schema.GroupVersionKind, unversionedType bool, err error) {
|
||||
if _, ok := obj.(runtime.Unstructured); ok {
|
||||
gvk := obj.GetObjectKind().GroupVersionKind()
|
||||
if len(gvk.Kind) == 0 {
|
||||
return nil, false, runtime.NewMissingKindErr("object has no kind field ")
|
||||
}
|
||||
if len(gvk.Version) == 0 {
|
||||
return nil, false, runtime.NewMissingVersionErr("object has no apiVersion field")
|
||||
}
|
||||
return []schema.GroupVersionKind{gvk}, false, nil
|
||||
}
|
||||
var lastErr error
|
||||
for _, typer := range d.typers {
|
||||
gvks, unversioned, err := typer.ObjectKinds(obj)
|
||||
if err != nil {
|
||||
lastErr = err
|
||||
continue
|
||||
}
|
||||
return gvks, unversioned, nil
|
||||
}
|
||||
if lastErr == nil {
|
||||
lastErr = runtime.NewNotRegisteredErrForType(reflect.TypeOf(obj))
|
||||
}
|
||||
return nil, false, lastErr
|
||||
}
|
||||
|
||||
// Recognizes returns true if the provided group,version,kind was in the
|
||||
// discovery information.
|
||||
func (d *UnstructuredObjectTyper) Recognizes(gvk schema.GroupVersionKind) bool {
|
||||
return d.registered[gvk]
|
||||
}
|
||||
|
||||
var _ runtime.ObjectTyper = &UnstructuredObjectTyper{}
|
Reference in New Issue
Block a user