class Chef::Knife::Bootstrap::TrainConnector

Public Class Methods

new(host_url, default_protocol, opts) click to toggle source
# File lib/chef/knife/bootstrap/train_connector.rb, line 38
def initialize(host_url, default_protocol, opts)
  @host_url = host_url
  @default_protocol = default_protocol
  @opts_in = opts
end

Public Instance Methods

config() click to toggle source
# File lib/chef/knife/bootstrap/train_connector.rb, line 44
def config
  @config ||= begin
                uri_opts = opts_from_uri(@host_url, @default_protocol)
                transport_config(@host_url, @opts_in.merge(uri_opts))
              end
end
connect!() click to toggle source

Establish a connection to the configured host.

@raise [TrainError] @raise [TrainUserError]

@return [TrueClass] true if the connection could be established.

# File lib/chef/knife/bootstrap/train_connector.rb, line 68
def connect!
  # Force connection to establish
  connection.wait_until_ready
  true
end
connection() click to toggle source
# File lib/chef/knife/bootstrap/train_connector.rb, line 51
def connection
  @connection ||= begin
               Train.validate_backend(config)
               train = Train.create(config[:backend], config)
               # Note that the train connection is not currently connected
               # to the remote host, but it's ready to go.
               train.connection
             end
end
del_file!(path) click to toggle source

Force-deletes the file at “path” from the remote host.

@param path [String] The path of the file on the remote host

# File lib/chef/knife/bootstrap/train_connector.rb, line 169
def del_file!(path)
  if windows?
    run_command!("If (Test-Path \"#{path}\") { Remove-Item -Force -Path \"#{path}\" }")
  else
    run_command!("rm -f \"#{path}\"")
  end
  nil
end
hostname() click to toggle source

@return [String] the configured hostname

# File lib/chef/knife/bootstrap/train_connector.rb, line 76
def hostname
  config[:host]
end
linux?() click to toggle source

Answers the question, “Am I connected to a linux host?”

@return [Boolean] true if the connected host is linux.

# File lib/chef/knife/bootstrap/train_connector.rb, line 89
def linux?
  connection.platform.linux?
end
normalize_path(path) click to toggle source

normalizes path across OS's - always use forward slashes, which Windows and *nix understand.

@param path [String] The path to normalize

@return [String] the normalized path

# File lib/chef/knife/bootstrap/train_connector.rb, line 185
def normalize_path(path)
  path.tr("\\", "/")
end
password_auth?() click to toggle source

Answers the question, “is this connection configured for password auth?” @return [Boolean] true if the connection is configured with password auth

# File lib/chef/knife/bootstrap/train_connector.rb, line 82
def password_auth?
  config.key? :password
end
run_command(command, &data_handler) click to toggle source

Runs a command on the remote host.

@param command [String] The command to run. @param data_handler [Proc] An optional block. When provided, inbound data will be published via `data_handler.call(data)`. This can allow callers to receive and render updates from remote command execution.

@return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status

# File lib/chef/knife/bootstrap/train_connector.rb, line 198
def run_command(command, &data_handler)
  connection.run_command(command, &data_handler)
end
run_command!(command, &data_handler) click to toggle source

Runs a command the remote host

@param command [String] The command to run. @param data_handler [Proc] An optional block. When provided, inbound data will be published via `data_handler.call(data)`. This can allow callers to receive and render updates from remote command execution.

@raise Chef::Knife::Bootstrap::RemoteExecutionFailed if an error occurs (non-zero exit status) @return [Train::Extras::CommandResult] an object containing stdout, stderr, and exit_status

# File lib/chef/knife/bootstrap/train_connector.rb, line 212
def run_command!(command, &data_handler)
  result = run_command(command, &data_handler)
  if result.exit_status != 0
    raise RemoteExecutionFailed.new(hostname, command, result)
  end

  result
end
temp_dir() click to toggle source

Creates a temporary directory on the remote host if it hasn't already. Caches directory location. For *nix, it will ensure that the directory is owned by the logged-in user

@return [String] the temporary path created on the remote host.

# File lib/chef/knife/bootstrap/train_connector.rb, line 117
def temp_dir
  @tmpdir ||= if windows?
                run_command!(MKTEMP_WIN_COMMAND).stdout.split.last
              else
                # Get a 6 chars string using secure random
                # eg. /tmp/chef_XXXXXX.
                # Use mkdir to create TEMP dir to get rid of mktemp
                dir = "#{DEFAULT_REMOTE_TEMP}/chef_#{SecureRandom.alphanumeric(6)}"
                run_command!("mkdir -p '#{dir}'")
                # Ensure that dir has the correct owner.  We are possibly
                # running with sudo right now - so this directory would be owned by root.
                # File upload is performed over SCP as the current logged-in user,
                # so we'll set ownership to ensure that works.
                run_command!("chown #{config[:user]} '#{dir}'") if config[:sudo]

                dir
              end
end
unix?() click to toggle source

Answers the question, “Am I connected to a unix host?”

@note this will always return true for a linux host because train classifies linux as a unix

@return [Boolean] true if the connected host is unix or linux

