helper.go 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162
  1. /*
  2. Copyright 2016 The Kubernetes Authors.
  3. Licensed under the Apache License, Version 2.0 (the "License");
  4. you may not use this file except in compliance with the License.
  5. You may obtain a copy of the License at
  6. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package discovery
  14. import (
  15. "fmt"
  16. metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
  17. "k8s.io/apimachinery/pkg/runtime/schema"
  18. "k8s.io/apimachinery/pkg/util/sets"
  19. apimachineryversion "k8s.io/apimachinery/pkg/version"
  20. )
  21. // MatchesServerVersion queries the server to compares the build version
  22. // (git hash) of the client with the server's build version. It returns an error
  23. // if it failed to contact the server or if the versions are not an exact match.
  24. func MatchesServerVersion(clientVersion apimachineryversion.Info, client DiscoveryInterface) error {
  25. sVer, err := client.ServerVersion()
  26. if err != nil {
  27. return fmt.Errorf("couldn't read version from server: %v\n", err)
  28. }
  29. // GitVersion includes GitCommit and GitTreeState, but best to be safe?
  30. if clientVersion.GitVersion != sVer.GitVersion || clientVersion.GitCommit != sVer.GitCommit || clientVersion.GitTreeState != sVer.GitTreeState {
  31. return fmt.Errorf("server version (%#v) differs from client version (%#v)!\n", sVer, clientVersion)
  32. }
  33. return nil
  34. }
  35. // NegotiateVersion queries the server's supported api versions to find
  36. // a version that both client and server support.
  37. // - If no version is provided, try registered client versions in order of
  38. // preference.
  39. // - If version is provided and the server does not support it,
  40. // return an error.
  41. // TODO negotiation should be reserved for cases where we need a version for a given group. In those cases, it should return an ordered list of
  42. // server preferences. From that list, a separate function can match from an ordered list of client versions.
  43. // This is not what the function has ever done before, but it makes more logical sense.
  44. func NegotiateVersion(client DiscoveryInterface, requiredGV *schema.GroupVersion, clientRegisteredGVs []schema.GroupVersion) (*schema.GroupVersion, error) {
  45. clientVersions := sets.String{}
  46. for _, gv := range clientRegisteredGVs {
  47. clientVersions.Insert(gv.String())
  48. }
  49. groups, err := client.ServerGroups()
  50. if err != nil {
  51. // This is almost always a connection error, and higher level code should treat this as a generic error,
  52. // not a negotiation specific error.
  53. return nil, err
  54. }
  55. versions := metav1.ExtractGroupVersions(groups)
  56. serverVersions := sets.String{}
  57. for _, v := range versions {
  58. serverVersions.Insert(v)
  59. }
  60. // If version explicitly requested verify that both client and server support it.
  61. // If server does not support warn, but try to negotiate a lower version.
  62. if requiredGV != nil {
  63. if !clientVersions.Has(requiredGV.String()) {
  64. return nil, fmt.Errorf("client does not support API version %q; client supported API versions: %v", requiredGV, clientVersions)
  65. }
  66. // If the server supports no versions, then we should just use the preferredGV
  67. // This can happen because discovery fails due to 403 Forbidden errors
  68. if len(serverVersions) == 0 {
  69. return requiredGV, nil
  70. }
  71. if serverVersions.Has(requiredGV.String()) {
  72. return requiredGV, nil
  73. }
  74. // If we are using an explicit config version the server does not support, fail.
  75. return nil, fmt.Errorf("server does not support API version %q", requiredGV)
  76. }
  77. for _, clientGV := range clientRegisteredGVs {
  78. if serverVersions.Has(clientGV.String()) {
  79. // Version was not explicitly requested in command config (--api-version).
  80. // Ok to fall back to a supported version with a warning.
  81. // TODO: caesarxuchao: enable the warning message when we have
  82. // proper fix. Please refer to issue #14895.
  83. // if len(version) != 0 {
  84. // glog.Warningf("Server does not support API version '%s'. Falling back to '%s'.", version, clientVersion)
  85. // }
  86. t := clientGV
  87. return &t, nil
  88. }
  89. }
  90. // if we have no server versions and we have no required version, choose the first clientRegisteredVersion
  91. if len(serverVersions) == 0 && len(clientRegisteredGVs) > 0 {
  92. return &clientRegisteredGVs[0], nil
  93. }
  94. // fall back to an empty GroupVersion. Most client commands no longer respect a GroupVersion anyway
  95. return &schema.GroupVersion{}, nil
  96. }
  97. // GroupVersionResources converts APIResourceLists to the GroupVersionResources.
  98. func GroupVersionResources(rls []*metav1.APIResourceList) (map[schema.GroupVersionResource]struct{}, error) {
  99. gvrs := map[schema.GroupVersionResource]struct{}{}
  100. for _, rl := range rls {
  101. gv, err := schema.ParseGroupVersion(rl.GroupVersion)
  102. if err != nil {
  103. return nil, err
  104. }
  105. for i := range rl.APIResources {
  106. gvrs[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{}
  107. }
  108. }
  109. return gvrs, nil
  110. }
  111. // FilteredBy filters by the given predicate. Empty APIResourceLists are dropped.
  112. func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1.APIResourceList {
  113. result := []*metav1.APIResourceList{}
  114. for _, rl := range rls {
  115. filtered := *rl
  116. filtered.APIResources = nil
  117. for i := range rl.APIResources {
  118. if pred.Match(rl.GroupVersion, &rl.APIResources[i]) {
  119. filtered.APIResources = append(filtered.APIResources, rl.APIResources[i])
  120. }
  121. }
  122. if filtered.APIResources != nil {
  123. result = append(result, &filtered)
  124. }
  125. }
  126. return result
  127. }
  128. type ResourcePredicate interface {
  129. Match(groupVersion string, r *metav1.APIResource) bool
  130. }
  131. type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool
  132. func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool {
  133. return fn(groupVersion, r)
  134. }
  135. // SupportsAllVerbs is a predicate matching a resource iff all given verbs are supported.
  136. type SupportsAllVerbs struct {
  137. Verbs []string
  138. }
  139. func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool {
  140. return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...)
  141. }