LoginSignup
7
7

More than 5 years have passed since last update.

test-kitchenでsshに接続するポートを複数の候補から自動決定する

Last updated at Posted at 2014-07-19

Chefレシピの中でsshdのポートを変更した時、プロビジョニングやloginの際に、変更前後でtest-kitchenなどの設定を都度変えないといけない。
めんどくさいので、複数のsshポートの候補から接続可能なポートを探しだして決定する機構がないか探したが、ちょっと探した感じだと見当たらなかったのでモンキーパッチ当てて逃げた。

実装

sshdlistenポートが

  • 22 => 2222

と変わる場合に、

.kitchen.yml
driver:
  name: vagrant
  network:
    - ["forwarded_port", {guest: 22, host: 2200}]
    - ["forwarded_port", {guest: 2222, host: 2201}]

provisioner:
  name: chef_solo

platforms:
  - name: centos-6.4

と変更前後のポートフォワーディングを共に設定した上で

# -*- encoding: utf-8 -*-
module Kitchen
  class SSH
    def login_command
      args  = %W{ -o UserKnownHostsFile=/dev/null }
      args += %W{ -o StrictHostKeyChecking=no }
      args += %W{ -o IdentitiesOnly=yes } if options[:keys]
      args += %W{ -o LogLevel=#{logger.debug? ? "VERBOSE" : "ERROR"} }
      args += %W{ -o ForwardAgent=#{options[:forward_agent] ? "yes" : "no"} } if options.key? :forward_agent
      Array(options[:keys]).each { |ssh_key| args += %W{ -i #{ssh_key}} }
      args += %W{ -p #{port}}
      args += %W{ #{username}@#{hostname}}

      LoginCommand.new(["ssh", *args])
    end

    private

    def establish_connection
      logger.debug("[SSH] opening connection to #{self}")
      Net::SSH.start(hostname, username, options.merge(port: port))
    end

    def port
      rescue_exceptions = [
        Errno::EACCES, Errno::EADDRINUSE, Errno::ECONNREFUSED,
        Errno::ECONNRESET, Errno::ENETUNREACH, Errno::EHOSTUNREACH,
        Net::SSH::Disconnect
      ]

      @__port ||= candidacy_ports.find do |port|
        retries = 3

        begin
          Net::SSH.start(hostname, username, options.merge(port: port))
          true
        rescue *rescue_exceptions => e
          if (retries -= 1) > 0
            logger.info("[SSH] connection failed, retrying (#{e.inspect})")
            sleep 1
            retry
          else
            logger.warn("[SSH] connection failed, terminating (#{e.inspect})")
            false
          end
        end
      end

      if @__port.nil?
        raise SSHFailed.new('not found enable ssh ports')
      end

      @__port
    end

    def candidacy_ports
      yaml = YAML.load(
        ERB.new(
          open(yaml_path).read
        ).result
      )
      @_candidacy_ports ||= yaml['driver']['network'].map {|config| config[1]['host'] }
    end

    def yaml_path
      File.expand_path(
        File.join(
          Dir.pwd,
          ENV['KITCHEN_YAML'] || '.kitchen.yml'
        )
      )
    end
  end
end

このパッチをロードすると、候補となるポートをフォワードポートの中から探索していき、成功すると以後全てのssh接続で成功ポートを使用するようになる。

$ kitchen login centos-64
       [SSH] connection failed, retrying (#<Errno::ECONNREFUSED: Connection refused - connect(2) for "127.0.0.1" port 2200>)
       [SSH] connection failed, retrying (#<Errno::ECONNREFUSED: Connection refused - connect(2) for "127.0.0.1" port 2200>)
$$$$$$ [SSH] connection failed, terminating (#<Errno::ECONNREFUSED: Connection refused - connect(2) for "127.0.0.1" port 2200>)
Last login: Sat Jul 19 17:01:24 2014 from 10.0.2.2
[vagrant@centos-64 ~]$ 

明らかに内部実装に追従できないし、パッチにすべきではないではないんだけどとりあえず動かすことを優先でやった。
なので、test-kitchenのバージョンによって動かない可能性が高いので注意。

7
7
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
7
7