ServerspecのRakefileをカスタマイズしてみた話
いじってたの6月なので既にだいぶ忘れているのですが、
やりたかったこととしては、
①attributes.yml方式で、fqdnではなくIPベースで通信先を指定したい。
②複数host実行でもパスワード入力1回にしたい。
③指定したhostに対して実行できるようにする。
これらを実現するためにRuby初心者ながら1日くらいかけてRakefileをカスタマイズしてみました。
以下、それぞれ説明します。
①attributes.yml方式で、fqdnではなくIPベースで通信先を指定したい。
Serverspecのデフォルトは対象host毎にディレクトリを作って実行する方式ですが、
自分の場合はAdvancedTips(http://serverspec.org/advanced_tips.html)の中で紹介されている
roleをattributes.ymlで指定する方式で使用しています。
サイトの例の場合、各hostのfqdn(host.domain)からrakeタスクのsymbolを生成しているのですが、
symbolには"."(ドット)が使えないようなので、ドットで分割した1番目の要素をrakeタスク名としています。
これはfqdnの場合はだいたいうまくいきますが、
自分の場合ローカルではIPアドレスベースで通信しており、
IPアドレスの1番目を使うとほとんどのhostでrakeタスク名が重複してしまいます。
そこでattributes.ymlのkeyにあたる部分にはhostの略称を書くようにして、
それをそのままrakeタスク名に使うようにしました。
また、実際に通信するIPアドレスはhostnameというattributeで別に定義します。
②複数ノード実行でもパスワード入力1回にしたい。
Serverspecにはsudo時のパスワードを入力する方法が用意されているのですが、
SUDO_PASSWORD=xxxxxxxx は画面に平文で書くことになるのでなんとなく却下、
ASK_SUDO_PASSWORD=1 だとsudoが行われる毎にパスワード聞かれてしまいます。
(sudoer指定しろって話かもしれませんが)
こういうのは最初に一回入力を求められて後はその値を使って欲しい。
ということでLDAP=1を指定したら入力1回だけさせて後は聞かないようにしました。
名前のとおりLDAPを利用してる環境で使うことを想定していて、
複数環境(LDAP利用するhostとLDAP利用しないhost)混ぜて使うことは想定していません。
attributeでフラグ付ければ個別操作できるとは思いましたが、
用途的に混ぜて使うことは無いだろうなと。
③指定したノードに対して実行できるようにする。
attribute.ymlに10種類書いたからといって毎回全部に実行するとは限らない。
例えばAdvancedTipsのをそのまま利用した場合、
rake serverspec:hostname
みたいにすれば1個1個指定することは出来ます。
ただ、今回の場合②と組み合わせる必要があったのと当然複数指定もしたかったので、
rake spec HOST=host1,host2
みたいに指定できるようにしました。
②と③に関してはrakeタスクの仕組みがよくわからず苦労しました。
そんなこんなでRuby素人がカスタマイズしたRakefileは以下。
require 'rake' require 'rspec/core/rake_task' require 'yaml' attributes = YAML.load_file('spec/attributes.yml') desc "Run serverspec" task :spec => 'serverspec:prepare' namespace :serverspec do attributes.keys.each do |key| desc "Run serverspec to #{key}" RSpec::Core::RakeTask.new(key.split('.')[0].to_sym) do |t| ENV['TARGET_HOST'] = key t.pattern = 'spec/{' + attributes[key][:roles].join(',') + '}/*_spec.rb' end end desc "Run serverspec to all hosts" task :all => attributes.keys.map {|key| 'serverspec:' + key.split('.')[0] } desc "Run bofore task for serverspec" task :prepare do if ENV['LDAP'] require 'highline/import' ENV['LDAP_PASSWORD'] = ask("Enter ldap password: ") { |q| q.echo = false } ENV['ASK_LDAP'] = '1' end if ENV['HOST'] ENV['HOST'].split(",").map {|host| Rake::Task['serverspec:' + host.split('.')[0]].invoke } else Rake::Task['serverspec:all'].invoke end end end
spec_helper.rb
require 'serverspec' require 'pathname' require 'net/ssh' require 'yaml' include Serverspec::Helper::Ssh include Serverspec::Helper::DetectOS include Serverspec::Helper::Attributes attributes = YAML.load_file('spec/attributes.yml') RSpec.configure do |c| c.color_enabled = true c.path = "/sbin:/usr/sbin" key = ENV['TARGET_HOST'] if ENV['ASK_SUDO_PASSWORD'] then require 'highline/import' c.sudo_password = ask("Enter sudo password: ") { |q| q.echo = false } elsif ENV['ASK_LDAP'] then c.sudo_password = ENV['LDAP_PASSWORD'] else c.sudo_password = ENV['SUDO_PASSWORD'] end c.host = attributes[key][:hostname] attr_set attributes[key] options = Net::SSH::Config.for(c.host) user = options[:user] || Etc.getlogin if ENV['ASK_LDAP'] options[:password] = ENV['LDAP_PASSWORD'] end c.ssh = Net::SSH.start(c.host, user, options) c.os = backend.check_os end
attributes.yml
host1: :roles: - web - db :hostname: 192.168.100.10 host2: :roles: - web :hostname: 192.168.100.20
Serverspecの知見は既に一杯あるし、自分以外使ってない素人仕事なのでどうかと思いましたが、
これも何かの勉強になった足跡として残しておきます。
自分なりにrakeタスクを理解できた瞬間があったつもりなんだけど、
間違ってたら恥ずかしいので詳しくは書かないでおきます。
変更点を色表示できたりしたら良かったかなとおもいつつ終了。