PryからSpecinfraを簡単に使うためのカスタムコマンドを作成してみました。
背景
Specinfraを使うとOSの違いをあまり意識することなく設定の確認やパッケージのインストールといった操作を行うことができます。
以下のようにSpecinfra::Runner
のメソッドを呼び出すことで使うことができます。
>> Specinfra::Runner.check_package_is_installed('nginx')
=> false
ライブラリから使うのには良いですが、Pryからちょっと使ってみたいというとき補完や定義を表示するのがしんどそうだったのでカスタムコマンドを作成してみました。
カスタムコマンドの導入
以下の内容を~/.pryrc
に追加することでカスタムコマンドが使えるようになります。
Pry::Commands.create_command "add-specinfra-commands" do
description "Add Specinfra Commands"
command_options :keep_retval => false
banner <<-BANNER
Usage:
add-specinfra-commands
BANNER
def setup
require 'specinfra'
end
def process
Specinfra.configuration.send(:backend, :exec)
os
Pry::Commands.create_command 'specinfra-ssh' do
description "Set Specinfra SSH parameters"
command_options :keep_retval => false
def options(opt)
login_user = ENV['USER'] || ENV['LOGNAME'] || Etc.getlogin || Etc.getpwuid.name
opt.on :l, 'Login Name', :default => login_user, :argument => true, :as => String
opt.on :i, 'Identity file', :default => '~/.ssh/id_rsa',:argument => true, :as => String
opt.on :p, 'Port', :default => 22, :argument => true, :as => Integer
end
def process
Specinfra.configuration.send(:backend, :ssh)
Specinfra.configuration.send(:host, args.first)
Specinfra.configuration.send(
:ssh_options, :user => opts[:l],
:port => opts[:p],
:keys => [File.expand_path(opts[:i])]
)
property[:os] = detect_os
end
end
get_resource_types.each do |resouce_type|
get_commands(resouce_type).each do |command, command_args|
command_args = command_args.map { |a|
case a.first
when :opt then "[#{a.last.to_s.upcase}]"
when :rest then "[#{a.last.to_s.upcase} ...]"
else a.last.to_s.upcase
end
}
description = "Usage: specinfra-#{command.to_s.gsub(/_/,'-')} [OPTIONS] #{command_args.join(' ')}"
target.eval <<-EOS
Pry::Commands.create_command 'specinfra-#{command.to_s.gsub(/_/,'-')}' do
description "#{description}"
command_options :keep_retval => true
def options(opt)
opt.on :n, :dry, 'Dry run'
end
def process
unless opts[:dry]
Specinfra::Runner.send(:#{command.to_s}, *args)
else
Specinfra.command.get(:#{command.to_s}, *args)
end
end
end
EOS
end
end
end
private
def get_resource_types
Specinfra::Command::Base.subclasses.select { |c|
c.to_s =~ /^Specinfra::Command::Base/
}
.sort { |a, b| a.to_s <=> b.to_s }
.map { |c| c.to_s.split('::')[-1] }
end
def get_commands(resource_type)
resource_type = resource_type.downcase
base_class = Specinfra::Command::Base.subclasses.detect { |c|
c.to_s.downcase =~ /^specinfra::command::base::#{resource_type}/
}
classes = [base_class]+ base_class.subclasses
commands = {}
classes.each do |c|
c.methods(false).each do |m|
action, sub_action = m.to_s.split('_', 2)
name = unless sub_action.nil?
"#{action}_#{resource_type}_#{sub_action}"
else
"#{action}_#{resource_type}"
end
next if commands.has_key?(name)
commands[name] = c.method(m).parameters
end
end
commands
end
end
使い方
pryを開始し次のコマンドを実行するとSpecinfra関連のコマンド群が追加されます。
>> add-specinfra-commands
追加されるコマンド群は大きく分けて以下の2つです。
- specinfra-ssh
- SSHの設定を行うためのコマンド
- specinfra-{action}-{resource_type}-{subaction}
- 各種アクションを実行するためのコマンド。Spec::Runnerのメソッドの
_
を-
に置換した名前となっています。
- 各種アクションを実行するためのコマンド。Spec::Runnerのメソッドの
specinfra-ssh
specinfra-ssh
は、SSHのオプションを簡単に設定するためのコマンドです。設定後にdetect_os
でproperty[:os]
を更新します。
>> specinfra-ssh -l root -p 22 X.X.X.X
>> os
=> {:family=>"redhat", :release=>"7.0.1406", :arch=>"x86_64"}
specinfra-{action}-{resource_type}-{subaction}
これらのコマンド群は実際にコマンドを実行するためのコマンドです。
>> specinfra-check-package-is-installed python
=> true
-h
でヘルプが表示されます。
>> specinfra-check-package-is-installed -h
Usage: specinfra-check-package-is-installed [OPTIONS] PACKAGE [VERSION]
-n, --dry Dry run
-h, --help Show this message.
-n
で実行されるコマンドが表示されます。ただし、実行結果のrubyによる処理は考慮されません。
>> specinfra-check-package-is-installed -n python
=> "/usr/local/bin/brew list -1 | grep -E '^python$'"
>> ./usr/local/bin/brew list -1 | grep -E '^python$'
python
例:CentOS7にMariaDBをインストール
使用例としてDigitalOceanにCentOS7の仮想マシンを作成しMariaDBをインストールしてみます。
仮想マシンの作成は前回の記事と同じくFogを使いました。(pryとfogでお手軽マルチクラウド対応CLI)
>> fog digitalocean
>> region = regions.find { |r| r.name == "Singapore 1" }
>> image = images.find {|i| i.name == "7.0 x64" && i.distribution == "CentOS" }
>> flavor = flavors.find { |f| f.name == "512MB" }
>> ssh_key = ssh_keys.first
>> server = servers.create(:name=>"specinfra-test", :region_id=>region.id, :image_id=>image.id, :flavor_id=>flavor.id, :ssh_key_ids=>[ssh_key.id])
>> server.wait_for { ready? }
Specinfraのコマンドを追加し、作成した仮想マシンへ接続するための情報を設定します。
>> add-specinfra-commands
>> specinfra-ssh -l root X.X.X.X
>> os
=> {:family=>"redhat", :release=>"7.0.1406", :arch=>"x86_64"}
>> specinfra-check-package-is-installed -n mariadb-server
=> "rpm -q mariadb-server"
>> specinfra-check-package-is-installed mariadb-server
=> false
>> specinfra-install-package mariadb-server
=> 長いので省略
>> specinfra-check-package-is-installed mariadb-server
=> true
>> specinfra-check-service-is-running -n mariadb
=> "systemctl is-active mariadb"
>> specinfra-check-service-is-running mariadb
=> false
>> specinfra-start-service -n mariadb
=> "systemctl start mariadb"
>> specinfra-start-service mariadb
=> #<Specinfra::CommandResult:0x007f912c9a24e0 @exit_signal=nil,
@exit_status=0, @stderr="", @stdout="">
>> specinfra-check-service-is-running mariadb
=> true
>> specinfra-check-service-is-enabled -n mariadb
=> "systemctl --plain list-dependencies multi-user.target | grep '\\(^\\| \\)mariadb.service$'"
>> specinfra-check-service-is-enabled mariadb
=> false
>> specinfra-enable-service -n mariadb
=> "systemctl enable mariadb"
>> specinfra-enable-service mariadb
=> #<Specinfra::CommandResult:0x007f9129bd6570
@exit_signal=nil,
@exit_status=0,
@stderr="ln -s '/usr/lib/systemd/system/mariadb.service' '/etc/systemd/system/multi-user.target.wants/mariadb.service'\n",
@stdout="">
>> specinfra-check-service-is-enabled mariadb
=> true
課題
作成するにあたって色々とハマりましたが特に以下の2つはまだ解決していません。もし知っている方がいたら教えて頂けるとありがたいです。
- eval族を使用している際にPryでソースコードを追う方法・コツ
- メソッドの引数のデフォルト値を取得する方法
参考
- Serverspec 宮下剛輔