Data bagsで複数形式のデータを扱う備忘録

とりあえず配列とかハッシュとかの扱い方をピックアップして備忘録を。

※data_bagのデータを扱うハンズオン風にしようとして失敗。とりあえず備忘録なのでご参考まで。

### ０．ツールの使い方ほか下準備

knife-soloとknife-solo_data_bagのgemが入ってなければ入れる。

```
gem install knife-solo knife-solo_data_bag
```

EDITOR環境変数をexportするようにどっかにかいとく

```
vi ~/.bashrc
export EDITOR=vi
```

knifeコマンドでdata_bagsを開けるときにEDITORに指定したものが使われます。

###  １．暗号鍵を作る、鍵の管理について

・暗号鍵をつくる

```
openssl rand -base64 512 > databag_key
```

作られた鍵をgitにコミットされないように.gitignoreに記載

```
vi .gitignore
databag_key
```

（※鍵は案件ごとに異なるイメージで、流出厳禁かつ無くすとデータが見えなくなります。）


###  ２．暗号鍵を~/chefrepo/.chef/knife.rbに設定する

```
vi ~/chefrepo/.chef/knife.rb
-----
chef_repo = File.join(File.dirname(__FILE__), "..")
current_dir = File.dirname(__FILE__)
log_level                :info
log_location             STDOUT
client_key               "#{current_dir}/dummy.pem"
validation_client_name   'chef-validator'
validation_key           "#{current_dir}/dummy.pem"
cookbook_path    ["#{chef_repo}/cookbooks", "#{chef_repo}/site-cookbooks"]
node_path        "#{chef_repo}/nodes"
role_path        "#{chef_repo}/roles"
environment_path "#{chef_repo}/environments"
data_bag_path    "#{chef_repo}/data_bags"
encrypted_data_bag_secret "#{chef_repo}/databag_key" ##★ここ

#puts Chef::Config.inspect
-----
```

###  ３．暗号化しないデータを登録する

encrypted_data_bag_secret に鍵を指定した状態でknife solo data bagコマンドを使うとデータが暗号化されて登録されます。
なので暗号化したくない場合はvi等の通常つかっているエディタでファイルを作成します。

```
vi ~/chefrepo/data_bags/<bag-name>/<json-data-file-name>.json
vi ~/chefrepo/data_bags/fluentd/loghost.json
-------
{
  "id": "loghost",
  "loghost": "log.mng.local",
  "key1": "value1",
  "key2": "value2"
}
-------
cat ~/chefrepo/data_bags/fluentd/loghost.json |json_verify
```

　※jsonデータのシンタックスチェックをjson_verifyやjsonlintに渡す等で実施すると効率が良いです。（最終行のカンマ有と途中の行のカンマ忘れ注意）
　※idが最も重要で、レシピ内部から呼び出す時にはidを用います（コピーした後の修正漏れに注意）。
　　つまりファイル名とidが一致している必要はなさげですが、jsonファイル名をidと同じにするとわかりやすいです。

###  ４．暗号化しないデータを取り出す

```
vi ~/chefrepo/site-cookbooks/<cookbook-name>/recipes/<recipe-name>.rb
-------
loghost = data_bag_item('fluentd',"loghost")['loghost']  ##★data_bag_item('bag名≒ディレクトリ名',"jsonファイル内id値")['キー値']
key1 = data_bag_item('fluentd',"loghost")['key1']
template "/etc/td-agent/extra/td-agent-syslog.conf" do
  action :create
  source "td-agent.conf.client-syslog.erb"
  notifies :restart, 'service[td-agent]'
  variables ({
   :loghost => loghost,   ##★ここでハッシュとして定義してる。:templateファイル内の@変数 => recipeで指定してる変数　
　 :key1 => key1
  })
end
-------

vi ~/chefrepo/site-cookbooks/<cookbook-name>/templates/default/td-agent.conf.client-syslog.erb
-------
  <store>
    type forward
    heartbeat_type tcp
    send_timeout 60s
    flush_interval 1s
    buffer_queue_limit 512
    buffer_type file
    buffer_path /var/tmp/fluent_buf
    expire_dns_cache 10
  <server>
      host <%= @loghost %>  ##★ここで<%= @var %>で指定した変数にrecipeのtemplateリソースのvariablesで指定した値が入る
      port 24224
  </server>
  </store>
-------
```

