client-go之restmapper包源码分析

571 阅读11分钟

@TOC

restmapper包

获取实现了RESTMapper(对应一个gv下的resources)接口的结构体

discovery.go

  • 结构体
    // APIGroupResources 是一个 API 组,具有版本到资源的映射。
    type APIGroupResources struct {
      // kubernetes/apiachinery下的APIGroup结构体,包含了group下的所有verisons
    	Group metav1.APIGroup
    	// 一个map,key:gv的字符串形式  value: kubernetes/apiachinery下的APIResource结构体(包含了group verison resource等信息)
    	VersionedResources map[string][]metav1.APIResource
    }
    
    // DeferredDiscoveryRESTMapper 是一个 RESTMapper,它将推迟RESTMapper 的初始化,直到第一次请求映射。
    type DeferredDiscoveryRESTMapper struct {
    	initMu   sync.Mutex
    	delegate meta.RESTMapper
    	cl       discovery.CachedDiscoveryInterface
    }
    
    // 获取代理(RESTMapper接口的实现类)
    func (d *DeferredDiscoveryRESTMapper) getDelegate() (meta.RESTMapper, error) {
    	d.initMu.Lock()
    	defer d.initMu.Unlock()
      // 如果delegate不为空,则返回
    	if d.delegate != nil {
    		return d.delegate, nil
    	}
      // 到这里说明DeferredDiscoveryRESTMapper内部还没有delegate(RESTMapper接口的实现类)
      // 上面讲到了该方法,使用discovery client来获取GroupResources
    	groupResources, err := GetAPIGroupResources(d.cl)
    	if err != nil {
    		return nil, err
    	}
      // 上面讲到了该方法,使用GroupResources构建一个PriorityRESTMapper
    	d.delegate = NewDiscoveryRESTMapper(groupResources)
    	return d.delegate, err
    }
    
    // 重置内部缓存的delegate(RESTMapper接口的实现类),将导致下一次映射请求重新发现(重新获取delegate)。
    func (d *DeferredDiscoveryRESTMapper) Reset() {
    	klog.V(5).Info("Invalidating discovery information")
    
    	d.initMu.Lock()
    	defer d.initMu.Unlock()
      // 强制使discovery client中缓存(缓存的gvr)的失效
    	d.cl.Invalidate()
      // 重置RESTMapper中的delegate为空
    	d.delegate = nil
    }
    
    // KindFor 获取部分资源并返回单个匹配项。 如果有多个匹配项,则返回错误。
    func (d *DeferredDiscoveryRESTMapper) KindFor(resource schema.GroupVersionResource) (gvk schema.GroupVersionKind, err error) {
      // 获取代理restMapper
    	del, err := d.getDelegate()
      // 如果err不为空,则表示有异常,返回
    	if err != nil {
    		return schema.GroupVersionKind{}, err
    	}
      // 使用代理restMapper(后续系列文章中会在分析apimachinery模块分析),通过gvr获取gvk
    	gvk, err = del.KindFor(resource)
      // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取KindFor)
    	if err != nil && !d.cl.Fresh() {
          // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil
    		d.Reset()
          // 循环执行
    		gvk, err = d.KindFor(resource)
    	}
    	return
    }
    
    // KindsFor 获取参数资源对应的所有按优先级顺序排列的gvk列表。
    func (d *DeferredDiscoveryRESTMapper) KindsFor(resource schema.GroupVersionResource) (gvks []schema.GroupVersionKind, err error) {
      // 获取代理restMapper
    	del, err := d.getDelegate()
    	if err != nil {
    		return nil, err
    	}
      // 使用代理restMapper(后续系列文章中会在分析apimachinery模块分析),通过gvr获取gvk的slice
    	gvks, err = del.KindsFor(resource)
      // 如果gvks长度为0且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取KindsFor)
    	if len(gvks) == 0 && !d.cl.Fresh() {
          // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil
    		d.Reset()
          // 循环执行
    		gvks, err = d.KindsFor(resource)
    	}
    	return
    }
    
    // ResourceFor 获取gvr对应匹配的单个gvr。如果有多个匹配项,则返回错误。
    func (d *DeferredDiscoveryRESTMapper) ResourceFor(input schema.GroupVersionResource) (gvr schema.GroupVersionResource, err error) {
      // 获取代理restMapper
    	del, err := d.getDelegate()
    	if err != nil {
    		return schema.GroupVersionResource{}, err
    	}
      // 使用代理restMapper(后续系列文章中会在分析apimachinery模块分析),通过gvr获取mapper中唯一的一个gvr
    	gvr, err = del.ResourceFor(input)
      // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取ResourceFor)
    	if err != nil && !d.cl.Fresh() {
          // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil
    		d.Reset()
          // 循环执行
    		gvr, err = d.ResourceFor(input)
    	}
    	return
    }
    
    // 类似于KindsFor,不具体分析
    func (d *DeferredDiscoveryRESTMapper) ResourcesFor(input schema.GroupVersionResource) (gvrs []schema.GroupVersionResource, err error) 
    
    // RESTMapping 为提供的组类型识别出首选资源映射。
    func (d *DeferredDiscoveryRESTMapper) RESTMapping(gk schema.GroupKind, versions ...string) (m *meta.RESTMapping, err error) {
      // 获取代理restMapper
    	del, err := d.getDelegate()
    	if err != nil {
    		return nil, err
    	}
      // 识别出特定的gk,不同版本的唯一一个首选restmapping(包含了gvk gvr scope)
    	m, err = del.RESTMapping(gk, versions...)
      // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取RESTMapping)
    	if err != nil && !d.cl.Fresh() {
          // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil
    		d.Reset()
          // 循环执行
    		m, err = d.RESTMapping(gk, versions...)
    	}
    	return
    }
    
    // RESTMappings 以粗略的内部首选顺序返回所提供组种类的 RESTMappings。如果没有找到种类,它将返回一个 NoResourceMatchError。
    func (d *DeferredDiscoveryRESTMapper) RESTMappings(gk schema.GroupKind, versions ...string) (ms []*meta.RESTMapping, err error) 
    
    // ResourceSingularizer 将资源名称从复数转换为单数(例如,从 pods 到 pod)。
    func (d *DeferredDiscoveryRESTMapper) ResourceSingularizer(resource string) (singular string, err error) {
      // 获取代理restMapper
    	del, err := d.getDelegate()
    	if err != nil {
    		return resource, err
    	}
      // 代理restmapper转化资源名称从复数转换为单数
    	singular, err = del.ResourceSingularizer(resource)
      // 如果err不为空且discovery client中的已更新表示为false(表明需要重置该延迟restMapper(DeferredDiscoveryRESTMapper)且重新获取ResourceSingularizer)
    	if err != nil && !d.cl.Fresh() {
          // 上面有做分析,主要是强制discovery client中的缓存失效,且重置d中的代理restmapper为nil
    		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)
    }
    
  • 函数
    // NewDiscoveryRESTMapper 根据gvrs返回 PriorityRESTMapper(gvk --  resource)。
    func NewDiscoveryRESTMapper(groupResources []*APIGroupResources) meta.RESTMapper {
      // 其实是一个RESTMapper数组
    	unionMapper := meta.MultiRESTMapper{}
      // 保存Priority(优先,注意其和preferred(首选)的区别,前者包含后者,前者表示apiserver可以识别的,后者是唯一的(前者顺序(这里的顺序是程序添加到scheme的versionPriority和observedVersions先后顺序)排列的第一个))的group
    	var groupPriority []string
    	// /v1 是特殊的。它应该永远排在第一位,因为他是core api
    	resourcePriority := []schema.GroupVersionResource{{Group: "", Version: "v1", Resource: meta.AnyResource}}
    	kindPriority := []schema.GroupVersionKind{{Group: "", Version: "v1", Kind: meta.AnyKind}}
      // 遍历参数groupResources
    	for _, group := range groupResources {
          // 追加group的name到优先group
    		groupPriority = append(groupPriority, group.Group.Name)
    
    		// 首先确保Preferred(首选)版本在Priority(优先)的最前面
    		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,
    				})
    			}
    		}
    
          // 遍历group下的所有versions
    		for _, discoveryVersion := range group.Group.Versions {
              // 获取map下key:discoveryVersion.Version的value(resource)
    			resources, ok := group.VersionedResources[discoveryVersion.Version]
              // 不存在,则下个循环
    			if !ok {
    				continue
    			}
    
    			// 过滤掉首选版本(以为上面已经添加过了)
    			if discoveryVersion.Version != group.Group.PreferredVersion.Version {
                  // 到这里,就是费首选版本的资源和kind的添加
    				resourcePriority = append(resourcePriority, schema.GroupVersionResource{
    					Group:    group.Group.Name,
    					Version:  discoveryVersion.Version,
    					Resource: meta.AnyResource,
    				})
    
    				kindPriority = append(kindPriority, schema.GroupVersionKind{
    					Group:   group.Group.Name,
    					Version: discoveryVersion.Version,
    					Kind:    meta.AnyKind,
    				})
    			}
              // 两层for循环获取的gv
    			gv := schema.GroupVersion{Group: group.Group.Name, Version: discoveryVersion.Version}
              // kubernetes/apimachinery的NewDefaultRESTMapper,获取空的restmapper
    			versionMapper := meta.NewDefaultRESTMapper([]schema.GroupVersion{gv})
              // 遍历上面在gv下获取的resources
    			for _, resource := range resources {
                  // 定义resource对的作用范围(默认是namespace)
    				scope := meta.RESTScopeNamespace
                  // 判断gv下的resource中Namespaced属性值
    				if !resource.Namespaced {
                      // 如果不是命名空间级别的,则scope是 root级别(也就是集群级别)
    					scope = meta.RESTScopeRoot
    				}
    
    				// 如果resource的名称中有斜杠,那么这是一个子资源,我们不应该为它们创建restmapper。
    				if strings.Contains(resource.Name, "/") {
    					continue
    				}
                  //复数和单数名称 
    				plural := gv.WithResource(resource.Name)
    				singular := gv.WithResource(resource.SingularName)
    				// 针对未列出单数形式和服务器遗留资源(core)。对于这些我们就需要猜测其单数的名称。
    				if len(resource.SingularName) == 0 {
                      // 获取一个猜测(定义好的逻辑)的单数形式
    					_, singular = meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind))
    				}
                  // 通过参数添加对应具体的versionMapper属性
    				versionMapper.AddSpecific(gv.WithKind(strings.ToLower(resource.Kind)), plural, singular, scope)
    				versionMapper.AddSpecific(gv.WithKind(resource.Kind), plural, singular, scope)
    				// TODO 这会产生实际上不起作用的不安全猜测,但它与以前的行为相匹配,可以看下源码中直接调用了meta.UnsafeGuessKindToResource(gv.WithKind(resource.Kind))
    				versionMapper.Add(gv.WithKind(resource.Kind+"List"), scope)
    			}
    			// 对于每个gv的mapper都会添加一个Kind为List(做什么使用?)
    			versionMapper.Add(gv.WithKind("List"), meta.RESTScopeRoot)
              // 追加到聚合mapper中
    			unionMapper = append(unionMapper, versionMapper)
    		}
    	}
      // 遍历全部可用的group slice
    	for _, group := range groupPriority {
          // 这里是Version和Resource都设置为*,为了match任意resource
    		resourcePriority = append(resourcePriority, schema.GroupVersionResource{
    			Group:    group,
    			Version:  meta.AnyVersion,
    			Resource: meta.AnyResource,
    		})
          // 这里是Version和Kind都设置为*,为了match任意Kind
    		kindPriority = append(kindPriority, schema.GroupVersionKind{
    			Group:   group,
    			Version: meta.AnyVersion,
    			Kind:    meta.AnyKind,
    		})
    	}
    
    	return meta.PriorityRESTMapper{
    		Delegate:         unionMapper, // 用来匹配任意的gvr/gvk
    		ResourcePriority: resourcePriority, // 在先使用Delegate match gvr后,如果出现匹配了多个gvr的情况,在使用resourcePriority循环匹配多个gvr的唯一的gvr
    		KindPriority:     kindPriority, // 在先使用Delegate match gvk后,如果出现匹配了多个gvk的情况,在使用resourcePriority循环匹配多个gvk的唯一的gvk
    	}
    }
    
    // GetAPIGroupResources 使用提供的discovery client 动态来收集gr信息并返回APIGroupResources slice。
    func GetAPIGroupResources(cl discovery.DiscoveryInterface) ([]*APIGroupResources, error) {
      // 在本系列文章中discovery中已做分析。主要是用于动态发现ApiGroups和ApiGroupResources
    	gs, rs, err := cl.ServerGroupsAndResources()
    	if rs == nil || gs == nil {
    		return nil, err
    	}
      // 保存key:groupversion value: apiresourceList
    	rsm := map[string]*metav1.APIResourceList{}
    	for _, r := range rs {
    		rsm[r.GroupVersion] = r
    	}
    
    	var result []*APIGroupResources
      // 循环遍历groupList,
    	for _, group := range gs {
          // 每个group初始化一个groupResources
    		groupResources := &APIGroupResources{
    			Group:              *group,
    			VersionedResources: make(map[string][]metav1.APIResource),
    		}
          // 根据group对应的versions,循环遍历
    		for _, version := range group.Versions {
              // 获取上面map(key:groupversion value: apiresourceList),是否存在对应的key
    			resources, ok := rsm[version.GroupVersion]
    			if !ok {
    				continue
    			}
              // 填充groupResources对应的VersionedResources
    			groupResources.VersionedResources[version.Version] = resources.APIResources
    		}
    		result = append(result, groupResources)
    	}
    	return result, nil
    }
    
    // NewDeferredDiscoveryRESTMapper 返回一个DeferredDiscoveryRESTMapper,它将使用提供的客户端延迟查询来获取发现信息以进行 REST 映射。
    func NewDeferredDiscoveryRESTMapper(cl discovery.CachedDiscoveryInterface) *DeferredDiscoveryRESTMapper {
    	return &DeferredDiscoveryRESTMapper{
    		cl: cl,
    	}
    }
    