# File lib/chef/knife/bootstrap/train_connector.rb, line 99
def unix?
  connection.platform.unix?
end
upload_file!(local_path, remote_path) click to toggle source

Uploads a file from “local_path” to “remote_path”

@param local_path [String] The path to a file on the local file system @param remote_path [String] The destination path on the remote file system. @return NilClass

# File lib/chef/knife/bootstrap/train_connector.rb, line 142
def upload_file!(local_path, remote_path)
  connection.upload(local_path, remote_path)
  nil
end
upload_file_content!(content, remote_path) click to toggle source

Uploads the provided content into the file “remote_path” on the remote host.

@param content [String] The content to upload into remote_path @param remote_path [String] The destination path on the remote file system. @return NilClass

# File lib/chef/knife/bootstrap/train_connector.rb, line 153
def upload_file_content!(content, remote_path)
  t = Tempfile.new("chef-content")
  t.binmode
  t << content
  t.close
  upload_file!(t.path, remote_path)
  nil
ensure
  t.close
  t.unlink
end
windows?() click to toggle source

Answers the question, “Am I connected to a Windows host?”

@return [Boolean] true if the connected host is Windows

# File lib/chef/knife/bootstrap/train_connector.rb, line 107
def windows?
  connection.platform.windows?
end

Private Instance Methods

missing_opts_from_ssh_config(config) click to toggle source

This returns a hash that consists of settings populated from SSH configuration that are not already present in the configuration passed in. This is necessary because train will default these values itself - causing SSH config data to be ignored

# File lib/chef/knife/bootstrap/train_connector.rb, line 300
def missing_opts_from_ssh_config(config)
  return {} unless config[:backend] == "ssh"

  host_cfg = ssh_config_for_host(config[:host])
  opts_out = {}
  host_cfg.each do |key, _value|
    if SSH_CONFIG_OVERRIDE_KEYS.include?(key) && !config.key?(key)
      opts_out[key] = host_cfg[key]
    end
  end
  opts_out
end
opts_from_caller(config, opts_in) click to toggle source

Returns a hash containing valid options for the current transport protocol that are not already present in config

# File lib/chef/knife/bootstrap/train_connector.rb, line 269
def opts_from_caller(config, opts_in)
  # Train.options gives us the supported config options for the
  # backend provider (ssh, winrm). We'll use that
  # to filter out options that don't belong
  # to the transport type we're using.
  valid_opts = Train.options(config[:backend])
  opts_in.select do |key, _v|
    valid_opts.key?(key) && !config.key?(key)
  end
end
opts_from_uri(uri, default_protocol) click to toggle source

Extract any of username/password/host/port/transport that are in the URI and return them as a config has

# File lib/chef/knife/bootstrap/train_connector.rb, line 282
def opts_from_uri(uri, default_protocol)
  # Train.unpack_target_from_uri only works for complete URIs in
  # form of proto://[user[:pass]@]host[:port]/
  # So we'll add the protocol prefix if it's not supplied.
  uri_to_check = if URI::DEFAULT_PARSER.make_regexp.match(uri)
                   uri
                 else
                   "#{default_protocol}://#{uri}"
                 end

  Train.unpack_target_from_uri(uri_to_check)
end
opts_inferred_from_winrm(config, opts_in) click to toggle source

Some winrm options are inferred based on other options. Return a hash of winrm options based on configuration already built.

# File lib/chef/knife/bootstrap/train_connector.rb, line 250
def opts_inferred_from_winrm(config, opts_in)
  return {} unless config[:backend] == "winrm"

  opts_out = {}

  if opts_in[:ssl]
    opts_out[:ssl] = true
    opts_out[:self_signed] = opts_in[:self_signed] || false
  end

  # See note here: https://github.com/mwrock/WinRM#example
  if %w{ssl plaintext}.include?(opts_in[:winrm_auth_method])
    opts_out[:winrm_disable_sspi] = true
  end
  opts_out
end
ssh_config_for_host(host) click to toggle source

Having this as a method makes it easier to mock SSH Config for testing.

# File lib/chef/knife/bootstrap/train_connector.rb, line 315
def ssh_config_for_host(host)
  require "net/ssh" unless defined?(Net::SSH)
  Net::SSH::Config.for(host)
end
transport_config(host_url, opts_in) click to toggle source

For a given url and set of options, create a config hash suitable for passing into train.

# File lib/chef/knife/bootstrap/train_connector.rb, line 225
def transport_config(host_url, opts_in)
  # These baseline opts are not protocol-specific
  opts = { target: host_url,
           www_form_encoded_password: true,
           transport_retries: 2,
           transport_retry_sleep: 1,
           backend: opts_in[:backend],
           logger: opts_in[:logger] }

  # Accepts options provided by caller if they're not already configured,
  # but note that they will be constrained to valid options for the backend protocol
  opts.merge!(opts_from_caller(opts, opts_in))

  # WinRM has some additional computed options
  opts.merge!(opts_inferred_from_winrm(opts, opts_in))

  # Now that everything is populated, fill in anything missing
  # that may be found in user ssh config
  opts.merge!(missing_opts_from_ssh_config(opts))

  Train.target_config(opts)
end