class Kitchen::Driver::SSHBase

Legacy base class for a driver that uses SSH to communication with an instance. This class has been updated to use the Instance's Transport to issue commands and transfer files and no longer uses the `Kitchen:SSH` class directly.

NOTE: Authors of new Drivers are encouraged to inherit from `Kitchen::Driver::Base` instead and existing Driver authors are encouraged to update their Driver class to inherit from `Kitchen::Driver::SSHBase`.

A subclass must implement the following methods:

@author Fletcher Nichol <fnichol@nichol.ca> @deprecated While all possible effort has been made to preserve the

original behavior of this class, future improvements to the Driver,
Transport, and Verifier subsystems may not be picked up in these
Drivers. When legacy Driver::SSHBase support is removed, this class
will no longer be available.

Public Class Methods

new(config = {}) click to toggle source

Creates a new Driver object using the provided configuration data which will be merged with any default configuration.

@param config [Hash] provided driver configuration

# File lib/kitchen/driver/ssh_base.rb, line 60
def initialize(config = {})
  init_config(config)
end

Public Instance Methods

cache_directory() click to toggle source

Cache directory that a driver could implement to inform the provisioner that it can leverage it internally

@return path [String] a path of the cache directory

# File lib/kitchen/driver/ssh_base.rb, line 187
def cache_directory; end
converge(state) click to toggle source

