> For the complete documentation index, see [llms.txt](https://teamsmiley.gitbook.io/devops/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://teamsmiley.gitbook.io/devops/go-lang/create-cli.md).

# kubectl같은 cli만들기

## github

<https://github.com/teamsmiley/go-cobra-viper-boilerplate>

## 사용 패키지

### cobra

cli만드는데 사용 <https://github.com/spf13/cobra>

### viper

설정을 저장하는데 사용 <https://github.com/spf13/viper>

## use cobra

```sh
go install github.com/spf13/cobra-cli@latest
mkdir ~/Desktop/mycli
cd ~/Desktop/mycli

cobra-cli init -h

cobra-cli init --viper

cobra-cli add ping
```

## cli 명령어 스타일

이렇게 만들면 다음처럼 생성이 된다.

```sh
mycli ping
```

그런데 `kubectl edit xxx` 스타일로 만들고 싶다.

```sh
cobra-cli add create
cobra-cli add get
cobra-cli add edit
cobra-cli add delete
```

![](/files/fF0Usww3fXqUzmlcleW0)

폴더를 생성하자.

![](/files/8CD0FSJKNp3GZnsTprD5)

package name change

```go
package create
package get
package edit
package delete
```

update root.go

import

```go
import (
 "fmt"
 "mycli/cmd/create"
 "mycli/cmd/delete"
 "mycli/cmd/edit"
 "mycli/cmd/get"
)
```

update createCmd to CreateCmd (소문자는 private 대문자는 public)

```go
rootCmd.AddCommand(create.CreateCmd)
rootCmd.AddCommand(get.GetCmd)
rootCmd.AddCommand(edit.EditCmd)
rootCmd.AddCommand(delete.DeleteCmd)
```

실행

```sh
go build
./mycli
```

![](/files/taddzt31dOg7xB2syH6p)

```sh
./mycli create
```

![](/files/RYM50tUw6tKGqMtV1bIh)

## add resource issue

다음을 crud에 추가한다.

```go
cmd.Help()
fmt.Println("create called")
```

```sh
cobra-cli add issue
```

move file under get folder and update package name to `get`

![](/files/5oouLLt8ysm30aT2x5IX)

update code

```go
// fmt.Println("issue called")
fmt.Println("get issue called")

// rootCmd.AddCommand(issueCmd)
GetCmd.AddCommand(issueCmd)
```

나머지 3개 파일도 복사해서 넣자.

테스트

![](/files/UUHKV1kNfJyyrvJNAb4l)

## 아규먼트 받기, 아규먼트 필수 처리

create-issue에 다음을 추가

```go
var (
 Summary     string
 Description string
)

func init() {
 CreateCmd.AddCommand(issueCmd)

 CreateCmd.Flags().StringVarP(&Summary, "summary", "s", "", "a summary of ticket")
 CreateCmd.Flags().StringVarP(&Description, "description", "d", "", "a description of ticket")

 if err := CreateCmd.MarkFlagRequired("summary"); err != nil {
  fmt.Println(err)
 }
 if err := CreateCmd.MarkFlagRequired("description"); err != nil {
  fmt.Println(err)
 }
}
```

## config

### username/password를 프로젝트 바깥에 저장

viper를 사용한다.

```go
// initConfig reads in config file and ENV variables if set.
func initConfig() {
 if cfgFile != "" {
  // Use config file from the flag.
  viper.SetConfigFile(cfgFile)
 } else {
  // Find home directory.
  home, err := os.UserHomeDir()
  cobra.CheckErr(err)

  // Search config in home directory with name ".mycli" (without extension).
  viper.AddConfigPath(home)
  viper.SetConfigType("yaml")
  viper.SetConfigName(".mycli")
 }
  viper.AutomaticEnv() // read in environment variables that match

 // If a config file is found, read it in.
 if err := viper.ReadInConfig(); err == nil {
  fmt.Fprintln(os.Stderr, "Using config file:", viper.ConfigFileUsed())
 }
```

이 코드가 벌써 root.go에 들어가 있다.

루트 디렉토리에서 .mycli.yaml 파일을 찾아서 읽어온다.

일단 저장을 해보자.

```sh
cobra-cli add configure
```

configure폴더를 만든후 파일을 이동해준다.

![](/files/SyCeMJuw4xuNYz3IDm38)

패키지 이름 configure 로 수정하고 아규컨트 추가 한다.

```go
var (
 JIRA_USERNAME string
 JIRA_TOKEN    string
)

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

  viper.Set("JIRA_USERNAME", JIRA_USERNAME)
  viper.Set("JIRA_TOKEN", JIRA_TOKEN)

  error := viper.SafeWriteConfig()
  if error != nil {
   fmt.Println(error)
  }

 },
}

func init() {
 ConfigureCmd.Flags().StringVarP(&JIRA_USERNAME, "jira-username", "u", "", "jira username")
 ConfigureCmd.Flags().StringVarP(&JIRA_TOKEN, "jira-token", "t", "", "jira token")
 if err := ConfigureCmd.MarkFlagRequired("jira-username"); err != nil {
  fmt.Println(err)
 }
 if err := ConfigureCmd.MarkFlagRequired("jira-token"); err != nil {
  fmt.Println(err)
 }
}
```

ConfigureCmd 로 대문자로 수정

아규먼트를 받아서 viper에 set하면 끝 그리고 SafeWriteConfig로 저장한다.

argument를 위해서 init함수를 수정하였다.

실행하고 설정을 저장해보자.

```sh
myctl configure --jira-username aaa --jira-token xxxx

# cat ~/.mycli.env
JIRA_USERNAME=xxx
JIRA_TOKEN=xxx
```

이제 읽어와서 사용해보자.

cmd/get/issue.go 에서 수정한다.

```go
Run: func(cmd *cobra.Command, args []string) {
 cmd.Help()
 fmt.Println("get issue called")
 // add
 fmt.Println(viper.GetString("JIRA_USERNAME"))
 fmt.Println(viper.GetString("JIRA_TOKEN"))
},
```

두줄을 추가하고 실행해보자.

```sh
go run main.go get issue

> get issue called
> aaa
> xxx
```

### 환경 변수를 이용해서 처리

설정파일이 아니고 환경 변수를 이용해서 처리해보자.

일단 설정파일을 지운다.

```sh
rm ~/.mycli.env
cat ~/.mycli.env
> cat: /Users/ragon/.mycli.env: No such file or directory
```

테스트 하자.

```sh
go run main.go get issue
#아무것도 출력되지 않는다.
```

환경변수를 설정한다.

```sh
export JIRA_USERNAME=aaa
export JIRA_TOKEN=xxxx
```

실행해보자.

```sh
go run main.go get issue
```

![](/files/ClGRTVYVs4en5PotyMrZ)

출력됨을 알수 있다.

### 주의

환경변수를 사용할때 소문자를 키값으로 사용하면 동작하지 않는다.

```sh
# 동작
export JIRA_USERNAME=aaa
export JIRA_TOKEN=xxxx
# 동작 안됨
export jira_username=aaa
export jira_token=xxxx
```

왜 이러는지 아시는분....

## todo

* resource file(json 또는 이미지 또는 다른 파일)을 읽어와서 처리하는 방법을 알아보자.
* resource file을 배포할때 어떻게 해야하는지 모르겟다.
