k8s各组件的cli部分都使用Cobra库实现,Cobra 中文文档 - 掘金 (juejin.cn),获取方式如下:
go get -u github.com/spf13/cobra@latest
cobra库中的Command结构体的字段,用于定义命令行工具的行为和选项。它们的作用如下:
Use: 命令名称。
Aliases: 命令的别名。
SuggestFor: 命令建议使用的单词列表。
Short: 命令简短描述。
GroupID: 命令所属的命令组。
Long: 命令详细描述。
Example: 命令的使用示例。
ValidArgs: 命令接受的参数列表。
ValidArgsFunction: 命令用于提供动态参数补全的函数。
Args: 命令的位置参数列表。
ArgAliases: 位置参数的别名。
BashCompletionFunction: 生成Bash补全的函数。
Deprecated: 命令是否已经过时的标志。
Annotations: 命令的附加注释信息。
Version: 命令版本号。
PersistentPreRun: 每次执行该命令之前都会执行的函数。
PersistentPreRunE: 每次执行该命令之前都会执行的返回错误的函数。
PreRun: 每次执行该命令之前都会执行的函数。
PreRunE: 每次执行该命令之前都会执行的返回错误的函数。
Run: 执行命令的函数。
RunE: 执行命令的返回错误的函数,RunE与Run的差别是,RunE执行有错误会直接return,Run无论是否有错误,都会继续执行后面PostRun和PersistentPostRun等逻辑。
PostRun: 每次执行该命令之后都会执行的函数。
PostRunE: 每次执行该命令之后都会执行的返回错误的函数。
PersistentPostRun: 每次执行该命令之后都会执行的函数。
PersistentPostRunE: 每次执行该命令之后都会执行的返回错误的函数。
FParseErrWhitelist : 忽略特定的解析错误
CompletionOptions :控制 shell 自动完成的选项
TraverseChildren: 解析父命令的标志后再执行子命令
Hidden : 隐藏命令,不在可用命令列表中显示
SilenceErrors : 静默下游错误
SilenceUsage : 静默错误时不显示用法
DisableFlagParsing : 禁用标志解析
DisableAutoGenTag : 禁用自动生成的标记
DisableFlagsInUseLine : 在打印帮助或生成文档时禁用“[flags]”在用法行中的添加
DisableSuggestions : 禁用基于Levenshtein距离的建议
SuggestionsMinimumDistance : 显示建议的最小Levenshtein距离
主要流程在cmd.NewDefaultKubectlCommand()中,构建KubectlOptions对象(Kubectl Command的配置对象),指定插件、命令行参数、通用配置和输入输出流,然后调用NewDefaultKubectlCommandWithArgs函数创建Command对象,NewDefaultKubectlCommandWithArgs中调用NewKubectlCommand构建Command对象。
创建了cobra.Command对象后,调用Kubectl封装的RunNoErrOutput方法,进入Command对象的执行。
每个子命令的主要处理逻辑(cobra.Command.Run函数)Complete、Validate 和 Run三个函数,其中 complete() 函数中会将命令行参数整理对命令行options进行初始化,设置一些默认值;Validate() 函数会对options中的选项进行检查,打印相应的错误提示信息;Run()函数中执行各子命令的主要处理流程。
创建资源对象的流程分为: 实例化Factory接口 、通过Builder和Visitor将资源对象描述文件(deployment.yaml)文本格式转换成资源对象。将资源对象以HTTP请求的方式发送给kube-apiserver,并得到响应结果。最终根据Visitor匿名函数集的errors判断是否成功创建了资源对象。
raw参数处理。
首先通过f.NewBuilder()实例化Builder对象, 通过函数Unstructured()、 Schema()、 ContinueOnError()、NamespaceParam()、 FilenameParam()、LabelSelectorParam()、 Flatten()对参数赋值和初始化, 将参数保存到Builder对象中。 最后通过Do()函数生成最终的rusult对象,设置rusult.visitor。
// 实例化builder对象
r := f.NewBuilder().
// 以map的方式传输数据对象,对响应内容中的数据做一层封装,这样就可以保留所有字段而不需要首先解析成一个struct
Unstructured().
//
Schema(schema).
// 配置result对象在出现错误的行为,意思很明显,在出错后继续
ContinueOnError().
// 基于命令行参数设置查询的namespace
NamespaceParam(cmdNamespace).DefaultNamespace().
// 解析文件名参数 参数 -f,文件名被存放在 b.paths 中
FilenameParam(enforceNamespace,&o.FilenameOptions).
// 解析标签选择器 参数 -l
LabelSelectorParam(o.Selector).
// 将对象展开,比如对象是[a, b], 如果没有flatten就是完成访问[a,b]作为一个整体,反之, 让外层函数分别访问a,b
Flatten().
// 基于之前的配置,生成最终的result对象
Do()
Do函数中设置rusult.visitor多层匿名函数嵌套关系如下:
result.Visitor = DecoratedVisitor { // 在函数Do函数中通过NewDecoratedVisitor函数执设置,并且注册了SetNamespace、RequireNamespace、FilterNamespace、RetrieveLazy等修饰函数。
visitor: ContinueOnErrorVisitor { // 在函数Do函数中,如果b.continueOnError为真设置,b.continueOnError在函数ContinueOnError()设置。
visitor: FlattenListVisitor { // 在函数Do函数中设置,这个感觉有点多余,在后面的流程中还会设置一个FlattenListVisitor。
visitor: FilteredVisitor { // 在函数visitByPaths中,含有Selector时设置,对每个对象对应的info对象进行检查,检查函数为FilterByLabelSelector(selector)。
visitor: FlattenListVisitor { // 在函数visitByPaths中,b.flatten为真时设置,b.flatten在上面的Flatten()函数中设为true。
Visitor: EagerVisitorList { // 在函数visitByPaths中将b.paths强转成EagerVisitorList,调用关系:Do() -> b.visitorResult() -> b.visitByPaths()。
[]b.paths FileVisitor { // 每个文件对应一个FileVisitor,所有的FileVisitor被append到 b.paths 数组中。
StreamVisitor: StreamVisitor {
Reader: r,
mapper: mapper,
Source: source,
Schema: b.schema,
},
},
},
},
},
},
},
}
执行Result.Visit(),该函数中会按照上面多层嵌套关系执行每一个Visit函数,按顺序 处理逻辑如下:
func (m *Helper) createResource(c RESTClient, resource, namespace string, obj runtime.Object, options *metav1.CreateOptions) (runtime.Object, error) {
// RESTFUL接口风格中,POST请求对应的就时CREATE方法
return c.Post().
NamespaceIfScoped(namespace,m.NamespaceScoped).
Resource(resource).
VersionedParams(options,metav1.ParameterCodec).
Body(obj).
// 发送请求
Do(context.TODO()).
// 将请求结果转换成runtime.Object
Get()
}