这一讲我们来看kubectl get获取资源对象命令,它的实现在NewCmdGet方法中
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指针,下面逐一分析各个函数的功能
这里要重点关注一下Do函数:
1.构建Visitor
2.注册从ApiServer获取资源对象的方法
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
}
最后,将我们获取到的资源对象打印出来即可,打印的具体实现方式这里不再细讲。