Posted at

Serverspec と Busybox

More than 3 years have passed since last update.

Serverspec では様々な種類のテストを記述することが出来るのですが、内部では Specinfra というクラスでそのテストの定義と実装をしています。

実際にテストする際に使用するツールは OS によって変わってきてしまうので、その差異を吸収する部分も Specinfra に書かれています。

Vagrant が様々なホストとゲスト OS でプロビジョン出来る仕掛けと同じです。

さて、その Specinfra ですが、boot2docker や拙作の DockerRoot のように Busybox がベースの OS 用の設定がなく、テストがうまくいきません。

そこで、簡易な(実際に使ってみてうまく動かなかった部分のみ) DockerRoot/Busybox 用拡張を書いてみました。

ここでは、単純に spec/spec_help.rb に追加して、拡張することにしました。

(本格的なのは面倒なのでw)

たぶん、他のタイプのテストでも動かないのがあるでしょう。そのうち対応するかも知れませんが、需要が無ければこのままですw


spec/spec_help.rb

class Specinfra::Command::Busybox

class Base < Specinfra::Command::Linux::Base
class Group < Specinfra::Command::Base::Group
class << self
def check_exists(group)
"grep #{escape(group)} /etc/group"
end

def check_has_gid(group, gid)
"grep #{escape(group)} /etc/group | cut -f 3 -d ':' | grep -w -- #{escape(gid)}"
end
end
end

class RoutingTable < Specinfra::Command::Base::RoutingTable
class << self
def check_has_entry(destination)
%Q{ip route show #{destination} | awk '{print $1, "via", $5, "dev", $3, " "}'}
end

alias :get_entry :check_has_entry
end
end

class User < Specinfra::Command::Base::User
class << self
def check_has_home_directory(user, path_to_home)
"grep #{escape(user)} /etc/passwd | cut -f 6 -d ':' | grep -w -- #{escape(path_to_home)}"
end

def check_has_login_shell(user, path_to_shell)
"grep #{escape(user)} /etc/passwd | cut -f 7 -d ':' | grep -w -- #{escape(path_to_shell)}"
end
end
end
end
end

class Specinfra::Helper::DetectOs::DockerRoot < Specinfra::Helper::DetectOs
def detect
if ( uname = run_command('uname -r').stdout ) && uname =~ /docker-root/i
family = nil
release = nil
os_release = run_command("cat /etc/os-release")
if os_release.success?
os_release.stdout.each_line do |line|
family = line.split('=').last.strip if line =~ /^ID_LIKE=/
release = line.split('=').last.strip if line =~ /^VERSION=/
end
end
family ||= 'linux'
{ :family => family, :release => release }
end
end
end

以下はこれを使ったサンプルです。


spec/docker-root/docker_spec.rb

require 'spec_helper'

describe user('docker') do
it { should exist }
it { should belong_to_group 'docker' }
it { should belong_to_primary_group 'docker' }
it { should have_uid 1000 }
it { should have_home_directory '/home/docker' }
it { should have_login_shell '/bin/bash' }
end

describe group('docker') do
it { should exist }
it { should have_gid 1000 }
end

describe 'Docker Daemon' do
context command('/etc/init.d/docker status') do
its(:stdout) { should match /^Docker .* is running.$/ }
end

context file('/var/run/docker.sock') do
it { should be_socket }
end

context interface('docker0') do
it { should exist }
it { should be_up }
end

context routing_table do
it do
should have_entry(
:destination => '172.17.0.0/16',
:interface => 'docker0',
:gateway => '172.17.0.1',
)
end
end
end

describe 'Linux kernel parameters' do
context linux_kernel_parameter('net.ipv4.ip_forward') do
its(:value) { should eq 1 }
end

context linux_kernel_parameter('net.ipv6.conf.all.forwarding') do
its(:value) { should eq 1 }
end
end


Vagrantfile

# A dummy plugin for DockerRoot to set hostname and network correctly at the very first `vagrant up`

module VagrantPlugins
module GuestLinux
class Plugin < Vagrant.plugin("2")
guest_capability("linux", "change_host_name") { Cap::ChangeHostName }
guest_capability("linux", "configure_networks") { Cap::ConfigureNetworks }
end
end
end

Vagrant.configure(2) do |config|
config.vm.define "docker-root"
config.vm.box = "ailispaw/docker-root"

if Vagrant.has_plugin?("vagrant-triggers") then
config.trigger.after [:up, :resume] do
info "Adjusting datetime after suspend and resume."
run_remote "sudo sntp -4sSc pool.ntp.org; date"
end
end

# Adjusting datetime before provisioning.
config.vm.provision :shell, run: "always" do |sh|
sh.inline = "sntp -4sSc pool.ntp.org; date"
end

config.vm.provision :docker do |docker|
docker.pull_images "busybox"
docker.run "simple-echo",
image: "busybox",
args: "-p 8080:8080 -v /usr/bin/dumb-init:/dumb-init:ro --entrypoint=/dumb-init",
cmd: "nc -p 8080 -l -l -e echo hello world!"
end

config.vm.network :forwarded_port, guest: 8080, host: 8080

if Vagrant.has_plugin?("vagrant-serverspec") then
config.vm.provision :serverspec do |spec|
spec.pattern = "spec/docker-root/*_spec.rb"
end
end
end

他にもサンプルとしてこちらで実際に利用しています。

https://github.com/ailispaw/docker-root-packer/tree/serverspec/spec