【Serverspec】Serverspec の Rakefile を調べた

日常業務で Serverspec を利用して構成管理をチェックしていますが、改めて「Serverspec」の内部構造について調べました。

まだまだ「Serverspec」のすべてを理解できていませんが、今後も「プログラミング」や「Ruby」や「Rake」を理解するために引き続き勉強していきます。

 

 

Serverspec をインストールする

Serverspecをインストールするのは簡単です。

[test@cent07 serverspec-test]$ gem install serverspec
Fetching: serverspec-2.41.3.gem (100%)
Successfully installed serverspec-2.41.3
Parsing documentation for serverspec-2.41.3
Installing ri documentation for serverspec-2.41.3
1 gem installed
[test@cent07 serverspec-test]$

 

 

Serverspec を実行する

Serverspec を実行します。

まずは Serverspec 管理用のディレクトリ(serverspec-test)を作成します。

[test@cent07 ~]$ mkdir serverspec-test

[test@cent07 ~]$ cd serverspec-test/

 

 

Serverspec の初期化をする

次に Serverspec の初期化をします。

[test@cent07 serverspec-test]$ serverspec-init
Select OS type:

  1) UN*X
  2) Windows

Select number: 1 ← 「1」を入力します。

Select a backend type:

  1) SSH
  2) Exec (local)

Select number: 2

 + spec/
 + spec/localhost/
 + spec/localhost/sample_spec.rb
 + spec/spec_helper.rb
 + Rakefile
 + .rspec
[test@cent07 serverspec-test]$ ls
Rakefile  spec

 

 

 

Rakefile ファイルの中身を解析

「Ruby」と「Rake」はまだまだ初心者だと思うので1行1行解析をしていきます。

内容的には「Serverspec」の勉強ではなく「Ruby」や「Rake」の勉強です。

[test@cent07 serverspec-test]$ cat Rakefile
require 'rake'
require 'rspec/core/rake_task'

task :spec    => 'spec:all'
task :default => :spec

namespace :spec do
  targets = []
  Dir.glob('./spec/*').each do |dir|
    next unless File.directory?(dir)
    target = File.basename(dir)
    target = "_#{target}" if target == "default"
    targets << target
  end

  task :all     => targets
  task :default => :all

  targets.each do |target|
    original_target = target == "_default" ? target[1..-1] : target
    desc "Run serverspec tests to #{original_target}"
    RSpec::Core::RakeTask.new(target.to_sym) do |t|
      ENV['TARGET_HOST'] = original_target
      t.pattern = "spec/#{original_target}/*_spec.rb"
    end
  end
end
[test@cent07 serverspec-test]$

 

 

Rake とは?

そもそも「Rake」とは一体何なのか分からなかったので調べました。

Rake は Make に似た Ruby で書かれたビルドツールです。

どのようにビルドするのかは、「Rakefile」に記述します。

rake は「Rakefile」の内容を解析し、「Rakefile」の内容に沿ってビルドをします。

 

※「ビルドする」とは、ソースコードをコンパイルしてライブラリなどリンクをして、最終的にプログラムとして使えるようにすることを言います。

 

■Rakeを使ったプログラム

[test@cent07 test01]$ pwd
/home/test/renshu/test01 ← ディレクトリはどこでも大丈夫です。
[test@cent07 test01]$ vi Rakefile
# coding: utf-8

task :hello do ← task でタスク名を指定して、do で閉じます。
  puts 'do task hello!' ← 実行したい Ruby プログラムを記述します。
end       ← end で閉じます。

 

■Rakeを実行

[test@cent07 test01]$ rake hello ← rake コマンドでタスク名(hello)を指定します。
do task hello! ← Ruby プログラム(puts 'do task hello!')が実行されました。
[test@cent07 test01]$

 

Rakefileとは?

rake は Rakefile の内容を解析し Rakefile の内容に従ってビルドを実行します。

 

Rakefile に簡単な例を記述してビルドをしてみます。

まずは「Rakefile」を作成します。

[test@cent07 src]$ vi Rakefile

CC = "gcc"

task :default => "hello"

file "hello" => "hello.o" do
  sh "#{CC} -o hello hello.o"
end

file "hello.o" => "hello.c" do
  sh "#{CC} -c hello.c"
end

[test@cent07 src]$

 

 

Rakefile を作成したら「rake」コマンドを実行します。

[test@cent07 src]$ rake
gcc -c hello.c
gcc -o hello hello.o
[test@cent07 src]$

 

ファイル一覧です。

[test@cent07 src]$ ll
合計 24
-rw-rw-r-- 1 test test  162  3月 11 13:22 Rakefile
drwxrwxr-x 2 test test   32  3月 11 13:16 _old
-rwxrwxr-x 1 test test 8520  3月 11 13:22 hello
-rw-rw-r-- 1 test test   77  3月 11 08:27 hello.c
-rw-rw-r-- 1 test test 1504  3月 11 13:22 hello.o
[test@cent07 src]$

 

参考URL

