这一讲我们来看kubectl get获取资源对象命令,它的实现在NewCmdGet方法中

  1. 获取GetOptions中保存的命令行输入的kubectl get 后面跟的参数(如--all-namespace,--chunk-size,--output等)
  2. 构建cmd get命令,注册get命令的实现方法
  3. 给get命令添加相应的参数来控制get命令的操作
func NewCmdGet(parent string, f cmdutil.Factory, streams genericclioptions.IOStreams) *cobra.Command {
	o := NewGetOptions(parent, streams)

    //构建cmd get命令
	cmd := &cobra.Command{
		Use: "get [(-o|--output=)json|yaml|wide|custom-columns=...|custom-columns-file=...|go-template=...|go-template-file=...|jsonpath=...|jsonpath-file=...] (TYPE[.VERSION][.GROUP] [NAME | -l label] | TYPE[.VERSION][.GROUP]/NAME ...) [flags]",
		DisableFlagsInUseLine: true,
		Short:   i18n.T("Display one or many resources"),
		Long:    getLong + "\n\n" + cmdutil.SuggestApiResources(parent),
		Example: getExample,
        //代码运行部分
		Run: func(cmd *cobra.Command, args []string) {
			cmdutil.CheckErr(o.Complete(f, cmd, args))
			cmdutil.CheckErr(o.Validate(cmd))
            //get请求具体实现方法
			cmdutil.CheckErr(o.Run(f, cmd, args))
		},
		SuggestFor: []string{"list", "ps"},
	}

	o.PrintFlags.AddFlags(cmd)

    //参数flag提供修饰符来控制动作命令的操作,即给命令添加参数,这些参数都保存在GetOptions中
	cmd.Flags().StringVar(&o.Raw, "raw", o.Raw, "Raw URI to request from the server.  Uses the transport specified by the kubeconfig file.")
	cmd.Flags().BoolVarP(&o.Watch, "watch", "w", o.Watch, "After listing/getting the requested object, watch for changes. Uninitialized objects are excluded if no object name is provided.")
	cmd.Flags().BoolVar(&o.WatchOnly, "watch-only", o.WatchOnly, "Watch for changes to the requested object(s), without listing/getting first.")
	cmd.Flags().Int64Var(&o.ChunkSize, "chunk-size", o.ChunkSize, "Return large lists in chunks rather than all at once. Pass 0 to disable. This flag is beta and may change in the future.")
	cmd.Flags().BoolVar(&o.IgnoreNotFound, "ignore-not-found", o.IgnoreNotFound, "If the requested object does not exist the command will return exit code 0.")
	cmd.Flags().StringVarP(&o.LabelSelector, "selector", "l", o.LabelSelector, "Selector (label query) to filter on, supports '=', '==', and '!='.(e.g. -l key1=value1,key2=value2)")
	cmd.Flags().StringVar(&o.FieldSelector, "field-selector", o.FieldSelector, "Selector (field query) to filter on, supports '=', '==', and '!='.(e.g. --field-selector key1=value1,key2=value2). The server only supports a limited number of field queries per type.")
	cmd.Flags().BoolVar(&o.AllNamespaces, "all-namespaces", o.AllNamespaces, "If present, list the requested object(s) across all namespaces. Namespace in current context is ignored even if specified with --namespace.")
	cmdutil.AddIncludeUninitializedFlag(cmd)
	addOpenAPIPrintColumnFlags(cmd, o)
	addServerPrintColumnFlags(cmd, o)
	cmd.Flags().BoolVar(&o.Export, "export", o.Export, "If true, use 'export' for the resources.  Exported resources are stripped of cluster-specific information.")
	cmdutil.AddFilenameOptionFlags(cmd, &o.FilenameOptions, "identifying the resource to get from a server.")
	return cmd
}

下面来看Get命令具体的实现函数Run,Run函数中最关键的一步是Build-Visitor机制的实现。什么是Build-Visitor机制:

Builder(建造者模式):建造者模式将部件和其组装过程分开,一步一步创建一个复杂的对象。用户只需要指定复杂对象的类型就可以得到该对象,而无须知道其内部的具体构造细节。

Visitor(访问者模式):把结构和数据分开,编写一个访问者,去访问数据结构中的元素,然后把对各元素的处理全部交给访问者类。这样,当需要增加新的处理时候,只需要编写新的 访问者类,让数据结构可以接受访问者的访问即可。可以理解成一个抽象类,不同子类均继承这一抽象类,实现各自的访问逻辑(访问者类),然后通过统一的接口来进行对数据结构的访问。

下面来看具体的实现代码:

