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

这篇具有很好参考价值的文章主要介绍了从k8s当中学习go cli脚手架开发利器-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 []stringerror {
   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 []stringerror {
   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 []stringerror {
   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 stringstring {
 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 []stringerror {
         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 stringerror {
   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文章来源地址https://www.toymoban.com/news/detail-834106.html

到了这里,关于从k8s当中学习go cli脚手架开发利器-cobra的文章就介绍完了。如果您还想了解更多内容,请在右上角搜索TOY模板网以前的文章或继续浏览下面的相关文章,希望大家以后多多支持TOY模板网!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处: 如若内容造成侵权/违法违规/事实不符,请点击违法举报进行投诉反馈,一经查实,立即删除!

领支付宝红包赞助服务器费用

相关文章

  • vue 脚手架新手入门(vue cli 3)

    vue 脚手架新手入门(vue cli 3)

    “:” 是指令 “v-bind”的缩写。用来绑定数据 @”是指令“v-on”的缩写。用来监听,并调用方法 下面是绑定class属性的数值。 监听点击动作 1.1、v-model 双向绑定 双向绑定 上面的操作等于 v-model 会将被绑定的值与 的值自动同步,这样我们就不必再使用事件处理函数了。 v-mo

    2024年02月09日
    浏览(382)
  • 使用vue-cli脚手架创建vue项目

    0.vue cli安装 vue cli2安装 vue cli2卸载 vue cli3安装 key通过命令查看当前安装的vue cli的版本 1. vue init vue init 是vue-cli2.x的初始化方式,可以使用github上面的一些模板来初始化项目 webpack是官方推荐的标准模板名。 vue-cli2.x项目向3.x迁移只需要把static目录复制到public目录下,老项目的

    2024年02月11日
    浏览(14)
  • Webpack项目学习:Vue-cli(脚手架)-优化配置 -ui库element-plus+减小打包体积 -按需加载+自定义主题+优化

    Webpack项目学习:Vue-cli(脚手架)-优化配置 -ui库element-plus+减小打包体积 -按需加载+自定义主题+优化

    安装 全部引入,在入口文件main.js  启动:npm start  按需引入 需要插件快速开始 | Element Plus (gitee.io)     更改默认配置 主题 | Element Plus (gitee.io)    如果有模块没有安装 ,安装一下即可 优化 关闭性能分析 文件单独打包 做缓存-

    2024年02月08日
    浏览(50)
  • mac下安装vue cli脚手架并搭建一个简易项目

    mac下安装vue cli脚手架并搭建一个简易项目

    1、确定本电脑下node和npm版本是否为项目所需版本。 2、下载vue脚手架 3、创建项目 如果有node,打开终端,输入node -v和npm -v , 确保node和npm的版本,(这里可以根据自己的需求去选择,如果对最新版本的内容有要求,也可以选择最新版本)如果没有node,可以点击nodejs官网去下载

    2024年02月15日
    浏览(54)
  • Vue2学习之第三章——Vue CLI脚手架

    Vue2学习之第三章——Vue CLI脚手架

    文档:https://cli.vuejs.org/zh/ 具体步骤 第一步(仅第一次执行):全局安装@vue/cli。 npm install -g @vue/cli 第二步:切换到你要创建项目的目录,然后使用命令创建项目 vue create xxxx 第三步:启动项目 npm run serve 备注: 如出现下载缓慢请配置 npm 淘宝镜像:npm config set registry https:/

    2024年01月19日
    浏览(591)
  • vue-cli 脚手架创建uniapp项目(微信小程序)

    vue-cli 脚手架创建uniapp项目(微信小程序)

    1、全局安装 vue-cli 脚手架(不建议用 5.0 版本,避免报错) 2、脚手架创建项目 3、选择 默认模板 即可 4、编译并启动项目  5、开发者工具,导入项目,注意路径 \\\"项目地址/dist/dev/mp-weixin\\\" 开发的规范 不能直接在开发者工具中修改代码,口诀:vscode做开发,开发者工具做调试

    2024年02月09日
    浏览(46)
  • 前端架构: 脚手架之Chalk和Chalk-CLI使用教程

    前端架构: 脚手架之Chalk和Chalk-CLI使用教程

    Chalk Chalk 是粉笔的意思, 它想表达的是,给我们的命令行中的文本添加颜色类似彩色粉笔的功能 在官方文档当中,它的 Highlights 核心特性 它的性能很高,没有三方依赖 它能够支持256以及真彩色的实现 也就是说这个库可以让你自己去定义它的色彩 并不是说命令行中当中的25

    2024年02月21日
    浏览(52)
  • 关于基于vue-cli脚手架创建vue项目(图文版)

    关于基于vue-cli脚手架创建vue项目(图文版)

    目录 一.vue-cli脚手架·概述(来源于官方文档) 二.创建流程 2.1 首先安装node.js,如未安装请移步到:安装node.js 2.2 安装脚手架vue-cli 2.2.1 使用npm install -g @vue/cli命令  2.2.1 使用vue -V 查看版本并检验是否安装成功  2.3 安装vue项目 2.3.1 使用命令 vue create 项目名 ​编辑 2.3.2 这里

    2024年02月07日
    浏览(150)
  • 从0搭建Vue3组件库(十):如何搭建一个 Cli 脚手架

    从0搭建Vue3组件库(十):如何搭建一个 Cli 脚手架

    本篇文章将实现一个名为 create-easyest 脚手架的开发,只需一个命令 npm init easyest 就可以将整个组件库开发框架拉到本地。 首先,我们在 packages 目录下新建 cli 目录,同执行 pnpm init 进行初始化,然后将包名改为 create-easyest 这里需要知道的是当我们执行 npm init xxx 或者 npm create xxx 的

    2024年02月08日
    浏览(47)
  • Vue--》超详细教程——vue-cli脚手架的搭建与使用

    Vue--》超详细教程——vue-cli脚手架的搭建与使用

    目录 vue-cli vue-cli 的安装 (可能出现的问题及其解决方法) vue-cli 创建 Vue 项目

    2024年01月17日
    浏览(47)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

博客赞助

微信扫一扫打赏

请作者喝杯咖啡吧~博客赞助

支付宝扫一扫领取红包,优惠每天领

二维码1

领取红包

二维码2

领红包