みんGO を読んでec2インスンスリストをタグ検索するコマンドラインツールを作ってみた


プロジェクトでGO言語に触れながら学習のためにコマンドラインツールを作り拡張させながら言語理解を深めようと目標を立てた。「みんなのGO言語」を参考にしながら自作のコマンドラインツールを作ったのでまとめます。

どんなコマンドラインツールを作ったか

ec2インスタンスをタグ検索してインスタンス情報を取得できるコマンドラインツールを作りました。
生成したインスタンスリストをpecoでインクリメンタルサーチできるようにして選択したインスタンスにsshできるようなzsh関数も合わせて作りました。
作ったサブコマンドとpecoを組み合わせればインスタンスへのssh接続が快適になります。

ec2インスタンスリストを生成してくれるコマンドラインツール

作りたいイメージは次のようなものです。

1
describe_es2 tag -tag-key Name *myweb*

コマンドを実行したイメージは次のようになります。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
$ describe_es2 tag -tag-key Name '*myweb*'
Completed saving file ./myweb001_i-xxxxxxxxxxxxxx, that content is ec2-xx-xx-xx-xx.region.compute.amazonaws.com
Completed saving file ./myweb002_i-xxxxxxxxxxxxxx, that content is ec2-xx-xx-xx-xx.region.compute.amazonaws.com
Completed saving file ./myweb003_i-xxxxxxxxxxxxxx, that content is ec2-xx-xx-xx-xx.region.compute.amazonaws.com
$ find . -type f
./myweb001_i-xxxxxxxxxxxxxx
./myweb002_i-xxxxxxxxxxxxxx
./myweb003_i-xxxxxxxxxxxxxx
$ cat ./myweb001_i-xxxxxxxxxxxxxx
ec2-xx-xx-xx-xx.region.compute.amazonaws.com // インスタンスのパブリックDNSがファイルの中身に保存されている

tagのサブコマンドには次のオプションを指定できるようにします。

aws認証プロフィールをmyprojectに指定して検索する場合は次のようなコマンドになります。

1
describe_es2 tag -credential-profile myproject '*myweb*'

サブコマンドにはgoogle/subcommandsを使った

今回はtag検索のみのツールですが今後の拡張でEC2 Container Serviceのクラスタ名を指定すればインスタンスリストが生成されるような拡張を考えているためサブコマンド化したかった。「みんなのGO言語」の「4.4 サブコマンドをもったCLIツール」ではmitchellh/cliの使い方を紹介いただいてますが情報が少なそうな「google/subcommands」を使ってみた。

サブコマンドをインターフェースとして定義できるので定義したサブコマンドのオプションのコード化など見通しの良いコードが書ける。
次からはgoogle/subcommandsを利用したコードの説明をしていきます。

google/subcommandsの実装例

サブコマンドのオプションを定義する

サブコマンドのオプションをstructで定義します

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
// オプションのaws認証情報とタグキー、regionを定義
type tagCmd struct {
	credential credential
	tagKey     string
	region     string
}

// aws認証情報は個別にstructで定義
type credential struct {
	profile  string
	filename string
}

subcommands.Commandのインターフェースを実装する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
package subcommands

// A Command represents a single command.
type Command interface {
	// Name returns the name of the command.
	Name() string

	// Synopsis returns a short string (less than one line) describing the command.
	Synopsis() string

	// Usage returns a long string explaining the command and giving usage
	// information.
	Usage() string

	// SetFlags adds the flags for this command to the specified set.
	SetFlags(*flag.FlagSet)

	// Execute executes the command and returns an ExitStatus.
	Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus
}

tagのサブコマンドがsubcommands.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
func (*tagCmd) Name() string {
	return "tag"
}

func (*tagCmd) Synopsis() string {
	return "Fetch the ec2 instance public dns name by tag search, then that stored to text file in the current directory."
}

func (*tagCmd) Usage() string {
	return `tag [-credential-profile default] [-credential-filename '~/.aws/credentials'] [-region ap-northeast-1] [-tag-key Name] '*dev*' :
  Created or updated text file
`
}

