Get

Get #

sequenceDiagram
  participant cmd as cmd.go
  participant get as get.go
  participant f as factoryImpl
  Note right of f: kubectl/pkg/cmd/util/factory_client_access.go

  cmd-->>+f: NewFactory()
  f-->>-cmd: Factory

  get-->>+f: NewBuilder()
  f-->>-get: *resource.Builder
  Note right of get: cli-runtime/pkg/resource/builder.go

NewFactory: factoryImpl #

NewFactory() 返回的 Factory 接口主要蕴含两个函数:

  • RESTClient: ()->*restclient.RESTClient:RestClient 接口包含的函数即是 Get/Post 等 HTTP 原语。
  • NewBuilder: ()->*resource.Builder:Builder 属于 cli-runtime 的核心库,处理命令行参数,转化成实际的 Resource 列表。

其实现位于 kubectl/pkg/cmd/util/factory_client_access.gofactoryImpl

RESTClientGetter #

factoryImpl 还实现了 RESTClientGetter 接口,这个接口主要包含了两个函数:

  • ToDiscoveryClient: ()->discovery.CachedDiscoveryInterface:CachedDiscoveryInterface 接口是带缓存的 DiscoveryInterface 的包装,用于发现 Server 支持的 API group、version 和 resource。
  • ToRESTMapper: ()->meta.RESTMapper:RESTMapper 的主要实现是位于 apimachinery/pkg/api/meta/priority.go 的 PriorityRESTMapper,以及 client-go 对其的封装:位于 client-go/restmapper/discovery.goDeferredDiscoveryRESTMapper。RESTMapper 可以将 resource 映射到 kind,也可以反之。

这两个函数实际上是一个调用 NewFactory 时传递进来的 clientGetter 的代理:

func NewFactory(clientGetter genericclioptions.RESTClientGetter) Factory {
	if clientGetter == nil {
		panic("attempt to instantiate client_access_factory with nil clientGetter")
	}

	f := &factoryImpl{
		clientGetter: clientGetter,
	}

	return f
}

func (f *factoryImpl) ToRESTConfig() (*restclient.Config, error) {
	return f.clientGetter.ToRESTConfig()
}

func (f *factoryImpl) ToRESTMapper() (meta.RESTMapper, error) {
	return f.clientGetter.ToRESTMapper()
}

clientGetter 实际上调用的是位于 cli-runtime/pkg/genericclioptions/config_flags.goConfigFlags,其于 staging/src/k8s.io/kubectl/pkg/cmd/cmd.go 中,NewFactory 前初始化。

Builder #

factoryImplNewBuilder 时,还将 clientGetter 传递给了 Builder,这使得 Builder 也能获取 RESTClient。

// kubectl/pkg/cmd/get/get.go

// Run performs the get operation.
func (o *GetOptions) Run(f cmdutil.Factory, cmd *cobra.Command, args []string) error {
  // ...
	r := f.NewBuilder().
		Unstructured().
		NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
		FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
		LabelSelectorParam(o.LabelSelector).
		FieldSelectorParam(o.FieldSelector).
		RequestChunksOf(chunkSize).
		ResourceTypeOrNameArgs(true, args...).
		ContinueOnError().
		Latest().
		Flatten().
		TransformRequests(o.transformRequests).
    Do()
  // ...

函数 ResourceTypeOrNameArgs 将参数分解为 ResourceType 和 Name。

Do() 中,首先调用了 visitorResult(),以命令行为 kubectl get jobs.batch 为例:

因为没有传递 Name,所以不会调用 visitByName(),而是对每个 ResourceType 调用 mappingFor

func (b *Builder) visitorResult() *Result {
	// visit items specified by name
	if len(b.names) != 0 {
		return b.visitByName()
	}

	if len(b.resources) != 0 {
		for _, r := range b.resources {
			_, err := b.mappingFor(r)
			if err != nil {
				return &Result{err: err}
			}
		}
		return &Result{err: fmt.Errorf("resource(s) were provided, but no name, label selector, or --all flag specified")}
	}
	return &Result{err: missingResourceError}
}

mappingFor 首先将 Resource 解析成 Group/Version/Resource 和 Group/Resource。

例如,endpointslice.discovery.k8s.io 将解析成 k8s.io/discovery/enpointslicejobs.batch 将解析成 batch/nil/jobsdeployments.v1.apps 将解析成 apps/v1/deployments

由于没有规定 CRD 的 Version 格式,因此在这一阶段并不知道 GVR 是否是正确的,例如 endpointslice.discovery.k8s.io 实际上是 endpointslice.v1beta1.discovery.k8s.io,应当解析成 discovery.k8s.io/v1beta1/enpointslice 而非 k8s.io/discovery/enpointslice

故而在从 RESTMapper 中取 Kind 时,需要进行两次。第一次用 GVR 取,第二次用 GR 取。

// staging/src/k8s.io/cli-runtime/pkg/resource/builder.go

func (b *Builder) mappingFor(resourceOrKindArg string) (*meta.RESTMapping, error) {
	fullySpecifiedGVR, groupResource := schema.ParseResourceArg(resourceOrKindArg)
	gvk := schema.GroupVersionKind{}
	restMapper, err := b.restMapperFn()
	if err != nil {
		return nil, err
	}

	if fullySpecifiedGVR != nil {
		gvk, _ = restMapper.KindFor(*fullySpecifiedGVR)
	}
	if gvk.Empty() {
		gvk, _ = restMapper.KindFor(groupResource.WithVersion(""))
	}
	if !gvk.Empty() {
		return restMapper.RESTMapping(gvk.GroupKind(), gvk.Version)
	}

ConfigFlags #

Config Flags 初始化位于:

// staging/src/k8s.io/kubectl/pkg/cmd/cmd.go

// NewKubectlCommand creates the `kubectl` command and its nested children.
func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
  // ..
	kubeConfigvncFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
	kubeConfigFlags.AddFlags(flags)
	matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
	matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())

	f := cmdutil.NewFactory(matchVersionKubeConfigFlags)

matchVersionKubeConfigFlags 也是一层对