shortcut.go

  • 结构体
    // shortcutExpander 是一个可用于 Kubernetes 资源的 RESTMapper.  它首先扩展资源(调用discovery client获取apiresources和shortcutResoure(却别与其他动态restmapper的地方)),然后调用 wrapped(代理restmapper)
    type shortcutExpander struct {
    	RESTMapper meta.RESTMapper
    
    	discoveryClient discovery.DiscoveryInterface
    }
    
    // getShortcutMappings 返回一组包含资源短名称的元组.
    // 首先,将从 API 服务器中获取潜在资源列表。
    // 接下来,我们将附加硬编码的资源列表 - 以向后兼容旧服务器。
    func (e shortcutExpander) getShortcutMappings() ([]*metav1.APIResourceList, []resourceShortcuts, error) {
    	res := []resourceShortcuts{}
    	// 获取服务器资源
    	_, apiResList, err := e.discoveryClient.ServerGroupsAndResources()
    	if err != nil {
    		klog.V(1).Infof("Error loading discovery information: %v", err)
    	}
      // 遍历获取的资源列表
    	for _, apiResources := range apiResList {
          // 解析gv
    		gv, err := schema.ParseGroupVersion(apiResources.GroupVersion)
    		if err != nil {
    			klog.V(1).Infof("Unable to parse groupversion = %s due to = %s", apiResources.GroupVersion, err.Error())
    			continue
    		}
          // 遍历该gv下的resource列表
    		for _, apiRes := range apiResources.APIResources {
              // 遍历该gvr的所有shortnames
    			for _, shortName := range apiRes.ShortNames {
                  // 组建resourceShortcuts,并追加到res中
    				rs := resourceShortcuts{
    					ShortForm: schema.GroupResource{Group: gv.Group, Resource: shortName},
    					LongForm:  schema.GroupResource{Group: gv.Group, Resource: apiRes.Name},
    				}
    				res = append(res, rs)
    			}
    		}
    	}
    
    	return apiResList, res, nil
    }
    
    // expandResourceShortcut 如果resource确实是一个快捷方式,将返回资源的扩展版本(pkg/api/meta.RESTMapper 可以理解的内容)。 
    // 如果未找到匹配项,我们将匹配组前缀。
    func (e shortcutExpander) expandResourceShortcut(resource schema.GroupVersionResource) schema.GroupVersionResource {
      // 获取apiserver的资源列表和快捷资源(包含了long和shortcut resource name)
    	if allResources, shortcutResources, err := e.getShortcutMappings(); err == nil {
    		// for循环中如果与完整资源名称完全匹配,则避免扩展(就是尝试匹配shortcut resource name)
    		for _, apiResources := range allResources {
    			gv, err := schema.ParseGroupVersion(apiResources.GroupVersion)
    			if err != nil {
    				continue
    			}
              // 如果resource参数的group不为nil且其不等于gv的group,表明不匹配
    			if len(resource.Group) != 0 && resource.Group != gv.Group {
    				continue
    			}
              // 遍历资源列表中的资源slice
    			for _, apiRes := range apiResources.APIResources {
                  // 如果参数resource的Resource和遍历item的Name相等,表明匹配
    				if resource.Resource == apiRes.Name {
    					return resource
    				}
                  // 如果参数resource的Resource和遍历item的SingularName(单数形式)相等,表明匹配
    				if resource.Resource == apiRes.SingularName {
    					return resource
    				}
    			}
    		}
    
          // for循环中如果与short资源名称完全匹配,则返回
    		for _, item := range shortcutResources {
              // 如果resource参数的group不为nil且其不等于shortcutResources的ShortForm的group,表明不匹配
    			if len(resource.Group) != 0 && resource.Group != item.ShortForm.Group {
    				continue
    			}
              // 如果参数resource的Resource和遍历item的ShortForm.Resource相等,表明匹配
    			if resource.Resource == item.ShortForm.Resource {
    				resource.Resource = item.LongForm.Resource
    				resource.Group = item.LongForm.Group
    				return resource
    			}
    		}
    
    		// 我们没有找到完全匹配的匹配项。并且resource的Group为空(表明是core api),那么返回参数resource(因为core api没有所谓的shortname,只有自定义资源有)
    		if len(resource.Group) == 0 {
    			return resource
    		}
          // 遍历shortcutResources(apiserver自定义资源resource list中定义的shortname集合)
    		for _, item := range shortcutResources {
              // 判断shortcutResources中item的shortform的Group是否是resource group的前缀
    			if !strings.HasPrefix(item.ShortForm.Group, resource.Group) {
    				continue
    			}
              // 如果参数resource的Resource和遍历item的ShortForm.Resource相等,表明匹配
    			if resource.Resource == item.ShortForm.Resource {
    				resource.Resource = item.LongForm.Resource
    				resource.Group = item.LongForm.Group
    				return resource
    			}
    		}
    	}
    
    	return resource
    }
    
    // 实现了 meta.RESTMapper的KindFor 方法,其他的类似
    func (e shortcutExpander) KindFor(resource schema.GroupVersionResource) (schema.GroupVersionKind, error) {
      // 先调用expandResourceShortcut方法(本文上面已分析)获取gvr,然后调用restmapper的kindFor方法
    	return e.RESTMapper.KindFor(e.expandResourceShortcut(resource))
    }
    
    // ResourceShortcuts 表示一个结构,其中包含如何从资源的快捷方式转换为其全名的信息。
    type resourceShortcuts struct {
    	ShortForm schema.GroupResource
    	LongForm  schema.GroupResource
    }
    
    
  • 函数
    // NewShortcutExpander 将一个 restmapper 包装在一个层(shortcutExpander)中,该层扩展了通过discovery client发现的快捷方式
    func NewShortcutExpander(delegate meta.RESTMapper, client discovery.DiscoveryInterface) meta.RESTMapper {
    	return shortcutExpander{RESTMapper: delegate, discoveryClient: client}
    }
    