(see Base#converge)

# File lib/kitchen/driver/ssh_base.rb, line 70
def converge(state) # rubocop:disable Metrics/AbcSize
  provisioner = instance.provisioner
  provisioner.create_sandbox
  sandbox_dirs = provisioner.sandbox_dirs

  instance.transport.connection(backcompat_merged_state(state)) do |conn|
    conn.execute(env_cmd(provisioner.install_command))
    conn.execute(env_cmd(provisioner.init_command))
    info("Transferring files to #{instance.to_str}")
    conn.upload(sandbox_dirs, provisioner[:root_path])
    debug("Transfer complete")
    conn.execute(env_cmd(provisioner.prepare_command))
    conn.execute(env_cmd(provisioner.run_command))
    info("Downloading files from #{instance.to_str}")
    provisioner[:downloads].to_h.each do |remotes, local|
      debug("Downloading #{Array(remotes).join(", ")} to #{local}")
      conn.download(remotes, local)
    end
    debug("Download complete")
  end
rescue Kitchen::Transport::TransportFailed => ex
  raise ActionFailed, ex.message
ensure
  instance.provisioner.cleanup_sandbox
end
create(state) click to toggle source

(see Base#create)

# File lib/kitchen/driver/ssh_base.rb, line 65
def create(state) # rubocop:disable Lint/UnusedMethodArgument
  raise ClientError, "#{self.class}#create must be implemented"
end
destroy(state) click to toggle source

(see Base#destroy)

# File lib/kitchen/driver/ssh_base.rb, line 128
def destroy(state) # rubocop:disable Lint/UnusedMethodArgument
  raise ClientError, "#{self.class}#destroy must be implemented"
end
legacy_state(state) click to toggle source
# File lib/kitchen/driver/ssh_base.rb, line 132
def legacy_state(state)
  backcompat_merged_state(state)
end
login_command(state) click to toggle source

(see Base#login_command)

# File lib/kitchen/driver/ssh_base.rb, line 142
def login_command(state)
  instance.transport.connection(backcompat_merged_state(state))
    .login_command
end
package(state) click to toggle source

Package an instance.

(see Base#package)

# File lib/kitchen/driver/ssh_base.rb, line 139
def package(state); end
remote_command(state, command) click to toggle source

Executes an arbitrary command on an instance over an SSH connection.

@param state [Hash] mutable instance and driver state @param command [String] the command to be executed @raise [ActionFailed] if the command could not be successfully completed

# File lib/kitchen/driver/ssh_base.rb, line 152
def remote_command(state, command)
  instance.transport.connection(backcompat_merged_state(state)) do |conn|
    conn.execute(env_cmd(command))
  end
end
setup(state) click to toggle source

(see Base#setup)

# File lib/kitchen/driver/ssh_base.rb, line 97
def setup(state)
  verifier = instance.verifier

  instance.transport.connection(backcompat_merged_state(state)) do |conn|
    conn.execute(env_cmd(verifier.install_command))
  end
rescue Kitchen::Transport::TransportFailed => ex
  raise ActionFailed, ex.message
end
ssh(ssh_args, command) click to toggle source

**(Deprecated)** Executes a remote command over SSH.

@param ssh_args [Array] ssh arguments @param command [String] remote command to invoke @deprecated This method should no longer be called directly and exists

to support very old drivers. This will be removed in the future.
# File lib/kitchen/driver/ssh_base.rb, line 164
def ssh(ssh_args, command)
  pseudo_state = { hostname: ssh_args[0], username: ssh_args[1] }
  pseudo_state.merge!(ssh_args[2])
  connection_state = backcompat_merged_state(pseudo_state)

  instance.transport.connection(connection_state) do |conn|
    conn.execute(env_cmd(command))
  end
end
verify(state) click to toggle source

(see Base#verify)

# File lib/kitchen/driver/ssh_base.rb, line 108
def verify(state) # rubocop:disable Metrics/AbcSize
  verifier = instance.verifier
  verifier.create_sandbox
  sandbox_dirs = Util.list_directory(verifier.sandbox_path)

  instance.transport.connection(backcompat_merged_state(state)) do |conn|
    conn.execute(env_cmd(verifier.init_command))
    info("Transferring files to #{instance.to_str}")
    conn.upload(sandbox_dirs, verifier[:root_path])
    debug("Transfer complete")
    conn.execute(env_cmd(verifier.prepare_command))
    conn.execute(env_cmd(verifier.run_command))
  end
rescue Kitchen::Transport::TransportFailed => ex
  raise ActionFailed, ex.message
ensure
  instance.verifier.cleanup_sandbox
end
verify_dependencies() click to toggle source

Performs whatever tests that may be required to ensure that this driver will be able to function in the current environment. This may involve checking for the presence of certain directories, software installed, etc.

@raise [UserError] if the driver will not be able to perform or if a

documented dependency is missing from the system
# File lib/kitchen/driver/ssh_base.rb, line 181
def verify_dependencies; end

Private Instance Methods

backcompat_merged_state(state) click to toggle source
# File lib/kitchen/driver/ssh_base.rb, line 191
def backcompat_merged_state(state)
  driver_ssh_keys = %w{
    forward_agent hostname password port ssh_key username
  }.map(&:to_sym)
  config.select { |key, _| driver_ssh_keys.include?(key) }.rmerge(state)
end
build_ssh_args(state) click to toggle source

Builds arguments for constructing a `Kitchen::SSH` instance.

@param state [Hash] state hash @return [Array] SSH constructor arguments @api private

# File lib/kitchen/driver/ssh_base.rb, line 203
def build_ssh_args(state)
  combined = config.to_hash.merge(state)

  opts = {}
  opts[:user_known_hosts_file] = "/dev/null"
  opts[:verify_host_key] = false
  opts[:keys_only] = true if combined[:ssh_key]
  opts[:password] = combined[:password] if combined[:password]
  opts[:forward_agent] = combined[:forward_agent] if combined.key? :forward_agent
  opts[:port] = combined[:port] if combined[:port]
  opts[:keys] = Array(combined[:ssh_key]) if combined[:ssh_key]
  opts[:logger] = logger

  [combined[:hostname], combined[:username], opts]
end
busser() click to toggle source

Returns the Busser object associated with the driver.

@return [Busser] a busser

# File lib/kitchen/driver/ssh_base.rb, line 346
def busser
  instance.verifier
end
env_cmd(cmd) click to toggle source

Adds http, https and ftp proxy environment variables to a command, if set in configuration data or on local workstation.

@param cmd [String] command string @return [String] command string @api private rubocop:disable Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity, Metrics/AbcSize

# File lib/kitchen/driver/ssh_base.rb, line 226
def env_cmd(cmd)
  return if cmd.nil?

  env = "env"
  http_proxy = config[:http_proxy] || ENV["http_proxy"] ||
    ENV["HTTP_PROXY"]
  https_proxy = config[:https_proxy] || ENV["https_proxy"] ||
    ENV["HTTPS_PROXY"]
  ftp_proxy = config[:ftp_proxy] || ENV["ftp_proxy"] ||
    ENV["FTP_PROXY"]
  no_proxy = if (!config[:http_proxy] && http_proxy) ||
      (!config[:https_proxy] && https_proxy) ||
      (!config[:ftp_proxy] && ftp_proxy)
               ENV["no_proxy"] || ENV["NO_PROXY"]
             end
  env << " http_proxy=#{http_proxy}"   if http_proxy
  env << " https_proxy=#{https_proxy}" if https_proxy
  env << " ftp_proxy=#{ftp_proxy}"     if ftp_proxy
  env << " no_proxy=#{no_proxy}"       if no_proxy

  env == "env" ? cmd : "#{env} #{cmd}"
end
print(msg) click to toggle source

Intercepts any bare print calls in subclasses and issues an INFO log event instead.

@param msg [String] message string

puts(msg) click to toggle source

Intercepts any bare puts calls in subclasses and issues an INFO log event instead.

@param msg [String] message string

# File lib/kitchen/driver/ssh_base.rb, line 314
def puts(msg)
  info(msg)
end
run_command(cmd, options = {}) click to toggle source

Delegates to Kitchen::ShellOut.run_command, overriding some default options:

  • `:use_sudo` defaults to the value of `config` in the Driver object

  • `:log_subject` defaults to a String representation of the Driver's class name

@see ShellOut#run_command

Calls superclass method Kitchen::ShellOut#run_command
# File lib/kitchen/driver/ssh_base.rb, line 335
def run_command(cmd, options = {})
  base_options = {
    use_sudo: config[:use_sudo],
    log_subject: Thor::Util.snake_case(self.class.to_s),
  }.merge(options)
  super(cmd, base_options)
end
run_remote(command, connection) click to toggle source

Executes a remote command over SSH.

@param command [String] remove command to run @param connection [Kitchen::SSH] an SSH connection @raise [ActionFailed] if an exception occurs @api private

# File lib/kitchen/driver/ssh_base.rb, line 255
def run_remote(command, connection)
  return if command.nil?

  connection.exec(env_cmd(command))
rescue SSHFailed, Net::SSH::Exception => ex
  raise ActionFailed, ex.message
end
transfer_path(locals, remote, connection) click to toggle source

Transfers one or more local paths over SSH.

@param locals [Array<String>] array of local paths @param remote [String] remote destination path @param connection [Kitchen::SSH] an SSH connection @raise [ActionFailed] if an exception occurs @api private

# File lib/kitchen/driver/ssh_base.rb, line 270
def transfer_path(locals, remote, connection)
  return if locals.nil? || Array(locals).empty?

  info("Transferring files to #{instance.to_str}")
  debug("TIMING: scp asynch upload (Kitchen::Driver::SSHBase)")
  elapsed = Benchmark.measure do
    transfer_path_async(locals, remote, connection)
  end
  delta = Util.duration(elapsed.real)
  debug("TIMING: scp async upload (Kitchen::Driver::SSHBase) took #{delta}")
  debug("Transfer complete")
rescue SSHFailed, Net::SSH::Exception => ex
  raise ActionFailed, ex.message
end
transfer_path_async(locals, remote, connection) click to toggle source
# File lib/kitchen/driver/ssh_base.rb, line 285
def transfer_path_async(locals, remote, connection)
  waits = []
  locals.map do |local|
    waits.push connection.upload_path(local, remote)
    waits.shift.wait while waits.length >= config[:max_ssh_sessions]
  end
  waits.each(&:wait)
end
wait_for_sshd(hostname, username = nil, options = {}) click to toggle source

Blocks until a TCP socket is available where a remote SSH server should be listening.

@param hostname [String] remote SSH server host @param username [String] SSH username (default: `nil`) @param options [Hash] configuration hash (default: `{}`) @api private

# File lib/kitchen/driver/ssh_base.rb, line 301
def wait_for_sshd(hostname, username = nil, options = {})
  pseudo_state = { hostname: hostname }
  pseudo_state[:username] = username if username
  pseudo_state.merge!(options)

  instance.transport.connection(backcompat_merged_state(pseudo_state))
    .wait_until_ready
end