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.go
的 factoryImpl
。
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.go
的DeferredDiscoveryRESTMapper
。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.go
的 ConfigFlags
,其于 staging/src/k8s.io/kubectl/pkg/cmd/cmd.go
中,NewFactory 前初始化。
Builder #
factoryImpl
在 NewBuilder
时,还将 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/enpointslice
;jobs.batch
将解析成 batch/nil/jobs
;deployments.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
也是一层对