func (p *tagCmd) SetFlags(f *flag.FlagSet) {
	f.StringVar(&p.credential.filename, "credential-filename", "", "optional: aws credential file name, when filename is empty, that will use '$HOME/.aws/credentials'")
	f.StringVar(&p.credential.profile, "credential-profile", "default", "optional: aws credential profile, default value is 'default'")
	f.StringVar(&p.region, "region", "ap-northeast-1", "optional: aws region, default value is 'ap-northeast-1'")
	f.StringVar(&p.tagKey, "tag-key", "Name", "target tag key, default value is 'Name'")
}

func (p *tagCmd) Execute(_ context.Context, f *flag.FlagSet, _ ...interface{}) subcommands.ExitStatus {
	// 省略
	return subcommands.ExitSuccess
}
1
"Execute(ctx context.Context, f *flag.FlagSet, args ...interface{}) ExitStatus

↑のメソッドはサブコマンドのメインの処理です。詳細は”github.com/nsoushi/describe-ec2”を参照してください。

定義したtagCmdをsubcommands.Registerで追加する

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
package main

func main() {
	subcommands.Register(subcommands.HelpCommand(), "")
	subcommands.Register(subcommands.FlagsCommand(), "")
	subcommands.Register(subcommands.CommandsCommand(), "")
	subcommands.Register(&tagCmd{}, "")

	flag.Parse()
	ctx := context.Background()
	os.Exit(int(subcommands.Execute(ctx)))
}

インターフェースを実装することでコマンドの使い方やヘルプがまとまる

上記のコードにある通り

1
Name()Synopsis()Usage()SetFlags(*flag.FlagSet)

↑を実装することでtagサブコマンドのヘルプが綺麗に出力されます。subcommands.Registerで登録したサブコマンドが列挙されている

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
describe-ec2
Usage: describe-ec2 <flags> <subcommand> <subcommand args>

Subcommands:
	commands         list all command names
	flags            describe all known top-level flags
	help             describe subcommands and their syntax
	tag              Fetch the ec2 instance public dns name by tag search, then that stored to text file in the current directory.


Use "describe-ec2 flags" for a list of top-level flags

tagサブコマンドのヘルプ出力

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
./describe-ec2 help tag help tag
tag [-credential-profile default] [-credential-filename '~/.aws/credentials'] [-region ap-northeast-1] [-tag-key Name] '*dev*' :
  Created or updated text file
  -credential-filename string
    	optional: aws credential file name, when filename is empty, that will use '$HOME/.aws/credentials'
  -credential-profile string
    	optional: aws credential profile, default value is 'default' (default "default")
  -region string
    	optional: aws region, default value is 'ap-northeast-1' (default "ap-northeast-1")
  -tag-key string
    	target tag key, default value is 'Name' (default "Name")

subcommands.Commandを使ってみて

「みんなのGO言語」を参考にして

作ったサブコマンドで生成したインスンスリストをpecoでインクリメンタルサーチする

次のようなzsh関数を使って生成したインスタンスリストをインクリメンタルサーチして選択したインスタンスにsshできます。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
peco-describe-ec2() {
  local MAXDEPTH=${1:-1}
  local BASE_DIR="${2:-`pwd`}"

  local FILENAME=$(find ${BASE_DIR} -maxdepth ${MAXDEPTH} -type f -exec basename {} ';' | peco | head -n 1)

  if [ -n "$FILENAME" ] ; then
    local HOST=`cat $BASE_DIR/$FILENAME`
    echo "ssh $HOST"
    BUFFER="ssh ${HOST}"
    zle accept-line
  fi
  zle clear-screen
}

ソースを公開しています

https://github.com/soushin/describe-ec2

使い方

1
2
3
4
$ go get github.com/nsoushi/describe-ec2/cmd/describe-ec2
$ source $GOPATH/src/github.com/nsoushi/describe-ec2/.zsh.describe_ec2
$ describe-ec2 tag '*AWS*'
$ peco-describe-ec2 // '*AWS*'がtag.Nameに含まれるインスタンスリストをpecoでインクリメンタルサーチして選択したらsshします