golang Cobra 包简单使用

Cobra 是一个 Golang 包,提供了简单的接口来创建命令行程序。同时也很容易的生成应用程序和命令,cobra create appnamecobra add cmdname 等。

一、概念

cobra 中几个个重要的概念,分别是 commands、arguments 和 flags:

  • commands 代表行为
  • arguments 就是命令行参数(或者称为位置参数)
  • flags 代表对行为的改变(命令行选项)

二、初步使用

1. 安装

$ go get -u github.com/spf13/cobra/cobra

2. 创建应用

$ cobra init --pkg-name appname

image-20210428154327426

3. 增加命令

$ cobra add eula
$ cobra add kazuha

image-20210428155039659

这两条命令生成了两个相关文件。

4. 尝试使用

首先确定包名,我姑且设置为

kelu.org/apptest

对 import 做个修改。

image-20210429112413943

初始化 go mod:

go mod init kelu.org/apptest
go mod tidy

image-20210429112607233

编译

go build

在本目录下生成了 apptest 可执行文件,运行:

./apptest
./apptest eula
./apptest kazuha

image-20210429112731078

image-20210429113707028

5. 代码结构

对着几个子命令文件 var kazuhaCmd = &cobra.Command{ 以下的代码看下就能知道大致的开发套路了。

image-20210429113106610

每个命令的内容都相对简单:

image-20210429113254806

三、编码开发

命令行中选项(flags)和参数(arguments)的区别,以 ls 命令为例:

ls -alh kelu*
  • alh对应的是选项(flags),以 - 或 – 开头
  • kelu* 对应的是参数(arguments),一般参数在所有选项

0. 具体功能

Run: func(cmd *cobra.Command, args []string) {
    fmt.Println("cobra demo program") // 在这里实现
},

1. 添加选项(flags)

根据选项的作用范围,可以把选项分为两类:

  • persistent,对于一些全局性的选项,比较适合设置为 persistent 类型,比如控制输出的 verbose 选项:既可以设置给该 Command,又可以设置给该 Command 的子 Command。

    var Verbose bool
    rootCmd.PersistentFlags().BoolVarP(&Verbose, "verbose", "v", false, "verbose output")
    
  • local,只能设置给指定的 Command。

    var Source string
    rootCmd.Flags().StringVarP(&Source, "source", "s", "", "Source directory to read from")
    

2. 添加参数(arguments)

cobra 默认提供了一些验证方法:

  • NoArgs - 如果存在任何位置参数,该命令将报错
  • ArbitraryArgs - 该命令会接受任何位置参数
  • OnlyValidArgs - 如果有任何位置参数不在命令的 ValidArgs 字段中,该命令将报错
  • MinimumNArgs(int) - 至少要有 N 个位置参数,否则报错
  • MaximumNArgs(int) - 如果位置参数超过 N 个将报错
  • ExactArgs(int) - 必须有 N 个位置参数,否则报错
  • ExactValidArgs(int) 必须有 N 个位置参数,且都在命令的 ValidArgs 字段中,否则报错
  • RangeArgs(min, max) - 如果位置参数的个数不在区间 min 和 max 之中,报错
var cmdTimes = &cobra.Command{
    Use: …
    Short: …
    Long: …
    Args: cobra.MinimumNArgs(1),
    Run: …
}

3. 帮助信息

cmd.SetHelpCommand(cmd *Command)
cmd.SetHelpFunc(f func(*Command, []string))
cmd.SetHelpTemplate(s string)

4. 提示信息

cmd.SetUsageFunc(f func(*Command) error)
cmd.SetUsageTemplate(s string)

5. prerun/postrun

var rootCmd = &cobra.Command{
    Use:   "cobrademo",
    Short: "sparkdev's cobra demo",
    Long: "the demo show how to use cobra package",
    PersistentPreRun: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Inside rootCmd PersistentPreRun with args: %v\n", args)
    },
    PreRun: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Inside rootCmd PreRun with args: %v\n", args)
    },
    Run: func(cmd *cobra.Command, args []string) {
        fmt.Printf("cobra demo program, with args: %v\n", args)
    },
    PostRun: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Inside rootCmd PostRun with args: %v\n", args)
    },
    PersistentPostRun: func(cmd *cobra.Command, args []string) {
        fmt.Printf("Inside rootCmd PersistentPostRun with args: %v\n", args)
    },
}

