この記事はデジタルキューブグループ エンジニアチームアドベントカレンダー2024 12月10日の記事です。
取り組みの経緯
今後プラットフォームエンジニアリングといった動きを強めたいと思った場合に、社内向けツールなどを作る機会も増えるかもしれない
普段システムの開発や保守を行わない人たちに対してはwebアプリとして提供したり、また、インフラチームなどに対してはcliから使えるものの方が都合のよい場合もある
普段はpythonでのバックエンド開発が多いため、pythonでのcliツール作成を試してみる
また、linux環境などでも使いやすいバイナリとして生成できるgolangでのcliツール作成も試す
goを触るのが初めてなので、cliツールのサンプル作成を通してgoも完全に理解する
自分のバックグラウンド
開発では以下の言語に触れてきた経験が多い
- node(typescript)
- python
なので、自分の頭の整理がてら、この辺の言語で使われてる仕組みと比較しながら理解していくメモ書きがあると思う
環境
- python 3.12.2
- golang 1.23.4
python版
clickというライブラリを使うと手軽でいいっぽい
以下の感じで普通にインストールすれば使える
$ pip install click
clickが無難にポピュラーそうなので使ってみたが、標準ライブラリの argparse
を使ったりするケースもありそうだし、他の競合として fire
、 typer
なんてのもあるっぽい
githubのスター数見るとfireが結構人気っぽいのでfireもそのうち試してみる
フォルダ構成は以下
.
├── cli_python.py
└── clipython
clipython
は cli_python.py
へのシンボリックリンク
本来、実行コマンドは以下となっている
$ python cli_python.py --help
$ python cli_python.py hello --help
$ python cli_python.py goodbye -n Sugo
シンボリックリンクを設置することで以下のように実行できるようにしている
$ ./clipython --help
$ ./clipython hello --help
$ ./clipython goodbye -n Sugo
サブコマンドとしてhelloとgoodbyeが実行できるようになっている
#!/usr/bin/env python
import click
@click.group()
def cli():
pass
@cli.command(help="こんにちはを誰かに言う")
@click.option("-n", "--name", type=str, help="Specify your name")
def hello(name):
click.echo(f"Hello {name}")
@cli.command(help="さよならを誰かに言う")
@click.option("-n", "--name", type=str, help="Specify the name of the other party")
def goodbye(name):
click.echo(f"Goodby {name}")
def main():
cli()
if __name__ == "__main__":
main()
以下は実際にコマンドを実行した例
$ ./clipython --help
Usage: clipython [OPTIONS] COMMAND [ARGS]...
Options:
--help Show this message and exit.
Commands:
goodbye さよならを誰かに言う
hello こんにちはを誰かに言う
$ ./clipython hello --help
Usage: clipython hello [OPTIONS]
こんにちはを誰かに言う
Options:
-n, --name TEXT Specify your name
--help Show this message and exit.
$ ./clipython goodbye -n Sugo
Goodbye Sugo
golang版
cobraというcliツール作成のためのフレームワーク的なのを使うのがいいっぽい
また、cobra-cliをインストールして使えるようにし、それでinitするとベースのものがいい感じに用意できるっぽい
とりあえず始めてみる
$ go mod init cli_go
$ go get -u github.com/spf13/cobra@latest
$ go install github.com/spf13/cobra-cli@latest
$ cobra-cli init --license MIT --viper=false
go mod init
がまだよくわからないし、たぶん命名規則もちょっと守れてなさそうではある
go get
が npm i
相当なものなんだろうということは理解できた
go install
もあまりわかっていない、ツールのグローバルインストールなイメージ
この段階で以下のような状態になる
.
├── main.go
├── cmd/
│ └── root.go
├── go.mod
├── go.sum
└── LICENSE
ちょっと雑だけど、 go.mod
は package.json
、 go.sum
は package-lock.json
くらいのイメージ
一旦デフォの状態で動くか見てみる
$ 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.
動くのでサブコマンドを実行できるよう続きを進める
$ cobra-cli add hello
$ cobra-cli add goodbye
この段階で以下のような状態になる
.
├── main.go
├── cmd/
│ ├── root.go
│ ├── hello.go
│ └── goodbye.go
├── go.mod
├── go.sum
└── LICENSE
各サブコマンドを編集していく
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var helloCmd = &cobra.Command{
Use: "hello",
Short: "こんにちはを誰かに言う",
Long: `こんにちはを誰かに言う
ことを長く説明する箇所`,
Run: func(cmd *cobra.Command, args []string) {
flag, _ := cmd.Flags().GetString("name")
greet := fmt.Sprintf("Hello %s", flag)
fmt.Println(greet)
},
}
func init() {
rootCmd.AddCommand(helloCmd)
helloCmd.Flags().StringP("name", "n", "ANONYMOUS", "Specify your name")
}
package cmd
import (
"fmt"
"github.com/spf13/cobra"
)
var goodbyeCmd = &cobra.Command{
Use: "goodbye",
Short: "さよならを誰かに言う",
Long: `さよならを誰かに言う
ことを長く説明する箇所`,
Run: func(cmd *cobra.Command, args []string) {
flag, _ := cmd.Flags().GetString("name")
greet := fmt.Sprintf("Goodbye %s", flag)
fmt.Println(greet)
},
}
func init() {
rootCmd.AddCommand(goodbyeCmd)
goodbyeCmd.Flags().StringP("name", "n", "ANONYMOUS", "Specify the name of the other party")
}
実行結果が以下
$ go run main.go hello
Hello ANONYMOUS
$ go run main.go goodbye
Goodbye ANONYMOUS
$ go run main.go hello --name Sugo
Hello Sugo
$ go run main.go hello -n Sugo
Hello Sugo
$ go run main.go goodbye --name Sugo
Goodbye Sugo
$ go run main.go goodbye -n Sugo
Goodbye Sugo
コマンドの説明もついでなのでちょっと書き換えてみる
package cmd
import (
"os"
"github.com/spf13/cobra"
)
var rootCmd = &cobra.Command{
Use: "cli_go",
- Short: "A brief description of your application",
- Long: `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.`,
+ Short: "コマンド全体の短い説明文",
+ Long: `コマンド全体の長い説明文`,
}
func Execute() {
err := rootCmd.Execute()
if err != nil {
os.Exit(1)
}
}
func init() {
rootCmd.Flags().BoolP("toggle", "t", false, "Help message for toggle")
}
ビルドしてみる
$ go build
実行してみる
$ ./cli_go
コマンド全体の長い説明文
Usage:
cli_go [command]
Available Commands:
completion Generate the autocompletion script for the specified shell
goodbye さよならを誰かに言う
hello こんにちはを誰かに言う
help Help about any command
Flags:
-h, --help help for cli_go
-t, --toggle Help message for toggle
Use "cli_go [command] --help" for more information about a command.
$ ./cli_go hello
Hello ANONYMOUS
$ ./cli_go hello -n Sugo
Hello Sugo
$ ./cli_go goodbye
Goodbye ANONYMOUS
$ ./cli_go goodbye --name Sugo
Goodbye Sugo
まとめ
完全に理解した
感想
click(python)で作成した方はpythonの実行環境が必要なため、アプリケーションサーバーなどにsshで入って運用/保守などで使うのであれば作るのも手軽でよさそう
pythonの実行環境によってはうまく動作しない可能性もあると思うので、各自に配布してローカルから使ってもらうなどといった場合には向かなさそう
逆にgolang版はバイナリを配るだけでよく、プラットフォーム的な制約は少なさそう
ただ、(まだ入門段階だからかもしれないが)golangの開発環境の整備や開発がなかなか面倒には感じた
↑
最初はこう思ったけど、ちょっと書いたり調べたりしてるとgolangの書き心地は割といいかもしれない、開発環境整備ももう少し理解したら案外すんなりかも
補足
これが地味に助かった
参考