zl程序教程

您现在的位置是:首页 >  其他

当前栏目

client-go discovery包源码解析

2023-04-18 16:47:26 时间

1. 概述

discovery包主要用来发现服务器支持的API组、版本和资源的方法,及服务端支持的swagger api
代码示例:

package main

import (
	"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)
		}
	}

}

2. 源码解读

2.1 discovery_client.go

  • 定义全局变量:
const (
	// defaultRetries指动态获取resource失败,重试的次数(例如CustomResourceDefinitions
	defaultRetries = 2
	// protobuf mime 类型
	openAPIV2mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf"

	// defaultTimeout是在RESTClient上未设置超时时每个请求的最大时间。默认为32秒,以便相对于其他存在的超时具有可区分的时间长度。	defaultTimeout = 32 * time.Second
	defaultTimeout = 32 * time.Second

	// defaultBurst是发现客户端的令牌桶速率限制的使用的默认burst
 	defaultBurst = 300

	AcceptV1 = 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.Interface
	ServerGroupsInterface
	ServerResourcesInterface
	ServerVersionInterface
	OpenAPISchemaInterface
	OpenAPIV3SchemaInterface
	// 返回仅接收旧版发现格式的当前发现客户端的副本,如果当前发现客户端不支持仅旧版发现,则返回指向该客户端的指针。
	WithLegacy() DiscoveryInterface
}


// AggregatedDiscoveryInterface扩展了DiscoveryInterface,使其包括一个可能返回发现资源和发现组的方法,这就是较新的聚合发现格式(APIGroupDiscoveryList)的作用。
type AggregatedDiscoveryInterface interface {
	DiscoveryInterface

	GroupsAndMaybeResources() (*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)
}
 

  • Discovery执行内部方法的函数:
    // 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中的versions
    		for _, 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具体实现的ServerResourcesForGroupVersion
    				apiResourceList, err := d.ServerResourcesForGroupVersion(groupVersion.String())
    
    				// lock to record results
    				resultLock.Lock()
    				defer resultLock.Unlock()
    
    				if err != nil {
    					// TODO: maybe restrict this to NotFound errors
    					failedGroups[groupVersion] = err
    				}
    				if apiResourceList != nil {
    					// 执行DiscoveryClient具体实现的ServerResourcesForGroupVersion
    					groupVersionResources[groupVersion] = apiResourceList
    				}
    			}()
    		}
    	}
    	wg.Wait()
    
    	return groupVersionResources, failedGroups
    }
    
    
    func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) {
    	var sgs *metav1.APIGroupList
    	var resources []*metav1.APIResourceList
    	var err error
    
    	// 如果传递的discover对象实现了AggregatedDiscoveryInterface,则尝试使用组和资源检索聚合发现。
    	if ad, ok := d.(AggregatedDiscoveryInterface); ok {
    		var resourcesByGV map[schema.GroupVersion]*metav1.APIResourceList
    		sgs, resourcesByGV, err = ad.GroupsAndMaybeResources()
    		for _, resourceList := range resourcesByGV {
    			resources = append(resources, resourceList)
    		}
    	} else {
    		// 获取apiGroupList
    		sgs, 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.APIGroupList
    	var failedGroups map[schema.GroupVersion]error
    	var groupVersionResources map[schema.GroupVersion]*metav1.APIResourceList
    	var 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 的选定APIResource
    	gvAPIResourceLists := 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] = &emptyAPIResourceList
    			result = append(result, &emptyAPIResourceList)
              		  // 遍历上面获取apiResourceList的APIResources
    
    			for i := range apiResourceList.APIResources {
    				apiResource := &apiResourceList.APIResources[i]
    				// 判断apiResource是否包含/,因为如果包含,则是子资源,所以舍弃
    				if strings.Contains(apiResource.Name, "/") {
    					continue
    				}
    				// 形成gv
    				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
    			}
    		}
    	}
    
    	// 根据 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) {
     	// 获取全部的PreferredResources
    	all, err := ServerPreferredResources(d)
    	// 调用helper.go的FilteredBy
    	return 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.APIResourceList
    	var resultGroups []*metav1.APIGroup
    	var err error
    	for 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
    }
    
    
    
  • DiscoveryClient实现了discover server支持的API组、版本和资源的方法
    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中应该只返回一个groupVersion
    	apiGroup.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 := acceptDiscoveryFormats
    	if d.UseLegacyDiscovery {
    		accept = AcceptV1
    	}
    	var responseContentType string
    	body, 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.APIVersions
    		err = 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.APIGroupDiscoveryList
    		err = 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 := acceptDiscoveryFormats
    	if d.UseLegacyDiscovery {
    		accept = AcceptV1
    	}
    	var responseContentType string
    	body, 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.APIGroupDiscoveryList
    		err = 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) {
    	 // 调用上面函数部分的ServerPreferredResources
    		rs, 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 version
    	body, err := d.restClient.Get().AbsPath("/version").Do(context.TODO()).Raw()
    	if err != nil {
    		return nil, err
    	}
    	var info version.Info
    	err = 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 设置为defaultTimeout
	if config.Timeout == 0 {
		config.Timeout = defaultTimeout
	}
	// if a burst limit is not already configured
	if config.Burst == 0 {
		// discovery预计是突发的,增加默认突发以适应查找许多API组的资源信息。
		// 匹配ConfigFlagsToDiscoveryClient()设置的突发。请参阅https://issue.k8s.io86149
		config.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 := *c
	if err := setDiscoveryDefaults(&config); err != nil {
		return nil, err
	}
	//  调用rest包下的client.go中的UnversionedRESTClientFor生成restclient
	httpClient, 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

}

2.2 helper.go

在这里插入图片描述

接口及其结构体:

// 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 {
	// 获取所有的groups
	groups, err := client.ServerGroups()
	if err != nil {
		// 几乎总是一个连接错误
		return err
	}
	// 提取groups中每项的groupversion
	versions := metav1.ExtractGroupVersions(groups)
	 // 去重
	serverVersions := sets.String{}
	for _, v := range versions {
		serverVersions.Insert(v)
	}
	// 判断是否包含
	if serverVersions.Has(requiredGV.String()) {
		return nil
	}

	// 如果serverVersions的没有元素,那么可能是403 Forbidden errors
	if 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转化为GroupVersion
		gv, err := schema.ParseGroupVersion(rl.GroupVersion)
		if err != nil {
			return nil, err
		}
        // 遍历APIResources
		for 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 := *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
}