CLI(Command Line Interface)란 터미널을 통해 사용자와 컴퓨터가 상호작용하는 인터페이스를 말합니다.
CLI는 그래픽을 통해 직관적으로 사용할 수 있는 GUI(Graphic User Interface)와 달리 명령줄로만 입력을 전달할 수 있다는 점이 불편할 수 있으나, 빠르고 가볍다는 장점으로 인해 현재에도 널리 사용되고 있습니다.
이런 CLI 환경에서 애플리케이션을 구동하기 위해 바이너리를 실행하는 것은 다소 불편할 수 있습니다.
왜냐하면 바이너리를 다른 설정으로 실행하고자 할시, 그 설정을 가진 버전으로 다시 빌드하고 실행해야 했기 때문인데요.
이 같은 문제를 해결하기 위해 "Command"라는 개념을 도입해 바이너리의 특정 로직을 실행하거나 구성 설정을 변경할 할 수 있습니다.
Command는 크게 Argument와 Flag로 이루어져 있는데요.
Argument는 Command를 실행하기 위해 제공하는 인자 값입니다.
Flag는 우리가 흔히 CLI 환경에서 명령줄에 명령어를 입력할때 부가적으로 제공하는 옵션입니다.
이 요소들을 비유하자면 Binary는 "주체"이고, Command는 "행위"이고, Argument는 행위의 "대상"이며, Flag는 행위 동작의 "변경"이라고 할 수 있습니다.
이 Command를 통해 우리는 CLI 환경에서 다양한 명령어들을 편리하게 사용하고 있습니다.
Go를 통해 이러한 CLI 인터페이스를 구현하기 위해서는 Go에서 제공하는 Standard Library인 flag를 사용할 수 있습니다.
하지만 flag 라이브러리로는 Flag만을 구현할 수 있고 Command와 Sub-Command를 구현할 수 없다는 단점이 존재합니다.
그래서 이번 포스팅에서는 이러한 단점을 극복할 수 있도록 Go로 현대적인 CLI 인터페이스 Command를 구현할 수 있는 Cobra에 대해서 알아보고자 합니다.
1. Cobra란?
Cobra는 CLI 인터페이스의 Command를 편리하게 구현해주는 Go 라이브러리입니다.
Kubernetes, Helm, Skaffold 등의 유명한 여러 Tool들이 Cobra를 사용해서 CLI 인터페이스를 구현했습니다.
이 말인 즉슨 우리에게 익숙한 Command, Sub-Command, Argument, Flag로 이루어진 Command 구조를 Cobra로 구현할 수 있다는 뜻입니다.
Cobra가 제공하는 중요 기능들은 다음과 같습니다.
- Sub-command, Flag 기반의 CLI 인터페이스
- Intelligent suggestions 기능 (EX : app srver... did you mean app server?)
- Command와 Flag에 대한 help 명령어 자동 생성 (-h, --help) + 커스텀 가능
- Shell autocomplete 자동 생성 (bash, zsh, fish, powershell)
- man pages 자동 생성
- Command 앨리어스 생성 기능
이번 포스팅에서는 이러한 기능을 제공하는 Cobra를 통해 CLI 인터페이스를 제공하는 애플리케이션을 생성해보겠습니다.
2. Go 환경 구성하기
Cobra를 사용한 Go 어플리케이션을 구현하기 위해 먼저 프로젝트 구조를 생성하겠습니다.
먼저 프로젝트 루트 디렉토리에서 아래 명령어를 실행해 Go 프로젝트를 생성합니다.
1
|
go mod init github.com/nangmans14/cmd
|
cs |
Cobra는 cobra generator라는 도구를 제공해 CLI 인터페이스 구현에 적절한 프로젝트 구조를 자동으로 생성해줍니다.
cobra generator를 사용해 프로젝트 구조를 생성하기 위해 아래 명령어로 cobra-cli를 설치합니다.
1
|
go install github.com/spf13/cobra-cli@latest
|
cs |
위 명령어가 정상적으로 실행되고 나면, Go가 $GOPATH/bin 디렉토리에 cobra-cli를 설치해 cobra-cli 명령어를 사용할 수 있게 됩니다.
cobra-cli를 설치하고 나면 아래 명령어를 실행해 프로젝트 구조를 생성합니다.
1
|
cobra-cli init
|
cs |
지금까지 생성된 프로젝트 구조는 다음과 같습니다.
1
2
3
4
5
6
7
8
9
|
.
├── LICENSE
├── cmd
│ └── root.go
├── go.mod
├── go.sum
└── main.go
1 directory, 5 files
|
cs |
3. Cobra를 통해 CLI 구현하기
3-1. Root Command 구현
Cobra를 통해 구현하는 CLI 인터페이스는 항상 1개의 Root Command를 가지고 있으며, 이는 애플리케이션의 main entrypoint입니다.
그 외에 Root Command에 붙는 Command는 모두 Sub Command로써 다른 로직을 실행하도록 구성할 수 있습니다.
Cobra의 Command는 &cobra.Command Struct를 통해 구현할 수 있습니다.
해당 Struct의 간략한 구성 요소들은 아래와 같습니다. 자세한 내용은 링크에서 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
type Command struct {
// 커맨드를 사용하는 방법을 표기합니다. (EX : add [-F file | -D dir]... [-f format] profile)
Use string
// 'help' 명령어를 실행했을시 출력되는 짧은 설명입니다.
Short string
// 'help <this-command>' 명령어를 실행했을시 출력되는 긴 설명입니다.
Long string
// 커맨드를 사용하는 예시를 표기합니다.
Example string
// 기대되는 인자의 개수를 지정합니다.
Args PositionalArgs
// 커맨드 로직 이전에 실행되는 함수를 지정합니다.
PreRun func(cmd *Command, args []string)
// PreRun과 같지만 에러를 반환합니다.
PreRunE func(cmd *Command, args []string) error
// 커맨드를 입력시 실행되는 주요 로직입니다.
Run func(cmd *Command, args []string)
// Run과 같지만 에러를 반환합니다.
RunE func(cmd *Command, args []string) error
}
|
cs |
https://pkg.go.dev/github.com/spf13/cobra#Command
cobra package - github.com/spf13/cobra - Go Packages
ExactValidArgs returns an error if there are not exactly N positional args OR there are any positional args that are not in the `ValidArgs` field of `Command` Deprecated: use MatchAll(ExactArgs(n), OnlyValidArgs) instead
pkg.go.dev
먼저 애플리케이션의 주요 진입점이 되는 Root Command를 구현해보도록 하겠습니다.
root.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "cmd",
Short: "A Root Command for nangmans14",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("This Command executes root command")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
|
cs |
Root Command를 구현하는 root.go에서는 "cobra.Command" Struct를 정의합니다.
"Run" 필드의 값은 커맨드 실행시 동작하는 주요 로직으로, 예시로 특정 문장을 출력하도록 구현했습니다.
"Execute()" 함수는 인자와 함께 커맨드를 실행하는 역할을 합니다.
"init()" 함수는 Execute() 함수 이후에 실행되는 함수로, 현재는 예시로 플래그를 정의하고 있습니다.
main.go
1
2
3
4
5
6
7
|
package main
import "github.com/nangmans14/cobra/cmd"
func main() {
cmd.Execute()
}
|
cs |
main.go에서는 cobra.Command의 Execute() 메소드를 실행해 커맨드를 구현합니다.위와 같은 코드를 작성한 뒤, 아래 명령어로 바이너리를 실행하면 Run 필드에 지정된 로직이 실행되는 것을 확인할 수 있습니다.
1
2
|
$ go run main.go
This Command executes root
|
cs |
Cobra는 help 플래그를 통한 도움말 로직을 자동으로 구현해주기 때문에, --help 플래그와 함께 바이너리를 실행하면 아래와 같이 지정된 문구가 출력되는 것을 볼 수 있습니다.
1
2
3
4
5
6
7
8
9
10
|
$ go run main.go --help
A longer description that spans multiple lines and likely contains
examples and usage of using your application.
Usage:
cmd [flags]
Flags:
-h, --help help for cmd
-t, --toggle Help message for toggle
|
cs |
3-2. Sub Command 구현
앞서 Sub Command는 Root Command 외에 실행할 수 있는 모든 커맨드를 의미한다고 언급했습니다.
정확히 말하자면 Cobra는 Sub Command들을 Root Command 중심의 Tree 구조로 구성할 수 있습니다.
이러한 Cobra의 기능 덕분에 Root Command 하위에 존재하는 Sub Command들이 존재하고, 이 Sub Command에는 또 하위의 Sub Command들이 존재할 수도 있도록 여러 층위의 구조로 구성이 가능합니다.
그리고 보통 CLI로 실행하고자 하는 주요 로직들은 이 Sub Command를 통해 실행하게 됩니다.
cobra-cli의 "add" 커맨드로 이 Sub Command를 간편하게 구현할 수 있습니다. subA라는 Sub Command를 추가하기 위해 아래와 같이 명령어를 실행합니다.
1
|
cobra-cli add subA
|
cs |
실행 후에는 root.go와 같은 디렉토리에 subA.go 파일이 생성되는 것을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
|
.
├── LICENSE
├── cmd
│ ├── root.go
│ └── subA.go
├── cobra
├── go.mod
├── go.sum
└── main.go
|
cs |
Cobra가 자동으로 생성한 subA.go 파일에는 아래와 같이 Sub Command 구현을 위한 코드가 짜여져 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
|
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var subACmd = &cobra.Command{
Use: "subA",
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("subA called")
},
}
func init() {
rootCmd.AddCommand(subACmd)
}
|
cs |
위 코드에서 중요한 부분은 init() 함수입니다.
init() 함수에서 rootCmd의 AddCommand() 메소드가 실행되는 것을 확인할 수 있는데요.
이 AddCommand() 메소드가 인자로 받는 cobra.Command를 Root Command의 Sub Command로 추가하게 됩니다.
이렇게 간편한 방식으로 Root Command와 Sub Command로 이루어진 Tree 구조를 형성할 수 있습니다.
아래 명령어로 "subA" Sub Command 실행 및 실행 결과를 확인할 수 있습니다.
1
2
|
% go run main.go subA
subA called
|
cs |
추가적으로 Root Commnad의 "help" 플래그를 통해서 어떤 Sub Command가 하위로 들어가 있는지도 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
|
$ go run main.go --help
A longer description that spans multiple lines and likely contains
examples and usage of using your application.
Usage:
cmd [flags]
cmd [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
help Help about any command
subA A brief description of your command
Flags:
-h, --help help for cmd
-t, --toggle Help message for toggle
Use "cmd [command] --help" for more information about a command.
|
cs |
3-3. Argument 구현
커맨드를 실행하기 위한 로직에 특정한 인자가 필요한 경우가 있을 수 있습니다.
이럴때 보통 커맨드 뒤에 따라오는 Argument를 통해서 인자를 제공하는데요.
이 Argument 또한 Cobra의 "Command" Struct를 통해서 간편하게 구현 가능합니다.
subA.go 파일의 "subACmd" 변수 선언 부분을 아래와 같이 변경합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
var subACmd = &cobra.Command{
Use: "subA",
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.`,
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
fmt.Printf("subA called with %s\n", args[0])
},
}
|
cs |
위 코드에서 중요한 부분은, "Args" 필드와 필드에 정의된 값입니다.
"Args" 필드는 해당 커맨드를 실행하는데 기대되는 PositionalArgs 타입의 Argument 값을 필요로 하는데요.
이 필드를 통해 기대되는 인자 개수, 인자 값의 범위, 최대/최소 인자 값 등 다양한 조건의 Argument를 정의할 수 있습니다.
Argument를 정의한 후에는 아래와 같이 인자를 이용한 로직을 구현할 수 있습니다.
1
2
|
$ go run main.go subA arg1
subA called with arg1
|
cs |
3-4. Flag 구현
명령어를 실행할때, 로직을 기존과 다른 기능으로 실행하기 위해 Flag를 사용할 수 있습니다.
이 Flag는 Go의 Standard Library를 통해서도 구현 가능하지만, Cobra를 통해서 더욱 간편하게 구현할 수 있습니다.
Cobra는 2 종류의 Flag를 제공합니다.
- Local flags : 해당 Flag를 사용하는 Command에만 적용되는 Flag입니다.
- Persistent flags : 해당 Flag 및 아래의 Sub Command에도 모두 적용되는 Flag입니다.
"subA" Sub Command에 Local Flag를 추가하기 위해 subA.go 파일을 수정합니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
|
package cmd
import (
"fmt"
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "cmd",
Short: "A Root Command for nangmans14",
Long: `A longer description that spans multiple lines and likely contains
examples and usage of using your application.`,
Run: func(cmd *cobra.Command, args []string) {
fmt.Println("This Command executes root command")
},
}
func Execute() {
if err := rootCmd.Execute(); err != nil {
fmt.Println(err)
os.Exit(1)
}
}
func init() {
rootCmd.PersistentFlags().StringVarP(&flag, "pfoo", "p", "pvar", "This is Command's Persistent Flag")
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
|
cs |
그리고 Persistent Flag를 추가하기 위해 root.go 파일의 init() 함수 부분을 아래와 같이 수정합니다.
1
2
3
4
|
func init() {
rootCmd.PersistentFlags().StringVarP(&flag, "pfoo", "p", "pvar", "This is Command's Persistent Flag")
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
|
cs |
그리고 "help" Flag와 함께 명령어를 실행하면 아래와 같이 추가한 Flag의 도움말이 출력되는 것을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
$ go run main.go subA --help
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:
cmd subA [flags]
Flags:
-f, --foo string This is subA Command's Local Flag (default "var")
-h, --help help for subA
Global Flags:
-p, --pfoo string This is Command's Persistent Flag (default "pvar")
|
cs |
출력된 도움말을 보면 Flags 란에 Local Flag가, Global Flags 란에 Persistent Flag가 각각 추가된 것을 볼 수 있습니다.
이제 해당 Flag들을 아래와 같이 사용할 수 있습니다.
1
2
3
4
5
|
$ go run main.go subA arg1 --foo local-var
subA called with arg1 , and flag with local-var
$ go run main.go subA arg1 --pfoo global-var
subA called with arg1 , and flag with global-var
|
cs |
각 Flag들은 "Flag()" 및 "PersistentFlag()" 함수에 등록된 긴 버전과 짧은 버전의 이름으로 호출할 수 있습니다.
또한 Default 값을 설정해 Flag를 명시하지 않았을시 할당할 값을 지정할 수도 있습니다.
4. DEMO
지금까지 Cobra가 제공하는 CLI 인터페이스 구현 기능들을 살펴봤습니다.
이번 장에서는 Cobra를 이용해 구현한 CLI DEMO를 살펴보겠습니다.
DEMO 어플리케이션은 Viper를 사용해 CLI Command와 함께 제공된 Flag 값을 Struct 값으로 매핑합니다.
viper는 Go의 Configuration 구현을 위한 라이브러리로, 자세한 내용은 아래 링크에서 확인할 수 있습니다.
https://github.com/spf13/viper
GitHub - spf13/viper: Go configuration with fangs
Go configuration with fangs. Contribute to spf13/viper development by creating an account on GitHub.
github.com
Cobra는 Viper와의 연동성을 제공하고 있기 때문에, 두 라이브러리의 조합으로 CLI 인터페이스에서 제공된 값을 Configuration으로 변환할 수 있습니다.
main.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
|
package main
import (
"fmt"
"log"
"os"
"os/signal"
"path"
"syscall"
"github.com/nangmans14/proglog/internal/agent"
"github.com/nangmans14/proglog/internal/config"
"github.com/spf13/cobra"
"github.com/spf13/cobra/doc"
"github.com/spf13/viper"
)
func main() {
var cli = &cli{}
var cmd = &cobra.Command{
Use: "proglog",
PreRunE: cli.setupConfig,
RunE: cli.run,
}
var header = &doc.GenManHeader{
Title: "DEMO",
}
if err := setupFlags(cmd); err != nil {
log.Fatal(err)
}
if err := cmd.Execute(); err != nil {
log.Fatal(err)
}
if err := doc.GenManTree(cmd, header, "/tmp"); err != nil {
log.Fatal(err)
}
}
type cli struct {
cfg cfg
}
type cfg struct {
agent.Config
ServerTLSConfig config.TLSConfig
PeerTLSConfig config.TLSConfig
}
func setupFlags(cmd *cobra.Command) error {
hostname, err := os.Hostname()
if err != nil {
log.Fatal(err)
}
cmd.Flags().String("config-file", "", "Path to config file.")
dataDir := path.Join(os.TempDir(), "proglog")
cmd.Flags().String("data-dir", dataDir, "Directory to store log and Raft data.")
cmd.Flags().String("node-name", hostname, "Unique server ID.")
cmd.Flags().String("bind-addr", "127.0.0.1:8401", "Address to bind Serf on.")
cmd.Flags().Int("rpc-port", 8400, "Port for RPC clients (and Raft) connections")
cmd.Flags().StringSlice("start-join-addrs", nil, "Serf addresses to join.")
cmd.Flags().Bool("bootstrap", false, "Bootstrap the cluster.")
cmd.Flags().String("acl-model-file", "", "Path to ACL model.")
cmd.Flags().String("acl-policy-file", "", "Path to ACL policy.")
cmd.Flags().String("server-tls-cert-file", "", "Path to server tls cert.")
cmd.Flags().String("server-tls-key-file", "", "Path to server tls key.")
cmd.Flags().String("server-tls-ca-file", "", "Path to server certificate authority.")
return viper.BindPFlags(cmd.Flags())
}
func (c *cli) setupConfig(cmd *cobra.Command, args []string) error {
var err error
configFile, err := cmd.Flags().GetString("config-file")
if err != nil {
return err
}
viper.SetConfigFile(configFile)
if err = viper.ReadInConfig(); err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); !ok {
return err
}
}
c.cfg.DataDir = viper.GetString("data-dir")
c.cfg.NodeName = viper.GetString("node-name")
c.cfg.BindAddr = viper.GetString("bind-addr")
c.cfg.RPCPort = viper.GetInt("rpc-port")
c.cfg.StartJoinAddrs = viper.GetStringSlice("start-join-addrs")
c.cfg.Bootstrap = viper.GetBool("bootstrap")
c.cfg.ACLModelFile = viper.GetString("acl-mode-file")
c.cfg.ACLPolicyFile = viper.GetString("acl-policy-file")
c.cfg.ServerTLSConfig.CertFile = viper.GetString("server-tls-cert-file")
c.cfg.ServerTLSConfig.KeyFile = viper.GetString("server-tls-key-file")
c.cfg.ServerTLSConfig.CAFile = viper.GetString("server-tls-ca-file")
c.cfg.PeerTLSConfig.CertFile = viper.GetString("peer-tls-cert-file")
c.cfg.PeerTLSConfig.KeyFile = viper.GetString("peer-tls-key-file")
c.cfg.PeerTLSConfig.CAFile = viper.GetString("peer-tls-ca-file")
if c.cfg.ServerTLSConfig.CertFile != "" &&
c.cfg.ServerTLSConfig.KeyFile != "" {
c.cfg.ServerTLSConfig.Server = true
c.cfg.Config.ServerTLSConfig, err = config.SetupTLSConfig(
c.cfg.ServerTLSConfig,
)
if err != nil {
return err
}
}
if c.cfg.PeerTLSConfig.CertFile != "" &&
c.cfg.PeerTLSConfig.KeyFile != "" {
c.cfg.PeerTLSConfig.Server = true
c.cfg.Config.PeerTLSConfig, err = config.SetupTLSConfig(
c.cfg.PeerTLSConfig,
)
if err != nil {
return err
}
}
return nil
}
func (c *cli) run(cmd *cobra.Command, args []string) error {
var err error
agent, err := agent.New(c.cfg.Config)
if err != nil {
return err
}
fmt.Printf("%+v\n", c)
sigc := make(chan os.Signal, 1)
signal.Notify(sigc, syscall.SIGINT, syscall.SIGTERM)
<-sigc
return agent.Shutdown()
}
|
cs |
위 코드는 CLI 인터페이스에서 제공된 Flag값을 기반으로 Configuration을 구성해 기존에 존재하는 애플리케이션인 "agent"의 설정을 변경하고 실행하는 로직을 담고 있습니다.
바이너리를 Flag 없이 실행하면, 아래와 같이 현재 Configuration Struct에 담긴 값을 출력함과 동시에 agent 어플리케이션이 동작합니다.
1
|
$ go run main.go
|
cs |
변경하고자 하는 값의 Flag를 지정하면, Configuration Struct에 담긴 값이 변경됩니다.
예를 들어 "--rpc-port" Flag의 값을 8500으로 지정하면, 해당 Struct의 값 또한 8500으로 변경되어 실행되는 것을 확인할 수 있습니다.
1
|
$ go run main.go --rpc-port 8500
|
cs |
이같은 구성은 Cobra를 이용해 Flag의 값을 받아와 Viper의 Configuration에 담고, 이를 Struct 값에 매핑하는 것으로 구현할 수 있습니다.
1
2
3
4
5
6
|
var cmd = &cobra.Command{
Use: "proglog",
PreRunE: cli.setupConfig,
RunE: cli.run,
}
|
cs |
위 코드에서 Command Struct의 "PreRunE" 필드에 지정된 함수는 "RunE" 필드에 지정된 함수가 실행되기 직전에 실행됩니다.
"setupConfig()" 메소드는 커맨드의 주요 로직이 실행되기 전에 동작하여야 하므로 Command struct의 "PreRunE" 필드를 통해 실행 순서를 구현할 수 있습니다.
추가적으로 Cobra의 doc 라이브러리를 이용해 man 페이지를 자동으로 생성할 수 있습니다.
위 코드에서는 "GenManTree()" 메소드를 이용해 자동으로 man 페이지가 생성했습니다. 생성한 man 페이지는 지정한 경로에 저장되어 "man" 명령어로 내용을 확인할 수 있습니다.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
$ man /tmp/proglog.1
DEMO(1) DEMO(1)
NAME
proglog -
SYNOPSIS
proglog [flags]
DESCRIPTION
OPTIONS
--acl-model-file="" Path to ACL model.
--acl-policy-file="" Path to ACL policy.
--bind-addr="127.0.0.1:8401" Address to bind Serf on.
--bootstrap[=false] Bootstrap the cluster.
--config-file="" Path to config file.
--data-dir="/var/folders/4t/yg7559n92q99pztw4hp1gqrw0000gn/T/proglog" Directory to store log and Raft data.
-h, --help[=false] help for proglog
--node-name="2022080014.local" Unique server ID.
--rpc-port=8400 Port for RPC clients (and Raft) connections
--server-tls-ca-file="" Path to server certificate authority.
--server-tls-cert-file="" Path to server tls cert.
--server-tls-key-file="" Path to server tls key.
--start-join-addrs=[] Serf addresses to join.
HISTORY
26-Jun-2023 Auto generated by spf13/cobra
Auto generated by spf13/cobra Jun 2023 DEMO(1)
|
cs |
5. 마무리
이번 포스팅에서는 Go의 CLI 인터페이스 라이브러리인 Cobra를 이용해 어플리케이션의 CLI 인터페이스를 구현해봤습니다.
Cobra는 help 메세지, 자동 입력, Tree 구조의 커맨드 구성 등 CLI 인터페이스를 구현하는데 필요한 다양한 기능을 제공하고 있습니다.
게다가 Go의 Configuration 라이브러리인 Viper와의 연동성도 높아 CLI를 통해 제공된 값을 Configuration에 간편하게 매핑할 수도 있습니다.
이번 포스팅에서는 다루지 않은 더 많은 기능들이 Cobra에서 제공되고 있기 때문에 CLI 인터페이스를 구현하고자 한다면 꼭 사용해야 할 라이브러리라고 할 수 있겠습니다.
이번 포스팅이 Go를 통해 CLI 인터페이스를 구현하고자 하는 분들에게 도움이 되었으면 합니다.
'Dev' 카테고리의 다른 글
Apache beam으로 Streaming & Batch 데이터 파이프라인을 생성해보자 (with GCP Dataflow) (1) | 2022.03.15 |
---|---|
Gradle을 이용해서 Springboot + GCP API 연동한 Java 프로젝트 생성 및 배포하기 (2) | 2022.02.12 |
Onclick vs AddEventListener 어떤 것을 사용해야 할까? (2) | 2020.03.27 |
Django 의 Password Validation 삽질기 (1) | 2020.03.24 |
Node.js + MongoDB 로 이미지 웹 만들어보기 (2) (0) | 2020.03.12 |