YJWANG

[Go-lang] Cobra를 사용해서 cli 프로그램 개발 본문

90.Programming

[Go-lang] Cobra를 사용해서 cli 프로그램 개발

왕영주 2021. 4. 30. 16:44

cobra 사용해서 cli tool을 개발해보고자 한다. 아무래도 Infra 작업을 하다보면 cli tool이 있으면 편하겠다는 생각을 하곤하는데

생각한 김에 만들어보고자합니다. cobra 프로젝트는 kubectl에서도 사용하는 프로젝트여서 잘 사용하고 익숙해지면 앞으로 작업 자동화에도 편할 것으로 보입니다.

우선 동작하는 방식에 대해 포스팅한 글로 프로그램은 현재 Ubuntu에서만 동작합니다.

refer to


구성 환경


$ cat /etc/os-release
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"

$ go version
go version go1.16.3 linux/amd64

사전 구성


프로젝트를 시작할 Dir를 생성하고 해당 Dir에서 GOPATH를 지정합니다.

~/문서/projects/golang/go-cli$ export GOPATH=$PWD

pkg 설치

# go get github.com/spf13/cobra/cobra

설치하면 해당 경로에 아래와 같이 파일이 자동으로 생성됩니다.

$ tree -L 2
.
├── bin
│   └── cobra
└── pkg
    ├── mod
    └── sumdb

cobra command를 이용해서 demo project를 생성합니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli$ ./bin/cobra init --pkg-name yjwang-init src/yjwan
g-init
Your Cobra application is ready at
/home/yjwang/문서/projects/golang/go-cli/src/yjwang-init

아래와 같이 파일들과 폴더가 자동으로 생성됩니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli$ ls -l src/yjwang-init/
합계 20
-rw-rw-r-- 1 yjwang yjwang 11358  4월 29 15:27 LICENSE
drwxr-x--x 2 yjwang yjwang  4096  4월 29 15:27 cmd
-rw-rw-r-- 1 yjwang yjwang   646  4월 29 15:27 main.go

이후 기본적으로 생성된 main.go를 실행 시켜 줍니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli$ cd src/yjwang-init/
yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go run main.go
main.go:18:8: package yjwang-init/cmd is not in GOROOT (/usr/local/go/src/yjwang-init/cmd)

필요한 디펜던시들을 읽어오지 못해 위 같은 에러가 발생한다.
Go 1.16 버전부터는 GO111MODULE 값이 on이기 때문에 go mod init으로 모듈을 추가해준다. 이후 필요 모듈을 현재 GOPATH에 추가해준다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go mod init
go: creating new go.mod: module yjwang-init
go: to add module requirements and sums:
        go mod tidy
yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go mod tidy
go: finding module for package github.com/spf13/viper
go: finding module for package github.com/mitchellh/go-homedir
go: finding module for package github.com/spf13/cobra
go: found github.com/mitchellh/go-homedir in github.com/mitchellh/go-homedir v1.1.0
go: found github.com/spf13/cobra in github.com/spf13/cobra v1.1.3
go: found github.com/spf13/viper in github.com/spf13/viper v1.7.1

그럼 아래와 같이 go.mod 파일이 생성된다.

module yjwang-init

go 1.16

require (
    github.com/mitchellh/go-homedir v1.1.0
    github.com/spf13/cobra v1.1.3
    github.com/spf13/viper v1.7.1
)

이후 main.go를 실행시켜본다. 이제 개발할 환경은 기본적으로 구성이 됐다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go run main.go
A longer description that spans multiple lines and likely contains
examples and usage of using your application. 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.

구성안


간단하게 아래 두 기능이 동작될 수 있도록 개발 해보겠습니다.

1.
# yjwang-init get
sub command를 입력하지 않으면 help 출력

2.
# yjwang-init get os-info
os 배포판 버전 및 kernel 버전 출력

개발


우선 cobra command를 이용해서 sub command인 get을 추가합니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ ../../bin/cobra add get
get created at /home/yjwang/문서/projects/golang/go-cli/src/yjwang-init

그럼 아래와 같이 cmd 경로에 get.go 파일이 생성됩니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ ls -l cmd/get.go
-rw-rw-r-- 1 yjwang yjwang 1580  4월 30 16:22 cmd/get.go

이제 1번 요구사항부터 될 수 있도록 개발합니다.

/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
    "github.com/spf13/cobra"
)

// getCmd represents the get command
var getCmd = &cobra.Command{
    Use:   "get",
    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) {
        cmd.Help()
    },
}

func init() {
    rootCmd.AddCommand(getCmd)

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

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

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

테스트합니다. help 문구 등은 아직 개발하지 않고 동작만 개발 하겠습니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go run main.go get
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.

Usage:
  yjwang-init get [flags]

소스에서 볼 수 있듯이 'cmd.Help()' 호출을 통해 간단하게 help 구문을 호출시킬 수 있습니다.
이제 cobra command를 이용해서 get command에 종속된 info command를 출력할 소스를 생성합니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ ../../bin/cobra add info -p getCmd
info created at /home/yjwang/문서/projects/golang/go-cli/src/yjwang-init

소스를 열어서 보면 func init() 이하의 getCmd.AddCommand(infoCmd) 부분이 다름을 알 수 있습니다.
이전의 get command는 rootCmd.AddCommand(getCmd) 인 것을 보면 차이를 알 수 있습니다.

또한 get help 내용도 바뀌었음을 알 수 있습니다. (info command 에 대한 내용이 추가됨)

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go run main.go get
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.

Usage:
  yjwang-init get [flags]
  yjwang-init get [command]

Available Commands:

  info        A brief description of your command

이제 info.go를 수정합니다.

/*
Copyright © 2021 NAME HERE <EMAIL ADDRESS>

Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at

    http://www.apache.org/licenses/LICENSE-2.0

Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cmd

import (
	"fmt"
	"io/ioutil"
	"os/exec"

	"github.com/spf13/cobra"
)

func check(e error) {
	if e != nil {
		panic(e)
	}
}

// infoCmd represents the info command
var infoCmd = &cobra.Command{
	Use:   "info",
	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) {

		osInfo, err := ioutil.ReadFile("/etc/os-release")
		check(err)
		fmt.Print(string(osInfo))

		kernelInfo, err := exec.Command("uname", "-r").Output()
		check(err)
		fmt.Print("Kernel Version : ", string(kernelInfo))
	},
}

func init() {
	getCmd.AddCommand(infoCmd)

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

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

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

sample output

# go run main.go get info

NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
Kernel Version : 5.8.0-50-generic

이제 1번과 2번 요구사항을 만족하는 프로그램이 만들어졌다. 이를 compile하여 binary로 만들어서 사용해보고자 합니다.

yjwang@yjwang-ThinkPad-T14s-Gen-1:~/문서/projects/golang/go-cli/src/yjwang-init$ go install

## 1번 요구사항
# go run main.go get
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

## 2번 요구사항
# go run main.go get info
NAME="Ubuntu"
VERSION="20.04.2 LTS (Focal Fossa)"
ID=ubuntu
ID_LIKE=debian
PRETTY_NAME="Ubuntu 20.04.2 LTS"
VERSION_ID="20.04"
HOME_URL="https://www.ubuntu.com/"
SUPPORT_URL="https://help.ubuntu.com/"
BUG_REPORT_URL="https://bugs.launchpad.net/ubuntu/"
PRIVACY_POLICY_URL="https://www.ubuntu.com/legal/terms-and-policies/privacy-policy"
VERSION_CODENAME=focal
UBUNTU_CODENAME=focal
Kernel Version : 5.8.0-50-generic
반응형