　※単独のkeyを取り出したいのでなければ以下のような書き方があります。

```
logdata = data_bag_item('fluentd',"#{account}_loghost")
loghost = logdata['loghost']
key1 = logdata['key1']
key2 = logdata['key2']
```

###  ５．knifeで暗号化するデータを登録する

　・soloだとknife solo data bag <subcommand> <bag-name> <id-name>という感じで、サブコマンドは以下がある

|Name|サブコマンド解説|
|:--|:--|
|create|新規作成。既存にやると上書きされるので注意。git checkoutで戻しましょう。|
|edit|編集。既存ファイルを編集したいときに。新規作成はできません。編集しないで閉じてもgit的には更新されてる風になるのでgit checkoutで戻しましょう。|
|show|中身をみたいだけのときに表示してくれる。json形式ではない。|
|list|bagのリストを表示してくれるだけ(data_bagsディレクトリ直下のディレクトリが一覧表示されるだけ)|


```
knife solo data bag create fluentd loghost
knife solo data bag edit fluentd loghost
knife solo data bag show fluentd loghost
```

　※knifeコマンド使う場合はシンタックスが間違ったデータを登録したままエディタを閉じようとするとエラーで閉じれないか再び開いたときに空になってると思います。

###  ６．暗号化するデータを取り出す

書きかたが以下のように変わるだけであとは同じ。

```
logdata = data_bag_item('fluentd',"#{account}_loghost")
　↓
logdata = Chef::EncryptedDataBagItem.load('fluentd',"#{account}_loghost")
```

###  ７．1次元の配列データを登録する

rubyにおける配列は、以下のように定義することが可能です。

```
array = ["var1","var2","var3"]
array = %w{"var1" "var2" "var3"}
```

配列を初期化した変数をデフォルトのattributeとして以下のように登録しておかないとエラーになる場合があります。(初期化の配置場所はレシピ内でもattributeでもどっちでもOK)

```
array = []
```

```
vi ~/chefrepo/data_bags/awskeys/zabbix_all.json
-------
{
  "id": "zabbix-all",
  "accounts": [ "hoge", "fuga", "piyo" ]
}
-------
```

###  ８．1次元の配列データを取り出す

配列データはeachで取り出せます。forとeachはおなじ機能のメソッドです(forの実体がeachだった気がする)。

```
accounts = key_data['accounts']
accounts.each do |account|
 cron "test_#{accounts}" do
    user "root"
    command "/bin/echo #{account} > /tmp/#{account}.txt 2>&1"
    hour "*"
    minute "0"
  end
end
```

###  ９．配列とハッシュの入れ子データを登録する

異なるkey=valueの集合データをハッシュといいます。
ハッシュはrubyだと以下のように定義できますが、chefのattribute的には、{ "key1": "var1", "key2": "var2",, }という感じです。
hash = { "key1" => "var1", "key2" => "var2", "key3" => "var3" }
以下の例では配列の中にハッシュが入っています。

```
vi ~/chefrepo/data_bags/awskeys/zabbix_all.json
-----------
{
  "id": "zabbix-all",
  "awskeys": [
    {
      "zabbix_account": "zabbix-prd",
      "region": "us-**st-2",
      "aws_access_key_id": "AKIA***************",
      "aws_secret_access_key": "****************************************"
    },
    {
      "zabbix_account": "zabbix-dev",
      "region": "us-**st-2",
      "aws_access_key_id": "AKIA*****************",
      "aws_secret_access_key": "****************************************"
    }
  ],
  "accounts": [ "hoge", "fuga", "piyo" ]
}
-----------
```

###  １０．配列とハッシュの入れ子データを取り出す

・まとめてとりだしてforで全部のデータを置くパターン
　templateファイル側にforループのメソッドを仕込んで取り出します。

```
vi ~/chefrepo/site-cookbooks/zabbix/recipes/cloudtrail.rb
-----------
awskeys = key_data['awskeys']
template "/root/.aws/config" do
  source "config_root.erb"
  mode 0700
  owner "root"
  group "root"
  variables ({
    :awskeys => awskeys
  })
end
-----------
vi ~/chefrepo/site-cookbooks/zabbix/templates/default/config_root.erb
-----------
<% for key in @awskeys %>
[profile <%= key["zabbix_account"] %>]
output=json
aws_access_key_id=<%= key["aws_access_key_id"] %>
aws_secret_access_key=<%= key["aws_secret_access_key"] %>
region=<%= key["region"] %>

<% end %>
-----------
```

