k8s源码分析3: Kubectl

k8s源码分析3: Kubectl

k8s源码分析3 Kubectl

之前介绍过K8s内部Client-go以及Kube-Scheduler的简单工作原理。

这次分析的是Kubectl的工作原理。这三者作用各不相同,Client-go是基础库,kube-schduler是常驻内存的后台组件服务。Kubectl则是一个命令行工具,也是我们平时使用最多的组件之一。

kubectl子命令很多,估计很少有人可以全部记住,作为开发,不知道大家有没有试着思考下,怎么样才可以比较灵活的实现这么丰富的功能,此外,命令行工具的用户友好也是很重要的。

1 Kubectl创建

一起看看Kubectl的启动:

// kubernetes\cmd\kubectl\kubectl.go
func main() {
    rand.Seed(time.Now().UnixNano())

    command := cmd.NewDefaultKubectlCommand()

    if err := command.Execute(); err != nil {
        os.Exit(1)
    }
}

去掉了一些无关代码,主要操作就是两个:

1. NewDefaultKubectlCommand(): 创建kubectl命令
2. command.Execute() : 执行command命令

重点看下创建的过程:

// 使用默认的初始化参数调用NewDefaultKubectlCommandWithArgs创建kubectl
func NewDefaultKubectlCommand() *cobra.Command {
    return NewDefaultKubectlCommandWithArgs(NewDefaultPluginHandler(plugin.ValidPluginFilenamePrefixes), os.Args, os.Stdin, os.Stdout, os.Stderr)
}

// 字面意思
func NewDefaultKubectlCommandWithArgs(pluginHandler PluginHandler, args []string, in io.Reader, out, errout io.Writer) *cobra.Command {
    // 实际创建kubectl
    cmd := NewKubectlCommand(in, out, errout)

    // 检测是否存在插件处理器
    if pluginHandler == nil {
        return cmd
    }

    if len(args) > 1 {
        cmdPathPieces := args[1:]

        // 根据传入的参数判断是否存在这样的子命令,如果不存在,则判断是否存在对应的插件可以调用,有的话就调用插件,调用完随即推出。
        if _, _, err := cmd.Find(cmdPathPieces); err != nil {
            if err := HandlePluginCommand(pluginHandler, cmdPathPieces); err != nil {
                fmt.Fprintf(errout, "Error: %v\n", err)
                os.Exit(1)
            }
        }
    }

    return cmd
}

NewDefaultKubectlCommandWithArgs方法里只有第一步是创建命令的,后续都在处理插件,插件的处理后续单独看,继续往调用栈里走,看内部的创建逻辑:


