serverspec
WinRM

ServerspecがWindows相手に動かない

More than 1 year has passed since last update.

今年(2016年)になってからrubygemsのWinRMでインターフェース変更が激しく周りが追いついていない。

例えば現状(2016/10/28)、ServerspecでWindows相手にテスト実行する環境を整えてrake実行しても以下のようなエラーが出て止まってしまうわけですが、それを回避するためのメモ。

[root@alpha-139 ~]# rake
/usr/bin/ruby -I/usr/local/share/gems/gems/rspec-core-3.5.4/lib:/usr/local/share/gems/gems/rspec-support-3.5.0/lib /usr/local/share/gems/gems/rspec-core-3.5.4/exe/rspec --pattern spec/testw/\*_spec.rb
/root/spec/spec_helper.rb:10:in `<top (required)>': uninitialized constant WinRM::WinRMWebService (NameError)

1.spec/spec_helper.rb を修正する

spec/spec_helper.rb は serverspec-init を実行すると生成されるもので、現状(serverspec (2.37.2))では以下の様なものですが、まずはここから動かんです。

(修正前)spec/spec_helper.rb
require 'serverspec'
require 'winrm'

set :backend, :winrm

user = <username>
pass = <password>
endpoint = "http://#{ENV['TARGET_HOST']}:5985/wsman"

winrm = ::WinRM::WinRMWebService.new(endpoint, :ssl, :user => user, :pass => pass, :basic_auth_only => true)
winrm.set_timeout 300 # 5 minutes max timeout for any operation
Specinfra.configuration.winrm = winrm

WinRM::WinRMWebServiceを、WinRM::Connectionを使用するように修正します。

(修正後)spec/spec_helper.rb
require 'serverspec'
require 'winrm'

set :backend, :winrm

opts = {
  user: "Administrator",
  password: "password",
  endpoint: "http://#{ENV['TARGET_HOST']}:5985/wsman",
  operation_timeout: 300,
}

winrm = WinRM::Connection.new(opts)
Specinfra.configuration.winrm = winrm

(参考)github - WinRb/WinRM
https://github.com/WinRb/WinRM

2.backend/winrm.rb を修正する

あー、元のコードをすべて掲載するのは面倒くさいことであるので省略しますが、specinfra(現時点のバージョン 2.63.3)の一部のファイルも新しいgem/winrmの実装に追いついていないので(powershellというメソッドが廃止されて、shell(:powershell) になっているのに対応していない)、そちらも修正する必要があります。
ちなみに修正しないと以下様なエラーが出ます

  1) Port "80" should be listening
     On host `testw'
     Failure/Error: it { should be_listening }
     NoMethodError:
       undefined method `powershell' for #<WinRM::Connection:0x00000003244ea8>

     # /usr/local/share/gems/gems/specinfra-2.63.3/lib/specinfra/backend/winrm.rb:14:in `run_command'
     # /usr/local/share/gems/gems/specinfra-2.63.3/lib/specinfra/runner.rb:27:in `run'
     # /usr/local/share/gems/gems/specinfra-2.63.3/lib/specinfra/runner.rb:19:in `method_missing'

とりあえず、bashプロンプトで以下を実行すると修正されます(rubyコードそのものではないです)。
念のためオリジナルの winrm.rb を winrm.rb.org という名前でとっています。

pushd /usr/local/share/gems/gems/specinfra-*/lib/specinfra/backend
if [ ! -e winrm.rb.org ] ; then cp winrm.rb winrm.rb.org ; fi
cat > winrm.rb << 'EOF'
module Specinfra
  module Backend
    class Winrm < Base
      include PowerShell::ScriptHelper

      def os_info
        { :family => 'windows', :release => nil, :arch => nil }
      end

      def run_command(cmd, opts={})
        script = create_script(cmd)
        winrm = get_config(:winrm)

        shell = winrm.shell(:powershell)
        result = shell.run(script)
        stdout = result.stdout
        stderr = result.stderr
        exit_status = result.exitcode
        shell.close

        if @example
          @example.metadata[:command] = script
          @example.metadata[:stdout]  = stdout + stderr
        end

        CommandResult.new :stdout => stdout, :stderr => stderr, :exit_status => exit_status
      end
    end
  end
end
EOF
popd

ちなみにspecinfraのgithubは以下ですが、フィードバックするのが手間なので親切な方がいれば是非・・
https://github.com/mizzy/specinfra

とはいえ、2016年はじめぐらいにgem/WinRMが認証プロトコルとして:negotiateに対応し始めたのですが、このおかげでWindows側にHTTPS/basic認証を有効にするとかの手間が要らなくなっており、これがそれなりにすばらしい。
この辺はAnsibleのためにpython/winrmでも早く実装してくれればと、