四、demo

package cmd

import (
	"fmt"
	"strings"

	"github.com/spf13/cobra"
)

// kazuhaCmd represents the kazuha command
var kazuhaCmd = &cobra.Command{
	Use:   "kazuha",
	Short: "A brief description of your command",
	Long: `A longer description that spans multiple lines and likely contains examples
and usage of using your command. For example:

Cobra is a CLI library for Go that empowers applications.
This application is a tool to generate the needed files
to quickly create a Cobra application.`,
	Run: func(cmd *cobra.Command, args []string) {
		fmt.Println("kazuha args:" + strings.Join(args, " "))
	},
}

var times int
var kaedeharaCmd = &cobra.Command{
	Use:   "kaedehara",
	Short: "kaedehara 楓原",
	Long:  `kaedehara 楓原万叶`,
	Args:  cobra.MinimumNArgs(1),
	Run: func(cmd *cobra.Command, args []string) {
		for i := 0; i < times; i++ {
			fmt.Println("kaedehara args:" + strings.Join(args, " "))
		}
	},
}

func init() {
	rootCmd.AddCommand(kazuhaCmd)
	kaedeharaCmd.Flags().IntVarP(&times, "times", "t", 1, "times loop for input")
	kazuhaCmd.AddCommand(kaedeharaCmd)

	// Here you will define your flags and configuration settings.

	// Cobra supports Persistent Flags which will work for this command
	// and all subcommands, e.g.:
	// kazuhaCmd.PersistentFlags().String("foo", "", "A help for foo")

	// Cobra supports local flags which will only run when this command
	// is called directly, e.g.:
	// kazuhaCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}

image-20210429145243364

参考资料


使用 dnspod 设置自定义解析

dnspod的专业版,可以自己定义某 IP 段去访问指定的主机服务,可以更细致的区分线路,或者屏蔽某个 DNS/区域用户访问网站。

这里记录如何查找本地网络 dns 的出口 IP。

  1. 首先确定本地的IP

    curl -s http://myip.ipip.net
    

    假如是 a.b.c.d

  2. 确定本地 ip 的出口IP段

    whois a.b.c.d
    

    你将得到这个IP的一些信息。查看 路由(route) 的信息,一般是一个b段或c段IP地址:

    a.b.0.0/16
    或者
    a.b.c.0/24
    
  3. 将这个IP段写入自定义线路配置

参考资料


vscode code server 配置 golang 和 php 开发环境

前几天更新了一篇blog,记录了怎么搭建 vscode code server。在使用场景中呢,我们不会使用一个没有语言环境的IDE,某些 IDE 的插件就是依赖于语言环境的。我日常使用 golang 和 php 比较多,这篇文章记录如何在 code server 中配置 golang 和 php 环境。

一、编译镜像

基于上一篇文章,得到了镜像 kelvinblood/code-server:base,在这个镜像的基础上继续编译新镜像。

有以下几个注意点:

  1. 基础镜像为先前编译的镜像 kelvinblood/code-server:base
  2. 配置了 golang 所需的环境变量 GOROOT 和 GOPATH,并打开了 GO111MODULE。
  3. 安装了 wget/ping/telnet 等常用的工具,方便定位问题。
  4. 安装了一些常用的 vscode 的 golang 插件。
  5. 安装了 go版本 1.16.3, php 版本7.1.5 和 最新版的composer。
FROM kelvinblood/code-server:base

ARG GOLANG_VERSION=1.16.3
ARG PHP_VERSION=7.1.5
ENV GOROOT /usr/local/go
ENV GOPATH /var/local/go
ENV PATH $PATH:$GOROOT/bin:$GOPATH/bin
ENV GO111MODULE on
ENV GOPROXY https://goproxy.cn

RUN \
 echo "**** install tools ****" && \
 apt-get update && \
 apt-get install -y \
	wget \
	iputils-ping \
	xinetd telnetd && \
 echo "**** install golang ****" && \
 GO_TGZ=go$(echo "$GOLANG_VERSION").linux-amd64.tar.gz && \
 wget https://golang.org/dl/${GO_TGZ} && \
 tar zxvf ${GO_TGZ} && \
 mv go /usr/local && \
 echo "**** go get for vscode ****" && \
 go get -v golang.org/x/tools/gopls && \
 go get -v github.com/ramya-rao-a/go-outline && \
# go get github.com/uudashr/gopkgs/cmd/gopkgs@latest && \
 go get -v github.com/cweill/gotests/gotests && \
 go get -v github.com/fatih/gomodifytags && \
 go get -v github.com/josharian/impl && \
 go get -v github.com/haya14busa/goplay/cmd/goplay && \
 go get -v github.com/go-delve/delve/cmd/dlv && \
 go get -v honnef.co/go/tools/cmd/staticcheck && \
 go get -v golang.org/x/tools/gopls && \
 echo "**** install php ****" && \
  apt-get -y install libssl-dev libcurl4-openssl-dev libbz2-dev libjpeg-dev libpng-dev libgmp-dev libicu-dev libmcrypt-dev freetds-dev libxslt-dev libcurl3-dev autoconf dpkg-dev file g++ gcc libc-dev make pkg-config re2c && \
  ln -s /usr/lib/x86_64-linux-gnu/libsybdb.a /usr/lib/libsybdb.a && \
  ln -s /usr/lib/x86_64-linux-gnu/libsybdb.so /usr/lib/libsybdb.so && \
  ln -s /usr/lib/x86_64-linux-gnu/libct.a /usr/lib/libct.a && \
  ln -s /usr/lib/x86_64-linux-gnu/libct.so /usr/lib/libct.so && \
  ln -s /usr/include/x86_64-linux-gnu/gmp.h /usr/include/gmp.h && \
  ln -s /usr/include/x86_64-linux-gnu/curl /usr/include/curl && \
  PHP_TGZ=php-${PHP_VERSION}.tar.gz && \
  wget http://am1.php.net/distributions/${PHP_TGZ} && \
  tar -xzvf ${PHP_TGZ} && \
  cd php-${PHP_VERSION} && \
  ./configure --prefix /usr/share/php7 --enable-fpm --enable-mbstring --enable-zip --enable-calendar --enable-bcmath --enable-exif --enable-intl --enable-opcache --enable-shmop --enable-soap --enable-sockets --with-fpm-user=www-data --with-fpm-group=www-data --with-pcre-regex --with-kerberos --with-openssl --with-mcrypt --with-zlib --with-bz2 --with-curl --with-gd --with-jpeg-dir=/usr/include/jpeg8 --with-png-dir=/usr/include/libpng12 --with-gettext --with-gmp --with-mhash --with-xsl && \
  make clean && \
  make && make install && \
  make test && \
  make clean && \
  ln -s /usr/share/php7/sbin/php-fpm /usr/local/bin/php-fpm && \
  ln -s /usr/share/php7/bin/php /usr/local/bin/php && \
  cd .. && \
  curl -sS https://getcomposer.org/installer | sudo php -- --install-dir=/usr/local/bin --filename=composer && \
 echo "**** clean up ****" && \
 pwd && \
 apt-get clean && \
 rm -rf \
	/tmp/* \
	/var/lib/apt/lists/* \
	/var/tmp/* \
        ${GO_TGZ} \
        ${PHP_TGZ}

docker build 需要比较长的时间,主要看机器性能和网络速度,需要一点耐心,快的也要10分钟,慢的大概要40分钟。

php 编译需要的内存比较高,如果只有1G内存的话最好把 swap 打开。

相应的 docker-compose.yml 文件和先前的没有差别。

二、常用插件

如下图所示,重要的就是这几个:

  • vim 是必不可少的
  • 简体汉化包
  • go包
  • laravel包
  • php Debug包

image-20210412141932034

三、主题和字体

左下角 -> 设置-> 颜色主题,不用看过多的主题,直奔Solarized Dark

image-20210412142238199

修改字体大小,默认14号字体太小了,改成18号

image-20210412142404907


go 命令备忘

这里简单记录几个常用的go命令。

go build

Go语言的编译速度非常快。Go 1.9 版本后默认利用Go语言的并发特性进行函数粒度的并发编译。

Go语言的程序编写基本以源码方式,无论是自己的代码还是第三方代码。

Go语言以 GOPATH 作为工作目录和一套完整的工程目录规则。因此 Go 语言中编译时无须像 C++ 一样配置各种包含路径、链接库地址等。

go build 命令主要用于编译代码,若有必要,会同时编译与之相关联的包。

go build 有很多种编译方法,如无参数编译、文件列表编译、指定包编译等,使用这些方法都可以输出可执行文件。

注意:需要编译的项目/文件一定要有 main package,且包含main函数,否则无法 build。

  1. go build 命令默认会编译当前目录下的所有 go 文件。如果没有main package,不会产生任何文件。

  2. 如果你只想编译其中某一个文件,可以在 go build 之后加上文件名,例如 go build a.go。

  3. 如果需要在 $GOPATH/bin 目录下生成相应的可执行文件,需要执行 go install 或者使用 go build -o </path/xxx>。

  4. go build 会忽略目录下以”_”或者”.”开头的go文件。

  5. 如果你的源代码针对不同的操作系统需要不同的处理,那么你可以根据不同的操作系统后缀来命名文件。例如有一个读取数组的程序,它对于不同的操作系统可能有如下几个源文件:

    array_linux.go 
    array_darwin.go 
    array_windows.go 
    array_freebsd.go
    

    go build 的时候会选择性地编译以系统名结尾的文件(Linux、Darwin、Windows、Freebsd)。例如Linux系统下面编译只会选择array_linux.go文件,其它系统命名后缀文件全部忽略。

go clean

用来移除当前源码包里面编译生成的文件

go get

用来动态获取远程代码包的,目前支持的有BitBucket、GitHub、Google Code和Launchpad。

这个命令在内部实际上分成了两步操作:

  1. 下载源码包
  2. 执行go install。

下载源码包的go工具会自动根据不同的域名调用不同的源码工具。

go install

go install 命令在内部实际上分成了两步操作:

  1. 生成结果文件(可执行文件或者.a包)
  2. 把结果移到 $GOPATH/pkg 或者 $GOPATH/bin

go test

自动读取源码目录下面名为 *_test.go 的文件,生成并运行测试用的可执行文件。

go doc

go doc 可以打印当前目录下文件的方法和类型定义

go doc
go doc -u xxxStruct
go doc http.Request

godoc

go get -v  golang.org/x/tools/cmd/godoc
godoc -http=:6060

以网页方式展现的Go文档,使得我们在不方便访问Go语言官方站点的情况下也可以查看Go语言文档,并且可以看自己代码的文档。

参考资料


搭建个人在线IDE —— vscode online

前年,微软在 Ignite 2019 大会上,正式发布了 「Visual Studio Online」。其中包含了微软托管的 WebVSCode,后来发布的 VSCode 1.40 支持开发者直接从 VSCode 的源代码编译出 WebVSCode

由于我目前开发环境众多,win/mac/Linux都有,虽然已经对代码做了容器化,还是需要重复下载代码,配置IDE环境,挺麻烦的。这篇文章简单记录我搭建在线IDE的过程。

安装

配置VSCode Online有几种方法:

  • 微软官方提供一个收费版本(含azure的服务器费用,捆绑销售),不推荐
  • 下载 VSCode 源代码,编译以后通过yarn web启动。配置难度大,不推荐
  • 通过 Code-Server 安装: https://code-server.dev/
  • 使用/修改现成的 docker 镜像:linuxserver/docker-code-server

我目前的方法是自行编译 linuxserver 提供的镜像。实际上只用linuxserver 的镜像已经足够了,没有特殊自定义要求的可以跳过这一部分。

这个文件是 code-server实际的启动命令,可以按需自定义修改:

#!/usr/bin/with-contenv bash

if [ -n "${PASSWORD}" ] || [ -n "${HASHED_PASSWORD}" ]; then
  AUTH="password"
else
  AUTH="none"
  echo "starting with no password"
fi

if [ -z ${PROXY_DOMAIN+x} ]; then
  PROXY_DOMAIN_ARG=""
else
  PROXY_DOMAIN_ARG="--proxy-domain=${PROXY_DOMAIN}"
fi

exec \
	s6-setuidgid abc \
		/usr/local/bin/code-server \
			--bind-addr 0.0.0.0:8443 \
			--user-data-dir /config/data \
			--extensions-dir /config/extensions \
			--disable-telemetry \
			--auth "${AUTH}" \
			"${PROXY_DOMAIN_ARG}" \
			/config/workspace

例如我将端口由 8443 改成了 80。

--bind-addr 0.0.0.0:80

修改 dockerfile 中的 expose 内容

EXPOSE 8443

改为

EXPOSE 80

在项目主目录进行编译:

docker build \
  --no-cache \
  --pull \
  -t kelvinblood/code-server:base .

运行

参考 https://github.com/linuxserver/docker-code-server 的帮助内容,我做了调整:

docker-compose.yml:

version: "2.1"
services:
  code-server:
    image: kelvinblood/code-server:base
    container_name: code-server
    network_mode: bridge
    environment:
      - PUID=0
      - PGID=0
      - TZ=Asia/Shanghai
      - PASSWORD=xxxx  #optional
#      - HASHED_PASSWORD= #optional
#      - SUDO_PASSWORD=abcd #optional
#      - SUDO_PASSWORD_HASH= #optional
      - PROXY_DOMAIN=xxx.kelu.org #optional
    volumes:
      - ./config:/config
      - /root/abc:/Workspace/abc
      - /root/xxx:/Workspace/xxx
#    ports:
#      - xxxx:80
    restart: unless-stopped

配置nginx

image-20210407170010631

在这一块我做多了几个安全认证,这几项都不是必须的,可以跳过,一个是使用https访问:

  ssl_certificate /etc/letsencrypt/live/code.kelu.org/fullchain.pem;
  ssl_certificate_key /etc/letsencrypt/live/code.kelu.org/privkey.pem;
  ssl_session_timeout 5m;
  ssl_protocols TLSv1 TLSv1.1 TLSv1.2;
  ssl_ciphers AESGCM:ALL:!DH:!EXPORT:!RC4:+HIGH:!MEDIUM:!LOW:!aNULL:!eNULL;
  ssl_prefer_server_ciphers on;

一个是nginx密码

  auth_basic "Please input password";
  auth_basic_user_file /etc/nginx/passwd/goaccess;

一个是允许特定IP访问:

     satisfy any;
     allow xxx;
     allow xxx;
     deny all;

最后要注意配置 websocket 代理配置,最后两行:

  location / {
     satisfy any;
     allow 121.31.34.22;
     allow 61.51.94.34;
     deny all;

     proxy_pass http://code-server.bj1.local;
     proxy_read_timeout 300s;
     proxy_send_timeout 300s;

     proxy_set_header Host $host;
     proxy_set_header X-Real-IP $remote_addr;
     proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
     
     proxy_set_header Accept-Encoding gzip;

     proxy_set_header Upgrade $http_upgrade;
     proxy_set_header Connection "upgrade";
  }
}

界面预览

image-20210407170700847

装上自己需要的插件,就可以开始开发了。

image-20210407171100100

参考资料


go 字符串拆分 split

简单记录 golang 使用 regexp.MustCompile 进行字符串拆分的用法。

假设目前的输入值为:

        "spec":{  
            "Container": {
                "default.cpu": "200m",
                "default.memory": "200Mi",
                "defaultRequest.cpu": "100m",
                "defaultRequest.memory": "100Mi",
                "max.cpu": "2",
                "max.memory": "1Gi",
                "maxLimitRequestRatio.cpu": "5",
                "maxLimitRequestRatio.memory": "4",
                "min.cpu": "100m",
                "min.memory": "3Mi"
            },
            "Pod": {
                "max.cpu": "2",
                "max.memory": "1Gi",
                "maxLimitRequestRatio.cpu": "5",
                "maxLimitRequestRatio.memory": "4",
                "min.cpu": "100m",
                "min.memory": "3Mi"
            }
        }

我们将 . 号前后的字符进行拆分,整合 key,append 最后输出为类似如下格式的 interface:

image-20210323120833531

嵌套两个 for 循环即可完成。

func exchange(spec interface{}) interface{} {
    var result []interface{}

	for kind, u := range spec.(map[string]interface{}) {
		limitsRule := make(map[string]interface{})
		limitsRule["type"] = kind
		for k, _ := range u.(map[string]interface{}) {
			s := regexp.MustCompile("[.!?]").Split(k, 2)
			limitsRule[s[0]] = nil
		}

		for k, v := range u.(map[string]interface{}) {
			s := regexp.MustCompile("[.!?]").Split(k, 2)
			var tmp map[string]string
			if limitsRule[s[0]] == nil {
				tmp = make(map[string]string)
			} else {
				tmp = limitsRule[s[0]].(map[string]string)
			}
			tmp[s[1]] = v.(string)
			limitsRule[s[0]] = tmp
		}

		result = append(result, limitsRule)
	}

	return result
}

参考资料