BFT名古屋 TECH BLOG

日々の業務で得た知識を所属するエンジニアたちがアウトプットしていきます。

【Serverspec】【yaml】変数を使用してテストコードを使いまわす

こんにちは! BFT名古屋支店の猫です。
新卒インフラ女子の備忘録 part2を書いていきたいと思います。

今回もインフラ業界でも流行りの "自動化"技術 の一つである、Serverspecについてです。

はじめに

Serverspecでは、hosts.ymlにテスト対象のIPアドレスと実行するテストコードが格納されたディレクトリ名(ロールと呼ばれます) を書くことで、

  • 一回の実行で
  • 複数のサーバに対して
  • 同じテスト

を行うことができます。つまり、テストコードを"使いまわせる"わけです。
これによりコードの煩雑化・冗長化を防ぐことができます。

イメージはこんな感じです↓
f:id:bftnagoya:20210115204413p:plain:w600

個別の値はどうするの?

ここで、
IPアドレスとか、個別の値をテストしたいときはどうするの?使い回せないんじゃない?』
という疑問が浮かぶと思います。

それを解決するのが、「テストコードに変数を使用し、hostsで値を定義する」という方法です。

これにより、

  • 同じテストコードを使って
  • テスト対象ごとに個別の値をテストする

ことができます。

イメージはこんな感じです↓
f:id:bftnagoya:20210126211627p:plain:w500

やってみよう

どのようにコードを書けばよいのか、簡単な例でご説明していきます。

今回の対象はLinuxサーバ2台(webサーバとdbサーバ)です。

  • websv → commonロール & websvロール
  • dbsv → commonロール & dbsvロール

のテストを行う想定となっています。commonロールは使いまわしです。

ディレクトリ構成

今回のディレクトリ構成はこんな感じです↓
f:id:bftnagoya:20210115214501p:plain:w700

コード

では順番にコードを見ていきましょう!

  1. Rakefile
  2. hosts.yml
  3. spec_helper.rb
  4. テストコード3つ

の順にご説明します。

Rakefile

まずはRakefileです。ここではhostsの中身をpropertiesという変数に格納します。
yamlファイルを読み込むためには、require 'yaml'が必要ですので、忘れないようにしましょう。

require 'rake'
require 'rspec/core/rake_task'
require 'yaml'

#propertiesにhosts.ymlの中身を格納(ハッシュ&配列) properties = YAML.load_file('/etc/server/spec//hosts.yml')

desc 'Run serverspec to all hosts' task :spec => 'serverspec:all' task :default => :spec

namespace :serverspec do task :all => properties.keys.map {|key| 'serverspec:' + key.split('.')[0] } properties.keys.each do |key| desc "Run serverspec to #{key}" RSpec::Core::RakeTask.new(key.split('.')[0].to_sym) do |t| ENV['TARGET_HOST'] = key puts "------------------------------------------------------------------------------------------" puts " #{key} START " puts "------------------------------------------------------------------------------------------" t.pattern = 'spec/{' + properties[key][:roles].join(',') + '}/*_spec.rb' t.rspec_opts = "--color --format d" end end end


②hosts.yml

続いてhosts.ymlです。対象ホストのIPアドレスの他にロールと変数、変数に格納する値を記載します。

192.168.XXX.YYY:
  :roles:
    - common
    - websv
  :host_vars:
    - :hostname: wanwan_server
    - :ipaddress: 192.168.XXX.YYY
    - :php_version: 7.2.11

192.168.XXX.ZZZ: :roles: - common - dbsv :host_vars: - :hostname: nyannyan_server - :ipaddress: 192.168.XXX.ZZZ - :mariadb_version: 10.3.17


③spec_helper.rb

spec_helper.rbはこのように書きます。
properties = YAML.load_file('/etc/server/spec//hosts.yml')Rakefileとかぶっているのがちょっとダサめですが見なかったことにしましょう。
(今回の趣旨からは外れますが) セキュリティのため、パスワードはべた書きせず環境変数を利用しています。

require 'serverspec'
require 'net/ssh'
require 'yaml'

set :backend, :ssh

#propertiesにhosts.ymlの中身を格納(ハッシュ&配列) properties = YAML.load_file('/etc/server/spec//hosts.yml')

if ENV['ASK_SUDO_PASSWORD'] begin require 'highline/import' rescue LoadError fail "highline is not available. Try installing it." end set :sudo_password, ask("Enter sudo password: ") { |q| q.echo = false } else set :sudo_password, ENV['SUDO_PASSWORD'] end

#TARGETHOSTはRakefileがテスト対象を格納する環境変数 host = ENV['TARGET_HOST']

#propatyに対象ホストの分のハッシュを格納 set_property properties[host]

options = Net::SSH::Config.for(host)

options[:user] ||= Etc.getlogin

set :host, options[:host_name] || host

if ENV['ASK_LOGIN_PASSWORD'] options[:password] = ask("\nEnter login password: ") { |q| q.echo = false } else options[:password] = ENV['LOGIN_PASSWORD'] end

set :ssh_options, options

④テストコード

最後にテストコードたちです。
変数は#{property[:host_vars][:ipaddress]}とも書けるのですが
#{}の中身が長くなるのが嫌で、最初にvarという変数に入れ直しています。

require 'spec_helper'
require 'yaml'

var = property[:host_vars][0]

#IPaddress describe host("#{var[:hostname]}") do its(:ipv4_address) {should eq "#{var[:ipaddress]}" } end

require 'spec_helper'
require 'yaml'

var = property[:host_vars][0]

#php/php-mysqlnd/php-json install check %w{ php php-mysqlnd php-json }.each do |pkg| describe package(pkg) do it { should be_installed /#{var[:php_version]}/ } end end

require 'spec_helper'
require 'yaml'

var = property[:host_vars][0]

describe package('mariadb-server') do it { should be_installed.with_version("#{var[:mariadb_version]}") } end

終わりに

今回は「変数を使用して、テストコードを使いまわす」ことについてご紹介しました。
改めて見ると、(これ、逆に分かりにくくなってない…?)と思わないでもないです…。

が、RubyYAMLの勉強になったのでよしとします!!!
もっとかっこいい(そして実用的な)コードが書けるようになりたい~!

以上、新卒インフラ女子の備忘録 part2でした。