江苏省高职重点专业群建设网站,专门做效果图的网站,互联网软件,中信建设四川分公司招聘1.前言
大部分的项目都会引入cobra来作为项目的命令行解析工具#xff0c;k8s当中大量使用cobra#xff0c;学习借鉴一下k8s当中是如何使用cobra#xff0c;在此记录一下。
2.cobra简介
cobra是一个提供简单接口来创建强大的现代CLI界面的库类似git git toolsk8s当中大量使用cobra学习借鉴一下k8s当中是如何使用cobra在此记录一下。
2.cobra简介
cobra是一个提供简单接口来创建强大的现代CLI界面的库类似git git toolscobra也是一个应用程序它会生成你的应用程序的脚手架来快速开发基于cobra的应用程序 cobra提供 简单的基于子命令的命令行app server、app fetch 等等 完全符合POSIX的标志包含短版本和长版本 嵌套子命令 全局、本地和级联的标志 使用 cobra init appname和cobra add cmdname 可以很容易生成应用程序和命令 智能提示app srver... did you mean app server? 自动生成命令和标志 自动识别 -h --help 等等为help标志 为应用程序自动shell补全bash、zsh、fish、powershell 为应用程序自动生成手册 命令别名 灵活定义帮助、用法等等 可选的与viper的紧密集成
3.分析
kubernetes当中的组件都是大量使用cobra这里挑选kubeadm的cora实现来模仿分析。
从入口开始
// cmd/kubeadm/kubeadm.go
package mainimport (k8s.io/kubernetes/cmd/kubeadm/appkubeadmutil k8s.io/kubernetes/cmd/kubeadm/app/util
)func main() {kubeadmutil.CheckErr(app.Run())
}此处直接调用了 app.Run() 对于golang的工程而言在cmd的第一层启动目录往往是越薄越好【1】所以此处包装了将真正的启动逻辑封装到到**app.Run()**当中。
app.Run() 的调用位置在cmd/kubeadm/app/kubeadm.go
package appimport (flagosgithub.com/spf13/pflagcliflag k8s.io/component-base/cli/flagk8s.io/klog/v2k8s.io/kubernetes/cmd/kubeadm/app/cmd
)// Run creates and executes new kubeadm command
func Run() error {klog.InitFlags(nil)pflag.CommandLine.SetNormalizeFunc(cliflag.WordSepNormalizeFunc)pflag.CommandLine.AddGoFlagSet(flag.CommandLine)pflag.Set(logtostderr, true)// We do not want these flags to show up in --help// These MarkHidden calls must be after the lines abovepflag.CommandLine.MarkHidden(version)pflag.CommandLine.MarkHidden(log-flush-frequency)pflag.CommandLine.MarkHidden(alsologtostderr)pflag.CommandLine.MarkHidden(log-backtrace-at)pflag.CommandLine.MarkHidden(log-dir)pflag.CommandLine.MarkHidden(logtostderr)pflag.CommandLine.MarkHidden(stderrthreshold)pflag.CommandLine.MarkHidden(vmodule)cmd : cmd.NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr)return cmd.Execute()
}在Run()在设定了一系列的参数信息后创建了cmd对象并执行cmd对象的Execute()这里的cmd对象就是一个cobra命令对象而Execute是cobra提供执行命令的方法cobra内部使用pflag库通过设置 pflag 属性可以对 cobra 的运行产生作用。pflag 也兼容 golang flag 库此处通过 AddGoFlagSet(flag.CommandLine) 实现了对 golang flag 的兼容。
cobra对象如何生成的是我们需要关心的**NewKubeadmCommand(os.Stdin, os.Stdout, os.Stderr)**的实现在cmd/kubeadm/app/cmd/cmd.go
// NewKubeadmCommand returns cobra.Command to run kubeadm command
func NewKubeadmCommand(in io.Reader, out, err io.Writer) *cobra.Command {var rootfsPath stringcmds : cobra.Command{Use: kubeadm,Short: kubeadm: easily bootstrap a secure Kubernetes cluster,Long: dedent.Dedent(┌──────────────────────────────────────────────────────────┐│ KUBEADM ││ Easily bootstrap a secure Kubernetes cluster ││ ││ Please give us feedback at: ││ https://github.com/kubernetes/kubeadm/issues │└──────────────────────────────────────────────────────────┘Example usage:Create a two-machine cluster with one control-plane node(which controls the cluster), and one worker node(where your workloads, like Pods and Deployments run).┌──────────────────────────────────────────────────────────┐│ On the first machine: │├──────────────────────────────────────────────────────────┤│ control-plane# kubeadm init │└──────────────────────────────────────────────────────────┘┌──────────────────────────────────────────────────────────┐│ On the second machine: │├──────────────────────────────────────────────────────────┤│ worker# kubeadm join arguments-returned-from-init │└──────────────────────────────────────────────────────────┘You can then repeat the second step on as many other machines as you like.),SilenceErrors: true,SilenceUsage: true,PersistentPreRunE: func(cmd *cobra.Command, args []string) error {if rootfsPath ! {if err : kubeadmutil.Chroot(rootfsPath); err ! nil {return err}}return nil},}cmds.ResetFlags()cmds.AddCommand(newCmdCertsUtility(out))cmds.AddCommand(newCmdCompletion(out, ))cmds.AddCommand(newCmdConfig(out))cmds.AddCommand(newCmdInit(out, nil))cmds.AddCommand(newCmdJoin(out, nil))cmds.AddCommand(newCmdReset(in, out, nil))cmds.AddCommand(newCmdVersion(out))cmds.AddCommand(newCmdToken(out, err))cmds.AddCommand(upgrade.NewCmdUpgrade(out))cmds.AddCommand(alpha.NewCmdAlpha())options.AddKubeadmOtherFlags(cmds.PersistentFlags(), rootfsPath)cmds.AddCommand(newCmdKubeConfigUtility(out))return cmds
}NewKubeadmCommand() 首先构造了 kubeadm的根命令对象cmds也就是 kubeadm 命令然后依次将kubeadm的子命令例如init、join、version等命令通过cmds.AddCommand方法添加到 cmds 对象cmd/kubeadm/app/kubeadm.go 中末尾执行的 cmd.Execute() 正是执行的这个 cmds 的 Execute() 方法
子命令当中NewCmdVersion()较为简单源码位置cmd/kubeadm/app/cmd/version.go
// newCmdVersion provides the version information of kubeadm.
func newCmdVersion(out io.Writer) *cobra.Command {cmd : cobra.Command{Use: version,Short: Print the version of kubeadm,RunE: func(cmd *cobra.Command, args []string) error {return RunVersion(out, cmd)},Args: cobra.NoArgs,}cmd.Flags().StringP(output, o, , Output format; available options are yaml, json and short)return cmd
}3.依样画葫芦
3.1目录结构
➜ cobra_project tree -CL 5
.
├── cmd
│ ├── app
│ │ ├── cloud.go
│ │ └── cmd
│ │ ├── cmd.go
│ │ ├── util
│ │ │ └── chroot_unix.go
│ │ └── version.go
│ └── cloud.go
├── go.mod
└── go.sum4 directories, 7 files3.2效果展示
➜ cobra_project go run cmd/cloud.go version
cloud version: 1.5.0
➜ cobra_project go run cmd/cloud.go version -h
Print the version of cloudUsage:cloud version [flags]Flags:-h, --help help for version-o, --output string Output format; available options are yaml, json and short
➜ cobra_project go run cmd/cloud.go version -o json
{clientVersion: 1.5.0
}
➜ cobra_project go run cmd/cloud.go┌──────────────────────────────────────────────────────────┐
│ This is cloud tools description │
│ │
└──────────────────────────────────────────────────────────┘Usage:cloud [command]Available Commands:completion Generate the autocompletion script for the specified shellhelp Help about any commandversion Print the version of cloudFlags:-h, --help help for cloudUse cloud [command] --help for more information about a command3.3实战
mkdir cobra_project/cmd/cloud.go文件
package mainimport (cobra_project/cmd/appfmtos
)func main() {if err : app.Run(); err ! nil {fmt.Fprintf(os.Stderr, error: %v\n, err)os.Exit(1)}os.Exit(0)
}/cmd/app/cloud.go文件
package appimport (cobra_project/cmd/app/cmdos
)func Run() error {cmd : cmd.NewCloudCommand(os.Stdin, os.Stdout, os.Stderr)return cmd.Execute()
}/cmd/app/cmd/cmd.go文件
package cmdimport (cloudutil cobra_project/cmd/app/cmd/utilgithub.com/spf13/cobraioregexpstrings
)// NewCloudCommand returns cobra.Command to run kubeadm command
func NewCloudCommand(in io.Reader, out, err io.Writer) *cobra.Command {var rootfsPath stringcmds : cobra.Command{Use: cloud,Short: cloud is powerful cloud native tool,Long: Dedent(┌──────────────────────────────────────────────────────────┐│ This is cloud tools description ││ │└──────────────────────────────────────────────────────────┘),SilenceErrors: true,SilenceUsage: true,PersistentPreRunE: func(cmd *cobra.Command, args []string) error {if rootfsPath ! {if err : cloudutil.Chroot(rootfsPath); err ! nil {return err}}return nil},}cmds.AddCommand(newCmdVersion(out))return cmds
}var (whitespaceOnly regexp.MustCompile((?m)^[ \t]$)leadingWhitespace regexp.MustCompile((?m)(^[ \t]*)(?:[^ \t\n]))
)func Dedent(text string) string {var margin stringtext whitespaceOnly.ReplaceAllString(text, )indents : leadingWhitespace.FindAllStringSubmatch(text, -1)// Look for the longest leading string of spaces and tabs common to all// lines.for i, indent : range indents {if i 0 {margin indent[1]} else if strings.HasPrefix(indent[1], margin) {// Current line more deeply indented than previous winner:// no change (previous winner is still on top).continue} else if strings.HasPrefix(margin, indent[1]) {// Current line consistent with and no deeper than previous winner:// its the new winner.margin indent[1]} else {// Current line and previous winner have no common whitespace:// there is no margin.margin break}}if margin ! {text regexp.MustCompile((?m)^margin).ReplaceAllString(text, )}return text
}/cmd/app/cmd/version文件
package cmdimport (encoding/jsonfmtgithub.com/pkg/errorsgithub.com/spf13/cobragopkg.in/yaml.v2io
)// Version provides the version information of cloud
type Version struct {ClientVersion string json:clientVersion
}func newCmdVersion(out io.Writer) *cobra.Command {cmd : cobra.Command{Use: version,Short: Print the version of cloud,RunE: func(cmd *cobra.Command, args []string) error {return RunVersion(out, cmd)},Args: cobra.NoArgs,}cmd.Flags().StringP(output, o, , Output format; available options are yaml, json and short)return cmd
}// RunVersion provides the version information of kubeadm in format depending on arguments
// specified in cobra.Command.
func RunVersion(out io.Writer, cmd *cobra.Command) error {v : Version{ClientVersion: 1.5.0,}const flag outputof, err : cmd.Flags().GetString(flag)if err ! nil {return errors.Wrapf(err, error accessing flag %s for command %s, flag, cmd.Name())}switch of {case :fmt.Fprintf(out, cloud version: %#v\n, v.ClientVersion)case short:fmt.Fprintf(out, %s\n, v.ClientVersion)case yaml:y, err : yaml.Marshal(v)if err ! nil {return err}fmt.Fprintln(out, string(y))case json:y, err : json.MarshalIndent(v, , )if err ! nil {return err}fmt.Fprintln(out, string(y))default:return errors.Errorf(invalid output format: %s, of)}return nil
}/cmd/app/cmd/util/chroot_unix.go文件
package utilimport (ospath/filepathsyscallgithub.com/pkg/errors
)// Chroot chroot()s to the new path.
// NB: All file paths after this call are effectively relative to
// rootfs
func Chroot(rootfs string) error {if err : syscall.Chroot(rootfs); err ! nil {return errors.Wrapf(err, unable to chroot to %s, rootfs)}root : filepath.FromSlash(/)if err : os.Chdir(root); err ! nil {return errors.Wrapf(err, unable to chdir to %s, root)}return nil
}4.总结
对于云开发者而言开发的时候可以多借鉴cncf项目当中的一些优秀的用法笔者在这块相对比较薄弱最近也在恶补这块的习惯共勉。
【1】cmd目录下的第一层逻辑通常建议比较薄可以参考k8当中的所有组件下的cmd目录以及golang工程标准的项目结构建议https://github.com/golang-standards/project-layout