Serverspecでansible-vaultの情報を使う


お久しぶりです。4月に就職して以来、約8ヶ月ぶりの投稿です。
今は田町でRuby、Ansible、Jenkins辺りと戯れつつクラウドを作っています。

学生時代は、研究室用に[hico-horiuchi/ansible-playbooks](https://github.com/hico-horiuchi/ansible-playbooks)を書いていました。
今見ると、roleで整理されていなかったり、tagやvarを使えていないなど、中々酷いものですね…。

そこで、5月の研修中に暇だったので、Ansible 2.0対応も兼ねて、自鯖用のPlaybookを書いてみました。
内容は「[ConoHa + DokkuでプライベートPaaS構築](http://qiita.com/hiconyan/items/e4c6de6c2466bd3880c4)」の環境を構築するものです。

  - [hico-horiuchi/hiconyan-com-ansible](https://github.com/hico-horiuchi/hiconyan-com-ansible)

今回は、ansible-vaultで暗号化した情報を、Serverspecで使うために工夫した内容を紹介します。
これにより、「設定ファイルにトークンが記述されているか」「パスワードを使ってログインできるか」などが確認できるようになります。

## ansible_specの紹介

Ansible + Serverspecでテストをするために、[volanja/ansible_spec](https://github.com/volanja/ansible_spec)を使っています。
ansible_specの詳細は、開発した方が書かれている下記の記事をご覧下さい。

  - [Ansibleの設定ファイルを使ってServerspecを実行するテンプレート作成用Gem(ansible_spec)を作りました。](http://qiita.com/volanja/items/5e97432d6b231dbb31c1)

今回は、ansible_specが生成した `spec/spec_helper.rb` に改造を施していきます。

## Rubyでansible-vaultの情報を読み込む

[ansible-vault](http://docs.ansible.com/ansible/playbooks_vault.html)は、Ansibleのvars(変数)を暗号化するためのツールです。
パスワードやトークンを記述したYAMLファイルを、暗号化・複合化することができます。

今回は、ansible-vaultの暗号化ファイルをRubyで読み込むために、[tpickett66/ansible-vault-rb](https://github.com/tpickett66/ansible-vault-rb)を使用します。
使い方は簡単で、下記のように暗号化ファイルのパスとパスワードを渡すだけで複合化してくれます。
**ただし、** `Ansible::Vault.read` **の返り値はStringなので、** `YAML.load` **でハッシュ化する必要があります。**

```ruby
require 'ansible/vault'
require 'yaml'

contents = Ansible::Vault.read(path: '/path/to/file', password: 'foobar')
vars = YAML.load(contents)
```

## Serverspecでansible-vaultの情報を使う

前置きが長くなりましたが、ここからが本題です。
Serverspecでansible-vault-rbを使うために、 `spec/spec_helper.rb` の末尾に下記の内容を追加します。
ソースコード全体は、[hico-horiuchi/hiconyan-com-ansible/spec/spec_helper.rb](https://github.com/hico-horiuchi/hiconyan-com-ansible/blob/master/spec/spec_helper.rb)をご覧下さい。

```ruby:spec/spec_helper.rb
vars = AnsibleSpec.get_variables(host, group_idx, hosts)
property = AnsibleSpec.get_properties[group_idx]
ansible_cfg = File.expand_path('../ansible.cfg', File.dirname(__FILE__))
vault_password_file = ''
vault_password = ''

# ansible.cfgからvault_password_fileのパスを抽出
File.open(ansible_cfg, 'r') do |f|
  vault_password_file = f.read.match(/^vault_password_file = (.+)$/)[1]
end

# vault_password_fileからパスワードを読込
File.open(File.expand_path(vault_password_file), 'r') do |f|
  vault_password = f.read.chomp
end

# 必要なroles/*/vars/secret.ymlをハッシュとして読込
property['roles'].each do |role|
  secret = "roles/#{role}/vars/secret.yml"
  if File.exist?(secret)
    vars.merge! YAML.load(Ansible::Vault.read(path: secret, password: vault_password))
  end
end

set_property vars
```

コメントでも補足していますが、処理手順は下記のようになっています。

  1. ansible.cfgからvault_password_fileのパスを抽出
    - ansible-playbook時にパスワードを入力しなくて良いように、設定ファイルにパスを記述している。
    - 私の場合は `~/.vault_password` にパスワードを書き出して保存している。
    - 詳細は「[Configuration file — Ansible Documentation](http://docs.ansible.com/ansible/intro_configuration.html#vault-password-file)」をご覧下さい。
  2. vault_password_fileからパスワードを読込
    - 単純なRubyのファイル操作で、vault_password_fileの中身を読み込む。
    - `~` などを展開するために `File.expand_path` で絶対パスを取得する。
  3. 必要な `roles/*/vars/secret.yml` をハッシュとして読込
    - 今回は暗号化ファイル( `secret.yml` )をrole毎に分けて `vars` ディレクトリに格納した。
    - `property['roles']` で対象ホストのroleが分かるので、 `secret.yml` が存在する場合は読み込む。

また、 `set_property vars` をすることで、Serverspecの中で `property` ハッシュで利用できるようになります。
実際のServerspecで利用している箇所は下記の2つです、参考にして下さい。

  - [hico-horiuchi/hiconyan-com-ansible/roles/mackerel/spec/config_spec.rb](https://github.com/hico-horiuchi/hiconyan-com-ansible/blob/master/roles/mackerel/spec/config_spec.rb#L12)
  - [hico-horiuchi/hiconyan-com-ansible/roles/redis/spec/config_spec.rb](https://github.com/hico-horiuchi/hiconyan-com-ansible/blob/master/roles/redis/spec/config_spec.rb#L13)

## おわりに

実は、この方式は8月中旬には確立していたのですが、仕事でバタバタしていて記事にするのが遅くなりました…。
社会人になると、趣味もそうですが、技術的に遊べる時間が少なくなって、中々大変です。

今回は `secret.yml` を `roles/*/vars` に配置しました。
別の方法として、groupで分けるために `group_vars/*/secret.yml` に配置する方法もありますね。
両方を実現しようとすると、Ansibleの変数読み込み順序も考慮する必要があるので、大変そうです。