読者です 読者をやめる 読者になる 読者になる

inifileを使ってServerspecのテストを書いてみる part3

気づいたらしばらくぶりの更新になってしまいました。。。


今日は、inifileを使ってテストを記述した場合と、should matchを使ってテストを記載した場合を比較していみたいと思います。

個人的にinifileの方が優れていると思う点は、以下の通りです。

  1. 正規表現の考慮が減るのでテストを記述しやすい
  2. 揺らぎやすい表記を無視してテストできる(=前後の空白など)
  3. どこがNGなのかわかりやすい

正規表現の考慮が減るのでテストを記述しやすい

php.iniのパラメータの一つである、session.save_pathを例にとります。
テストコードで比較するとこんな感じです。

should matchの場合

    its(:content) { should match %r{^session.save_path = "\/var\/lib\/php\/session"$} }

inifileの場合

      expect(conf['Session']['session.save_path']).to eq "/var/lib/php/session"

正規表現を使って記述する必要があるので、パスを表す/をエスケープしたり、テストしたい記述の先頭に^末尾に$をつけて、記述の前後に余計な文字が含まれている場合に誤検知しないようにするなど、考慮するポイントが様々あります。
正規表現の方がいい場面もあるかもしれませんが、inifileの場合は正規表現で表す必要は無くなります。
直感的にあるべき姿をイメージしやすいのはinifileを使った書き方ではないかと思います。

揺らぎやすい表記を無視してテストできる

例えばphp.iniの設定がこのようになっていたとします。

short_open_tag=Off

should matchだと=の前後の空白も考慮して描かないとNGになってしまいますが(本来はOKであってほしい)、inifileならOKで検出してくれます。

Package "php"
  should be installed

php.ini
  File "/etc/php.ini"
    should be file
    configures check php.ini

Finished in 1.84 seconds (files took 3.82 seconds to load)
3 examples, 0 failures

こういったケースもあるので、inifileをテストに活用するのはアリだと思っています。
(コーディング規約の統一という意味では、NGで拾いたいという考え方もあるかもしれません。)

どこがNGなのかわかりやすい

例えば、php.iniの設定で行末に余計な文字を入れてしまったとしましょう。

short_open_tag = Offf

should matchを使った場合

  1) php.ini File "/etc/php.ini" content should match /^short_open_tag = Off$/
     On host `WEB01'
     Failure/Error: its(:content) { should match %r{^short_open_tag = Off$} }
       expected "[PHP]\n\n;;;;;;;;;;;;;;;;;;;\n; About php.ini   ;\n;;;;;;;;;;;;;;;;;;;\n; PHP's initialization file,... shared memory segment\n;sysvshm.init_mem = 10000\n\n\n; Local Variables:\n; tab-width: 4\n; End:\n" to match /^short_open_tag = Off$/
       Diff:
       @@ -1,2 +1,1660 @@
       -/^short_open_tag = Off$/
...
php.iniのファイルの内容がずらっと出る
...

間違っているテストケースはわかりますが、どこがどう間違っているのか一見わかりません。

inifileの場合

  1) files File "/etc/php.ini" configures check
     On host `WEB01'
     Failure/Error: expect(conf['PHP']['short_open_tag']).to eq 'Offf'

       expected: "Off"
            got: "Offf"

       (compared using ==)
       sudo -p 'Password: ' env PATH="/sbin:/usr/sbin:$PATH" /bin/sh -c cat\ /etc/php.ini\ 2\>\ /dev/null\ \|\|\ echo\ -n
       [PHP]

expectedgotの部分を見れば、期待される値(Off)に対してどんな値が得られたか(Offf)がすぐわかるので、NGの原因を掴みやすいと思います。

逆にinifileを使わないほうが良い場面

inifileは、セクションとキーで構成されるファイルを扱うものなので、そうでない設定ファイルには適用できないと思います。
例えばhttpd.confなどが挙げられます。
この辺は未検証なので断言できませんが、多分できないでしょう。

テスト対象の設定ファイルに応じて、inifileshould matchを使い分けたいところです。


inifileを使ってServerspecのテストを書いてみる part2

今回は、inifileのインストール&テストの記述をしていきます。
GitHub - TwP/inifile: Native Ruby package for reading and writing INI files

インストール

インストールはいたって簡単で、gemパッケージを追加するだけです。
なので、Gemfileinifileを記述してインストールしましょう。

$ cat Gemfile
# frozen_string_literal: true
source "https://rubygems.org"

gem "rake"
gem "serverspec"
gem "highline"
gem "inifile"

