Class: ChefApply::TargetHost

Inherits:
Object
  • Object
show all
Defined in:
lib/chef_apply/target_host.rb

Defined Under Namespace

Classes: ChefNotInstalled, ConnectionFailure, RemoteExecutionFailed, UnsupportedTargetOS

Constant Summary collapse

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

[:user, :port, :proxy].freeze
MANIFEST_PATHS =
{
  # TODO - use a proper method to query the win installation path -
  #        currently we're assuming the default, but this can be customized
  #        at install time.
  #        A working approach is below - but it runs very slowly in testing
  #        on a virtualbox windows vm:
  #        (over winrm) Get-WmiObject Win32_Product | Where {$_.Name -match 'Chef Client'}
  windows: "c:\\opscode\\chef\\version-manifest.json",
  linux: "/opt/chef/version-manifest.json",
}.freeze
MKTMP_WIN_CMD =
"$parent = [System.IO.Path]::GetTempPath();" +
"[string] $name = [System.Guid]::NewGuid();" +
"$tmp = New-Item -ItemType Directory -Path " +
"(Join-Path $parent $name);" +
"$tmp.FullName"
MKTMP_LINUX_CMD =
"d=$(mktemp -d -p${TMPDIR:-/tmp} chef_XXXXXX); echo $d".freeze

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Constructor Details

#initialize(host_url, opts = {}, logger = nil) ⇒ TargetHost

Returns a new instance of TargetHost.



36
37
38
39
40
41
# File 'lib/chef_apply/target_host.rb', line 36

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

Instance Attribute Details

#backendObject (readonly)

Returns the value of attribute backend.



23
24
25
# File 'lib/chef_apply/target_host.rb', line 23

def backend
  @backend
end

#configObject (readonly)

Returns the value of attribute config.



23
24
25
# File 'lib/chef_apply/target_host.rb', line 23

def config
  @config
end

#reporterObject (readonly)

Returns the value of attribute reporter.



23
24
25
# File 'lib/chef_apply/target_host.rb', line 23

def reporter
  @reporter
end

#transport_typeObject (readonly)

Returns the value of attribute transport_type.



23
24
25
# File 'lib/chef_apply/target_host.rb', line 23

def transport_type
  @transport_type
end

Class Method Details

.instance_for_url(target, opts = {}) ⇒ Object



29
30
31
32
33
34
# File 'lib/chef_apply/target_host.rb', line 29

def self.instance_for_url(target, opts = {})
  opts = { target: @url }
  target_host = new(target, opts)
  target_host.connect!
  target_host
end

Instance Method Details

#apply_ssh_config(config, opts_in) ⇒ Object



67
68
69
70
71
72
73
74
75
76
77
78
79
80
# File 'lib/chef_apply/target_host.rb', line 67

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

#architectureObject



106
107
108
# File 'lib/chef_apply/target_host.rb', line 106

def architecture
  platform.arch
end

#base_osObject



114
115
116
117
118
119
120
121
122
123
124
# File 'lib/chef_apply/target_host.rb', line 114

def base_os
  if platform.family == "windows"
    :windows
  elsif platform.linux?
    :linux
  else
    # TODO - this seems like it shouldn't happen here, when
    # all the caller is doing is asking about the OS
    raise ChefApply::TargetHost::UnsupportedTargetOS.new(platform.name)
  end
end

#chown(path, owner = nil) ⇒ Object

Simplified chown - just sets user , defaults to connection user. Does not touch group. Only has effect on non-windows targets



198
199
200
201
202
# File 'lib/chef_apply/target_host.rb', line 198

def chown(path, owner = nil)
  return if base_os == :windows
  owner ||= user
  run_command!("chown #{owner} '#{path}'")
end

#connect!Object



82
83
84
85
86
87
88
89
90
91
92
# File 'lib/chef_apply/target_host.rb', line 82

def connect!
  return unless @backend.nil?
  @backend = train_connection.connection
  @backend.wait_until_ready
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

