knifeコマンドが面倒すぎるので「knife-helper」というpluginを作ったのでチュートリアル(knife-zero,knife-ec2で)


タイトルの通り、作りました。
詳しい経緯などは[コチラをどうぞ。](http://marcy.hatenablog.com/entry/2015/04/09/004605)

# どんなPlugin？
`Chef Server`及び`knife-zero`を使う場合に、`knife`コマンドがまともに使えるようにするまでの下準備と、実際に使う時のコマンドが複雑で到底覚えられないというのがツライ。
なので、それを解決するために２つのサブコマンドを提供します。

1. 最低限の事前準備を行う`helper init`コマンド
2. `yaml`で定義したショートカットコマンドを実行する`helper exec`コマンド

# チュートリアル

ちなみに、このチュートリアルで使用したファイル群は以下にあります。
https://github.com/marcy-terui/knife-helper-example


## 導入
普通のRubyプロジェクト的に`bundler`を使う形で進めます。

以下のような、`Gemfile`を用意します。

```ruby:Gemfile
source "https://rubygems.org"

gem "chef"
gem "knife-helper"
gem "berkshelf"
gem "knife-ec2"
gem "knife-zero"
```

インストールします。

```shell
$ bundle
```


## initコマンド実行
以下のコマンドを実行します。
今回は、`knife-zero`を使用するため、ローカルモードで動かしたいので、`-l`あるいは`--local`オプションを指定します。
また、`Berkshelf`を使用するため`-B`あるいは`--berks`オプションを指定します。[^1]

ちなみに、`Librarian-Chef`を使用する場合は`-L`または`--librarian`オプションを指定します。

```shell
$ bundle exec knife helper init -l -B
```

すると、以下の様なChefの設定を定義する`.chef/knife.rb`と、ショートカットコマンドを定義するための`.knife.helper.yml`、`Berksfile`が生成されます。[^2]


```ruby:.chef/knife.rb
local_mode true
cookbook_path ["./cookbooks", "./site-cookbooks"]
chef_repo_path "./"
```

`cookbook_path `,`chef_repo_path`はオプションで変えられます。
`cookbook_path`が`-c`、`chef_repo_path`が`-r`です。

```yaml:.knife.helper.yml
---
settings:
  command_base: /Users/marcy/github/knife-helper-example/vendor/bundle/ruby/2.1.0/bin/knife

commands:
  - name: default
    command:
    condition:
    options:
```
~~`exec_path`は環境ごとに自動で設定されます。
複数人で使用する場合は`bundle exec knife`にしておくと良いかもしれません。[^3]~~
`command_base`は環境ごとに自動で設定されます。
複数人で使用する場合、`bundle exec`で実行するよう`-b`または`--bundle`オプションを付けると以下のようになり、環境ごとのpathの差異に関係なく実行できるようにしておくとよいでしょう。

```yaml
settings:
  command_base: bundle exec knife
```

# ショートカットコマンドの定義
`.knife.helper.yml`にショートカットコマンドを定義します。
`ERBテンプレート`となっているため、Rubyコードの埋め込みも可能です。

```yaml:.knife.helper.yml
---
settings:
  command_base: /Users/marcy/github/knife-helper-example/vendor/bundle/ruby/2.1.0/bin/knife

commands:
  - name: create
    command: ec2 server create
    options:
      aws-access-key-id: <%= ENV['AWS_ACCESS_KEY_ID'] %>
      aws-secret-access-key: <%= ENV['AWS_SECRET_ACCESS_KEY'] %>
      region: ap-southeast-1
      flavor: t2.micro
      image: ami-68d8e93a
      tags: Name=knife-helper-example
      ssh-key: knife-helper-example
      subnet: subnet-3dc43e58
      security-group-ids: sg-713bc314
      ssh-user: ec2-user
      identity-file: ~/.ssh/knife-helper-example.pem
      template-file: template.erb
      associate-public-ip:
  - name: bootstrap
    command: zero bootstrap ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com
    options:
      node-name: knife-helper-example
      environment: test
      hint: ec2
      ssh-user: ec2-user
      identity-file: ~/.ssh/knife-helper-example.pem
      sudo:
  - name: converge
    command: zero chef_client
    condition: chef_environment:test
    options:
      ssh-user: ec2-user
      identity-file: ~/.ssh/knife-helper-example.pem
      attribute: ec2.public_ipv4
      sudo:
```



### 各keyの説明
-----------
|key名|説明|
|:---|:--------------------------------------
|name|サブコマンド名（実行時はこれを指定する）|
|condition|search機能を使う場合の条件|
|options|オプション。Hash形式でkeyがオプション名、valueが値。value指定が必要ないオプションはkeyのみ記述する。|

`options`は必ずしもロング名ではなく、1文字名で書いても良いですが、`yaml`の見た目的にだいぶ残念な感じになりますｗ

## ノード作成
`knife-ec2`でサーバノードを調達します。

今回は`Chef Server`へ繋がず、`knife-zero`から`bootstrap`したいので、bootstrapアクションを差し替えるためのテンプレートファイルを用意しておきます。

```shell:template.erb
bash -c '
sudo yum -y update
'
```

先ほど定義した`create`ショートカットコマンドを利用します。
`knife helper exec ショートカット名`で実行できます。

```shell
$ bundle exec knife helper exec create
Instance ID: i-ad186e60
Flavor: t2.micro
Image: ami-68d8e93a
Region: ap-southeast-1
Availability Zone: ap-southeast-1b
Security Group Ids: sg-713bc314
Tags: Name: knife-helper-example
SSH Key: knife-helper-example

Waiting for EC2 to create the instance.............
Subnet ID: subnet-3dc43e58
Tenancy: default
Public DNS Name: ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com
Private IP Address: 172.31.3.169

Waiting for sshd access to become available..........done
Doing old-style registration with the validation key at ...
Delete your validation key in order to use your user credentials instead

Connecting to ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com

<snip>

Instance ID: i-ad186e60
Flavor: t2.micro
Image: ami-68d8e93a
Region: ap-southeast-1
Availability Zone: ap-southeast-1b
Security Group Ids: sg-713bc314
Tags: Name: knife-helper-example
SSH Key: knife-helper-example
Root Device Type: ebs
Root Volume ID: vol-e85021e6
Root Device Name: /dev/xvda
Root Device Delete on Terminate: true
Subnet ID: subnet-3dc43e58
Tenancy: default
Public DNS Name: ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com
Private IP Address: 172.31.3.169
Environment: _default
```

できました。

# knife-zeroからbootstrap
とりあえず、後で使うために仮の`Environment`を定義しておきます。

```shell
$ bundle exec knife environment create test
```

ショートカットコマンドから`boottrap`を実行します。

```shell
$ bundle exec knife helper exec bootstrap
Doing old-style registration with the validation key at ...
Delete your validation key in order to use your user credentials instead

Connecting to ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com
ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com Installing Chef Client...

<snip>

ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com Chef Client finished, 0/0 resources updated in 4.994025311 seconds
```

# レシピを流す
とりあえず、Community cookbookの`nginx`でも流してみることにします。

`Berksfile`を以下のように書き換えます。

```ruby:Berksfile
source "https://supermarket.chef.io"

cookbook "nginx"
```

cookbookを収集します。

```shell
$ bundle exec berks vendor cookbooks
```

ノードの`run_list`に追加します。

```
$ bundle exec knife node run_list add knife-helper-example recipe[nginx]
```

では、ショートカットコマンドから実行します。

```shell
$ bundle exec knife helper exec converge
52.74.50.188 Starting Chef Client, version 12.2.1
52.74.50.188 resolving cookbooks for run list: ["nginx"]

<snip>

52.74.50.188 Running handlers:
52.74.50.188 Running handlers complete
52.74.50.188 Chef Client finished, 22/25 resources updated in 32.196846458 seconds
```

よく焼けたようですね。

# ちなみに
`knife helper exec`に`-p`または`--print`オプションをつけると、実行はせずに実行されるコマンドを出力します。

どんなコマンドになるか？

```shell
$ bundle exec knife helper exec create -p
/Users/marcy/github/knife-helper-example/vendor/bundle/ruby/2.1.0/bin/knife ec2 server create --aws-access-key-id AKIAIIKXXXXXXX --aws-secret-access-key XXXXXXXXXXXXXXXXXXXXXX --region ap-southeast-1 --flavor t2.micro --image ami-68d8e93a --tags Name=knife-helper-example --ssh-key knife-helper-example --subnet subnet-3dc43e58 --security-group-ids sg-713bc314 --ssh-user ec2-user --identity-file ~/.ssh/knife-helper-example.pem --template-file template.erb --associate-public-ip

$ bundle exec knife helper exec bootstrap -p
/Users/marcy/github/knife-helper-example/vendor/bundle/ruby/2.1.0/bin/knife zero bootstrap ec2-52-74-50-188.ap-southeast-1.compute.amazonaws.com --node-name knife-helper-example --environment test --hint ec2 --ssh-user ec2-user --identity-file ~/.ssh/knife-helper-example.pem --sudo

$ bundle exec knife helper exec converge -p
/Users/marcy/github/knife-helper-example/vendor/bundle/ruby/2.1.0/bin/knife zero chef_client 'chef_environment:test' --ssh-user ec2-user --identity-file ~/.ssh/knife-helper-example.pem --attribute ec2.public_ipv4 --sudo
```


こんな複雑なコマンドを少しでも簡単に、使いやすく、使い回ししやすいようにと`knife-helper`を作った次第です。

より良い方法等あれば、フィードバックいただけると幸いです。


[^1]: インストールがクソ重くてヘイト溜まるけど、慣れたのとなんだかんだデファクトの地位を確立してる感あるので不本意ながら
[^2]: 2015.04.09現在
[^3]: オプション化 ~~するかも~~しました