// NewKubectlCommand 创建kubcel命令,并添加相应的子命令
func NewKubectlCommand(in io.Reader, out, err io.Writer) *cobra.Command {
    warningHandler := rest.NewWarningWriter(err, rest.WarningWriterOptions{Deduplicate: true, Color: term.AllowsColorOutput(err)})
    warningsAsErrors := false
    // 所有子命令的父命令
    // 这里可以看到kubectl默认的说明
    cmds := &cobra.Command{
        Use:   "kubectl",
        Short: i18n.T("kubectl controls the Kubernetes cluster manager"),
        Long: templates.LongDesc(`
      kubectl controls the Kubernetes cluster manager.

      Find more information at:
            https://kubernetes.io/docs/reference/kubectl/overview/`),
        // 默认的方法是打印出help信息
        Run: runHelp,
        // 调用时,各个阶段的 Hook 方法
        // 命令运行前回调
        PersistentPreRunE: func(*cobra.Command, []string) error {
            rest.SetDefaultWarningHandler(warningHandler)
            return initProfiling()
        },
        // 运行后回调
        PersistentPostRunE: func(*cobra.Command, []string) error {
            if err := flushProfiling(); err != nil {
                return err
            }
            if warningsAsErrors {
                count := warningHandler.WarningCount()
                switch count {
                case 0:
                    // no warnings
                case 1:
                    return fmt.Errorf("%d warning received", count)
                default:
                    return fmt.Errorf("%d warnings received", count)
                }
            }
            return nil
        },
        BashCompletionFunction: bashCompletionFunc,
    }

    flags := cmds.PersistentFlags()
    flags.SetNormalizeFunc(cliflag.WarnWordSepNormalizeFunc)

    // 参数的归一化方法,主要作用是将所有 "_" 转化成 "-"
    flags.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)

    // 为kubectl添加默认的性能测算参数,默认是关闭的
    // 如果在参数中设置为开启,则会进行测算,具体可见命令调用前后的回调
    addProfilingFlags(flags)

    flags.BoolVar(&warningsAsErrors, "warnings-as-errors", warningsAsErrors, "Treat warnings received from the server as errors and exit with a non-zero exit code")

    kubeConfigFlags := genericclioptions.NewConfigFlags(true).WithDeprecatedPasswordFlag()
    kubeConfigFlags.AddFlags(flags)
    matchVersionKubeConfigFlags := cmdutil.NewMatchVersionFlags(kubeConfigFlags)
    matchVersionKubeConfigFlags.AddFlags(cmds.PersistentFlags())

    cmds.PersistentFlags().AddGoFlagSet(flag.CommandLine)

    f := cmdutil.NewFactory(matchVersionKubeConfigFlags)

    i18n.LoadTranslations("kubectl", nil)

    cmds.SetGlobalNormalizationFunc(cliflag.WarnWordSepNormalizeFunc)

    ioStreams := genericclioptions.IOStreams{In: in, Out: out, ErrOut: err}

    // 添加当前的子命令组
    groups := templates.CommandGroups{
        {
            Message: "Basic Commands (Beginner):",
            Commands: []*cobra.Command{
                create.NewCmdCreate(f, ioStreams),
                expose.NewCmdExposeService(f, ioStreams),
                run.NewCmdRun(f, ioStreams),
                set.NewCmdSet(f, ioStreams),
            },
        },
        {
            Message: "Basic Commands (Intermediate):",
            Commands: []*cobra.Command{
                explain.NewCmdExplain("kubectl", f, ioStreams),
                get.NewCmdGet("kubectl", f, ioStreams),
                edit.NewCmdEdit(f, ioStreams),
                delete.NewCmdDelete(f, ioStreams),
            },
        },
        {
            Message: "Deploy Commands:",
            Commands: []*cobra.Command{
                rollout.NewCmdRollout(f, ioStreams),
                scale.NewCmdScale(f, ioStreams),
                autoscale.NewCmdAutoscale(f, ioStreams),
            },
        },
        {
            Message: "Cluster Management Commands:",
            Commands: []*cobra.Command{
                certificates.NewCmdCertificate(f, ioStreams),
                clusterinfo.NewCmdClusterInfo(f, ioStreams),
                top.NewCmdTop(f, ioStreams),
                drain.NewCmdCordon(f, ioStreams),
                drain.NewCmdUncordon(f, ioStreams),
                drain.NewCmdDrain(f, ioStreams),
                taint.NewCmdTaint(f, ioStreams),
            },
        },
        {
            Message: "Troubleshooting and Debugging Commands:",
            Commands: []*cobra.Command{
                describe.NewCmdDescribe("kubectl", f, ioStreams),
                logs.NewCmdLogs(f, ioStreams),
                attach.NewCmdAttach(f, ioStreams),
                cmdexec.NewCmdExec(f, ioStreams),
                portforward.NewCmdPortForward(f, ioStreams),
                proxy.NewCmdProxy(f, ioStreams),
                cp.NewCmdCp(f, ioStreams),
                auth.NewCmdAuth(f, ioStreams),
                debug.NewCmdDebug(f, ioStreams),
            },
        },
        {
            Message: "Advanced Commands:",
            Commands: []*cobra.Command{
                diff.NewCmdDiff(f, ioStreams),
                apply.NewCmdApply("kubectl", f, ioStreams),
                patch.NewCmdPatch(f, ioStreams),
                replace.NewCmdReplace(f, ioStreams),
                wait.NewCmdWait(f, ioStreams),
                kustomize.NewCmdKustomize(ioStreams),
            },
        },
        {
            Message: "Settings Commands:",
            Commands: []*cobra.Command{
                label.NewCmdLabel(f, ioStreams),
                annotate.NewCmdAnnotate("kubectl", f, ioStreams),
                completion.NewCmdCompletion(ioStreams.Out, ""),
            },
        },
    }
    groups.Add(cmds)

    filters := []string{"options"}

    alpha := NewCmdAlpha(f, ioStreams)
    if !alpha.HasSubCommands() {
        filters = append(filters, alpha.Name())
    }

    templates.ActsAsRootCommand(cmds, filters, groups...)

    for name, completion := range bashCompletionFlags {
        if cmds.Flag(name) != nil {
            if cmds.Flag(name).Annotations == nil {
                cmds.Flag(name).Annotations = map[string][]string{}
            }
            cmds.Flag(name).Annotations[cobra.BashCompCustom] = append(
                cmds.Flag(name).Annotations[cobra.BashCompCustom],
                completion,
            )
        }
    }

    // 添加一些不在默认分组内的方法
    cmds.AddCommand(alpha)
    cmds.AddCommand(cmdconfig.NewCmdConfig(f, clientcmd.NewDefaultPathOptions(), ioStreams))
    cmds.AddCommand(plugin.NewCmdPlugin(f, ioStreams))
    cmds.AddCommand(version.NewCmdVersion(f, ioStreams))
    cmds.AddCommand(apiresources.NewCmdAPIVersions(f, ioStreams))
    cmds.AddCommand(apiresources.NewCmdAPIResources(f, ioStreams))
    cmds.AddCommand(options.NewCmdOptions(ioStreams.Out))

    return cmds
}