$ bundle install
...

$ bundle exec gem list | grep inifile
inifile (3.0.0)

テストの記述

ではテストを記述していきます。
今回は、php.iniに記述されたパラメータshort_open_tag = Offmysql.connect_timeout = 60を例に書いていきます。
早速、実際のコードを示します。

     1  require 'spec_helper'
     2  require 'inifile'
     3
     4  # パッケージがインストールされているか
     5  describe package('php') do
     6    it { should be_installed }
     7  end
     8
     9  # php.iniの設定は正しいか
    10  describe 'php.ini' do
    11    describe file('/etc/php.ini') do
    12      # ファイルは存在するか
    13      it { should be_file }
    14
    15      # 各パラメータの設定は正しいか
    16      it 'configures check' do
    17        conf = IniFile.new.parse(subject.content)
    18        expect(conf['PHP']['short_open_tag']).to eq 'Off'
    19        expect(conf['MySQL']['mysql.connect_timeout']).to eq 60
    20      end
    21    end
    22  end

ポイントを解説します。

point1:requireでinifileを呼び出す

2行目のrequire 'inifile'で、inifileモジュールを使えるようにします。

point2:expectを使ってセクション・キーのペアで設定値をテスト

15〜20行目が、テスト部分に該当します。
17行目で、php.ini構文解析した結果を変数confに格納しています。
18行目と19行目で各パラメータの値をテストしています。
例えば、expect(conf['PHP']['short_open_tag']).to eq 'Off'は、セクション[PHP]のキーshort_open_tagの値がOffであることを確認します。
実際のphp.iniファイルと照らし合わせてみると良いでしょう。
以下、抜粋。

$ cat modules/php/templates/etc/php.ini.erb
...
[PHP]
...
short_open_tag = Off
...
[MySQL]
...
mysql.connect_timeout = 60
...

今回示した部分以外のテストしたい場合も、同様に記述すればOKです。

テストしてみる

実際にテストするとこのような結果になります。
configures checkの部分がinifileを使ってテストした部分に該当します。
ちなみに、configures checkという表示は、テストコードの16行目でit 'configures check' doと記述した部分が該当します。

$ TARGET_HOST=WEB01 ASK_SUDO_PASSWORD=1 bundle exec rspec spec/WEB01/php_spec.rb
Enter sudo password:

Package "php"
  should be installed

php.ini
  File "/etc/php.ini"
    should be file
    configures check

Finished in 2 seconds (files took 2.99 seconds to load)
3 examples, 0 failures

こんな感じで、テストすることが可能です。
次回は、should matchを使ってテストを記述した場合との比較をしてみたいと思います。


inifileを使ってServerspecのテストを書いてみる part1

前回記事で、「phpの設定ファイルであるphp.iniファイルのテストは、inifileを使うことをお勧めします。」と述べました。
今日から数回に分けて、その辺を深掘りします。

GitHub - TwP/inifile: Native Ruby package for reading and writing INI files

inifileとは

Rubyパッケージの一種で、php.iniなどセクションキーで構成された設定ファイル(INI file)をRubyで扱えるようにできるモジュールです。
ここで、セクション・キーとはこんなものを指します。

[section]    ; これはセクション
key_a=1      ; これはキー
key_b=2      ; これはキー

私はServerspecのテストで使うことが多いですが、Serverspecに限らずRubyでINI fileを扱いたい場面であればどこでも活用できると思います。

テストで利用すると何が良いのか

前回記事で説明した通り、単純にshould matchを使っただけでは誤検知する恐れのある、以下のようなケースを回避できます。

  • コメント行に、テストしたい文字列が入っている
  • 行末・行頭の誤字があるが、それを除くとテストしたい文字列となる

厳密にはshould matchでも正規表現を使えば回避できないことはないです。
ですが、inifileならキーの名前とその値をrubyのObjectクラスとして利用できるので、純粋に設定だけを意識すれば良くなります(コメント行とか余計なことは考えなくても良い)。
そういった意味でも、inifileの方をお勧めします。

今回はここまで

次回以降、inifileのインストール・コードの記述・テストの実行をやっていこうと思います。


puppetでLAMP構成を構築する part4

前回puppetizeしたphpをServerspecでテストしてみたいと思います。

はじめに

前回・今回の記事では「puppetize」→「Serverspec」の順で作業してしまってますが、本当は、テスト駆動開発の考えかた的に「Serverspecでテストケースを記述」→「puppetize」の順に進めたほうが望ましいです。
少しだけ具体的に言うと、「先にあるべき姿を持った上でテストケースを記述(Serverspec)し、それを満たすように実装(puppetize)をする」という流れですね。
次回以降(mysqlなど)は、この順序で進めたいと思います。

