diff options
Diffstat (limited to 'vendor/k8s.io/client-go/discovery')
-rw-r--r-- | vendor/k8s.io/client-go/discovery/discovery_client.go | 455 | ||||
-rw-r--r-- | vendor/k8s.io/client-go/discovery/helper.go | 121 | ||||
-rw-r--r-- | vendor/k8s.io/client-go/discovery/restmapper.go | 304 | ||||
-rw-r--r-- | vendor/k8s.io/client-go/discovery/unstructured.go | 95 |
4 files changed, 975 insertions, 0 deletions
diff --git a/vendor/k8s.io/client-go/discovery/discovery_client.go b/vendor/k8s.io/client-go/discovery/discovery_client.go new file mode 100644 index 000000000..011dd9ecf --- /dev/null +++ b/vendor/k8s.io/client-go/discovery/discovery_client.go @@ -0,0 +1,455 @@ +/* +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/emicklei/go-restful-swagger12" + + "github.com/go-openapi/loads" + "github.com/go-openapi/spec" + "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" + "k8s.io/client-go/pkg/api/v1" + restclient "k8s.io/client-go/rest" +) + +// defaultRetries is the number of times a resource discovery is repeated if an api group disappears on the fly (e.g. ThirdPartyResources). +const defaultRetries = 2 + +// DiscoveryInterface holds the methods that discover server-supported API groups, +// versions and resources. +type DiscoveryInterface interface { + RESTClient() restclient.Interface + ServerGroupsInterface + ServerResourcesInterface + ServerVersionInterface + SwaggerSchemaInterface + OpenAPISchemaInterface +} + +// CachedDiscoveryInterface is a DiscoveryInterface with cache invalidation and freshness. +type CachedDiscoveryInterface interface { + DiscoveryInterface + // Fresh returns true if no cached data was used that had been retrieved before the instantiation. + 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) +} + +// SwaggerSchemaInterface has a method to retrieve the swagger schema. +type SwaggerSchemaInterface interface { + // SwaggerSchema retrieves and parses the swagger API schema the server supports. + SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, 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() (*spec.Swagger, 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{} + } + + // append the group retrieved from /api to the list if not empty + if len(v.Versions) != 0 { + apiGroupList.Groups = append(apiGroupList.Groups, apiGroup) + } + 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 +} + +// SwaggerSchema retrieves and parses the swagger API schema the server supports. +// TODO: Replace usages with Open API. Tracked in https://github.com/kubernetes/kubernetes/issues/44589 +func (d *DiscoveryClient) SwaggerSchema(version schema.GroupVersion) (*swagger.ApiDeclaration, error) { + if version.Empty() { + return nil, fmt.Errorf("groupVersion cannot be empty") + } + + groupList, err := d.ServerGroups() + if err != nil { + return nil, err + } + groupVersions := metav1.ExtractGroupVersions(groupList) + // This check also takes care the case that kubectl is newer than the running endpoint + if stringDoesntExistIn(version.String(), groupVersions) { + return nil, fmt.Errorf("API version: %v is not supported by the server. Use one of: %v", version, groupVersions) + } + var path string + if len(d.LegacyPrefix) > 0 && version == v1.SchemeGroupVersion { + path = "/swaggerapi" + d.LegacyPrefix + "/" + version.Version + } else { + path = "/swaggerapi/apis/" + version.Group + "/" + version.Version + } + + body, err := d.restClient.Get().AbsPath(path).Do().Raw() + if err != nil { + return nil, err + } + var schema swagger.ApiDeclaration + err = json.Unmarshal(body, &schema) + if err != nil { + return nil, fmt.Errorf("got '%s': %v", string(body), err) + } + return &schema, nil +} + +// OpenAPISchema fetches the open api schema using a rest client and parses the json. +// Warning: this is very expensive (~1.2s) +func (d *DiscoveryClient) OpenAPISchema() (*spec.Swagger, error) { + data, err := d.restClient.Get().AbsPath("/swagger.json").Do().Raw() + if err != nil { + return nil, err + } + msg := json.RawMessage(data) + doc, err := loads.Analyzed(msg, "") + if err != nil { + return nil, err + } + return doc.Spec(), err +} + +// 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 +} + +// NewDiscoveryClientForConfig 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 + +} + +// New creates a new DiscoveryClient for the given RESTClient. +func NewDiscoveryClient(c restclient.Interface) *DiscoveryClient { + return &DiscoveryClient{restClient: c, LegacyPrefix: "/api"} +} + +func stringDoesntExistIn(str string, slice []string) bool { + for _, s := range slice { + if s == str { + return false + } + } + return true +} + +// 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 +} diff --git a/vendor/k8s.io/client-go/discovery/helper.go b/vendor/k8s.io/client-go/discovery/helper.go new file mode 100644 index 000000000..353d34b3c --- /dev/null +++ b/vendor/k8s.io/client-go/discovery/helper.go @@ -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...) +} diff --git a/vendor/k8s.io/client-go/discovery/restmapper.go b/vendor/k8s.io/client-go/discovery/restmapper.go new file mode 100644 index 000000000..756669001 --- /dev/null +++ b/vendor/k8s.io/client-go/discovery/restmapper.go @@ -0,0 +1,304 @@ +/* +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" + "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) + + 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 + } + + 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 + } + versionMapper.Add(gv.WithKind(resource.Kind), scope) + // TODO only do this if it supports listing + 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{} diff --git a/vendor/k8s.io/client-go/discovery/unstructured.go b/vendor/k8s.io/client-go/discovery/unstructured.go new file mode 100644 index 000000000..ee72d668b --- /dev/null +++ b/vendor/k8s.io/client-go/discovery/unstructured.go @@ -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 ( + "fmt" + + "k8s.io/apimachinery/pkg/runtime" + "k8s.io/apimachinery/pkg/runtime/schema" +) + +// UnstructuredObjectTyper provides a runtime.ObjectTyper implmentation for +// runtime.Unstructured object based on discovery information. +type UnstructuredObjectTyper struct { + registered map[schema.GroupVersionKind]bool +} + +// NewUnstructuredObjectTyper returns a runtime.ObjectTyper for +// unstructred objects based on discovery information. +func NewUnstructuredObjectTyper(groupResources []*APIGroupResources) *UnstructuredObjectTyper { + dot := &UnstructuredObjectTyper{registered: make(map[schema.GroupVersionKind]bool)} + 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 +} + +// ObjectKind returns the group,version,kind of the provided object, or an error +// if the object in not runtime.Unstructured or has no group,version,kind +// information. +func (d *UnstructuredObjectTyper) ObjectKind(obj runtime.Object) (schema.GroupVersionKind, error) { + if _, ok := obj.(runtime.Unstructured); !ok { + return schema.GroupVersionKind{}, fmt.Errorf("type %T is invalid for dynamic object typer", obj) + } + + return obj.GetObjectKind().GroupVersionKind(), nil +} + +// 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) { + gvk, err := d.ObjectKind(obj) + if err != nil { + return nil, false, err + } + + return []schema.GroupVersionKind{gvk}, false, nil +} + +// 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] +} + +// IsUnversioned returns false always because runtime.Unstructured objects +// should always have group,version,kind information set. ok will be true if the +// object's group,version,kind is api.Registry. +func (d *UnstructuredObjectTyper) IsUnversioned(obj runtime.Object) (unversioned bool, ok bool) { + gvk, err := d.ObjectKind(obj) + if err != nil { + return false, false + } + + return false, d.registered[gvk] +} + +var _ runtime.ObjectTyper = &UnstructuredObjectTyper{} |