Chefにおけるattributeの構造と設定書式をどうしようか。ハッシュのキーに設定値を置く気持ち悪さと便利さ

Chef経験者には今更なのかもしれないが、とりとめ無く、少し考えをまとめてみた。

#Chefのattributeとして適したパターン

個人的に遭遇した、Chefのattributeとして適したパターンは、以下のケースで分類できる様に思える。

- OS設定関係
    - 設定ファイルの編集による設定、ユーザー、グループ、ディレクトリー、サービスの設定などの処理が多く、Chefのリソースとシンプルに対応がつくケースが多い。
    - その為、attributeの構造も、Chefのリソースに即した構造にすると、理解、実装しやすい。
- 色々なことを設定できる万能コマンドが提供されているミドルウェア製品
    - db2のdb2コマンド、PowerHA SystemMirror for AIX/Linuxのclmgrコマンド、pacemakerのpcsコマンドなど
    - attributeの構造は、万能コマンドに渡されるパラメーターを如何に抽象化して扱い易くできるかがポイント。
レシピはパラメーターを組み立ててその万能コマンドを呼び出すイメージ。 
- 複雑な設定ファイルを読み込んで設定する
    - templateリソースの独断場
    - attribute構成の抽象化は、それほどは必要ない印象
- それ以外
    - 色々なコマンドによる様々な処理が必要な製品
    - 適したattribute構造も一意には決まらないかも。

ここではOS関係に関して書いてみる。

#OS設定関係のクックブックにおけるattributeパターン1 : ユーザーデータをChefのリソース構造に即したhashで扱う

上に書いた通り、Chefのリソース構造に即した構造にする

##attribute構造の例


```

└── 'my_cb'
    └── '1章foo'
        └── '1節bar'
            └── 'file'
                ├── '/tmp/test1.txt'            # ユーザーデータ
                │   ├── 'content'
                │   │   └── "hello world.\n"    # ユーザーデータ
                │   ├── 'mode'
                │   │   └── '0755'              # ユーザーデータ
                │   ├── 'owner'
                │   │   └── 'usr1'              # ユーザーデータ
                │   └── 'group'
                │       └── 'usr1'              # ユーザーデータ
                │
                └── '/tmp/test2.txt'            # ユーザーデータ
                    ├── 'content'
                    │   └── "hello world 2.\n"  # ユーザーデータ
                    ├── 'mode'
                    │   └── '0755'              # ユーザーデータ
                    ├── 'owner'
                    │   └── 'usr2'              # ユーザーデータ
                    └── 'group'
                        └── 'usr2'              # ユーザーデータ

```



##パターン1の書式


###jsonによるattribute

- 何かで作成したjsonで受け取ることが要件なら記述方法の検討は不要。
    - ただ、jsonを人手で作って設定するというのであれば合理的でない。jsonは人が直接作成したり編集したりする物ではないと思う。



###Ruby DSLによる書式例1-1 : ハッシュの書式

```ruby:attributes/default.rb

default['my_cb'] = { '1章foo' => { '1節bar' => { 'file' => { 
  '/tmp/test1.txt' => { 'content' => "hello world.\n", 'mode' => '0755', 'owner' => 'usr1', 'group' => 'usr1' },
  '/tmp/test2.txt' => { 'content' => "hello world 2.\n", 'mode' => '0755', 'owner' => 'usr2', 'group' => 'usr2' },
} } } }

```

- こんな書き方も可能という例を示したかっただけ。
- やりすぎ感
- 前後の{}が面倒そう

###Ruby DSLによる書式例1-2 : 一つのhashを代入

```ruby:attributes/default.rb

default['my_cb']['1章foo']['1節bar']['file'] = {
  '/tmp/test1.txt' => { 'content' => "hello world.\n", 'mode' => '0755', 'owner' => 'usr1', 'group' => 'usr1', },
  '/tmp/test2.txt' => { 'content' => "hello world 2.\n", 'mode' => '0755', 'owner' => 'usr2', 'group' => 'usr2', },
}

```


hashとしてマージをしたものを代入する。
キーとして使用するファイル名情報も代入される側に含まれる。
ユーザーデータは代入する側にある(i.e. =の右にある。)



###Ruby DSLによる書式例1-2' : 対象要素毎にhashを代入

```ruby:attributes/default.rb
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt'] = { 'content' => "hello world.\n", 'mode' => '0755', 'owner' => 'usr1', 'group' => 'usr1' } 
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt'] = { 'content' => "hello world 2.\n", 'mode' => '0755', 'owner' => 'usr2', 'group' => 'usr2' }
```

- ファイル単位で属性込みで、代入を行う。
- 内容はマージされる。('/tmp/testN.txt'を代入元に移動してしまうと同じキーに対する代入となりマージされない。)
- Excelで2次元の表から作ることもそれほど難しくはない。
- hashの=>を用いた記法とdefault[]への代入が混在してしまう。


###Ruby DSLによる書式例1-3 : 属性値をdefault[]に代入

```ruby:attributes/default.rb
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['content'] = "hello world.\n"
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['mode'] = '0755'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['owner'] = 'usr1'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['group'] = 'usr1'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['content'] = "hello world 2.\n"
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['mode'] = '0755'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['owner'] = 'usr2'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['group'] = 'usr2'
```

