discovery包主要用来发现服务器支持的API组、版本和资源的方法,及服务端支持的swagger api
代码示例:
package mainimport ("fmt""k8s.io/apimachinery/pkg/runtime/schema""k8s.io/client-go/discovery""k8s.io/client-go/tools/clientcmd""log"
)func main() {config, err := clientcmd.BuildConfigFromFlags("", "./config")if err != nil {log.Fatal(err)}discoverClient, err := discovery.NewDiscoveryClientForConfig(config)if err != nil {log.Fatal(err)}_, apiResourceList, err := discoverClient.ServerGroupsAndResources()for _, v := range apiResourceList {gv, err := schema.ParseGroupVersion(v.GroupVersion)if err != nil {log.Fatal(err)}for _, resource := range v.APIResources {fmt.Println("name:", resource.Name, " ", "group:", gv.Group, " ", "version:", gv.Version)}}}
const (// defaultRetries指动态获取resource失败,重试的次数(例如CustomResourceDefinitionsdefaultRetries = 2// protobuf mime 类型openAPIV2mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"// defaultTimeout是在RESTClient上未设置超时时每个请求的最大时间。默认为32秒,以便相对于其他存在的超时具有可区分的时间长度。 defaultTimeout = 32 * time.SeconddefaultTimeout = 32 * time.Second// defaultBurst是发现客户端的令牌桶速率限制的使用的默认burstdefaultBurst = 300AcceptV1 = runtime.ContentTypeJSON// 聚合的discovery类型(当前为v2beta1)。注意:目前,我们假设“g”、“v”和“as”的顺序来自服务器。只有当我们能够做出这样的假设时,我们才能比较这个字符串AcceptV2Beta1 = runtime.ContentTypeJSON + ";" + "g=apidiscovery.k8s.io;v=v2beta1;as=APIGroupDiscoveryList"// 通过设置descovery的可接受的类型顺序,确定聚合descovery的优先级acceptDiscoveryFormats = AcceptV2Beta1 + "," + AcceptV1
// DiscoveryInterface 动态发现服务器支持的API groups,versions and resources.
type DiscoveryInterface interface {RESTClient() restclient.InterfaceServerGroupsInterfaceServerResourcesInterfaceServerVersionInterfaceOpenAPISchemaInterfaceOpenAPIV3SchemaInterface// 返回仅接收旧版发现格式的当前发现客户端的副本,如果当前发现客户端不支持仅旧版发现,则返回指向该客户端的指针。WithLegacy() DiscoveryInterface
}// AggregatedDiscoveryInterface扩展了DiscoveryInterface,使其包括一个可能返回发现资源和发现组的方法,这就是较新的聚合发现格式(APIGroupDiscoveryList)的作用。
type AggregatedDiscoveryInterface interface {DiscoveryInterfaceGroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error)
}// CachedDiscoveryInterface 是一个具有缓存失效和刷新的 DiscoveryInterface。
// 注意:如果ServerResourcesForGroupVersion方法返回缓存未命中错误,
// 用户需要显式调用Invalidate清除缓存,否则下次会返回同样的缓存未命中错误。type CachedDiscoveryInterface interface {DiscoveryInterface//如果在缓存未能找到,Fresh 应该告诉调用者是否重试(false = 重试,true = 不需要重试)。// TODO: this needs to be revisited, this interface can't be locked properly// and doesn't make a lot of sense.Fresh() bool//Invalidate 强制不使用早于当前时间的缓存数据Invalidate()
}// ServerGroupsInterface 具有获取 API 服务器上支持的group的方法
type ServerGroupsInterface interface {// ServerGroups 返回支持的组,包括支持的版本和首选版本等信息。ServerGroups() (*metav1.APIGroupList, error)
}// ServerResourcesInterface 具有获取 API 服务器上支持的resource的方法
type ServerResourcesInterface interface {// ServerResourcesForGroupVersion 返回组和版本支持的资源ServerResourcesForGroupVersion(groupVersion string) (*metav1.APIResourceList, error)// ServerGroupsAndResources为所有组和版本返回支持的组和资源。// 返回的组和资源列表可能是非零的,即使在非零错误的情况下也会有部分结果ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)// ServerPreferredResources使用服务器首选的版本返回支持的资源。// 返回的组和资源列表可能是非零的,即使在非零错误的情况下也会有部分结果。ServerPreferredResources() ([]*metav1.APIResourceList, error)// ServerPreferredNamespacedResources使用服务器首选的版本返回受支持的命名空间资源。// 返回的资源列表可能是非nil,即使在非nil错误的情况下也会有部分结果。ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error)
}// ServerVersionInterface 有一个检索服务器版本的方法。
type ServerVersionInterface interface {// ServerVersion 检索并解析服务器的版本(git 版本)。ServerVersion() (*version.Info, error)
}// OpenAPISchemaInterface 有一个方法来检索open API的schema。
type OpenAPISchemaInterface interface {// OpenAPISchema 检索并解析服务器支持的 swagger API 模式。OpenAPISchema() (*openapi_v2.Document, error)
}
// fetchServerResourcesForGroupVersions使用Discovery客户端并行获取指定组的资源。
func fetchGroupVersionResources(d DiscoveryInterface, apiGroups *metav1.APIGroupList) (map[schema.GroupVersion]*metav1.APIResourceList, map[schema.GroupVersion]error) {groupVersionResources := make(map[schema.GroupVersion]*metav1.APIResourceList)failedGroups := make(map[schema.GroupVersion]error)wg := &sync.WaitGroup{}resultLock := &sync.Mutex{}for _, apiGroup := range apiGroups.Groups {// 遍历每个group中的versionsfor _, version := range apiGroup.Versions {groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}wg.Add(1)// 开启goruntime执行go func() {defer wg.Done()defer utilruntime.HandleCrash()// 执行DiscoveryClient具体实现的ServerResourcesForGroupVersionapiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String())// lock to record resultsresultLock.Lock()defer resultLock.Unlock()if err != nil {// TODO: maybe restrict this to NotFound errorsfailedGroups[groupVersion] = err}if apiResourceList != nil {// 执行DiscoveryClient具体实现的ServerResourcesForGroupVersiongroupVersionResources[groupVersion] = apiResourceList}}()}}wg.Wait()return groupVersionResources, failedGroups
}func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {var sgs *metav1.APIGroupListvar resources []*metav1.APIResourceListvar err error// 如果传递的discover对象实现了AggregatedDiscoveryInterface,则尝试使用组和资源检索聚合发现。if ad, ok := d.(AggregatedDiscoveryInterface); ok {var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceListsgs, resourcesByGV, err = ad.GroupsAndMaybeResources()for _, resourceList := range resourcesByGV {resources = append(resources, resourceList)}} else {// 获取apiGroupListsgs, err = d.ServerGroups()}if sgs == nil {return nil, nil, err}// 获取sgs中所有apiGroup的地址resultGroups := []*metav1.APIGroup{}for i := range sgs.Groups {resultGroups = append(resultGroups, &sgs.Groups[i])}if resources != nil {return resultGroups, resources, nil}// DiscoveryClient并行获取指定组列表的资源groupVersionResources, failedGroups := fetchGroupVersionResources(d, sgs)// 按discoveryclient发现的组/版本顺序排列结果result := []*metav1.APIResourceList{}for _, apiGroup := range sgs.Groups {for _, version := range apiGroup.Versions {gv := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}if resources, ok := groupVersionResources[gv]; ok {result = append(result, resources)}}}if len(failedGroups) == 0 {return resultGroups, result, nil}return resultGroups, result, &ErrGroupDiscoveryFailed{Groups: failedGroups}
}// ServerPreferredResources使用提供的发现接口查找首选资源
func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {var serverGroupList *metav1.APIGroupListvar failedGroups map[schema.GroupVersion]errorvar groupVersionResources map[schema.GroupVersion]*metav1.APIResourceListvar err error// 如果传递的Discovery对象实现了的AggregatedDiscoveryInterface,则尝试检索组和资源。ad, ok := d.(AggregatedDiscoveryInterface)if ok {serverGroupList, groupVersionResources, err = ad.GroupsAndMaybeResources()} else {serverGroupList, err = d.ServerGroups()}if err != nil {return nil, err}// DiscoveryClient并行获取指定组列表的资源if groupVersionResources == nil {groupVersionResources, failedGroups = fetchGroupVersionResources(d, serverGroupList)}result := []*metav1.APIResourceList{}grVersions := map[schema.GroupResource]string{} // GroupResource 的选定版本grAPIResources := map[schema.GroupResource]*metav1.APIResource{} // selected APIResource GroupResource 的选定APIResourcegvAPIResourceLists := map[schema.GroupVersion]*metav1.APIResourceList{} // 用于稍后分组的 APIResourceList 的蓝图for _, apiGroup := range serverGroupList.Groups {for _, version := range apiGroup.Versions {groupVersion := schema.GroupVersion{Group: apiGroup.Name, Version: version.Version}// 判断groupVersionResources是否存在apiResourceList, ok := groupVersionResources[groupVersion]if !ok {continue}// 创建空列表,稍后在另一个循环中填充emptyAPIResourceList := metav1.APIResourceList{GroupVersion: version.GroupVersion,}gvAPIResourceLists[groupVersion] = &emptyAPIResourceListresult = append(result, &emptyAPIResourceList)// 遍历上面获取apiResourceList的APIResourcesfor i := range apiResourceList.APIResources {apiResource := &apiResourceList.APIResources[i]// 判断apiResource是否包含/,因为如果包含,则是子资源,所以舍弃if strings.Contains(apiResource.Name, "/") {continue}// 形成gvgv := schema.GroupResource{Group: apiGroup.Name, Resource: apiResource.Name}if _, ok := grAPIResources[gv]; ok && version.Version != apiGroup.PreferredVersion.Version {// only override with preferred versioncontinue}grVersions[gv] = version.VersiongrAPIResources[gv] = apiResource}}}// 根据 GroupVersion 将选定的 APIResources 分组到 APIResourceLists(地址变量,改变值也是改变了result的value)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}
}// ServerPreferredNamespacedResources使用提供的discovery接口查找首选的命名空间资源
func ServerPreferredNamespacedResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {// 获取全部的PreferredResourcesall, err := ServerPreferredResources(d)// 调用helper.go的FilteredByreturn FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {return r.Namespaced}), all), err
}// withRetries会重试给定的恢复函数,以防ServerGroup()返回后服务器支持的组发生更改
func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {var result []*metav1.APIResourceListvar resultGroups []*metav1.APIGroupvar err errorfor i := 0; i < maxRetries; i++ {resultGroups, result, err = f()if err == nil {return resultGroups, result, nil}if _, ok := err.(*ErrGroupDiscoveryFailed); !ok {return nil, nil, err}}return resultGroups, result, err
}
type DiscoveryClient struct {// restClient http.client的封装restClient restclient.Interface// api path的前缀LegacyPrefix string// 强制客户端仅请求“未聚合”(旧版)发现。UseLegacyDiscovery bool
}
var _ AggregatedDiscoveryInterface = &DiscoveryClient{}// 将metav1.APIVersions转换为metav1.API组。APIVersions由旧版v1使用,因此组将为“”。
// 其实是把入参APIVersions中每项的GroupVersion替换为version
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// 在api中应该只返回一个groupVersionapiGroup.PreferredVersion = groupVersions[0]return
}// GroupsAndMaybeResources返回discovery组,以及(如果是新的聚合发现格式)由groupversion索引资源。
// 合并来自api和api的discovery组和资源(聚合或不聚合)。
// 必须首先对旧组进行排序。服务器将以聚合发现格式或遗留格式返回两个端点(api、apis)。
// 为了安全起见,只有当两个端点都返回了资源时,才会返回资源。
func (d *DiscoveryClient) GroupsAndMaybeResources() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {// Legacy group ordered first (there is only one -- core/v1 group). Returned groups must// be non-nil, but it could be empty. Returned resources, apiResources map could be nil.groups, resources, err := d.downloadLegacy()if err != nil {return nil, nil, err}// Discovery groups and (possibly) resources downloaded from /apis.apiGroups, apiResources, aerr := d.downloadAPIs()if aerr != nil {return nil, nil, aerr}// Merge apis groups into the legacy groups.for _, group := range apiGroups.Groups {groups.Groups = append(groups.Groups, group)}// For safety, only return resources if both endpoints returned resources.if resources != nil && apiResources != nil {for gv, resourceList := range apiResources {resources[gv] = resourceList}} else if resources != nil {resources = nil}return groups, resources, err
}// downloadLegacy在api处返回旧版v1 GVR的discovery组和可能的资源,如果发生错误,则返回错误。如果服务器返回未聚合的发现,
// 则资源映射可能为零。
func (d *DiscoveryClient) downloadLegacy() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {accept := acceptDiscoveryFormatsif d.UseLegacyDiscovery {accept = AcceptV1}var responseContentType stringbody, err := d.restClient.Get().AbsPath("/api").SetHeader("Accept", accept).Do(context.TODO()).ContentType(&responseContentType).Raw()// Special error handling for 403 or 404 to be compatible with older v1.0 servers.// Return empty group list to be merged with /apis.if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {return nil, nil, err}if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {return &metav1.APIGroupList{}, nil, nil}apiGroupList := &metav1.APIGroupList{}var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList// Switch on content-type server responded with: aggregated or unaggregated.switch responseContentType {case AcceptV1:var v metav1.APIVersionserr = json.Unmarshal(body, &v)if err != nil {return nil, nil, err}apiGroup := metav1.APIGroup{}if len(v.Versions) != 0 {apiGroup = apiVersionsToAPIGroup(&v)}apiGroupList.Groups = []metav1.APIGroup{apiGroup}case AcceptV2Beta1:var aggregatedDiscovery apidiscovery.APIGroupDiscoveryListerr = json.Unmarshal(body, &aggregatedDiscovery)if err != nil {return nil, nil, err}apiGroupList, resourcesByGV = SplitGroupsAndResources(aggregatedDiscovery)default:return nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)}return apiGroupList, resourcesByGV, nil
}// downloadAPI返回discovery组和(如果是聚合格式)discovery资源。返回的组将始终存在,但资源映射可能为零。
func (d *DiscoveryClient) downloadAPIs() (*metav1.APIGroupList, map[schema.GroupVersion]*metav1.APIResourceList, error) {accept := acceptDiscoveryFormatsif d.UseLegacyDiscovery {accept = AcceptV1}var responseContentType stringbody, err := d.restClient.Get().AbsPath("/apis").SetHeader("Accept", accept).Do(context.TODO()).ContentType(&responseContentType).Raw()// Special error handling for 403 or 404 to be compatible with older v1.0 servers.// Return empty group list to be merged with /api.if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) {return nil, nil, err}if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) {return &metav1.APIGroupList{}, nil, nil}apiGroupList := &metav1.APIGroupList{}var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList// Switch on content-type server responded with: aggregated or unaggregated.switch responseContentType {case AcceptV1:err = json.Unmarshal(body, apiGroupList)if err != nil {return nil, nil, err}case AcceptV2Beta1:var aggregatedDiscovery apidiscovery.APIGroupDiscoveryListerr = json.Unmarshal(body, &aggregatedDiscovery)if err != nil {return nil, nil, err}apiGroupList, resourcesByGV = SplitGroupsAndResources(aggregatedDiscovery)default:return nil, nil, fmt.Errorf("Unknown discovery response content-type: %s", responseContentType)}return apiGroupList, resourcesByGV, nil
}// ServerGroups返回支持的组,其中包含支持的版本和首选版本等信息。 实现ServerGroupsInterface接口
func (d *DiscoveryClient) ServerGroups() (*metav1.APIGroupList, error) {groups, _, err := d.GroupsAndMaybeResources()if err != nil {return nil, err}return groups, nil
}// 实现了ServerResourcesInterface的ServerResourcesForGroupVersion方法 注意获取resource在不同版本下的path
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")}// 这里体现了获取core group的resource的path为/api/v1// 获取非core group的resource的path为/apis/$GROUP_NAME/$VERSION 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(context.TODO()).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
}// ServerGroupsAndResources返回所有组和版本支持的资源。
func (d *DiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {return withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {return ServerGroupsAndResources(d)})
}// ServerPreferredResources使用服务器首选的版本返回支持的资源。
func (d *DiscoveryClient) ServerPreferredResources() ([]*metav1.APIResourceList, error) {_, rs, err := withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {// 调用上面函数部分的ServerPreferredResourcesrs, err := ServerPreferredResources(d)return nil, rs, err})return rs, err
}// ServerPreferredNamespacedResources使用服务器首选的版本返回受支持的命名空间资源。
func (d *DiscoveryClient) ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) {return ServerPreferredNamespacedResources(d)
}// ServerPreferredNamespacedResources使用提供的discovery接口查找首选的命名空间资源
func ServerPreferredNamespacedResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) {all, err := ServerPreferredResources(d)return FilteredBy(ResourcePredicateFunc(func(groupVersion string, r *metav1.APIResource) bool {return r.Namespaced}), all), err
}// ServerVersion检索并解析服务器的版本(git版本)。
func (d *DiscoveryClient) ServerVersion() (*version.Info, error) {// 获取服务端的version 等效于kubectl versionbody, err := d.restClient.Get().AbsPath("/version").Do(context.TODO()).Raw()if err != nil {return nil, err}var info version.Infoerr = json.Unmarshal(body, &info)if err != nil {return nil, fmt.Errorf("unable to parse the server version: %v", err)}return &info, nil
}// OpenAPISchema使用rest客户端获取openAPIv2模式,并解析proto。
func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) {data, err := d.restClient.Get().AbsPath("/openapi/v2").SetHeader("Accept", openAPIV2mimePb).Do(context.TODO()).Raw()if err != nil {if errors.IsForbidden(err) || errors.IsNotFound(err) || errors.IsNotAcceptable(err) {// 未在旧服务器中找到注册的单个端点,请尝试获取旧端点// TODO:当kubectlclient不能与1.9服务器一起使用时删除此端点data, err = d.restClient.Get().AbsPath("/swagger-2.0.0.pb-v1").Do(context.TODO()).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
}
// 设置默认属性
func setDiscoveryDefaults(config *restclient.Config) error {config.APIPath = ""config.GroupVersion = nil// 如果Timeout == 0 设置为defaultTimeoutif config.Timeout == 0 {config.Timeout = defaultTimeout}// if a burst limit is not already configuredif config.Burst == 0 {// discovery预计是突发的,增加默认突发以适应查找许多API组的资源信息。// 匹配ConfigFlagsToDiscoveryClient()设置的突发。请参阅https://issue.k8s.io86149config.Burst = defaultBurst}// 用来编解码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 为给定的配置创建一个新的 DiscoveryClient。此客户端可用于发现 API 服务器中支持的资源。
func NewDiscoveryClientForConfig(c *restclient.Config) (*DiscoveryClient, error) {config := *cif err := setDiscoveryDefaults(&config); err != nil {return nil, err}// 调用rest包下的client.go中的UnversionedRESTClientFor生成restclienthttpClient, err := restclient.HTTPClientFor(&config)if err != nil {return nil, err}return NewDiscoveryClientForConfigAndClient(&config, httpClient)
}// 类似于NewDiscoveryClientForConfig ,区别是此方法如果出现err,则panic
func NewDiscoveryClientForConfigOrDie(c *restclient.Config) *DiscoveryClient {client, err := NewDiscoveryClientForConfig(c)if err != nil {panic(err)}return client}
接口及其结构体:
// ResourcePredicate 有一个方法来检查资源是否匹配给定条件
type ResourcePredicate interface {Match(groupVersion string, r *metav1.APIResource) bool
}// 如果ResourcePredicateFunc与基于自定义条件的资源匹配,则返回true。.
type ResourcePredicateFunc func(groupVersion string, r *metav1.APIResource) bool// Match 是 ResourcePredicateFunc 的包装器.
func (fn ResourcePredicateFunc) Match(groupVersion string, r *metav1.APIResource) bool {return fn(groupVersion, r)
}// supportsAllVerbs 是一个匹配资源的谓词,如果所有给定的动词都被支持,则匹配
type SupportsAllVerbs struct {Verbs []string
}// 匹配检查资源是否包含所有给定的动词。
func (p SupportsAllVerbs) Match(groupVersion string, r *metav1.APIResource) bool {return sets.NewString([]string(r.Verbs)...).HasAll(p.Verbs...)
}
函数
// MatchesServerVersion 查询服务器以将客户端的构建版本(git hash) 与服务器的构建版本进行比较。如果无法链接服务器或版本不完全匹配,则返回错误。
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", 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)", sVer, clientVersion)}return nil
}// 如果服务器没有所需的版本,ServerSupportsVersion 将返回错误
func ServerSupportsVersion(client DiscoveryInterface, requiredGV schema.GroupVersion) error {// 获取所有的groupsgroups, err := client.ServerGroups()if err != nil {// 几乎总是一个连接错误return err}// 提取groups中每项的groupversionversions := metav1.ExtractGroupVersions(groups)// 去重serverVersions := sets.String{}for _, v := range versions {serverVersions.Insert(v)}// 判断是否包含if serverVersions.Has(requiredGV.String()) {return nil}// 如果serverVersions的没有元素,那么可能是403 Forbidden errorsif len(serverVersions) == 0 {return nil}return fmt.Errorf("server does not support API version %q", requiredGV)
}// GroupVersionResources 将 APIResourceLists 转换为 GroupVersionResources,并作为map的key形成map返回。
func GroupVersionResources(rls []*metav1.APIResourceList) (map[schema.GroupVersionResource]struct{}, error) {gvrs := map[schema.GroupVersionResource]struct{}{}// 遍历for _, rl := range rls {// str的gv转化为GroupVersiongv, err := schema.ParseGroupVersion(rl.GroupVersion)if err != nil {return nil, err}// 遍历APIResourcesfor i := range rl.APIResources {// 形成gvr并插入map中gvrs[schema.GroupVersionResource{Group: gv.Group, Version: gv.Version, Resource: rl.APIResources[i].Name}] = struct{}{}}}return gvrs, nil
}// FilteredBy 按给定ResourcePredicate过滤
func FilteredBy(pred ResourcePredicate, rls []*metav1.APIResourceList) []*metav1.APIResourceList {result := []*metav1.APIResourceList{}for _, rl := range rls {filtered := *rlfiltered.APIResources = nilfor 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
}