はじめに
serverspecのwinrmクライアントとして「winrs.exe」を試してみました。その結果処理時間がかなり短縮されることがわかりました。
また、Sysinternalsに含まれる「PsExec.exe」も試してみましたが、こちらは実行するコマンドの文字列数に制限があるようでServerspecでは利用できないことがわかりました。
今回は「winrs.exe」の置き換えについて記載します。
バージョン
記事作成で用いたOS・モジュールのバージョンはこちらです。
- Windows 7 Pro SP1 32bit 日本語版
- ruby 2.2.3p173 (2015-08-18 revision 51636) [i386-mingw32]
- rspec (3.4.0)
- rspec-core (3.4.1)
- rspec-its (1.2.0)
- serverspec (2.26.0)
- specinfra (2.47.0)
※rubyスクリプトは全て文字コードを「UTF-8」で保存しています。
※バッチファイルは「Shift-JIS」で保存しています。
WinRMクライアントの作成
winrmバックエンドからwinrs.exeを実行するためのクラスを作成します。
※Open3.capture3を使うと固まってしまうのでsystemuを使いました。
require 'rubygems'
require 'open3'
require 'systemu'
require 'kconv'
class CustomWinRs
@my_name = 'CustomWinRs'
def initialize(args = {})
@user_name = args['user'] if args.key?('user')
@user_pass = args['pass'] if args.key?('pass')
@host_name = args['host'] if args.key?('host')
@winrm_client = args['winrm'] if args.key?('winrm')
@run_mode = args['mode'] if args.key?('mode')
end
def run_command(command)
retcode = -1
if @run_mode == "ps1"
exec_cmd = "powershell -NoProfile -encodedCommand #{command}"
else
command = "#{command}".gsub("\\\"","\"").gsub("\"","\\\"")
exec_cmd = "powershell -NoProfile -command \"#{command}\""
end
exec_cmd = "#{@winrm_client} -r:#{@host_name} -u:#{@user_name} -p:#{@user_pass} #{exec_cmd}"
# systemuの不具合回避
case "#{exec_cmd}".length
when 21,282,3866 then
exec_cmd = "#{@winrm_client} -remote:#{@host_name} -u:#{@user_name} -p:#{@user_pass} #{exec_cmd}"
end
begin
# stdout, stderr, status = Open3.capture3(exec_cmd)
status, stdout, stderr = systemu "#{exec_cmd}"
retcode = status.exitstatus if ! status.nil?
retcode = -1 if retcode.nil? || "#{retcode}" == ''
rescue Exception => excptn
puts Kconv.tosjis( "EXCEPTION : #{excptn.message}" )
retcode = -1
stdout = ""
stderr = excptn.message
end
return {:exitcode => retcode, :stdout => stdout, :stderr => stderr}
end
def run_cmd(command)
@run_mode = "cmd"
return run_command(command)
end
alias :cmd :run_cmd
def run_powershell_script(command)
@run_mode = "ps1"
return run_command(Base64.strict_encode64("#{command}".encode('UTF-16LE')))
end
alias :powershell :run_powershell_script
end
spec_helper.rbでWinRMクライアントを変更します。
require 'serverspec'
require 'yaml'
WINRM_CLIENT_GEM_WINRM = 1
WINRM_CLIENT_PS1_WINRM = 2
WINRM_CLIENT_CSP_WINRM = 3
WINRM_CLIENT_EXE_WINRS = 4
WINRM_CLIENT_EXE_PSEXE = 5
winrm_client = WINRM_CLIENT_EXE_WINRS
params = {}
params['hostname'] = ENV['TARGET_HOST']
params['run_env'] = "development"
puts ""
puts "##################################################"
puts "HOST: #{params['hostname']} : #{params['run_env']} : winrm_client = #{winrm_client}"
puts "##################################################"
properties = YAML.load_file("env/properties.#{params['run_env']}.yml")
set_property = properties
set :backend, :winrm
set :os, :family => 'windows'
Specinfra.configuration.customwinrm = true
case winrm_client
when WINRM_CLIENT_GEM_WINRM
require 'winrm'
endpoint = "http://#{ENV['TARGET_HOST']}:5985/wsman"
winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => "#{properties[:winrm_user]}", :pass => "#{properties[:winrm_pass]}", :basic_auth_only => true)
winrm.set_timeout 300
when WINRM_CLIENT_PS1_WINRM
require 'CustomWinRm'
winrm = CustomWinRm.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => ".\\lib\\WinRm.ps1"})
when WINRM_CLIENT_CSP_WINRM
require 'CustomWinRm'
winrm = CustomWinRm.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => ".\\lib\\rmtcmd.exe"})
when WINRM_CLIENT_EXE_WINRS
require 'CustomWinRs'
winrm = CustomWinRs.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => "C:\\Windows\\system32\\winrs.exe"})
Specinfra.configuration.os_encoding = "Windows-31J"
Specinfra.configuration.file_encoding = "Shift-JIS"
Specinfra.configuration.script_encoding = "Windows-31J"
when WINRM_CLIENT_EXE_PSEXE
require 'CustomPsExec'
winrm = CustomPsExec.new({"user" => "#{properties[:winrm_user]}", "pass" => "#{properties[:winrm_pass]}", "host" => "#{ENV['TARGET_HOST']}", "winrm" => "C:\\Tool\\SysinternalsSuite\\PsExec.exe"})
end
Specinfra.configuration.winrm = winrm
SpecInfraの修正
以前書いた記事Serverspecで使うWinRMクライアントをInvoke-commandに置き換えてみたで修正した「lib\specinfra\backend\winrm.rb」以外に以下のファイルを修正します。
46c46,51
< %Q!Get-Content("#{file}") | Out-String!
---
> # MODIFIED
> if Specinfra.configuration.file_encoding.nil?
> %Q!Get-Content("#{file}") | Out-String!
> else
> %Q![Io.File]::ReadAllText("#{file}", [System.Text.Encoding]::GetEncoding("#{Specinfra.configuration.file_encod
ing}"))!
> end
91c96,101
< exec %Q!(Get-Content("#{file}") | Out-String) -match '#{convert_regexp(pattern)}'!
---
> # MODIFIED
> if Specinfra.configuration.file_encoding.nil?
> exec %Q!(Get-Content("#{file}") | Out-String) -match '#{convert_regexp(pattern)}'!
> else
> exec %Q![Io.File]::ReadAllText("#{file}", [System.Text.Encoding]::GetEncoding("#{Specinfra.configuration.fil
e_encoding}")) -match "#{convert_regexp(pattern)}"!
> end
100c110,115
< exec %Q!(CropText -text (Get-Content("#{file}") | Out-String) -fromPattern '#{convert_regexp(from)}' -toPatter
n '#{convert_regexp(to)}') -match '#{pattern}'!
---
> # MODIFIED
> if Specinfra.configuration.file_encoding.nil?
> exec %Q!(CropText -text (Get-Content("#{file}") | Out-String) -fromPattern '#{convert_regexp(from)}' -toPatt
ern '#{convert_regexp(to)}') -match '#{pattern}'!
> else
> exec %Q!(CropText -text ([Io.File]::ReadAllText("#{file}"), [System.Text.Encoding]::GetEncoding("#{Specinfra
.configuration.file_encoding}")) -fromPattern "#{convert_regexp(from)}" -toPattern "#{convert_regexp(to)}") -match "#{pa
ttern}"!
> end
40c40,44
< script_text = script_text.encode('ASCII-8BIT')
---
> if Specinfra.configuration.script_encoding.nil?
> script_text = script_text.encode('ASCII-8BIT')
> else
> script_text = script.strip.encode('UTF-16LE', Specinfra.configuration.script_encoding)
> end
Serverspecの修正
Serverspec側も少しだけ修正を加えていきます。
61a62,80
> # ADD
> # rspec-core-3.4.1/lib/rspec/core/formatters/exception_presenter.rb:77:in `join': incompatible character encodings: Windows-31J and UTF-8 (Encoding::CompatibilityError)
> def fully_formatted(failure_number, colorizer=::RSpec::Core::Formatters::ConsoleCodes)
> lines = fully_formatted_lines(failure_number, colorizer)
> # lines.join("\n") << "\n"
> tmp = ""
> lines.each {|line| tmp << "#{line}\n".encode('UTF-8')}
> tmp
> end
> end
> end
>
> # ADD
> # rspec-core-3.4.1/lib/rspec/core/source.rb:23:in `split': invalid byte sequence in Windows-31J (ArgumentError)
> if defined?(RSpec::Core::Source)
> class RSpec::Core::Source
> def lines
> @lines ||= source.scrub('?').split("\n")
> end
0a1
> require "kconv" # ADD
4c5,8
< command_result.stdout
---
> # MODIFIED
> # command_result.stdout
> # Kconv.toutf8( "#{command_result.stdout}" ).encode("Windows-31J")
> return_result(command_result.stdout)
8c12,15
< command_result.stderr
---
> # MODIFIED
> # command_result.stderr
> # Kconv.toutf8( "#{command_result.stderr}" ).encode("Windows-31J")
> return_result(command_result.stderr)
18a26,35
>
> # MODIFIED
> def return_result(result)
> if Specinfra.configuration.os_encoding.nil?
> result
> else
> Kconv.toutf8( "#{result}" ).encode(Specinfra.configuration.os_encoding)
> end
> end
>
1a2
> require "kconv" # ADD
113c114,119
< @content
---
> # MODIFIED
> if Specinfra.configuration.os_encoding.nil?
> @content
> else
> Kconv.toutf8("#{@content}").encode(Specinfra.configuration.os_encoding)
> end
テストの実行
それでは実行してみます。
PS C:\serverspec\sample> rake
##################################################
HOST: REMOTEHOST : development : winrm_client = 4
##################################################
Command "ipconfig /all | Select-String ノード"
stdout
should match /ハイブリッド/
File "C:\serverspec\test.cmd"
should be file
should contain "あいうえお"
should not contain "かきくけこ"
content
should match /あいうえお/
content
should not match /かきくけこ/
Finished in 6.5 seconds (files took 1.29 seconds to load)
6 examples, 0 failures
PS C:\serverspec\sample>
かなり処理時間が短縮されました。