Zack Williams | e940c7a | 2019-08-21 14:25:39 -0700 | [diff] [blame] | 1 | /* |
| 2 | Copyright 2016 The Kubernetes Authors. |
| 3 | |
| 4 | Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | you may not use this file except in compliance with the License. |
| 6 | You may obtain a copy of the License at |
| 7 | |
| 8 | http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | |
| 10 | Unless required by applicable law or agreed to in writing, software |
| 11 | distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | See the License for the specific language governing permissions and |
| 14 | limitations under the License. |
| 15 | */ |
| 16 | |
| 17 | package discovery |
| 18 | |
| 19 | import ( |
| 20 | "fmt" |
| 21 | |
| 22 | metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" |
| 23 | "k8s.io/apimachinery/pkg/runtime/schema" |
| 24 | "k8s.io/apimachinery/pkg/util/sets" |
| 25 | apimachineryversion "k8s.io/apimachinery/pkg/version" |
| 26 | ) |
| 27 | |
| 28 | // MatchesServerVersion queries the server to compares the build version |
| 29 | // (git hash) of the client with the server's build version. It returns an error |
| 30 | // if it failed to contact the server or if the versions are not an exact match. |
| 31 | func MatchesServerVersion(clientVersion apimachineryversion.Info, client DiscoveryInterface) error { |
| 32 | sVer, err := client.ServerVersion() |
| 33 | if err != nil { |
| 34 | return fmt.Errorf("couldn't read version from server: %v", err) |
| 35 | } |
| 36 | // GitVersion includes GitCommit and GitTreeState, but best to be safe? |
| 37 | if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit || clientVersion.GitTreeState != sVer.GitTreeState { |
| 38 | return fmt.Errorf("server version (%#v) differs from client version (%#v)", sVer, clientVersion) |
| 39 | } |
| 40 | |
| 41 | return nil |
| 42 | } |
| 43 | |
| 44 | // ServerSupportsVersion returns an error if the server doesn't have the required version |
| 45 | func ServerSupportsVersion(client DiscoveryInterface, requiredGV schema.GroupVersion) error { |
| 46 | groups, err := client.ServerGroups() |
| 47 | if err != nil { |
| 48 | // This is almost always a connection error, and higher level code should treat this as a generic error, |
| 49 | // not a negotiation specific error. |
| 50 | return err |
| 51 | } |
| 52 | versions := metav1.ExtractGroupVersions(groups) |
| 53 | serverVersions := sets.String{} |
| 54 | for _, v := range versions { |
| 55 | serverVersions.Insert(v) |
| 56 | } |
| 57 | |
| 58 | if serverVersions.Has(requiredGV.String()) { |
| 59 | return nil |
| 60 | } |
| 61 | |
| 62 | // If the server supports no versions, then we should pretend it has the version because of old servers. |
| 63 | // This can happen because discovery fails due to 403 Forbidden errors |
| 64 | if len(serverVersions) == 0 { |
| 65 | return nil |
| 66 | } |
| 67 | |
| 68 | return fmt.Errorf("server does not support API version %q", requiredGV) |
| 69 | } |
| 70 | |
| 71 | // GroupVersionResources converts APIResourceLists to the GroupVersionResources. |
| 72 | func GroupVersionResources(rls []*metav1.APIResourceList) (map[schema.GroupVersionResource]struct{}, error) { |
| 73 | gvrs := map[schema.GroupVersionResource]struct{}{} |
| 74 | for _, rl := range rls { |
| 75 | gv, err := schema.ParseGroupVersion(rl.GroupVersion) |
| 76 | if err != nil { |
| 77 | return nil, err |
| 78 | } |
| 79 | for i := range rl.APIResources { |
| 80 | gvrs[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{} |
| 81 | } |
| 82 | } |
| 83 | return gvrs, nil |
| 84 | } |
| 85 | |
| 86 | // FilteredBy filters by the given predicate. Empty APIResourceLists are dropped. |
| 87 | func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1.APIResourceList { |
| 88 | result := []*metav1.APIResourceList{} |
| 89 | for _, rl := range rls { |
| 90 | filtered := *rl |
| 91 | filtered.APIResources = nil |
| 92 | for i := range rl.APIResources { |
| 93 | if pred.Match(rl.GroupVersion, &rl.APIResources[i]) { |
| 94 | filtered.APIResources = append(filtered.APIResources, rl.APIResources[i]) |
| 95 | } |
| 96 | } |
| 97 | if filtered.APIResources != nil { |
| 98 | result = append(result, &filtered) |
| 99 | } |
| 100 | } |
| 101 | return result |
| 102 | } |
| 103 | |
| 104 | // ResourcePredicate has a method to check if a resource matches a given condition. |
| 105 | type ResourcePredicate interface { |
| 106 | Match(groupVersion string, r *metav1.APIResource) bool |
| 107 | } |
| 108 | |
| 109 | // ResourcePredicateFunc returns true if it matches a resource based on a custom condition. |
| 110 | type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool |
| 111 | |
| 112 | // Match is a wrapper around ResourcePredicateFunc. |
| 113 | func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool { |
| 114 | return fn(groupVersion, r) |
| 115 | } |
| 116 | |
| 117 | // SupportsAllVerbs is a predicate matching a resource iff all given verbs are supported. |
| 118 | type SupportsAllVerbs struct { |
| 119 | Verbs []string |
| 120 | } |
| 121 | |
| 122 | // Match checks if a resource contains all the given verbs. |
| 123 | func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool { |
| 124 | return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...) |
| 125 | } |