Class: Chef::Provisioning::SshDriver::Driver

Inherits:
Driver
  • Object
show all
Includes:
Helpers
Defined in:
lib/chef/provisioning/ssh_driver/driver.rb

Overview

Provisions Machines Using SSH.

Instance Attribute Summary collapse

Class Method Summary collapse

Instance Method Summary collapse

Methods included from Helpers

#deep_hashify, #false_or_value, #ip_is_valid?, #log_debug, #log_info, #log_ts, #stringify_keys, #strip_hash_nil, #symbolize_keys, #valid_ip?, #valid_ssh_options

Constructor Details

#initialize(driver_url, config) ⇒ Driver

Returns a new instance of Driver.



31
32
33
34
35
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 31

def initialize(driver_url, config)
  super(driver_url, config)
  scheme, cluster_path = driver_url.split(':', 2)
  @cluster_path = cluster_path
end

Instance Attribute Details

#cluster_pathObject (readonly)

Returns the value of attribute cluster_path.



25
26
27
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 25

def cluster_path
  @cluster_path
end

Class Method Details

.canonicalize_url(driver_url, config) ⇒ Object



37
38
39
40
41
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 37

def self.canonicalize_url(driver_url, config)
  scheme, cluster_path = driver_url.split(':', 2)
  cluster_path = File.expand_path(cluster_path || File.join(Chef::Config.config_dir, 'provisioning/ssh'))
  "ssh:#{cluster_path}"
end

.from_url(driver_url, config) ⇒ Object



27
28
29
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 27

def self.from_url(driver_url, config)
  Driver.new(driver_url, config)
end

Instance Method Details

#allocate_machine(action_handler, machine_spec, machine_options) ⇒ 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/provisioning/ssh_driver/driver.rb', line 43

def allocate_machine(action_handler, machine_spec, machine_options)
  existing_machine         = ssh_machine_exists?(machine_spec)
  ssh_machine_file_updated = create_machine(action_handler, machine_spec, machine_options)

  if !existing_machine || !machine_spec.location
    machine_spec.location = {
      'driver_url' => driver_url,
      'driver_version' => Chef::Provisioning::SshDriver::VERSION,
      'target_name' => machine_spec.name,
      'ssh_machine_file' => ssh_machine_file_updated,
      'allocated_at' => Time.now.utc.to_s,
      'updated_at' => Time.now.utc.to_s,
      'host' => action_handler.host_node
    }
  elsif ssh_machine_file_updated
    machine_spec.location['updated_at'] = Time.now.utc.to_s
  end

  if machine_spec.location && (machine_spec.location['driver_version'] != Chef::Provisioning::SshDriver::VERSION)
    machine_spec.location['driver_version'] = Chef::Provisioning::SshDriver::VERSION
  end

end

#connect_to_machine(machine_spec, machine_options) ⇒ Object



78
79
80
81
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 78

def connect_to_machine(machine_spec, machine_options)
  ssh_machine = existing_ssh_machine_to_sym(machine_spec)
  machine_for(machine_spec, ssh_machine)
end

#convergence_strategy_for(ssh_machine) ⇒ Object



136
137
138
139
140
141
142
143
144
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 136

def convergence_strategy_for(ssh_machine)
  if ssh_machine[:transport_options][:is_windows]
    Chef::Provisioning::ConvergenceStrategy::InstallMsi.
      new(ssh_machine[:convergence_options], config)
  else
    Chef::Provisioning::ConvergenceStrategy::InstallSh.
      new(ssh_machine[:convergence_options], config)
  end
end

#create_machine(action_handler, machine_spec, machine_options) ⇒ Object



295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 295

def create_machine(action_handler, machine_spec, machine_options)
  ensure_ssh_cluster(action_handler)

  machine_options_hash_for_sym = deep_hashify(machine_options)
  symbolized_machine_options   = symbolize_keys(machine_options_hash_for_sym)
  validate_machine_options(action_handler, machine_spec, symbolized_machine_options)
  # end


  # def create_ssh_machine(action_handler, machine_spec, machine_options)
  log_info("File is = #{ssh_machine_file(machine_spec)}")
  log_info("current_machine_options = #{machine_options.to_s}")

  machine_options_hash_for_s = deep_hashify(machine_options)
  stringy_machine_options    = stringify_keys(machine_options_hash_for_s)
  given_machine_options      = create_machine_hash(stringy_machine_options)

  if ssh_machine_exists?(machine_spec)
    existing_machine_hash = existing_ssh_machine(machine_spec)
    if !existing_machine_hash.eql?(given_machine_options)
      create_machine_file(action_handler, machine_spec, given_machine_options)
    else
      return false
    end
  else
    file_updated = create_machine_file(action_handler, machine_spec, given_machine_options)
    file_updated
  end