r := f.NewBuilder().
		Unstructured().
		NamespaceParam(o.Namespace).DefaultNamespace().AllNamespaces(o.AllNamespaces).
		FilenameParam(o.ExplicitNamespace, &o.FilenameOptions).
		LabelSelectorParam(o.LabelSelector).
		FieldSelectorParam(o.FieldSelector).
		ExportParam(o.Export).
		RequestChunksOf(o.ChunkSize).
		IncludeUninitialized(o.IncludeUninitialized).
		ResourceTypeOrNameArgs(true, args...).
		ContinueOnError().
		Latest().
		Flatten().
		TransformRequests(func(req *rest.Request) {
			// We need full objects if printing with openapi columns
			if o.PrintWithOpenAPICols {
				return
			}
			if o.ServerPrint && o.IsHumanReadablePrinter && !o.Sort {
				group := metav1beta1.GroupName
				version := metav1beta1.SchemeGroupVersion.Version

				tableParam := fmt.Sprintf("application/json;as=Table;v=%s;g=%s, application/json", version, group)
				req.SetHeader("Accept", tableParam)
			}
		}).
		Do()

可以看到,这是一个一连串的链式函数,都指向了Builder指针,下面逐一分析各个函数的功能

  • NewBuilder返回一个Builder对象,该对象有助于从磁盘和服务器加载对象,并实现CLI与通用资源交互的通用模式。
  • Unstructured更新构建器,以便它将请求和发送非结构化对象。 非结构化对象基于对象的JSON结构以map格式保留服务器发送的所有字段,这意味着当客户端读取然后写入对象时不会丢失任何数据。
  • NamespaceParam,DefaultNamespace,AllNamespaces共同确定了资源对象的命名空间。
  • FilenameParam将输入分为两类:URL和文件(文件,目录,STDIN),分别构建FileVisitor或StreamVisitor,底层均为StreamVisitor,添加到Builder.path数组中
  • LabelSelectorParam定义了一个应该应用于要加载的对象类型的选择器。这不会影响从磁盘或URL加载的文件。 如果参数为空,则无操作
  • FieldSelectorParam功能同上
  • ExportParam接收命令行指定的--export
  • RequestChunksOf接收命令行指定的--chunk-size
  • IncludeUninitialized接受这些资源的include-uninitialized布尔值
  • ResourceTypeOrNameArgs解析传入的资源类型/名称参数
         其中调用的NormalizeMultipleResourcesArgs将传入的多个资源转换为资源元组,a,b,c,d作为转换为a / d  ,b / d , c / d,例如get node node1 node2 变成 node/node1,node/node2
         splitResourceTypeName对resource/name进行拆分,分别赋值给resource,name,并以resourceTuple{Resource: resource, Name: name}的形式返回,存储到Builder的resourceTuples中
  • ContinueOnError将尝试加载和访问尽可能多的对象,即使某些访问返回错误或某些对象无法加载。
  • Latest将获取从服务器的URL或文件加载的任何对象的最新副本。
  • Flatten将任何具有名为“Items”的字段的对象转换为单独的条目,并为其提供各自的项目,这些字段是runtime.Object兼容类型的数组。
  • TransformRequests更改由此Builder发起的客户端进行的API调用请求。 传递一个空列表以清除修饰符。
  • Do返回一个Result对象,其中包含一个Visitor,用于访问被Builder标识的资源。

这里要重点关注一下Do函数:

1.构建Visitor

  • 如果b.path不为空(在FilenameParam中创建),则通过path来创建Visitor
  • 如果labelselector或fieldselector不为空,则根据selector来创建Visitor
  • 如果resourceTuples不为空,则根据资源类型和名字来创建Visitor
  • 如果names不为空,则根据name来创建Visitor

2.注册从ApiServer获取资源对象的方法

  • 构建helper,通过回调函数VisitorFunc实现了Visitor接口
  • 注册从ApiServer获取资源对象的方法RetrieveLazy
  • 注册从返回数据中提取资源信息发方法NewDecoratedVisitor

3.将结果以result变量返回,result结构体如下:

type Result struct {
	err     error
      //访问资源对象的Visitor
	visitor Visitor

	sources            []Visitor
	singleItemImplied  bool
	targetsSingleItems bool

	mapper       *mapper
	ignoreErrors []utilerrors.Matcher

	// populated by a call to Infos
        // info结构体中保存了从ApiServer中获取到的资源对象
	info []*Info
}
func (b *Builder) Do() *Result {
	r := b.visitorResult()//构建Visitor
	r.mapper = b.Mapper()
	if r.err != nil {
		return r
	}
	if b.flatten {
		r.visitor = NewFlattenListVisitor(r.visitor, b.objectTyper, b.mapper)
	}
	//helpers实现了Visitor接口
	helpers := []VisitorFunc{}
	//注册获取数据前的动作
	if b.defaultNamespace {
		helpers = append(helpers, SetNamespace(b.namespace))
	}
	if b.requireNamespace {
		helpers = append(helpers, RequireNamespace(b.namespace))
	}
	helpers = append(helpers, FilterNamespace)
	if b.requireObject {
		//注册从Apiserver获取数据的方法
		//RetrieveLazy最终也是调用helper.go中的API
		helpers = append(helpers, RetrieveLazy)
	}
	//注册从返回数据中提取信息的方法
	r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
	if b.continueOnError {
		r.visitor = ContinueOnErrorVisitor{r.visitor}
	}
	return r
}

最后,将我们获取到的资源对象打印出来即可,打印的具体实现方式这里不再细讲。

Logo

K8S/Kubernetes社区为您提供最前沿的新闻资讯和知识内容

更多推荐