この記事はSansan Advent Calendar 2015の14日目です。
何書こうかなと考えたのですが、新鮮なネタを持ちあわせておらず、今年のAdvent Calenderで一切話題が無かったChefについて書きます。
Test Kitchen + Serverspec(busser-serverspec) でChefレシピのテストを書いているのですが、ChefのAttributesによってテストケースが変わる場合にとても困っていました。
例えば、ユーザを作成するレシピとそのテストを書きたいとします。
※ パスワードは今回設定しないとします。
default["users"]["username"] = "foo"
default["users"]["home_dir"] = "/home/foo"
user "#{node['user']['username']}" do
home "#{node['users']['home_dir']}"
supports :manage_home => true
action :create
end
require 'spec_helper'
describe user("foo") do
it { should exist }
it { should have_home_directory "/home/foo" }
end
describe file("/home/foo") do
it { should be_directory }
it { should be_owned_by "foo" }
it { should be_grouped_into "foo" }
end
Serverspecだと例のように直接ユーザ名とホームディレクトリを指定してテストを書くことになりますが、もし、サーバ(Node)やRoles,Environmentsで、override_attributes
等でAttributesが変わったり上書きされている場合だとそのままではテストが通りません。
調べてみると、.kitchen.yml
で設定すれば良いという記事をよく見かけますが、多くのレシピを作っている場合、すべてのAttributesを.kitchen.yml
に書くのはとても大変です。また、Attributesを変更する度に.kitchen.yml
も変更しなければならないため、変更漏れがあるとテストが落ちて面倒です。
そこでやっと本題に入りますが、ServerspecからChefのAttributesを参照することでテストを柔軟に書けるようにしたいと思います。
ディレクトリ構成
chef-repo
├── .kitchen.yml
├── site-cookbooks
├── environments
├── nodes
├── roles
├── data_bags
└── test
└── integration
├── default
│ └── serverspec
│ └── default_spec.rb
├── helpers
│ └── serverspec
│ └── spec_helper.rb
└── shared
└── users
└── default.rb
test
ディレクトリがメインなのでそれ以外は端折っています。
test/integration/shared
ディレクトリ以下には各Cookbookに書いたレシピに対応したテストを置きます。
test/integration/default
は後述するテストスイート名です。
各種設定ファイル
driver:
name: docker_cli
transport:
name: docker_cli
provisioner:
name: chef_zero
data_path: test/integration/shared
platforms:
- name: centos-7.1
driver_config:
username: root
image: centos:centos7
privileged: true
command: /sbin/init
suites:
- name: default
run_list:
- recipe[users::default]
attributes:
このファイルで重要な設定は2つです。プロバイダのname
とdata_path
です。
プロバイダはChef-Zeroにします(Chef-Soloではない)。
Chef-Zeroというのは雑な説明をすると、ローカルで動かす簡易的なChef Serverです(合ってる?)。
後述しますが、Chef-Zeroにすることが今回のキモとなります。
また、data_path
は作成したServerspecファイルのパスを指定します。
ドライバは何でも良いですが、Docker_CLIにしました。普通のDockerドライバでも良いのですが、SSHポート開ける必要がないのでこちらの方が楽かなと思ってこちらにしました。
require 'serverspec'
require 'json'
# ※1
base_spec_dir = Pathname.new(File.expand_path('../../../../kitchen/data', __FILE__))
Dir[base_spec_dir.join('./**/*.rb')].sort.each{ |f| require f }
set :backend, :exec
# ※2
attributes = {}
node_dir = File.expand_path('../../../../kitchen/nodes', __FILE__)
Dir.glob(File.join(node_dir, '*.json')) do |json_file|
node = JSON.parse(File.read(json_file))
attributes = node["default"]
%W{normal override}.each do |priority|
if node[priority].nil? then
next
end
node[priority].each do |key, attr|
if ! attributes[key].nil? then
if attr.kind_of?(Array) then
attributes.merge!({ key => attr })
else
attributes[key].merge!(attr)
end
else
attributes.merge!({ key => attr })
end
end
end
# ※3
attributes["roles"] = node["automatic"]["roles"]
attributes["chef_environment"] = node["chef_environment"]
next
end
set_property attributes
このヘルパーファイルでは後半(※2)が重要です。
Test Kitchenを実行することで、Dockerコンテナ上で以下のようなファイルが置かれます。
# Chefレシピなどのファイル郡
/tmp/kitchen/
├── site-cookbooks
│ └── users
│ ├── attributes
│ │ └── default.rb
│ ├── metadata.rb
│ ├── README.md
│ └── recipes
│ └── default.rb
├── data
│ └── users
│ └── default.rb
├── environments
├── nodes
│ └── default-centos-71.json
└── roles
# Serverspec(busser-serverspec)のファイル郡
/tmp/verifier/
├── bin
├── gems
└── suites
└── serverspec
├── default_spec.rb
└── spec_helper.rb
Chef-Zeroプロバイダで実行することで、/tmp/kitchen/nodes/<node>.json
にChef実行したノード情報が入っているので、set_property
として読み込ませることでServerspecから参照できるようにしています。override_attributes
については同じ属性があった場合は上書きし、同じ属性がない場合はプロパティに追加しています。 また、RoleとEnvironmentもプロパティに入れることもできます(※3)。
ちなみに、.kitchen.yml
のdata_path
で指定したファイル郡は/tmp/kitchen/
以下に置かれますので、ヘルパーファイルの前半(※1)で読み込ませています。
あとはテストは以下のように修正して$ kitchen test
を実行すればテストが通るはずです。
require 'spec_helper'
describe 'default' do
include_examples "users::default"
end
shared_examples 'users::default' do
describe user("#{property['users']['username']}") do
it { should exist }
it { should have_home_directory "#{property['users']['home_dir']}" }
end
describe file("#{property['users']['home_dir']}") do
it { should be_directory }
it { should be_owned_by "#{property['users']['username']}" }
it { should be_grouped_into "#{property['users']['username']}" }
end
end
最後に
あまり綺麗な形ではないかもしれませんが、Serverspec(+Test Kitchen)からChefのAttributesを参照できるようにしました。
もっと上手い方法があればぜひコメントしていただければ幸いです。
本来はTest Kitchen(busser-serverspec)が機能として追加してくれれば一番いいんですけどね・・・。