end

#create_machine_file(action_handler, machine_spec, machine_options) ⇒ Object



325
326
327
328
329
330
331
332
333
334
335
336
337
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 325

def create_machine_file(action_handler, machine_spec, machine_options)
  file_path = ssh_machine_file(machine_spec)
  machine_options_hash = deep_hashify(machine_options)
  stringy_machine_options = stringify_keys(machine_options_hash)
  options_parsed = ::JSON.parse(stringy_machine_options.to_json)
  json_machine_options = ::JSON.pretty_generate(options_parsed)
  Chef::Provisioning.inline_resource(action_handler) do
    file file_path do
      content json_machine_options
    end
  end
  file_path
end

#create_machine_hash(machine_options) ⇒ Object



384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 384

def create_machine_hash(machine_options)
  if !machine_options['transport_options']['host']
    machine_options['transport_options']['host'] = machine_options['transport_options']['ip_address'] ||
      machine_options['transport_options']['hostname']
  end
  validate_transport_options_host(machine_options['transport_options']['host'])
  unless machine_options['transport_options']['is_windows']
    machine_options['transport_options']['options'] ||= {}
    unless machine_options['transport_options']['username'] == 'root'
      machine_options['transport_options']['options']['prefix'] = 'sudo '
    end
  end
  ensure_has_keys_or_password(machine_options['transport_options'])
  machine_options.to_hash
end

#create_ssh_transport(ssh_machine) ⇒ Object



146
147
148
149
150
151
152
153
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 146

def create_ssh_transport(ssh_machine)
  hostname    = ssh_machine[:transport_options][:host]
  username    = ssh_machine[:transport_options][:username]
  ssh_options = ssh_machine[:transport_options][:ssh_options]
  options     = ssh_machine[:transport_options][:options]
  Chef::Provisioning::Transport::SSH.new(hostname, username,
                                         ssh_options, options, config)
end

#create_winrm_transport(ssh_machine) ⇒ Object



155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 155

def create_winrm_transport(ssh_machine)
  # # TODO IPv6 loopback?  What do we do for that?
  hostname = ssh_machine[:transport_options][:host] ||
    ssh_machine[:transport_options][:ip_address]
  port = ssh_machine[:transport_options][:port] || 5985
  # port = forwarded_ports[port] if forwarded_ports[port]
  endpoint = "http://#{hostname}:#{port}/wsman"
  type = :plaintext
  options = {
    :user => ssh_machine[:transport_options][:username],
    :pass => ssh_machine[:transport_options][:password],
    :disable_sspi => true
  }
  Chef::Provisioning::Transport::WinRM.new(endpoint, type, options, config)
end

#delete_ssh_machine(action_handler, machine_spec) ⇒ Object



339
340
341
342
343
344
345
346
347
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 339

def delete_ssh_machine(action_handler, machine_spec)
  if ::File.exists?(ssh_machine_file(machine_spec))
    Chef::Provisioning.inline_resource(action_handler) do
      file registry_file do
        action :delete
      end
    end
  end
end

#destroy_machine(action_handler, machine_spec, machine_options) ⇒ Object



83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 83

def destroy_machine(action_handler, machine_spec, machine_options)
  ssh_machine = ssh_machine_exists?(machine_spec)

  if !ssh_machine || !::File.exists?(machine_spec.location['ssh_machine_file'])
    raise "SSH Machine #{machine_spec.name} does not have a machine file associated with it!"
	  else
    Chef::Provisioning.inline_resource(action_handler) do
	      file machine_spec.location['ssh_machine_file'] do
		action :delete
		backup false
	      end
	    end 
	  end


end

#ensure_has_keys_or_password(transport_hash) ⇒ Object



400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 400

def ensure_has_keys_or_password(transport_hash)
  if transport_hash['is_windows']
    password = transport_hash['password'] || false
    has_either = (password && password.kind_of?(String))
  else
    if transport_hash['ssh_options']
      ssh_hash = transport_hash['ssh_options']
      keys = ssh_hash['keys'] || false
      password = ssh_hash['password'] || false
      has_either = ((password && password.kind_of?(String)) ||
                    (keys && !keys.empty? && keys.kind_of?(Array)))
    else
      has_either = false
    end
  end
  raise 'No Keys OR Password, No Can Do Compadre' unless has_either
  has_either