kubectl命令的创建过程就是初始化Kubectl命令,明确命令使用前后的回调函数,然后将几组子命令添加到根命令下。

2 Kubectl执行

kubectl大部分命令的执行其实是交给子命令模块去做的。Cobra规定了,子命令与Root命令的基本代码结构,如果要添加子命令,符合一般约束的前提下,在cmd目录下新建一个文件即可。

  ▾ appName/
    ▾ cmd/
        add.go
        your.go
        commands.go
        here.go
      main.go

可以看一下目前Kubectl下的子命令:


目前kubectl支持的子命令肯定都会在这里,还记得之前kubectl的构造函数么?K8s的代码里,将这些子命令分为了几个种类:

种类 命令
Basic Commands (Beginner) create\expose\run\set
Basic Commands (Intermediate) explain\get\edit\delete
Deploy Commands rollout\scale\autoscale
Cluster Management Commands certificate\clusterinfo\top\cordon\uncordon\drain\taint
Troubleshooting and Debugging Commands describe\logs\attach\exec\portforward\proxy\cp\auth\debug
Advanced Commands diff\apply\patch\replace\wait\kustomize
Settings Commands label\annotate\completion
其他 version\options\config\apiversions\apiresources

里面有一些命令比较常用,有一些我也是第一次知道,比如debug\wait。

这些子命令都通过AddCommand方法,添加到Command结构体的成员变量commands中了。commands作为一个数组,主要用于存储程序支持的子命令。