- 属性毎に代入する。
- default[]への代入しかないので分かりやすく、Excelなどで式を作成するのが容易。


###Ruby DSLによる書式例1-3' : 行を属性ごとに並び替え


```ruby:attributes/default.rb
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['content'] = "hello world.\n"
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['content'] = "hello world 2.\n"
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['mode'] = '0755'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['mode'] = '0755'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['owner'] = 'usr1'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['owner'] = 'usr2'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test1.txt']['group'] = 'usr1'
default['my_cb']['1章foo']['1節bar']['file']['/tmp/test2.txt']['group'] = 'usr2'

```

- 個人的にはこの書き方をすることが多い
- 同じ種類で並んでいた方が違いが目立つ
- Excelで式を作ることは簡単
    - 元となるExcel上ではソートの為の専用の列を設けて、必要に応じて例3と例3'の並び替えを行なっている。

##パターン1の書式のおすすめ

個人的には、書式例1-3', 1-3, 1-2'が良い様に思う。
巨大な式は作るのも読むのも大変なのでできるだけ分割。

##ユーザーデータがkeyに含まれることの気持ち悪さ
'/tmp/text.txt' といった値がキーになっていることに対してやや反発した意見を受けた。(というか私も初めはそうだった。)こんな感じか。

- ハッシュのキーにユーザーデータが入ることが気持ち悪い。また、例3,3'ではユーザーデータなのに代入による設定では無いことに違和感
    - これらは、例2'書式などで解消/緩和できるかもしれない。分かり易さ、使いやすさなどから選択すれば良い。 
- ハッシュのキーに入っているユーザーデータを取り出しにくいのでは？


hashに対する.eachを使うことで、簡単に使える。むしろその為にキーにユーザーデータで使用

###キーとして入っているユーザーデータの取り出し


```ruby:recipes/default.rb

node['my_cb']['1章foo']['1節bar']['file'].each do |f_name, f_attr|
  file f_name do
    content f_attr['content']
    mode f_attr['mode']
    owner f_attr['owner']
    group f_attr['group']
  end
end

```
かえって簡単


#OS設定関係のクックブックにおけるattributeパターン2 : ユーザーデータ部分をArrayで扱う


それでもユーザーデータ部分をArrayで並列に並べて管理したいというケースはあるかもしれない。
また、crontabの内容の様な、その要素を一意に決定づけるキーが無いなど、Arrayが必要なケースもある。




##構造の例


```

└── 'my_cb'
    └── '1章foo'
        └── '2節baz'
            └── 'file'
                └── '/etc/crontab'
                    └── 'content'
                        └── [ '0 11 * * * /usr/bin/errclear -d S,O 30', 
                              '0 12 * * * /usr/bin/errclear -d H 90' ]
```

注

- 要素の追加時には、(キーが同じなのでマージされず、)<< などでArrayに要素を追加する必要がある
- 優先順位が異なる場合に、マージとなるケースと上書きとなるケースがある
    - ここでは同じ優先順位内での操作のみを考える。

参考
[ChefのAttributeについて](http://metasearch.osdn.jp/wiki/index.php?Chef%A4%CEAttribute%A4%CB%A4%C4%A4%A4%A4%C6)



##パターン2の書式


###jsonによるattribute
- 上と同じく、何かで作成したjsonで受け取ることが要件なら記述方法の検討は不要。


###Ruby DSLによる書式例2-1 : 全体をhashで記述

```ruby:attributes/default.rb
default['my_cb'] = { '1章foo' => { '2節baz' => { 'file' => { '/etc/crontab' => { 'content' => [
  '0 11 * * * /usr/bin/errclear -d S,O 30',
  '0 12 * * * /usr/bin/errclear -d H 90',
] } } } } }
```

- 参考まで


###Ruby DSLによる書式例2-2 :  一つのArrayを代入

```ruby:attributes/default.rb
default['my_cb']['1章foo']['2節baz']['file']['/etc/crontab']['content'] = [
  '0 11 * * * /usr/bin/errclear -d S,O 30',
  '0 12 * * * /usr/bin/errclear -d H 90',
]
```


Arrayを代入する。
ユーザーデータは代入する側にある(i.e. =の右にある。)




###Ruby DSLによる書式例2-3 : 要素毎に代入

```ruby:attributes/default.rb
default['my_cb']['1章foo']['2節baz']['file']['/etc/crontab']['content'] = default['my_cb']['1章foo']['2節baz']['file']['/etc/crontab']['content'].to_a << '0 11 * * * /usr/bin/errclear -d S,O 30'
default['my_cb']['1章foo']['2節baz']['file']['/etc/crontab']['content'] = default['my_cb']['1章foo']['2節baz']['file']['/etc/crontab']['content'].to_a << '0 12 * * * /usr/bin/errclear -d H 90'
```
1行がそれなりに長いが、全ての行を同じフォーマットで書けるので非常にシンプル。
個人的にはよくこの書式を使っている。

参考
[ChefでattributeをRuby DSL形式で書く場合に、要素を追加するArrayの初期化](https://qiita.com/hiroyuki_onodera/items/43b0e17de981dd9b3bfc)


##パターン2の書式おすすめ

ユーザーデータ部分をArrayで扱うと色々考慮が必要なので、可能ならhashで扱う。
Arrayが必要な場合には、個人的には例2-3がお勧め。