テストの記述

前回のpuppetizeでは、以下をpuppetizeしています。

  1. epelリポジトリの追加
  2. remiリポジトリの追加
  3. phpのインストール
  4. php.iniの配置

なので、各々をServerspecでテストしましょう。
テストコードはこんな感じです。
特にクセのある記述はないので、解説は省略します。

$ cat spec/WEB01/php_spec.rb
require 'spec_helper'

# 1. epelリポジトリは存在するか
describe yumrepo('epel') do
  it { should exist }
  it { should be_enabled }
end

# 2. remiリポジトリは存在するか
describe yumrepo('remi') do
  it { should exist }
  it { should be_enabled }
end

# 3. パッケージがインストールされているか
describe package('php') do
  it { should be_installed }
end

# 4. php.iniは配置されているか
describe 'php.ini' do
  describe file('/etc/php.ini') do
    it { should be_file }
  end
end

あとはテストを実行してみましょう。

$ ASK_SUDO_PASSWORD=1 bundle exec rake spec:WEB01
....
Yumrepo "epel"
  should exist
  should be enabled

Yumrepo "remi"
  should exist
  should be enabled

Package "php"
  should be installed

php.ini
  File "/etc/php.ini"
    should be file
....
Finished in 6.09 seconds (files took 2.55 seconds to load)
○○ examples, 0 failures

php.iniの記述内容をテストしたい場合

php.iniの変更は行ってないので、今回はphp.iniの記述内容のテストは省略しています。
もしテストしたい場合、inifileを使ってテストすることをお勧めします。
GitHub - TwP/inifile: Native Ruby package for reading and writing INI files

its(:content) { should match(/****/) }でも記述はできますが、コメント行に同じ記述があるなどした場合、それを拾ってテストが通ってしまう場合などがあります。

例)its(:content) { should match(/short_open_tag = On/) }の場合
以下のような記述もOKになってしまう。

...
; short_open_tag = On
short_open_tag = Off
...

正規表現^$を活用すればshould matchでも記述できるといえばできますが、=の間の空白有無も考慮して書くなど少々面倒です。
そういった意味でもinifileの方がオススメです。
詳しくは次回解説したいと思います。


puppetでLAMP構成を構築する part3

前回の続きで、phpのpuppetizeをしていきたいと思います。

phpのモジュール化

せっかくなのでphpもモジュール化しようと思います。
モジュール化の方針は、以前のapacheのpuppetizeとおおよそ同じ構造にしますので、そちらを参照ください。
PuppetでApacheを管理してみる part2 - ressyのナレッジ的なブログ

manifestの記述

早速manifestを記述しましょう。
前回の話を踏まえ、リポジトリの追加をしてから、phpをインストールします。

ちなみにインストールの流れは、以下を参考にしています。
CentOSにPHP5.6をインストール - Qiita

$ cat modules/php/manifests/init.pp
class php(
  $enabled = 'false',
) {
  case $enabled {
    'true': {
      # epelリポジトリの追加(remiを追加するために必要)
      package { 'epel-release':
        ensure   => installed,
        source   => 'http://dl.fedoraproject.org/pub/epel/6/x86_64/epel-release-6-8.noarch.rpm'    ,
        provider => rpm,
      }

      # remiリポジトリの追加
      package { 'remi-release':
        ensure   => installed,
        source   => 'http://rpms.famillecollet.com/enterprise/remi-release-6.rpm',
        provider => rpm,
        require  => Package['epel-release'],
      }

      # phpのインストール
      package { [
          'php',
          'php-devel',
          'php-mysql',
        ]:
        ensure  => present,
        install_options => [ '--enablerepo=remi', '--enablerepo=remi-php56' ],
        require  => Package['remi-release'],
      }

      # phpの設定ファイル
      file { '/etc/php.ini':
        ensure  => present,
        content => file('/etc/puppet/modules/php/files/etc/php.ini'),
        require => Package['php'],
      }
    }

    'false': {
       package { 'php':
         ensure => absent,
       }
    }
  }
}

ポイントを解説します。

epel/remiリポジトリの追加

epel、remiリポジトリの追加は、packageリソースで記述すればOKです。
その際に、sourceでインストール元URL、providerでパッケージマネージャを指定します。
ちなみにphp5.6を入れる目的でremiを追加しますが、remiを入れるにはepelリポジトリが必要なので、同様にpuppetizeします。