end

#ensure_ssh_cluster(action_handler) ⇒ Object



286
287
288
289
290
291
292
293
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 286

def ensure_ssh_cluster(action_handler)
  _cluster_path = cluster_path
  unless ::File.exists?(_cluster_path)
    Chef::Provisioning.inline_resource(action_handler) do
      ssh_cluster _cluster_path
    end
  end
end

#existing_ssh_machine(machine_spec) ⇒ Object



349
350
351
352
353
354
355
356
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 349

def existing_ssh_machine(machine_spec)
  if ssh_machine_exists?(machine_spec)
    existing_machine_hash = JSON.parse(File.read(ssh_machine_file(machine_spec)))
    existing_machine_hash.to_hash
  else
    return {}
  end
end

#existing_ssh_machine_to_sym(machine_spec) ⇒ Object



358
359
360
361
362
363
364
365
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 358

def existing_ssh_machine_to_sym(machine_spec)
  if ssh_machine_exists?(machine_spec)
    existing_machine_hash = existing_ssh_machine(machine_spec)
    symbolize_keys(existing_machine_hash)
  else
    return false
  end
end

#machine_for(machine_spec, machine_options) ⇒ Object



110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 110

def machine_for(machine_spec, machine_options)
  ssh_machine = existing_ssh_machine_to_sym(machine_spec)

  if !ssh_machine
    raise "SSH Machine #{machine_spec.name} does not have a machine file associated with it!"
  end

  if ssh_machine[:transport_options][:is_windows]
    Chef::Provisioning::Machine::WindowsMachine.new(machine_spec,
                                                    transport_for(ssh_machine),
                                                    convergence_strategy_for(ssh_machine))
  else
    Chef::Provisioning::Machine::UnixMachine.new(machine_spec,
                                                 transport_for(ssh_machine),
                                                 convergence_strategy_for(ssh_machine))
  end
end

#ready_machine(action_handler, machine_spec, machine_options) ⇒ Object



67
68
69
70
71
72
73
74
75
76
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 67

def ready_machine(action_handler, machine_spec, machine_options)
  ssh_machine = existing_ssh_machine_to_sym(machine_spec)

  if !ssh_machine
    raise "SSH Machine #{machine_spec.name} does not have a machine file associated with it!"
  end

  wait_for_transport(action_handler, ssh_machine, machine_spec, machine_options)
  machine_for(machine_spec, machine_options)
end

#ssh_machine_exists?(machine_spec) ⇒ Boolean

Returns:

  • (Boolean)


367
368
369
370
371
372
373
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 367

def ssh_machine_exists?(machine_spec)
  if machine_spec.location
    ::File.exists?(ssh_machine_file(machine_spec))
  else
    false
  end
end

#ssh_machine_file(machine_spec) ⇒ Object



375
376
377
378
379
380
381
382
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 375

def ssh_machine_file(machine_spec)
  if machine_spec.location && machine_spec.location['ssh_machine_file']
    machine_spec.location['ssh_machine_file']
  else
    ssh_machine_file = ::File.join(@cluster_path, "#{machine_spec.name}.json")
    ssh_machine_file
  end
end

#stop_machine(action_handler, machine_spec, machine_options) ⇒ Object



100
101
102
103
104
105
106
107
108
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 100

def stop_machine(action_handler, machine_spec, machine_options)
  ssh_machine = existing_ssh_machine_to_sym(machine_spec)

  if !ssh_machine
    raise "SSH Machine #{machine_spec.name} does not have a machine file associated with it!"
  end

  action_handler.report_progress("SSH Machine #{machine_spec.name} is existing hardware login and power off.")
end

#transport_for(ssh_machine) ⇒ Object



128
129
130
131
132
133
134
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 128

def transport_for(ssh_machine)
  if ssh_machine[:transport_options][:is_windows]
    create_winrm_transport(ssh_machine)
  else
    create_ssh_transport(ssh_machine)
  end
end

#validate_machine_options(action_handler, machine_spec, machine_options) ⇒ Object



192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 192