// kubectl的执行逻辑
func (c *Command) ExecuteC() (cmd *Command, err error) {
    if c.ctx == nil {
        c.ctx = context.Background()
    }

    // 以根命令的形式运行
    if c.HasParent() {
        return c.Root().ExecuteC()
    }

    // 窗口检测回调函数,主要作用是检测当前程序是否是被鼠标点击触发的,如果不是命令行模式,报错然后退出
    if preExecHookFn != nil {
        preExecHookFn(c)
    }

    // 初始化帮助信息,覆盖默认的帮助信息
    c.InitDefaultHelpCmd()

    args := c.args

    // 一些异常case的检测
    if c.args == nil && filepath.Base(os.Args[0]) != "cobra.test" {
        args = os.Args[1:]
    }

    // 命令补全初始化
    c.initCompleteCmd(args)

    var flags []string
    // 这一步很关键
    // 这一步会明确实际执行的子命令,然后赋值给cmd
    if c.TraverseChildren {
        cmd, flags, err = c.Traverse(args)
    } else {
        cmd, flags, err = c.Find(args)
    }
    if err != nil {
        // 如果没有找到对应的子命令,提示出错
        if cmd != nil {
            c = cmd
        }
        if !c.SilenceErrors {
            c.PrintErrln("Error:", err.Error())
            c.PrintErrf("Run '%v --help' for usage.\n", c.CommandPath())
        }
        return c, err
    }

    // 子命令执行前的准备
    cmd.commandCalledAs.called = true
    if cmd.commandCalledAs.name == "" {
        cmd.commandCalledAs.name = cmd.Name()
    }

    // context传输
    if cmd.ctx == nil {
        cmd.ctx = c.ctx
    }

    // 执行
    err = cmd.execute(flags)
    if err != nil {
        // Always show help if requested, even if SilenceErrors is in
        // effect
        if err == flag.ErrHelp {
            cmd.HelpFunc()(cmd, args)
            return cmd, nil
        }

        // If root command has SilentErrors flagged,
        // all subcommands should respect it
        if !cmd.SilenceErrors && !c.SilenceErrors {
            c.PrintErrln("Error:", err.Error())
        }

        // If root command has SilentUsage flagged,
        // all subcommands should respect it
        if !cmd.SilenceUsage && !c.SilenceUsage {
            c.Println(cmd.UsageString())
        }
    }
    return cmd, err
}

// command的执行框架
// 按照
// 1. 解析命令行参数
// 2. help参数检测
// 3. 运行前检测
// 4. preRun
// 5. p.PersistentPreRun | p.PersistentPreRunE  p是父命令
// 6. PreRunE | PreRun
// 7. RunE | Run
// 8. PostRunE | PostRun
// 9. p.PersistentPostRunE | p.PersistentPostRun
func (c *Command) execute(a []string) (err error) {
    if c == nil {
        return fmt.Errorf("Called Execute() on a nil Command")
    }

    if len(c.Deprecated) > 0 {
        c.Printf("Command %q is deprecated, %s\n", c.Name(), c.Deprecated)
    }

    // initialize help and version flag at the last point possible to allow for user
    // overriding
    c.InitDefaultHelpFlag()
    c.InitDefaultVersionFlag()

    err = c.ParseFlags(a)
    if err != nil {
        return c.FlagErrorFunc()(c, err)
    }

    // If help is called, regardless of other flags, return we want help.
    // Also say we need help if the command isn't runnable.
    helpVal, err := c.Flags().GetBool("help")
    if err != nil {
        // should be impossible to get here as we always declare a help
        // flag in InitDefaultHelpFlag()
        c.Println("\"help\" flag declared as non-bool. Please correct your code")
        return err
    }

    if helpVal {
        return flag.ErrHelp
    }

    // for back-compat, only add version flag behavior if version is defined
    if c.Version != "" {
        versionVal, err := c.Flags().GetBool("version")
        if err != nil {
            c.Println("\"version\" flag declared as non-bool. Please correct your code")
            return err
        }
        if versionVal {
            err := tmpl(c.OutOrStdout(), c.VersionTemplate(), c)
            if err != nil {
                c.Println(err)
            }
            return err
        }
    }

    if !c.Runnable() {
        return flag.ErrHelp
    }

    c.preRun()

    argWoFlags := c.Flags().Args()
    if c.DisableFlagParsing {
        argWoFlags = a
    }

    if err := c.ValidateArgs(argWoFlags); err != nil {
        return err
    }

    for p := c; p != nil; p = p.Parent() {
        if p.PersistentPreRunE != nil {
            if err := p.PersistentPreRunE(c, argWoFlags); err != nil {
                return err
            }
            break
        } else if p.PersistentPreRun != nil {
            p.PersistentPreRun(c, argWoFlags)
            break
        }
    }
    if c.PreRunE != nil {
        if err := c.PreRunE(c, argWoFlags); err != nil {
            return err
        }
    } else if c.PreRun != nil {
        c.PreRun(c, argWoFlags)
    }

    if err := c.validateRequiredFlags(); err != nil {
        return err
    }
    if c.RunE != nil {
        if err := c.RunE(c, argWoFlags); err != nil {
            return err
        }
    } else {
        c.Run(c, argWoFlags)
    }
    if c.PostRunE != nil {
        if err := c.PostRunE(c, argWoFlags); err != nil {
            return err
        }
    } else if c.PostRun != nil {
        c.PostRun(c, argWoFlags)
    }
    for p := c; p != nil; p = p.Parent() {
        if p.PersistentPostRunE != nil {
            if err := p.PersistentPostRunE(c, argWoFlags); err != nil {
                return err
            }
            break
        } else if p.PersistentPostRun != nil {
            p.PersistentPostRun(c, argWoFlags)
            break
        }
    }

    return nil
}