#connection_config(host_url, opts_in, logger) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
# File 'lib/chef_apply/target_host.rb', line 43

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],
                      non_interactive: true,
                      # Prevent long delays due to retries on auth failure.
                      # This does reduce the number of attempts we'll make for transient conditions as well, but
                      # train does not currently exposes these as separate controls. Ideally I'd like to see a 'retry_on_auth_failure' option.
                      connection_retries: 2,
                      connection_retry_sleep: 0.15,
                      logger: ChefApply::Log }
  if opts_in.key? :ssl
    connection_opts[:ssl] = opts_in[:ssl]
    connection_opts[:self_signed] = (opts_in[:ssl_verify] === false ? true : false)
  end

  [:sudo_password, :sudo, :sudo_command, :password, :user].each do |key|
    connection_opts[key] = opts_in[key] if opts_in.key? key
  end

  Train.target_config(connection_opts)
end

#get_chef_version_manifestObject



172
173
174
175
176
177
# File 'lib/chef_apply/target_host.rb', line 172

def get_chef_version_manifest
  path = MANIFEST_PATHS[base_os()]
  manifest = backend.file(path)
  return :not_found unless manifest.file?
  JSON.parse(manifest.content)
end

#hostnameObject



102
103
104
# File 'lib/chef_apply/target_host.rb', line 102

def hostname
  config[:host]
end

#installed_chef_versionObject

Returns the installed chef version as a Gem::Version, or raised ChefNotInstalled if chef client version manifest can’t be found.

Raises:



149
150
151
152
153
154
155
156
157
158
159
# File 'lib/chef_apply/target_host.rb', line 149

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 = get_chef_version_manifest()
  raise ChefNotInstalled.new if manifest == :not_found
  # We'll split the version here because  unstable builds (where we currently
  # 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

#mkdir(path) ⇒ Object

create a dir. set owner to the connecting user if host isn’t windows so that scp – which uses the connecting user – can upload into it.



181
182
183
184
185
186
187
188
189
190
191
192
# File 'lib/chef_apply/target_host.rb', line 181

def mkdir(path)
  if base_os == :windows
    run_command!("New-Item -ItemType Directory -Force -Path #{path}")
  else
    # 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 -
    # which is done as the connecting user.
    run_command!("mkdir -p #{path}")
    chown(path, user)
  end
  nil
end

#mktempObject

Create temporary dir and return the path. 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 - which is done as the connecting user.



216
217
218
219
220
221
222
223
224
225
226
227
# File 'lib/chef_apply/target_host.rb', line 216

def mktemp
  if base_os == :windows
    res = run_command!(MKTMP_WIN_CMD)
    res.stdout.chomp.strip
  else
    # # TODO should we keep chmod 777?
    res = run_command!("bash -c '#{MKTMP_LINUX_CMD}'")
    path = res.stdout.chomp.strip
    chown(path)
    path
  end
end

#platformObject



126
127
128
# File 'lib/chef_apply/target_host.rb', line 126

def platform
  backend.platform
end

#run_command(command) ⇒ Object



138
139
140
# File 'lib/chef_apply/target_host.rb', line 138

def run_command(command)
  backend.run_command command
end

#run_command!(command) ⇒ Object



130
131
132
133
134
135
136
# File 'lib/chef_apply/target_host.rb', line 130

def run_command!(command)
  result = run_command(command)
  if result.exit_status != 0
    raise RemoteExecutionFailed.new(@config[:host], command, result)
  end
  result
end

#upload_file(local_path, remote_path) ⇒ Object



142
143
144
# File 'lib/chef_apply/target_host.rb', line 142

def upload_file(local_path, remote_path)
  backend.upload(local_path, remote_path)
end

#userObject

Returns the user being used to connect. Defaults to train’s default user if not specified defaulted in .ssh/config (for ssh connections), as set up in ‘#apply_ssh_config’.



96
97
98
99
100
# File 'lib/chef_apply/target_host.rb', line 96

def user
  return config[:user] unless config[:user].nil?
  require "train/transports/ssh"
  Train::Transports::SSH.default_options[:user][:default]
end

#versionObject



110
111
112
# File 'lib/chef_apply/target_host.rb', line 110

def version
  platform.release
end