# 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을 배포할때 어떻게 해야하는지 모르겟다.


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://teamsmiley.gitbook.io/devops/go-lang/create-cli.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