就是这么简单,kubectl的初始化和子命令调用看起来没有什么太大的复杂性。

3 子命令结构设计

Kubectl的子命令,主要基于三种设计模式:建造者模式、访问者模式、装饰器模式。

建造者模式(Builder Pattern)比较简单,就是使用多个简单的对象一步一步构建成一个复杂的对象。

访问者模式(Visitor Pattern)是一种行为型设计模式,可以将算法和操作对象的结构进行分离,遵循开放、封闭原则的一种方法。我们需要重点看下这个模式是如何工作的。

3.1 访问者模式示例

写一个简单的访问者模式的应用,主要元素有访问对象、访问者、调用方

type Visitor func(person Person)

// 基类
// 被访问的对象,通过accept方法接受访问
type Person interface {
    accept(visitor Visitor)
}

// 存储学生信息的类型
// 实现了Person接口
type Student struct {
    Name  string `json:"name"`
    Age   int    `json:"age"`
    Score int    `json:"score"`
}

func (s Student) accept(visitor Visitor) {
    visitor(s)
}

// 存储教师信息
type Teacher struct {
    Name   string `json:"name"`
    Age    int    `json:"age"`
    Course string `json:"course"`
}

func (t Teacher) accept(visitor Visitor) {
    visitor(t)
}

定义两个简单的访问器

// 导出json格式数据的访问器
func JsonVisitor(person Person) {
    bytes, err := json.Marshal(person)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(bytes))
}

// 导出yaml格式信息的访问器
func YamlVisitor(person Person) {
    bytes, err := yaml.Marshal(person)
    if err != nil {
        panic(err)
    }
    fmt.Println(string(bytes))
}

调用一下

func main() {
    s := Student{Age: 10, Name: "小明", Score: 90}
    t := Teacher{Name: "李", Age: 35, Course: "数学"}
    persons := []Person{s, t}

    for _, person := range persons {
        person.accept(JsonVisitor)
        person.accept(YamlVisitor)
    }
}

上面是一个简单的示例,看起来有一点像策略模式。两者没有本质区别,都是针对多态的一种处理。不过访问者模式更侧重对于被访问者的状态的修改,而策略模式更侧重的是处理逻辑的扩展。实际用的时候不用考虑那么多,怎么方便怎么来就好了。
上面的例子比较简单,复杂的情况下,一个结构体里会有很多不同的状态,每个访问器负责修改一部分状态。kubectl中就是这样的场景。

3.2 Kubectl对于访问者模式的应用