http://www2s.biglobe.ne.jp/~idesaku/sss/tech/rake/

 

こうして見ると下から順に処理をしていくようです。

Rakefile を作成したら「rake」コマンドを実行します。

[test@cent07 src]$ rake
gcc -c hello.c ← 最初にコンパイルしてオブジェクトファイルを作成します。リンクは実行しません。
gcc -o hello hello.o ← 次にオブジェクトファイルを基に実行ファイル名を指定しつつ作成します。
[test@cent07 src]$

 

 

■Rakefileの例

この例で何となくどんな処理をしているのか分かると思います。

こうして見ると「Rakefile」は「Ruby」の構文と「rake」の構文が混ざっています。

前処理に「first_01」と「second_01」のファイルを作成しておきます。

これがないと「firstの処理」及び「secondの処理」でエラーになります。

[test@cent07 src]$ vi Rakefile
# 前処理
sh "touch first_01"
sh "touch second_01"

task :default => "hello"

file "hello" => ["first", "second"] do
  puts "firstとsecondのファイル処理が完了したら最後に実行"
  sh "ls -l"
end

file "first" => "first_01" do
  puts "firstの処理"
  #sh "touch first_01"
  sh "touch first"
end

file "second" => "second_01" do
  puts "secondの処理"
  #sh "touch second_01"
  sh "touch second"
end

[test@cent07 src]$

 

 

rakeコマンドを実行します。

[test@cent07 src]$ rake
touch first_01
touch second_01
firstの処理
touch first
secondの処理
touch second
firstとsecondのファイル処理が完了したら最後に実行
ls -l
合計 4
-rw-rw-r-- 1 test test 422  3月 11 13:55 Rakefile
drwxrwxr-x 2 test test 138  3月 11 13:55 _old
-rw-rw-r-- 1 test test   0  3月 11 14:00 first
-rw-rw-r-- 1 test test   0  3月 11 14:00 first_01
-rw-rw-r-- 1 test test   0  3月 11 14:00 second
-rw-rw-r-- 1 test test   0  3月 11 14:00 second_01
[test@cent07 src]$

 

 

 

require 

外部のライブラリを読み込みます。

また自分が作った Ruby プログラム(xxxx.rb)を読み込みたい時に使用します。

 

task : xxxx

task :spec    => 'spec:all'

「spec」という Rake タスクを実行しています。

「=>」は前提となるタスクを指定しています。

例えば、「task :hello_second => :hello_first」は、「hello_second」を実行するためには、事前に「hello_first」を実行しておく必要があるという意味になります。

 

 

 

task :default => :spec

「task :default」は、rake コマンドで何もタスクを指定しない場合に実行されるタスクです。

「task :default => :spec」は、何もタスクを指定しない場合は「spec」タスクを実行します。

 

task :spec => 'spec:all'
task :default => :spec

これは、タスクを指定しない場合は「spec」タスクを実行し、「spec」タスクを指定した場合は「spec:all」を実行します。

taskだけの場合はアクションはありません。

 

 

■再度 task の整理

※勘違いしていたらすみません。

task :spec    => 'spec:all' ← specは「namespace」の「spec」、allは「namespace :spec」の「all」タスクの事を表わしています。「task :spec」を実行する前に「spec:all」を実行しておきます。
task :default => :spec ← タスクを何も指定しなかったら「namespace :spec」を実行します。

 

namespace :spec do
  targets = []
  Dir.glob('./spec/*').each do |dir|
    next unless File.directory?(dir)
    target = File.basename(dir)
    target = "_#{target}" if target == "default"
    targets << target
  end

  task :all     => targets
  task :default => :all

  targets.each do |target|
    original_target = target == "_default" ? target[1..-1] : target
    desc "Run serverspec tests to #{original_target}"
    RSpec::Core::RakeTask.new(target.to_sym) do |t|
      ENV['TARGET_HOST'] = original_target
      t.pattern = "spec/#{original_target}/*_spec.rb"
    end
  end
end

 

 

 

■Rakefile(task)の例

2つのタスクを実行する例

[test@cent07 01]$ vi Rakefile

task :default => :taskall ← 「rake」コマンドだけ実行した場合、デフォルトの「taskall」が実行されます。
task "taskall" => ["first-task","second-task"] ← 「taskall」は「first-task」と「second-task」を実行します。

task "first-task" do
  puts "first-task"
end

task "second-task" do
  puts "second-task"
end

[test@cent07 01]$

 

rake実行例

[test@cent07 01]$ rake
first-task
second-task
[test@cent07 01]$

 

 

namespace

namespace はタスクの名前空間(名前のこと)を表わすことができます。

→タスクの名前を定義できるということです。

 

namespace :xxx で、タスク「xxx」に処理を定義することができます。

namespace :spec do ← タスク「spec」 

 

namespace は「end」で閉じます。

 

namespace は入れ子にできます。

namespace :test01 do

 

  namespace :test02 do

 

  end

 

end

 

 

targets = []

これは単純に「targets」という配列を要素が空で初期化をしています。

 