　※templateファイル内では<% %>内にrubyのコードが書けます。よってifとかelsifとかも書けます。

・特定の文字列に一致する値だけ取り出したいパターン(レシピ抜粋例）
　こちらはレシピ側で目的にあったメソッドを用いて値を整えて取り出す形です。

```
  ## set client variables for secure_forward.
  sfvariables = {}
  fluentusers.each do |fluentuser|
    if fluentuser.has_value?("#{account}")         ##一致するアカウントが含まれているハッシュの場合だけ、、
      fluentuser.values_at(:username, :password)   ##指定したkeyを抽出したハッシュデータをもとのハッシュに入れて、
      sfvariables = fluentuser                     ##ピックアップした値を新しいハッシュに代入
    end
  end
  sfvariables.merge! ({                            ##ほかのハッシュと結合したかったらして!がついてるので上書き。
        :shared_key => shared_key,
        :sslcacrt => sslcacrtfile,
        :servers => servers,
  })
  ## set client template for secure_forward.
  template "/etc/td-agent/extra/td-agent-#{ssl_conf}" do
    action :create
    source "td-agent.conf.server-#{ssl_conf}.erb"
    notifies :restart, 'service[td-agent]'
    variables sfvariables                          ##templateリソースでvariablesにさっき作ったハッシュを指定してtemplateファイル内の@と差し替える。
  end
```

###  １１．証明書を登録する

catしてperlとかで末尾の改行を文字列に置換した値をkeyに対するvalueとして1行で登録する感じです。暗号化必須です。

```
cat certdata.txt | perl -pe 's/\n/\\n/g'
knife solo data bag create certs mng-wildcard2
{
  "id": "mng-wildcard2",
  "crt": "ここに1行に変換した値をコピペ",
  "key": "ここに1行に変換した値をコピペ",
  "cacrt": "ここに1行に変換した値をコピペ"
}
```

###  １２．証明書を取り出す

・certsに暗号化された証明書本体を入れて、sslでその証明書をポインタして、
　roleそのたのアトリビュートでssl側のdata_bags名を指定するみたいなイモヅル方式にしています。
　理由としては証明書自体の更新作業はシンプルにしたい需要から。

```
 vi ~/chefrepo/site-cookbooks/nginx/recipes/nginx_ssl.rb
---------------------
environment = node.chef_environment
data_type = node['nginx']['data_type']
datafile = "#{data_type}_#{environment}"
  sslclientconf_type = node['nginx']['sslclientconf_type']
  sslkeydir = '/etc/nginx'
  ssl_databag = data_bag_item("ssl", "#{datafile}")

  # SSL Server Certificate
  sslcert_type = ssl_databag["cert-type"]
  servercerts_databag = Chef::EncryptedDataBagItem.load("certs", "#{sslcert_type}")
  sslkeyfile = "#{sslcert_type}.key"
  sslcrtfile = "#{sslcert_type}.crt"
  sslcrtdata = servercerts_databag["crt"]
  sslkeydata = servercerts_databag["key"]

  variables.merge!({
    :sslcrt => sslcrtfile,
    :sslkey => sslkeyfile
  })

  # SSL Server Certificate and Key
  file "#{sslkeydir}/#{sslcrtfile}" do
    notifies :restart, 'service[nginx]'
    content  sslcrtdata
  end

  file "#{sslkeydir}/#{sslkeyfile}" do
    notifies :restart, 'service[nginx]'
    content sslkeydata
  end
---------------------
```

　※fileリソースのcontentを使うとtemplateが要らないので簡単シンプルに。
　※クライアント証明書を使う使わない他の分岐が入ると上記よりはforとかmerge!とか多用になるかも。

###  １３．参考資料

[template — Chef Docs](https://docs.chef.io/resource_template.html)
[databags を使ってみた一部始終（1） - ようへいの日々精進](http://inokara.hateblo.jp/entry/2013/05/05/084251)
[配列のなかに入れ子な複数のハッシュのdata_bagsのjsonをchefで扱うレシピ - Qiita](http://qiita.com/smallpalace/private/66e80df6de693779c224)