在k8s中,存在各种各样的资源类型,每个类型都包含复杂的状态信息,有些是公用的,有的是独有的。
kubectl的子命令,需要对不同的资源类型做出处理,
处理流程上:1. 读取命令行参数、或者读取指定文件、或者读取url,构建命令 2. 调用k8s的client,向API Server发起请求 3. 完成处理

处理逻辑上,可以抽象为: 1. 获取参数 2. Builder模式构建一个资源的集合 3. 使用visitor模式处理这些资源状态的集合,包括本地资源的修改操作、向Api Server的请求操作 3. 完成处理

这是一个比较抽象的过程。下面可以具体看一下实际实现的代码

// Info 封装了一些client调用时所需要的基本信息
type Info struct {
    // 只有需要远端调用的时候才会初始化client和mapping
    Client RESTClient
    Mapping *meta.RESTMapping

    // 指定namespace的时候才会设置这个参数
    Namespace string
    Name      string

    // 可选参数 url或者文件路径
    Source string

    Object runtime.Object
    ResourceVersion string
}

// 访问器基类,用于修改资源类型的操作
type Visitor interface {
    Visit(VisitorFunc) error
}

type VisitorFunc func(*Info, error) error

可以看到,Visitor类型里包含方法Visit,Visit的参数是一个VisitorFunc。

为了方便理解Visitor的使用,先举个例子,我们定义几个不同类型的Visitor

// name visitor
// 假设这个visitor主要用于访问 Info 结构中的 Name 和 NameSpace 成员
type NameVisitor struct {
  visitor Visitor
}

func (v NameVisitor) Visit(fn VisitorFunc) error {
  return v.visitor.Visit(func(info *Info, err error) error {
    fmt.Println("NameVisitor() before call function")
    err = fn(info, err)
    if err == nil {
      fmt.Printf("==> Name=%s, NameSpace=%s\n", info.Name, info.Namespace)
    }
    fmt.Println("NameVisitor() after call function")
    return err
  })
}

// Other Visitor
// 这个Visitor主要用来访问 Info 结构中的 OtherThings 成员
type OtherThingsVisitor struct {
  visitor Visitor
}

func (v OtherThingsVisitor) Visit(fn VisitorFunc) error {
  return v.visitor.Visit(func(info *Info, err error) error {
    fmt.Println("OtherThingsVisitor() before call function")
    err = fn(info, err)
    if err == nil {
      fmt.Printf("==> OtherThings=%s\n", info.OtherThings)
    }
    fmt.Println("OtherThingsVisitor() after call function")
    return err
  })
}

// Log Visitor
type LogVisitor struct {
  visitor Visitor
}
func (v LogVisitor) Visit(fn VisitorFunc) error {
  return v.visitor.Visit(func(info *Info, err error) error {
    fmt.Println("LogVisitor() before call function")
    err = fn(info, err)
    fmt.Println("LogVisitor() after call function")
    return err
  })
}
// 调用逻辑
func main() {
  info := Info{}
  var v Visitor = &info
  v = LogVisitor{v}
  v = NameVisitor{v}
  v = OtherThingsVisitor{v}

  loadFile := func(info *Info, err error) error {
    info.Name = "Hao Chen"
    info.Namespace = "MegaEase"
    info.OtherThings = "We are running as remote team."
    return nil
  }
  v.Visit(loadFile)
}

上面的代码,每个visitor里

  • 有一个 Visitor 接口成员,这里意味着多态。
  • 在实现 Visit() 方法时,其调用了自己结构体内的那个 VisitorVisitor() 方法,这其实是一种修饰器的模式,用另一个Visitor修饰了自己

调用后,输出如下信息

LogVisitor() before call function
NameVisitor() before call function
OtherThingsVisitor() before call function
==> OtherThings=We are running as remote team.
OtherThingsVisitor() after call function
==> Name=Hao Chen, NameSpace=MegaEase
NameVisitor() after call function
LogVisitor() after call function

