class ChefCore::TargetHost
Constants
- SSH_CONFIG_OVERRIDE_KEYS
These values may exist in .ssh/config but will be ignored by train in favor of its defaults unless we specify them explicitly. See
apply_ssh_config
Attributes
Public Class Methods
We're borrowing a page from train here - because setting up a reliable connection for testing is a multi-step process, we'll provide this method which instantiates a TargetHost
connected to a train mock backend. If the family/name provided resolves to a suported OS, this instance will mix-in the supporting methods for the given platform; otherwise those methods will raise NotImplementedError.
# File lib/chef_core/target_host.rb, line 36 def self.mock_instance(url, family: "unknown", name: "unknown", release: "unknown", arch: "x86_64") # Specifying sudo: false ensures that attempted operations # don't fail because the mock platform doesn't support sudo target_host = TargetHost.new(url, { sudo: false }) # Don't pull in the platform-specific mixins automatically during connect # Otherwise, it will raise since it can't resolve the OS without the mock. target_host.instance_variable_set(:@mocked_connection, true) target_host.connect! # We need to provide this mock before invoking mix_in_target_platform, # otherwise it will fail with an unknown OS (since we don't have a real connection). target_host.backend.mock_os( family: family, name: name, release: release, arch: arch ) # Only mix-in if we can identify the platform. This # prevents mix_in_target_platform! from raising on unknown platform during # tests that validate unsupported platform behaviors. if target_host.base_os != :other target_host.mix_in_target_platform! end target_host end
# File lib/chef_core/target_host.rb, line 66 def initialize(host_url, opts = {}, logger = nil) @config = connection_config(host_url, opts, logger) @transport_type = Train.validate_backend(@config) apply_ssh_config(@config, opts) if @transport_type == "ssh" @train_connection = Train.create(@transport_type, config) end
Public Instance Methods
# File lib/chef_core/target_host.rb, line 111 def apply_ssh_config(config, opts_in) # If we don't provide certain options, they will be defaulted # within train - in the case of ssh, this will prevent the .ssh/config # values from being picked up. # Here we'll modify the returned @config to specify # values that we get out of .ssh/config if present and if they haven't # been explicitly given. host_cfg = ssh_config_for_host(config[:host]) SSH_CONFIG_OVERRIDE_KEYS.each do |key| if host_cfg.key?(key) && opts_in[key].nil? config[key] = host_cfg[key] end end end
# File lib/chef_core/target_host.rb, line 173 def architecture platform.arch end
# File lib/chef_core/target_host.rb, line 181 def base_os if platform.windows? :windows elsif platform.linux? :linux else :other end end
Simplified chown - just sets user, defaults to connection user. Does not touch group. Only has effect on non-windows targets
# File lib/chef_core/target_host.rb, line 287 def chown(path, owner); raise NotImplementedError; end
Establish connection to configured target.
# File lib/chef_core/target_host.rb, line 128 def connect! # Keep existing connections return unless @backend.nil? @backend = train_connection.connection @backend.wait_until_ready # When the testing function `mock_instance` is used, it will set # this instance variable to false and handle this function call # of mixin functions based on the mocked platform. mix_in_target_platform! unless @mocked_connection rescue Train::UserError => e raise ConnectionFailure.new(e, config) rescue Train::Error => e # These are typically wrapper errors for other problems, # so we'll prefer to use e.cause over e if available. raise ConnectionFailure.new(e.cause || e, config) end
# File lib/chef_core/target_host.rb, line 73 def connection_config(host_url, opts_in, logger) connection_opts = { target: host_url, sudo: opts_in[:sudo] === false ? false : true, www_form_encoded_password: true, key_files: opts_in[:identity_file] || opts_in[:key_files], non_interactive: true, # Prevent password prompts connection_retries: 2, connection_retry_sleep: 1, logger: opts_in[:logger] || ChefCore::Log } target_opts = Train.unpack_target_from_uri(host_url) if opts_in.key?(:ssl) && opts_in[:ssl] connection_opts[:ssl] = opts_in[:ssl] connection_opts[:self_signed] = opts_in[:self_signed] || (opts_in[:ssl_verify] === false ? true : false) end target_opts[:host] = host_url if target_opts[:host].nil? target_opts[:backend] = "ssh" if target_opts[:backend].nil? connection_opts = connection_opts.merge(target_opts) # From WinRM gem: It is recommended that you :disable_sspi => true if you are using the plaintext or ssl transport. # See note here: https://github.com/mwrock/WinRM#example if %w{ssl plaintext}.include?(target_opts[:winrm_transport]) target_opts[:winrm_disable_sspi] = true end connection_opts = connection_opts.merge(target_opts) # Anything we haven't explicitly set already, pass through to train. Train.options(target_opts[:backend]).keys.each do |key| if opts_in.key?(key) && !connection_opts.key?(key) connection_opts[key] = opts_in[key] end end Train.target_config(connection_opts) end
Recursively delete directory
# File lib/chef_core/target_host.rb, line 295 def del_dir(path); raise NotImplementedError; end
# File lib/chef_core/target_host.rb, line 297 def del_file(path); raise NotImplementedError; end
Retrieve the contents of a remote file. Returns nil if the file didn't exist or couldn't be read.
# File lib/chef_core/target_host.rb, line 226 def fetch_file_contents(remote_path) result = backend.file(remote_path) if result.exist? && result.file? result.content else nil end end
# File lib/chef_core/target_host.rb, line 169 def hostname config[:host] end
Platform-specific installation of packages
# File lib/chef_core/target_host.rb, line 290 def install_package(target_package_path); raise NotImplementedError; end
Returns the installed chef version as a Gem::Version, or raised ChefNotInstalled
if chef client version manifest can't be found.
# File lib/chef_core/target_host.rb, line 238 def installed_chef_version return @installed_chef_version if @installed_chef_version # Note: In the case of a very old version of chef (that has no manifest - pre 12.0?) # this will report as not installed. manifest = read_chef_version_manifest # We split the version here because unstable builds install from) # are in the form "Major.Minor.Build+HASH" which is not a valid # version string. @installed_chef_version = Gem::Version.new(manifest["build_version"].split("+")[0]) end
create a directory. because we run all commands as root, this will also set group:owner to the connecting user if host isn't windows so that scp – which uses the connecting user – will have permissions to upload into it.
# File lib/chef_core/target_host.rb, line 274 def make_directory(path) mkdir(path) chown(path, user) path end
# File lib/chef_core/target_host.rb, line 147 def mix_in_target_platform! case base_os when :linux require "chef_core/target_host/linux" class << self; include ChefCore::TargetHost::Linux; end when :windows require "chef_core/target_host/windows" class << self; include ChefCore::TargetHost::Windows; end when :other raise ChefCore::TargetHost::UnsupportedTargetOS.new(platform.name) end end
normalizes path across OS's
# File lib/chef_core/target_host.rb, line 281 def normalize_path(p) # NOTE BOOTSTRAP: was action::base::escape_windows_path p.tr("\\", "/") end
# File lib/chef_core/target_host.rb, line 299 def omnibus_manifest_path(); raise NotImplementedError; end
TODO 2019-01-29 not expose this, it's internal implemenation. Same with backend
.
# File lib/chef_core/target_host.rb, line 192 def platform backend.platform end
# File lib/chef_core/target_host.rb, line 251 def read_chef_version_manifest manifest = fetch_file_contents(omnibus_manifest_path) raise ChefNotInstalled.new if manifest.nil? JSON.parse(manifest) end
# File lib/chef_core/target_host.rb, line 205 def run_command(command, &data_handler) backend.run_command command, &data_handler end
# File lib/chef_core/target_host.rb, line 196 def run_command!(command, &data_handler) result = run_command(command, &data_handler) if result.exit_status != 0 raise RemoteExecutionFailed.new(@config[:host], command, result) end result end
TODO spec
# File lib/chef_core/target_host.rb, line 210 def save_as_remote_file(content, remote_path) t = Tempfile.new("chef-content") t << content t.close upload_file(t.path, remote_path) ensure t.close t.unlink end
Creates and caches location of temporary directory on the remote host using platform-specific implementations of make_temp_dir This will also set ownership to the connecting user instead of default of root when sudo'd, so that the dir can be used to upload files using scp as the connecting user.
The base temp dir is cached and will only be created once per connection lifetime.
# File lib/chef_core/target_host.rb, line 265 def temp_dir dir = make_temp_dir chown(dir, user) dir end
# File lib/chef_core/target_host.rb, line 220 def upload_file(local_path, remote_path) backend.upload(local_path, remote_path) end
Returns the user being used to connect. Defaults to train's default user if not specified
# File lib/chef_core/target_host.rb, line 161 def user return config[:user] unless config[:user].nil? require "train/transports/ssh" # TODO - this should use the right transport, not default to SSH Train::Transports::SSH.default_options[:user][:default] end
# File lib/chef_core/target_host.rb, line 177 def version platform.release end
# File lib/chef_core/target_host.rb, line 292 def ws_cache_path; raise NotImplementedError; end
Private Instance Methods
# File lib/chef_core/target_host.rb, line 307 def ssh_config_for_host(host) require "net/ssh" Net::SSH::Config.for(host) end
# File lib/chef_core/target_host.rb, line 303 def train_connection @train_connection end