def validate_machine_options(action_handler, machine_spec, machine_options)
  error_msgs = []
  valid = true

  if !machine_options[:transport_options]
    error_msgs << ":transport_options required."
    valid = false
  else
    if machine_options[:transport_options][:is_windows]
      # Validate Windows Options.
      req_and_valid_fields = [:is_windows, [:host, :ip_address], :username, :password]
      one_of_fields = req_and_valid_fields.select{ |i| i.kind_of?(Array)}

      missing = req_and_valid_fields.flatten - machine_options[:transport_options].keys

      one_of_fields.each do |oof|
        if oof == oof & missing
          error_msgs << ":transport_options => :#{oof.join(" or :")} required."
        end
        missing -= oof
      end

      missing.each do |missed|
        error_msgs << ":transport_options => :#{missed} required."
        valid = false
      end

      extras = machine_options[:transport_options].keys - req_and_valid_fields.flatten

      extras.each do |extra|
        error_msgs << ":transport_options => :#{extra} not allowed." unless extra == :port
        valid = false
      end
    else
      # Validate Unix Options
      req_fields = [[:host, :hostname, :ip_address], :username]
      one_of_fields = req_fields.select{ |i| i.kind_of?(Array)}

      missing = req_fields.flatten - machine_options[:transport_options].keys

      one_of_fields.each do |oof|
        if oof == oof & missing
          error_msgs << ":transport_options => :#{oof.join(" or :")} required."
        end
        missing -= oof
      end

      missing.each do |missed|
        error_msgs << ":transport_options => :#{missed} required."
        valid = false
      end

      valid_fields = [:is_windows, :host, :hostname, :ip_address, :username, :ssh_options, :options]

      extras = machine_options[:transport_options].keys - valid_fields

      extras.each do |extra|
        error_msgs << ":transport_options => :#{extra} not allowed."
        valid = false
      end

      if machine_options[:transport_options][:ssh_options]
        valid_fields = valid_ssh_options

        extras = machine_options[:transport_options][:ssh_options].keys - valid_fields

        extras.each do |extra|
          error_msgs << ":transport_options => ssh_options => :#{extra} not allowed."
          valid = false
        end
      end

      if machine_options[:transport_options][:options]
        valid_fields = [:prefix, :ssh_pty_enable, :ssh_gateway]

        extras = machine_options[:transport_options][:options].keys - valid_fields

        extras.each do |extra|
          error_msgs << ":transport_options => :options => :#{extra} not allowed."
          valid = false
        end
      end
    end
  end

  if !valid
    exception_string = "Machine Options for #{machine_spec.name} are invalid cannot create machine."
    error_msgs.each do |string|
      exception_string = "#{exception_string}\n  #{string}"
    end
    raise exception_string
  end
end

#validate_transport_options_host(target_host) ⇒ Object



419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 419

def validate_transport_options_host(target_host)
  rh = Resolv::Hosts.new
  rd = Resolv.new

  begin
    rh.getaddress(target_host)
    in_hosts_file = true
  rescue
    in_hosts_file = false
  end

  begin
    rd.getaddress(target_host)
    in_dns = true
  rescue
    in_dns = false
  end

  valid_ip = ( target_host =~ Resolv::IPv4::Regex ||
               target_host =~ Resolv::IPv6::Regex )

  raise 'Host is not a Valid IP or Resolvable Hostname' unless ( valid_ip || in_hosts_file || in_dns )
end

#wait_for_transport(action_handler, ssh_machine, machine_spec, machine_options) ⇒ Object



171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
# File 'lib/chef/provisioning/ssh_driver/driver.rb', line 171

def wait_for_transport(action_handler, ssh_machine, machine_spec, machine_options)
  time_elapsed = 0
  sleep_time = 10
  max_wait_time = 120
  transport = transport_for(ssh_machine)
  unless transport.available?
    if action_handler.should_perform_actions
      action_handler.report_progress "waiting for #{machine_spec.name} (#{ssh_machine[:transport_options][:ip_address]} on #{driver_url}) to be connectable (transport up and running) ..."
      while time_elapsed < max_wait_time && !transport.available?
        action_handler.report_progress "been waiting #{time_elapsed}/#{max_wait_time} -- sleeping #{sleep_time} seconds for #{machine_spec.name} (#{ssh_machine[:transport_options][:ip_address]} on #{driver_url}) to be connectable ..."
        sleep(sleep_time)
        time_elapsed += sleep_time
      end
      unless transport.available?
        raise "Machine #{machine_spec.name} (#{ssh_machine[:transport_options][:ip_address]} on #{driver_url}) did not become ready within 120 seconds"
      end
      action_handler.report_progress "#{machine_spec.name} is now connectable"
    end
  end
end