显而易见,上面的代码有以下几种功效:

  • 解耦了数据和程序。
  • 使用了修饰器模式
  • 还做出来pipeline的模式

搞清楚上述逻辑后,在回过来看k8s的实现就会一目了然

// Decorate 就是装饰的意思,显而易见,装饰过的访问器
type DecoratedVisitor struct {
    visitor    Visitor
    // 一个装饰器集合
    decorators []VisitorFunc
}

// Visit implements Visitor
// 1. 下潜一层,调用自己的Visitor内的访问器方法
// 2. 调用自己 所有装饰者方法,即自身的访问器
// 3. 最后再调用传入的fn
// 达到一种嵌套调用、链式调用的效果
func (v DecoratedVisitor) Visit(fn VisitorFunc) error {
    return v.visitor.Visit(func(info *Info, err error) error {
        if err != nil {
            return err
        }
        for i := range v.decorators {
            if err := v.decorators[i](info, nil); err != nil {
                return err
            }
        }
        return fn(info, nil)
    })
}

上面的代码并不复杂,

  • 用一个 DecoratedVisitor 的结构来存放所有的VistorFunc函数
  • NewDecoratedVisitor 可以把所有的 VisitorFunc转给它,构造 DecoratedVisitor 对象。
  • DecoratedVisitor实现了 Visit() 方法,里面就是来做一个for-loop,顺着调用所有的 VisitorFunc

用一个非常巧妙的方法,把装饰器模式和访问者模式结合在了一起,又把操作与数据解耦、操作与操作解耦,叹为观止!

那怎么调用呢?

info := Info{}
var v Visitor = &info
v = NewDecoratedVisitor(v, NameVisitor, OtherVisitor)

v.Visit(LoadFile)

举个例子,kubectl的apply方法的实现:

// 路径 vendor/k8s.io/kubectl/pkg/cmd/apply/apply.go
func (o *ApplyOptions) GetObjects() ([]*resource.Info, error) {
    var err error = nil
    if !o.objectsCached {
        // 通过builder模式,逐步构建一个完整的资源对象
        r := o.Builder.
            Unstructured().
            Schema(o.Validator).
            ContinueOnError().
            NamespaceParam(o.Namespace).DefaultNamespace().
            FilenameParam(o.EnforceNamespace, &o.DeleteOptions.FilenameOptions).
            LabelSelectorParam(o.Selector).
            Flatten().
            Do()
        o.objects, err = r.Infos()
        o.objectsCached = true
    }
    return o.objects, err
}

// 路径 k8s.io/cli-runtime/pkg/resource/builder.go
func (b *Builder) Do() *Result {
    r := b.visitorResult()
    r.mapper = b.Mapper()
    if r.err != nil {
        return r
    }
    if b.flatten {
        r.visitor = NewFlattenListVisitor(r.visitor, b.objectTyper, b.mapper)
    }
    // 访问器方法集合
    helpers := []VisitorFunc{}
    if b.defaultNamespace {
        // 设置namespace的访问器 SetNamespace
        helpers = append(helpers, SetNamespace(b.namespace))
    }
    if b.requireNamespace {
        // 需要namespace的提示操作 RequireNamespace
        helpers = append(helpers, RequireNamespace(b.namespace))
    }
    // 过滤namespace的访问器 FilterNamespace
    helpers = append(helpers, FilterNamespace)
    if b.requireObject {
        // 另一个访问器
        helpers = append(helpers, RetrieveLazy)
    }
    if b.continueOnError {
        // 构造被装饰的访问者
        r.visitor = NewDecoratedVisitor(ContinueOnErrorVisitor{r.visitor}, helpers...)
    } else {
        r.visitor = NewDecoratedVisitor(r.visitor, helpers...)
    }
    return r
}

kubectl的子命令基本都是这样的工作模式。

具体的业务逻辑就需要看实际的处理需求了。

发表评论