phpのインストール

remiリポジトリからパッケージをインストールする場合、オプションでremiリポジトリを有効にする必要があります。
yumコマンドでインストールする例を挙げると以下のようになり、これをpuppetizeする必要があります。

$  sudo yum install --enablerepo=remi --enablerepo=remi-php56 php php-devel php-mysql

ポイントは--enablerepo=remi --enablerepo=remi-php56をどうpuppetizeするかです。
具体的には、pacakgeリソースでinstall_optionsを使って記載すればOKです。

phpの設定ファイル

ここは特記事項はないかなと思います。
あえて言えば、php.iniで変数を使ってpuppetizeするような箇所は設けてないので、filesで管理していることくらいでしょうか。

moduleの呼び出し

こちらの記事で作成した、site.ppphpモジュール分を追加します。

# cat /etc/puppet/manifests/site.pp
node 'WEB01' {
    # WEB01ではApache/phpは運用する
    class { 'http':
        enabled => 'true',
        port => '80'
   }

   # phpを使用する
    class { 'php':
        enabled => 'true',
   }
}

node default {
    # 他のサーバではApache/phpは運用しない
    class { 'http':
        enabled => 'false',
   }

    # phpは使用しない
    class { 'php':
        enabled => 'true',
   }

}

ざっとこんなところでしょうか。
phpのpuppetizeについて、最低限の実装はできたかと思います。


puppetでLAMP構成を構築する part2

今回から、LAMP構成の一要素であるphpのpuppetize(puppetで構成管理すること)を取り上げたいと思います。
今日の記事では、puppetize前の注意事項を取り上げます。

環境

phpインストールをpuppetizeする前に

シンプルにpuppetizeする場合、pacakgeリソースを使用すれば良いのは容易に想像がつくと思います。
っが、phpのバージョンによっては少しだけ工夫が必要になります。

例えば、CentOS6系にphp5.6をインストールしたいとします。
しかし、yum providesを実行するとphp 5.3系しかリポジトリから取得できない状態となっています。

$ cat /etc/redhat-release
CentOS release 6.8 (Final)
$ 
$ yum provides php
読み込んだプラグイン:fastestmirror, security
Loading mirror speeds from cached hostfile
 * base: ftp.iij.ad.jp
 * epel: ftp.riken.jp
 * extras: ftp.iij.ad.jp
 * remi-safe: repo1.sea.innoscale.net
 * updates: ftp.iij.ad.jp
extras                                                                                                                                                 | 3.4 kB     00:00
puppetlabs-deps                                                                                                                                        | 2.5 kB     00:00
puppetlabs-products                                                                                                                                    | 2.5 kB     00:00
remi-safe                                                                                                                                              | 2.9 kB     00:00
remi-safe/primary_db                                                                                                                                   | 705 kB     00:00
updates                                                                                                                                                | 3.4 kB     00:00
updates/primary_db                                                                                                                                     | 3.7 MB     00:00
php-5.3.3-47.el6.x86_64 : PHP scripting language for creating dynamic web sites
リポジトリー        : base
一致          :



php-5.3.3-48.el6_8.x86_64 : PHP scripting language for creating dynamic web sites
リポジトリー        : updates
一致          :

ですので、例えば以下のようにpuppetizeすると、php5.3系がインストールされてしまいます。
仮にensure => latestとしても一緒です。

      package { 'php':
        ensure  => present,
      }

CentOS6系にphp5.6を入れるには、事前にremiをyumリポジトリに追加しておく必要があります。
remiとはリポジトリの一種で、これを追加しておくことで、CentOS標準のリポジトリだけでは提供されていないパッケージもインストールできるようになります。
似たものとしてepelやrpmforgeなどがあります。

Remi's RPM repository

php5.6はremiで提供されているので、/etc/yum.repo.d/にremiを追加しておくことでyumでインストールできるようになります。
puppetizeする際も、このことを考慮する必要があります。

次回の記事で、このことを踏まえてpuppetizeを始めたいと思います。


puppetでLAMP構成を構築する part1

puppetの話をひさしぶりに再開しようと思います。
過去にこの記事で、apacheの構成管理を行いました。
PuppetでApacheを管理してみる part3 - ressyのナレッジ的なブログ

その延長でphpmysqlも構成管理し、LAMP構成にしたい思います。

想定する構成

勉強環境を使ってこんな構成にしたいと思います。

こんな構成を実現するpuppetizeをしていきたいと思います。