class Kitchen::Transport::Dokken::Connection

@author Sean OMeara <sean@sean.io>

Public Instance Methods

docker_connection() click to toggle source
# File lib/kitchen/transport/dokken.rb, line 70
def docker_connection
  @docker_connection ||= ::Docker::Connection.new(options[:docker_host_url], options[:docker_host_options])
end
execute(command) click to toggle source
# File lib/kitchen/transport/dokken.rb, line 74
def execute(command)
  return if command.nil?

  with_retries { @runner = ::Docker::Container.get(instance_name, {}, docker_connection) }
  with_retries do
    o = @runner.exec(Shellwords.shellwords(command), wait: options[:timeout], "e" => { "TERM" => "xterm" }) do |_stream, chunk|
      logger << chunk
    end
    @exit_code = o[2]
  end

  raise Transport::DockerExecFailed.new("Docker Exec (#{@exit_code}) for command: [#{command}]", @exit_code) if @exit_code != 0
end
login_command() click to toggle source
# File lib/kitchen/transport/dokken.rb, line 177
def login_command
  @runner = options[:instance_name].to_s
  cols = `tput cols`
  lines = `tput lines`
  args = ["exec", "-e", "COLUMNS=#{cols}", "-e", "LINES=#{lines}", "-it", @runner, "/bin/bash", "-login", "-i"]
  LoginCommand.new(options[:login_command], args)
end
upload(locals, remote) click to toggle source
# File lib/kitchen/transport/dokken.rb, line 88
def upload(locals, remote)
  if options[:host_ip_override]
    # Allow connecting to any ip/hostname to support sibling containers
    ssh_ip = options[:host_ip_override]
    ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]

  elsif /unix:/.match?(options[:docker_host_url])
    if options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostIp] == "0.0.0.0"
      ssh_ip = options[:data_container][:NetworkSettings][:IPAddress]
      ssh_port = "22"
    else
      # we should read the proper mapped ip, since this allows us to upload the files
      ssh_ip = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostIp]
      ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]
    end

  elsif /tcp:/.match?(options[:docker_host_url])
    name = options[:data_container][:Name]

    # DOCKER_HOST
    docker_host_url_ip = options[:docker_host_url].split("tcp://")[1].split(":")[0]

    # mapped IP of data container
    candidate_ip = ::Docker::Container.all.find do |x|
      x.info["Names"][0].eql?(name)
    end.info["NetworkSettings"]["Networks"]["dokken"]["IPAddress"]

    # mapped port
    candidate_ssh_port = options[:data_container][:NetworkSettings][:Ports][:"22/tcp"][0][:HostPort]

    debug "candidate_ip - #{candidate_ip}"
    debug "candidate_ssh_port - #{candidate_ssh_port}"

    if port_open?(candidate_ip, candidate_ssh_port)
      debug "candidate_ip - #{candidate_ip}/#{candidate_ssh_port} open"
      ssh_ip = candidate_ip
      ssh_port = candidate_ssh_port

    elsif port_open?(candidate_ip, "22")
      ssh_ip = candidate_ip
      ssh_port = "22"
      debug "candidate_ip - #{candidate_ip}/22 open"
    else
      ssh_ip = docker_host_url_ip
      ssh_port = candidate_ssh_port
    end
  else
    raise Kitchen::UserError, "docker_host_url must be tcp:// or unix://"
  end

  debug "ssh_ip : #{ssh_ip}"
  debug "ssh_port : #{ssh_port}"

  tmpdir = Dir.tmpdir + "/dokken/"
  FileUtils.mkdir_p tmpdir.to_s, mode: 0o777
  tmpdir += Process.uid.to_s
  FileUtils.mkdir_p tmpdir.to_s
  File.write("#{tmpdir}/id_rsa", insecure_ssh_private_key)
  FileUtils.chmod(0o600, "#{tmpdir}/id_rsa")

  begin
    rsync_cmd = "/usr/bin/rsync -a -e"
    rsync_cmd << " '"
    rsync_cmd << "ssh -2"
    rsync_cmd << " -i #{tmpdir}/id_rsa"
    rsync_cmd << " -o CheckHostIP=no"
    rsync_cmd << " -o Compression=no"
    rsync_cmd << " -o PasswordAuthentication=no"
    rsync_cmd << " -o StrictHostKeyChecking=no"
    rsync_cmd << " -o UserKnownHostsFile=/dev/null"
    rsync_cmd << " -o LogLevel=ERROR"
    rsync_cmd << " -p #{ssh_port}"
    rsync_cmd << "'"
    rsync_cmd << " #{locals.join(" ")} root@#{ssh_ip}:#{remote}"
    debug "rsync_cmd :#{rsync_cmd}:"
    `#{rsync_cmd}`
  rescue Errno::ENOENT
    debug "Rsync is not installed. Falling back to SCP."
    locals.each do |local|
      Net::SCP.upload!(ssh_ip,
                       "root",
                       local,
                       remote,
                       recursive: true,
                       ssh: { port: ssh_port, keys: ["#{tmpdir}/id_rsa"] })
    end
  end
end

Private Instance Methods

image_prefix() click to toggle source
# File lib/kitchen/transport/dokken.rb, line 197
def image_prefix
  options[:image_prefix]
end
instance_name() click to toggle source
# File lib/kitchen/transport/dokken.rb, line 187
def instance_name
  options[:instance_name]
end
with_retries() { || ... } click to toggle source
# File lib/kitchen/transport/dokken.rb, line 201
def with_retries
  tries = 20
  begin
    yield
    # Only catch errors that can be fixed with retries.
  rescue ::Docker::Error::ServerError, # 404
         ::Docker::Error::UnexpectedResponseError, # 400
         ::Docker::Error::TimeoutError,
         ::Docker::Error::IOError => e
    tries -= 1
    retry if tries > 0
    raise e
  end
end
work_image() click to toggle source
# File lib/kitchen/transport/dokken.rb, line 191
def work_image
  return "#{image_prefix}/#{instance_name}" unless image_prefix.nil?

  instance_name
end