主页 > 互联网  > 

从k8s当中学习gocli脚手架开发利器-cobra


1.前言

大部分的项目都会引入cobra来作为项目的命令行解析工具,k8s当中大量使用cobra,学习借鉴一下k8s当中是如何使用cobra,在此记录一下。

2.cobra简介

cobra是一个提供简单接口来创建强大的现代CLI界面的库类似git  & git tools,cobra也是一个应用程序,它会生成你的应用程序的脚手架来快速开发基于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 main import (  "k8s.io/kubernetes/cmd/kubeadm/app"  kubeadmutil "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 app import (  "flag"  "os"  "github.com/spf13/pflag"  cliflag "k8s.io/component-base/cli/flag"  "k8s.io/klog/v2"  "k8s.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 above  pflag.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 string  cmds := &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.sum 4 directories, 7 files 3.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 cloud Usage:   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 shell   help        Help about any command   version     Print the version of cloud Flags:   -h, --help   help for cloud Use "cloud [command] --help" for more information about a command 3.3实战 mkdir cobra_project

/cmd/cloud.go文件

package main import (  "cobra_project/cmd/app"  "fmt"  "os" ) 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 app import (  "cobra_project/cmd/app/cmd"  "os" ) func Run() error {  cmd := cmd.NewCloudCommand(os.Stdin, os.Stdout, os.Stderr)  return cmd.Execute() }

/cmd/app/cmd/cmd.go文件

package cmd import (  cloudutil "cobra_project/cmd/app/cmd/util"  "github.com/spf13/cobra"  "io"  "regexp"  "strings" ) // NewCloudCommand returns cobra.Command to run kubeadm command func NewCloudCommand(in io.Reader, out, err io.Writer) *cobra.Command {  var rootfsPath string  cmds := &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 string  text = 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:    // it's 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 cmd import (    "encoding/json"    "fmt"    "github.com/pkg/errors"    "github.com/spf13/cobra"    "gopkg.in/yaml.v2"    "io" ) // 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 = "output"    of, 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 util import (    "os"    "path/filepath"    "syscall"    "github.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

标签:

从k8s当中学习gocli脚手架开发利器-cobra由讯客互联互联网栏目发布,感谢您对讯客互联的认可,以及对我们原创作品以及文章的青睐,非常欢迎各位朋友分享到个人网站或者朋友圈,但转载请说明文章出处“从k8s当中学习gocli脚手架开发利器-cobra

上一篇
uniappapp更新

下一篇
RustWeb小项目