helm插件记录(源码解读)

时间:2022-10-30 20:11:02
借鉴:helm插件指南。https://zhuanlan.zhihu.com/p/36310109.
          helm源码地址:https://github.com/kubernetes/helm.
之前查询helm插件功能资料,发现很少。只能查看helm源码并对比上述文档。

现个人记录如下:

helm/pkg/plugin/plugin.go
type Metadata struct {
Name string `json:"name"`
Version string `json:"version"`
Usage string `json:"usage"`
Description string `json:"description"`
Command string `json:"command"`
IgnoreFlags bool `json:"ignoreFlags"`
UseTunnel bool `json:"useTunnel"`
Hooks Hooks
Downloaders []Downloaders `json:"downloaders"`
}

先贴上例子:https://github.com/technosophos/helm-template/blob/master/plugin.yaml
name: "template"
version: "2.5.1+2"
usage: "render templates on the local client"
description: "Render templates on the local client."
command: "$HELM_PLUGIN_DIR/tpl"
hooks:
  install: "$HELM_PLUGIN_DIR/install-binary.sh"

参数简单说明:
name是插件的名称。当Helm执行插件时,这是它将使用的名称(例如,helm NAME将调用此插件)。
version是插件的SemVer 2版本。 usage和description都用于生成命令的帮助文本。
ignoreFlags告诉H​​elm 不会将参数传递给插件。
useTunnel指示插件需要一个隧道去连接Tiller。
command是这个插件在调用时会执行的命令。
hook设置install,会在helm plugin install安装插件时,执行操作。

downloaders:如果安装了这样的插件,Helm可以通过调用command指定的协议方案与存储库repo进行交互。特殊存储库应与常规存储库类似添加:特殊存储库helm repo add favorite myprotocol://example.com/ 的规则与常规存储库的规则相同:Helm必须能够下载index.yaml文件以发现并缓存可用charts列表。

helm plugin install:

helm plugin命令结构与其他命令一样,代码在helm/cmd/helm/plugin*.go
直接上简化版源码:plugin_install.go
代码逻辑比较简单:
初始化install实例:installer.NewForSource,根据source会实例化本地插件或者下载器
执行安装操作:如果是本地数据源是本地,则创建软连接,将install参数链接到相应的插件目录。
LoadDir:检查插件目录,是否合法。
runHook安装install的hook参数。上例中hooks.install(https://github.com/technosophos/helm-template/blob/master/install-binary.sh)
下载相应的二进制文件。并将二进制放入相应的插件目录。

func (pcmd *pluginInstallCmd) run() error {
i, err := installer.NewForSource(pcmd.source, pcmd.version, pcmd.home)
err := installer.Install(i)
p, err := plugin.LoadDir(i.Path())
err := runHook(p, plugin.Install) //函数在plugin.go中

}

上述helm插件安装方式。不依赖其他组件,并只是在插件目录检查plugin文件.因此,可手动创建目录也能够通过helm plugin list查到该插件。
插件使用:helm PLUGIN_NAME
helm在执行时,加载helm所有一级命令,并读取插件目录中的已安装插件。并在loadPlugins(helm/cmd/helm/load_plugins.go)函数中将plugin.yaml转换成helm命令(cobra.Command)
cobra.Command是Google开发的一个命令行库。还挺好用,值得推荐。kubernetes和helm的命令行用的也是这个库。
源码如下:
findPlugins查找插件目录。
manuallyProcessArgs,processParent加载一些参数(主要是环境变量,k8s集群)信息。
插件执行主要程序:plugin.yaml中的IgnoreFlags,UseTunnel分别在此刻用到了。代码逻辑分别在PrepareCommand函数中,UseTunnel在该函数中。
RunE会在执行插件命令调用。
func loadPlugins(baseCmd *cobra.Command, out io.Writer) {
found, err := findPlugins(settings.PluginDirs())
if err != nil {
fmt.Fprintf(os.Stderr, "failed to load plugins: %s", err)
return
}


processParent := func(cmd *cobra.Command, args []string) ([]string, error) {
k, u := manuallyProcessArgs(args)
if err := cmd.Parent().ParseFlags(k); err != nil {
return nil, err
}
return u, nil
}


for _, plug := range found {
plug := plug
md := plug.Metadata
if md.Usage == "" {
md.Usage = fmt.Sprintf("the %q plugin", md.Name)
}


c := &cobra.Command{
Use:   md.Name,
Short: md.Usage,
Long:  md.Description,
RunE: func(cmd *cobra.Command, args []string) error {
u, err := processParent(cmd, args)
if err != nil {
return err
}

plugin.SetupPluginEnv(settings, md.Name, plug.Dir)
main, argv := plug.PrepareCommand(u)


prog := exec.Command(main, argv...)
prog.Env = os.Environ()
prog.Stdin = os.Stdin
prog.Stdout = out
prog.Stderr = os.Stderr
if err := prog.Run(); err != nil {
if eerr, ok := err.(*exec.ExitError); ok {
os.Stderr.Write(eerr.Stderr)
return fmt.Errorf("plugin %q exited with error", md.Name)
}
return err
}
return nil
},
DisableFlagParsing: true,
}

if md.UseTunnel {
c.PreRunE = func(cmd *cobra.Command, args []string) error {
// Parse the parent flag, but not the local flags.
if _, err := processParent(cmd, args); err != nil {
return err
}
return setupConnection(cmd, args)
}
}


baseCmd.AddCommand(c)
}
}


插件执行主要程序:plug.PrepareCommand(u)(helm/pkg/plugin/plugin.go)
比较明显的看到:加载plugin.yaml文件中Command
func (p *Plugin) PrepareCommand(extraArgs []string) (string, []string) {
parts := strings.Split(os.ExpandEnv(p.Metadata.Command), " ")
main := parts[0]
baseArgs := []string{}
if len(parts) > 1 {
baseArgs = parts[1:]
}
if !p.Metadata.IgnoreFlags {
baseArgs = append(baseArgs, extraArgs...)
}
return main, baseArgs
}


因此,总体逻辑大致是查询插件目录(读取插件列表加入helm命令中)----加载环境变量(本地的环境变量,可能存在的k8s集群信息)----执行Command命令;

因为插件命令也会加入到helm命令中,因此,plugin.yaml中的Name不能跟原有的命令重复。