discovery包
用于发现服务器支持的API 组、版本和资源的方法及服务端支持的swagger api
discovery_client.go
- discovery_client.go
- 定义全局变量
const ( // 如果动态获取resource失败,重试的次数 defaultRetries = 2 // protobuf mime 类型 mimePb = "application/com.github.proto-openapi.spec.v2@v1.0+protobuf" // defaultTimeout 是未在 RESTClient 上设置超时时每个请求的最长时间。默认为 32 秒,以便相对于存在的其他超时具有可区分的时间长度。 defaultTimeout = 32 * time.Second ) - 接口
// DiscoveryInterface 动态发现服务器支持的API groups,versions and resources. type DiscoveryInterface interface { RESTClient() restclient.Interface ServerGroupsInterface ServerResourcesInterface ServerVersionInterface OpenAPISchemaInterface } // CachedDiscoveryInterface 是一个具有缓存失效和刷新的 DiscoveryInterface。 // 注意:如果ServerResourcesForGroupVersion方法返回缓存未命中错误, // 用户需要显式调用Invalidate清除缓存,否则下次会返回同样的缓存未命中错误。 type CachedDiscoveryInterface interface { DiscoveryInterface // 如果在缓存未能找到,Fresh 应该告诉调用者是否重试(false = 重试,true = 不需要重试)。 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) // ServerResources 返回全部支持的资源。 // 注意: 即使在非 nil 错误的情况下,返回的资源列表也可能是非 nil 和部分结果。 // 不推荐使用:改用 ServerGroupsAndResources。 ServerResources() ([]*metav1.APIResourceList, error) // ServerGroupsAndResources 返回所有组和版本支持的组和资源。 // 注意:返回的组和资源列表可能是非 nil 的,即使在非 nil 错误的情况下也是如此。 ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) // ServerPreferredResources 返回具有服务器首选版本的受支持资源。 // 注意: 即使在非 nil 错误的情况下,返回的资源列表也可能是非 nil 和部分结果。 ServerPreferredResources() ([]*metav1.APIResourceList, error) // ServerPreferredNamespacedResources 返回受支持命名空间的服务器首选的版本资源。 // 注意: 即使在非 nil 错误的情况下,返回的资源列表也可能是非 nil 和部分结果。 ServerPreferredNamespacedResources() ([]*metav1.APIResourceList, error) } // ServerVersionInterface 有一个检索服务器版本的方法。 type ServerVersionInterface interface { // ServerVersion 检索并解析服务器的版本(git 版本)。 ServerVersion() (*version.Info, error) } // OpenAPISchemaInterface 有一个方法来检索开放的 API 架构。 type OpenAPISchemaInterface interface { // OpenAPISchema 检索并解析服务器支持的 swagger API 模式。 OpenAPISchema() (*openapi_v2.Document, error) } - 关于DiscoveryClient执行内部方法的函数
// fetchServerResourcesForGroupVersions 使用DiscoveryClient并行获取指定组列表的资源 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()) resultLock.Lock() defer resultLock.Unlock() if err != nil { failedGroups[groupVersion] = err } if apiResourceList != nil { // 即使出现错误,也可能会返回一些返回值 groupVersionResources[groupVersion] = apiResourceList } }() } } wg.Wait() return groupVersionResources, failedGroups } // ServerResources 使用提供的发现接口来查找所有组和版本支持的资源 // 不推荐使用:改用 ServerGroupsAndResources。 func ServerResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) { _, rs, err := ServerGroupsAndResources(d) return rs, err } func ServerGroupsAndResources(d DiscoveryInterface) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { // 获取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]) } // 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 使用提供的DiscoveryClient来查找首选资源 func ServerPreferredResources(d DiscoveryInterface) ([]*metav1.APIResourceList, error) { // 获取apiGroupList serverGroupList, err := d.ServerGroups() if err != nil { return nil, err } // DiscoveryClient并行获取指定组列表的资源 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 { // 仅覆盖首选版本,否则舍弃 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 使用提供的DiscoveryClient来查找首选是命名空间资源 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 重试执行次数给定的恢复函数,直到没有err。方法体省略 func withRetries(maxRetries int, f func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error)) ([]*metav1.APIGroup, []*metav1.APIResourceList, error) - DiscoveryClient结构体
// DiscoveryClient 实现了发现服务器支持的 API 组、版本和资源的功能。 type DiscoveryClient struct { // restClient http.client的封装 restClient restclient.Interface // api path的前缀 LegacyPrefix string } // 将 metav1.APIVersions 转换为 metav1.APIGroup。其实是把入参APIVersions中每项的GroupVersion替换为version,相当于group是 ""。 // 比较简单,不做具体代码展示,可以自行下载源码查看 func apiVersionsToAPIGroup(apiVersions *metav1.APIVersions) (apiGroup metav1.APIGroup) // 实现ServerGroupsInterface接口 func (d *DiscoveryClient) ServerGroups() (apiGroupList *metav1.APIGroupList, err error) { // 获取在 /api (d.LegacyPrefix = /api,k8s下core group)下公开的 groupVersions v := &metav1.APIVersions{} err = d.restClient.Get().AbsPath(d.LegacyPrefix).Do(context.TODO()).Into(v) apiGroup := metav1.APIGroup{} if err == nil && len(v.Versions) != 0 { // 从这里可以看出,旧版k8s中gvk中g为"" apiGroup = apiVersionsToAPIGroup(v) } if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) { return nil, err } // 获取在 /apis 下公开的 groupVersions,具有分组概念的 apiGroupList = &metav1.APIGroupList{} err = d.restClient.Get().AbsPath("/apis").Do(context.TODO()).Into(apiGroupList) if err != nil && !errors.IsNotFound(err) && !errors.IsForbidden(err) { return nil, err } // 与 v1.0 服务器兼容,如果是 403 或 404,忽略并返回我们从 /api 获得的任何内容 if err != nil && (errors.IsNotFound(err) || errors.IsForbidden(err)) { apiGroupList = &metav1.APIGroupList{} } // 如果不为空,则将从 /api 检索到的组添加到列表中 if len(v.Versions) != 0 { apiGroupList.Groups = append([]metav1.APIGroup{apiGroup}, apiGroupList.Groups...) } return apiGroupList, 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 { if groupVersion == "v1" && (errors.IsNotFound(err) || errors.IsForbidden(err)) { return resources, nil } return nil, err } return resources, nil } // 实现了ServerResourcesInterface的ServerResources, 其实内部调用的是ServerGroupsAndResources func (d *DiscoveryClient) ServerResources() ([]*metav1.APIResourceList, error) // 实现了ServerResourcesInterface的ServerGroupsAndResources func (d *DiscoveryClient) ServerGroupsAndResources() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { return withRetries(defaultRetries, func() ([]*metav1.APIGroup, []*metav1.APIResourceList, error) { // 调用上面函数部分的ServerGroupsAndResources 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) } //ServerVersion 检索并解析服务器的版本(git 版本)。等效于kubectl version 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 客户端获取开放 api 模式并解析原型。 func (d *DiscoveryClient) OpenAPISchema() (*openapi_v2.Document, error) - 关于获取DiscoveryClient的函数
// 设置默认属性 func setDiscoveryDefaults(config *restclient.Config) error { config.APIPath = "" // 清空APIPath config.GroupVersion = nil // 清空GroupVersion // 如果Timeout == 0 设置为defaultTimeout if config.Timeout == 0 { config.Timeout = defaultTimeout } if config.Burst == 0 && config.QPS < 100 { config.Burst = 100 } // 用来编解码 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 client, err := restclient.UnversionedRESTClientFor(&config) return &DiscoveryClient{restClient: client, LegacyPrefix: "/api"}, err } // 类似于NewDiscoveryClientForConfig ,区别是此方法如果出现err,则panic func NewDiscoveryClientForConfigOrDie(c *restclient.Config) *DiscoveryClient
- 定义全局变量
helper.go
- helper.go
- 接口及结构体
// ResourcePredicate 有一个方法来检查资源是否匹配给定条件 type ResourcePredicate interface { Match(groupVersion string, r *metav1.APIResource) bool } // 函数类型ResourcePredicateFunc 类似于ResourcePredicate 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) } // upportsAllVerbs 是一个匹配资源的谓词,如果所有给定的动词都被支持,则匹配 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 }
- 接口及结构体