Dir.glob('./spec/*').each do |dir|

Dir.glob でディレクトリを表示することができます。

次の ('./spec/*') で「spec」ディレクトリ配下のファイル名を取得することができます。

ちなみに現在の状態です。

[test@cent07 spec]$ pwd
/home/test/serverspec-test/spec
[test@cent07 spec]$ ls
localhost spec_helper.rb ← localhostやその他ホスト名のディレクトリを取得することができます。
[test@cent07 spec]$

 

取得した値を「dir」に入れて each で繰り返し処理を実行します。

 

next unless File.directory?(dir)

「next」で一番内側の繰り返しを抜けます。

「unless」は「もし~でなければ」という意味です。

「dir」がディレクトリかどうかチェックしています。

ここでの処理はdirの内容がディレクトリでなければ一番内側の繰り返しを抜けるということをやっています。

 

 

target = File.basename(dir)

ディレクトリ名を変数「target」に格納します。

 

target = "_#{target}" if target == "default"

もし「target」の中身が「default」の場合は、先頭にアンダーバー「_」を付けてtarget変数に格納します。

※「default」は Ruby の予約語ではありませんが、Rakeの中で「default」が使われているのでアンダーバーを付けるということでしょうか。

 

targets << target

target 変数の中身を targets 配列の末尾に追加します。

 

 

 

 

targets.each do |target|

target 配列の中身を取り出して繰り返し処理をします。

 

 

original_target = target == "_default" ? target[1..-1] : target

target[1..-1] は、配列の範囲を指定しています。

返り値は対象の範囲の「配列」です。

 

※しかしこの場合は「targets」配列ではなく「target」であることに注意が必要です。

つまり、target変数(_default)が入っている場合、「default」で返ってくるという処理をしています。

 

target == "_default" ? target[1..-1] : targetは、「三項演算子」です。

xx ? yy : zz の三項演算子の形式です。

 

条件式 ? 真の場合の値 : 偽の場合の値

 

「original_target = target == "_default" ? target[1..-1] : target」の条文を解説すると、

もし target の値が

という処理になります。

つまり、target変数の中身が「_default」の場合は「default」で返ってきて、original_target に格納します。

 

 

desc "Run serverspec tests to #{original_target}"

"Run serverspec tests to #{original_target}" でどのターゲットに対して処理をしているのか宣言しています。

 

 

RSpec::Core::RakeTask.new(target.to_sym) do |t|

この行は、Ruby で一番苦手な分野です。

「::」は、「クラス」や「モジュール」や「メソッド」の呼出しです。

または「::」は、名前解決をする際に入れ子(ネスト)環境下での場所を特定します。単純に言うと、場所を特定します。

(概念を分かりやすく説明するのは難しいです)

 

to_sym は、文字列から「シンボル」へ変換します。

target.to_sym で、「target」変数の中身の文字列をシンボルに変換します。

※Rubyはここらへんの処理が簡単に直感的にできるので柔軟にプログラミングができます。私的には非常に分かりやすくてプログラミングしやすい言語だと思っています。

 

「RSpec::Core::RakeTask.new(target.to_sym) do |t|」の処理は、target変数の中身をシンボルに変換してRakeTask.newでオブジェクトを作成し、そのオブジェクトを「t」に格納して以下の処理をしています。

 

ENV['TARGET_HOST'] = original_target

original_targetの中身を環境変数 ENV['TARGET_HOST'] に格納します。

 

【例】

take :test do

  puts ENV['TARGET_HOST']

  puts ENV['TEST']

end

 

プログラム実行

$ rake TARGET_HOST=centOS7 TEST=test

 

プログラム実行結果

centOS7

test

 

 

t.pattern = "spec/#{original_target}/*_spec.rb"

ファイル「spec/ターゲット名/*.spec.rb」を「t」に対して実行します。

実行する処理は、spec.rbファイル全部です。

 

 

serverspec-runnerで更に Serverspec の機能が拡張可能

Serverspec だけでは多数のホストをチェックする際に結果の出力が冗長に感じることがあります。

しかし「serverspec-runner」を導入することで複数台のホストのチェック結果を見やすく出力することが可能になります。

 

【Serverspec】【serverspec-runner】インストールと基本設定

 

 

 

 

参考URL

Rakeについての解説が非常に分かりやすかったです。

http://www2s.biglobe.ne.jp/~idesaku/sss/tech/rake/

 

 

Serverspecの本

何度も読み返しました。

そのたびに「Ruby」「RSpec」を勉強してカスタマイズしたいなと思います。

 

Serverspec【インフラ構成管理ツール】著者:宮下剛輔

 

 

まとめ

Serverspecのソースまで理解しようとすると、非常にハードルが上がり難しく感じます。

しかしRakefileのように1行1行を何とか言語化していくと、少しずつ理解できるような気がします。

ただそうは言ってもまだまだハードルは高いです。

 

 

 

Posted by 100%レンタルサーバーを使いこなすサイト管理人