category_expansion.go

  • 接口
    // CategoryExpander 将类别字符串映射到 GroupResource。
    // Categories 是一组资源的分类或“标签”。
    type CategoryExpander interface {
    	Expand(category string) ([]schema.GroupResource, bool)
    }
    
  • 结构体
    // SimpleCategoryExpander 使用categories到 GroupResource 映射的静态映射来实现 CategoryExpander 接口.
    type SimpleCategoryExpander struct {
    	Expansions map[string][]schema.GroupResource
    }
    
    // 实现CategoryExpander接口
    func (e SimpleCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
    	ret, ok := e.Expansions[category]
    	return ret, ok
    }
    
    // discoveryCategoryExpander 结构让 REST 客户端包装 (discoveryClient) 来检索 APIResourceList 列表, 然后转换为 fallbackExpander
    type discoveryCategoryExpander struct {
    	discoveryClient discovery.DiscoveryInterface
    }
    
    // 实现CategoryExpander接口
    func (e discoveryCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
    	// 从服务器获取所有支持的组和版本资源.
    	_, apiResourceLists, _ := e.discoveryClient.ServerGroupsAndResources()
      // 如果没有找到资源,返回
    	if len(apiResourceLists) == 0 {
    		return nil, false
    	}
      
      // 保存获取的map(key:category value: []schema.GroupResource)
    	discoveredExpansions := map[string][]schema.GroupResource{}
      // 遍历从apiserver获取的apiResourceLists
    	for _, apiResourceList := range apiResourceLists {
          // 解析gv
    		gv, err := schema.ParseGroupVersion(apiResourceList.GroupVersion)
    		if err != nil {
    			continue
    		}
    		// 按类别收集 GroupResources
    		for _, apiResource := range apiResourceList.APIResources {
              // 获取apiResource的Categories
    			if categories := apiResource.Categories; len(categories) > 0 {
                  // 遍历 并追加到discoveredExpansions
    				for _, category := range categories {
    					groupResource := schema.GroupResource{
    						Group:    gv.Group,
    						Resource: apiResource.Name,
    					}
    					discoveredExpansions[category] = append(discoveredExpansions[category], groupResource)
    				}
    			}
    		}
    	}
      // 从discoveredExpansions中获取参数category对应的[]schema.GroupResource
    	ret, ok := discoveredExpansions[category]
    	return ret, ok
    }
    
    // UnionCategoryExpander 实现了 CategoryExpander 接口.
    // 它将给定的类别字符串映射到列表中所有 CategoryExpander 返回的扩展的联合。
    type UnionCategoryExpander []CategoryExpander
    
    // 实现CategoryExpander的Expand方法
    func (u UnionCategoryExpander) Expand(category string) ([]schema.GroupResource, bool) {
      // 定义返回变量
    	ret := []schema.GroupResource{}
    	ok := false
    
    	// 为列表中的每个 CategoryExpander 展开类别并合并/组合结果。
    	for _, expansion := range u {
    		curr, currOk := expansion.Expand(category)
    
    		for _, currGR := range curr {
    			found := false
    			for _, existing := range ret {
    				if existing == currGR {
    					found = true
    					break
    				}
    			}
    			if !found {
    				ret = append(ret, currGR)
    			}
    		}
    		ok = ok || currOk
    	}
    
    	return ret, ok
    }
    
  • 函数
    // NewDiscoveryCategoryExpander 返回一个类别扩展器,它利用 API 中的“类别”字段(discovery clinet 请求apiserver获取),通过发现客户端找到。
    func NewDiscoveryCategoryExpander(client discovery.DiscoveryInterface) CategoryExpander {
    	if client == nil {
    		panic("Please provide discovery client to shortcut expander")
    	}
    	return discoveryCategoryExpander{discoveryClient: client}
    }