chef-provisioning-vsphere 0.8.2 → 0.8.3.dev

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,671 +1,679 @@
1
- require 'chef'
2
- require 'cheffish/merged_config'
3
- require 'chef/provisioning/driver'
4
- require 'chef/provisioning/machine/windows_machine'
5
- require 'chef/provisioning/machine/unix_machine'
6
- require 'chef/provisioning/vsphere_driver/clone_spec_builder'
7
- require 'chef/provisioning/vsphere_driver/version'
8
- require 'chef/provisioning/vsphere_driver/vsphere_helpers'
9
- require 'chef/provisioning/vsphere_driver/vsphere_url'
10
-
11
- module ChefProvisioningVsphere
12
- # Provisions machines in vSphere.
13
- class VsphereDriver < Chef::Provisioning::Driver
14
- include Chef::Mixin::ShellOut
15
-
16
- def self.from_url(driver_url, config)
17
- VsphereDriver.new(driver_url, config)
18
- end
19
-
20
- # Create a new Vsphere provisioner.
21
- #
22
- # ## Parameters
23
- # connect_options - hash of options to be passed to RbVmomi::VIM.connect
24
- # :host - required - hostname of the vSphere API server
25
- # :port - optional - port on the vSphere API server (default: 443)
26
- # :path - optional - path on the vSphere API server (default: /sdk)
27
- # :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
28
- # :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
29
- # :user - required - user name to use in connection to vSphere API server
30
- # :password - required - password to use in connection to vSphere API server
31
- def self.canonicalize_url(driver_url, config)
32
- config = symbolize_keys(config)
33
- [ driver_url || URI::VsphereUrl.from_config(config).to_s, config ]
34
- end
35
-
36
- def self.symbolize_keys(h)
37
- Hash === h ?
38
- Hash[
39
- h.map do |k, v|
40
- [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
41
- end
42
- ] : h
43
- end
44
-
45
- def initialize(driver_url, config)
46
- super(driver_url, config)
47
-
48
- uri = URI(driver_url)
49
- @connect_options = {
50
- provider: 'vsphere',
51
- host: uri.host,
52
- port: uri.port,
53
- use_ssl: uri.use_ssl,
54
- insecure: uri.insecure,
55
- path: uri.path
56
- }
57
-
58
- if driver_options
59
- @connect_options[:user] = driver_options[:user]
60
- @connect_options[:password] = driver_options[:password]
61
- end
62
- end
63
-
64
- attr_reader :connect_options
65
-
66
- # Acquire a machine, generally by provisioning it. Returns a Machine
67
- # object pointing at the machine, allowing useful actions like setup,
68
- # converge, execute, file and directory. The Machine object will have a
69
- # "node" property which must be saved to the server (if it is any
70
- # different from the original node object).
71
- #
72
- # ## Parameters
73
- # action_handler - the action_handler object that is calling this method; this
74
- # is generally a action_handler, but could be anything that can support the
75
- # ChefMetal::ActionHandler interface (i.e., in the case of the test
76
- # kitchen metal driver for acquiring and destroying VMs; see the base
77
- # class for what needs providing).
78
- # node - node object (deserialized json) representing this machine. If
79
- # the node has a provisioner_options hash in it, these will be used
80
- # instead of options provided by the provisioner. TODO compare and
81
- # fail if different?
82
- # node will have node['normal']['provisioner_options'] in it with any options.
83
- # It is a hash with this format:
84
- #
85
- # -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
86
- # -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
87
- # :datacenter
88
- # :resource_pool
89
- # :cluster
90
- # :datastore
91
- # :template_name
92
- # :template_folder
93
- # :vm_folder
94
- # :winrm {...} (not yet implemented)
95
- # :ssh {...}
96
- #
97
- # Example bootstrap_options for vSphere:
98
- # TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
99
- # 'bootstrap_options' => {
100
- # 'template_name' =>'centos6.small',
101
- # 'template_folder' =>'Templates',
102
- # 'vm_folder' => 'MyApp'
103
- # }
104
- #
105
- # node['normal']['provisioner_output'] will be populated with information
106
- # about the created machine. For vSphere, it is a hash with this
107
- # format:
108
- #
109
- # -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
110
- # -- vm_folder: name of the vSphere folder containing the VM
111
- #
112
- def allocate_machine(action_handler, machine_spec, machine_options)
113
- merge_options! machine_options
114
-
115
- if machine_spec.location
116
- Chef::Log.warn(
117
- "Checking to see if #{machine_spec.location} has been created...")
118
- vm = vm_for(machine_spec)
119
- if vm
120
- Chef::Log.warn 'returning existing machine'
121
- return vm
122
- else
123
- Chef::Log.warn machine_msg(
124
- machine_spec.name,
125
- machine_spec.location['server_id'],
126
- 'no longer exists. Recreating ...'
127
- )
128
- end
129
- end
130
- bootstrap_options = machine_options[:bootstrap_options]
131
-
132
- action_handler.report_progress full_description(
133
- machine_spec, bootstrap_options)
134
-
135
- vm = find_or_create_vm(bootstrap_options, machine_spec, action_handler)
136
-
137
- add_machine_spec_location(vm, machine_spec)
138
-
139
- action_handler.performed_action(machine_msg(
140
- machine_spec.name,
141
- vm.config.instanceUuid,
142
- 'created'
143
- ))
144
- vm
145
- end
146
-
147
- def merge_options!(machine_options)
148
- @config = Cheffish::MergedConfig.new(
149
- { machine_options: machine_options },
150
- @config
151
- )
152
- end
153
-
154
- def add_machine_spec_location(vm, machine_spec)
155
- machine_spec.location = {
156
- 'driver_url' => driver_url,
157
- 'driver_version' => VERSION,
158
- 'server_id' => vm.config.instanceUuid,
159
- 'is_windows' => is_windows?(vm),
160
- 'allocated_at' => Time.now.utc.to_s,
161
- 'ipaddress' => vm.guest.ipAddress
162
- }
163
- end
164
-
165
- def find_or_create_vm(bootstrap_options, machine_spec, action_handler)
166
- vm = vsphere_helper.find_vm(
167
- bootstrap_options[:vm_folder],
168
- machine_spec.name
169
- )
170
- if vm
171
- Chef::Log.info machine_msg(
172
- machine_spec.name,
173
- vm.config.instanceUuid,
174
- 'already created'
175
- )
176
- else
177
- vm = clone_vm(
178
- action_handler,
179
- bootstrap_options,
180
- machine_spec.name
181
- )
182
- end
183
- vm
184
- end
185
-
186
- def full_description(machine_spec, bootstrap_options)
187
- description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
188
- bootstrap_options.to_hash.each_pair do |key,value|
189
- description << " #{key}: #{value.inspect}"
190
- end
191
- description
192
- end
193
-
194
- def machine_msg(name, id, action)
195
- "Machine - #{action} - #{name} (#{id} on #{driver_url})"
196
- end
197
-
198
- def ready_machine(action_handler, machine_spec, machine_options)
199
- merge_options! machine_options
200
-
201
- vm = start_machine(action_handler, machine_spec, machine_options)
202
- if vm.nil?
203
- raise "Machine #{machine_spec.name} does not have a server "\
204
- 'associated with it, or server does not exist.'
205
- end
206
-
207
- bootstrap_options = machine_options[:bootstrap_options]
208
-
209
- transport_respond?(
210
- machine_options,
211
- vm,
212
- action_handler,
213
- machine_spec
214
- )
215
-
216
- machine = machine_for(machine_spec,machine_options)
217
-
218
- setup_extra_nics(action_handler, bootstrap_options, vm, machine)
219
-
220
- if has_static_ip(bootstrap_options) && !is_windows?(vm)
221
- setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
222
- end
223
-
224
- machine
225
- end
226
-
227
- def setup_extra_nics(action_handler, bootstrap_options, vm, machine)
228
- networks=bootstrap_options[:network_name]
229
- if networks.kind_of?(String)
230
- networks=[networks]
231
- end
232
- return if networks.nil? || networks.count < 2
233
-
234
- new_nics = vsphere_helper.add_extra_nic(
235
- action_handler,
236
- vm_template_for(bootstrap_options),
237
- bootstrap_options,
238
- vm
239
- )
240
- if is_windows?(vm) && !new_nics.nil?
241
- new_nics.each do |nic|
242
- nic_label = nic.device.deviceInfo.label
243
- machine.execute_always(
244
- "Disable-Netadapter -Name '#{nic_label}' -Confirm:$false")
245
- end
246
- end
247
- end
248
-
249
- def transport_respond?(
250
- machine_options,
251
- vm,
252
- action_handler,
253
- machine_spec
254
- )
255
- bootstrap_options = machine_options[:bootstrap_options]
256
-
257
- # this waits for vmware tools to start and the vm to presebnt an ip
258
- # This may just be the ip of a newly cloned machine
259
- # Customization below may change this to a valid ip
260
- wait_until_ready(action_handler, machine_spec, machine_options, vm)
261
-
262
- if !machine_spec.location['ipaddress'] || !has_ip?(machine_spec.location['ipaddress'], vm)
263
- # find the ip we actually want
264
- # this will be the static ip to assign
265
- # or the ip reported back by the vm if using dhcp
266
- # it *may* be nil if just cloned
267
- vm_ip = ip_to_bootstrap(bootstrap_options, vm)
268
- transport = nil
269
- unless vm_ip.nil?
270
- transport = transport_for(machine_spec, bootstrap_options[:ssh], vm_ip)
271
- end
272
-
273
- unless !transport.nil? && transport.available? && has_ip?(vm_ip, vm)
274
- attempt_ip(machine_options, action_handler, vm, machine_spec)
275
- end
276
- machine_spec.location['ipaddress'] = vm.guest.ipAddress
277
- action_handler.report_progress(
278
- "IP address obtained: #{machine_spec.location['ipaddress']}")
279
- end
280
-
281
- wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
282
-
283
- begin
284
- wait_for_transport(action_handler, machine_spec, machine_options, vm)
285
- rescue Timeout::Error
286
- # Only ever reboot once, and only if it's been less than 10 minutes
287
- # since we stopped waiting
288
- if machine_spec.location['started_at'] ||
289
- remaining_wait_time(machine_spec, machine_options) < -(10*60)
290
- raise
291
- else
292
- Chef::Log.warn(machine_msg(
293
- machine_spec.name,
294
- vm.config.instanceUuid,
295
- 'started but SSH did not come up. Rebooting...'
296
- ))
297
- restart_server(action_handler, machine_spec, machine_options)
298
- wait_until_ready(action_handler, machine_spec, machine_options, vm)
299
- wait_for_transport(action_handler, machine_spec, machine_options, vm)
300
- end
301
- end
302
- end
303
-
304
- def attempt_ip(machine_options, action_handler, vm, machine_spec)
305
- vm_ip = ip_to_bootstrap(machine_options[:bootstrap_options], vm)
306
-
307
- wait_for_ip(vm, machine_options, action_handler)
308
-
309
- unless has_ip?(vm_ip, vm)
310
- action_handler.report_progress "rebooting..."
311
- if vm.guest.toolsRunningStatus != "guestToolsRunning"
312
- msg = 'tools have stopped. current power state is '
313
- msg << vm.runtime.powerState
314
- msg << ' and tools state is '
315
- msg << vm.guest.toolsRunningStatus
316
- msg << '. powering up server...'
317
- action_handler.report_progress(msg.join)
318
- vsphere_helper.start_vm(vm)
319
- else
320
- restart_server(action_handler, machine_spec, machine_options)
321
- end
322
- wait_for_ip(vm, machine_options, action_handler)
323
- end
324
- end
325
-
326
- def wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
327
- return unless bootstrap_options[:customization_spec]
328
- return unless bootstrap_options[:customization_spec][:domain]
329
-
330
- domain = bootstrap_options[:customization_spec][:domain]
331
- if is_windows?(vm) && domain != 'local'
332
- start = Time.now.utc
333
- trimmed_name = machine_spec.name.byteslice(0,15)
334
- expected_name="#{trimmed_name}.#{domain}"
335
- action_handler.report_progress(
336
- "waiting to domain join and be named #{expected_name}")
337
- until (Time.now.utc - start) > 30 ||
338
- (vm.guest.hostName == expected_name) do
339
- print '.'
340
- sleep 5
341
- end
342
- end
343
- end
344
-
345
- def wait_for_ip(vm, machine_options, action_handler)
346
- bootstrap_options = machine_options[:bootstrap_options]
347
- vm_ip = ip_to_bootstrap(bootstrap_options, vm)
348
- ready_timeout = machine_options[:ready_timeout] || 300
349
- msg = "waiting up to #{ready_timeout} seconds for customization"
350
- msg << " and find #{vm_ip}" unless vm_ip == vm.guest.ipAddress
351
- action_handler.report_progress msg
352
-
353
- start = Time.now.utc
354
- until (Time.now.utc - start) > ready_timeout || has_ip?(vm_ip, vm) do
355
- action_handler.report_progress(
356
- "IP addresses found: #{all_ips_for(vm)}")
357
- vm_ip ||= ip_to_bootstrap(bootstrap_options, vm)
358
- sleep 5
359
- end
360
- end
361
-
362
- def all_ips_for(vm)
363
- vm.guest.net.map { |net| net.ipAddress}.flatten
364
- end
365
-
366
- def has_ip?(ip, vm)
367
- all_ips_for(vm).include?(ip)
368
- end
369
-
370
- # Connect to machine without acquiring it
371
- def connect_to_machine(machine_spec, machine_options)
372
- merge_options! machine_options
373
- machine_for(machine_spec, machine_options)
374
- end
375
-
376
- def destroy_machine(action_handler, machine_spec, machine_options)
377
- merge_options! machine_options
378
- vm = vm_for(machine_spec)
379
- if vm
380
- action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
381
- begin
382
- vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
383
- vm.Destroy_Task.wait_for_completion
384
- rescue RbVmomi::Fault => fault
385
- raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
386
- ensure
387
- machine_spec.location = nil
388
- end
389
- end
390
- end
391
- strategy = convergence_strategy_for(machine_spec, machine_options)
392
- strategy.cleanup_convergence(action_handler, machine_spec)
393
- end
394
-
395
- def stop_machine(action_handler, machine_spec, machine_options)
396
- merge_options! machine_options
397
- vm = vm_for(machine_spec)
398
- if vm
399
- action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
400
- vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
401
- end
402
- end
403
- end
404
-
405
- def start_machine(action_handler, machine_spec, machine_options)
406
- merge_options! machine_options
407
- vm = vm_for(machine_spec)
408
- if vm
409
- action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
410
- vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
411
- end
412
- end
413
- vm
414
- end
415
-
416
- def restart_server(action_handler, machine_spec, machine_options)
417
- action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
418
- stop_machine(action_handler, machine_spec, machine_options)
419
- start_machine(action_handler, machine_spec, machine_options)
420
- machine_spec.location['started_at'] = Time.now.utc.to_s
421
- end
422
- end
423
-
424
- protected
425
-
426
- def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
427
- host_lookup = machine.execute_always('host google.com')
428
- if host_lookup.exitstatus != 0
429
- if host_lookup.stdout.include?("setlocale: LC_ALL")
430
- machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
431
- end
432
- distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
433
- Chef::Log.info "Found distro:#{distro}"
434
- if distro == 'Ubuntu'
435
- distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
436
- Chef::Log.info "Found distro version:#{distro_version}"
437
- if distro_version>= 12.04
438
- Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
439
- interfaces_file = "/etc/network/interfaces"
440
- nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
441
- machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
442
- machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
443
- machine.execute_always('/etc/init.d/networking restart')
444
- machine.execute_always('apt-get -qq update')
445
- end
446
- end
447
- end
448
- end
449
-
450
- def has_static_ip(bootstrap_options)
451
- if bootstrap_options.has_key?(:customization_spec)
452
- bootstrap_options = bootstrap_options[:customization_spec]
453
- if bootstrap_options.has_key?(:ipsettings)
454
- bootstrap_options = bootstrap_options[:ipsettings]
455
- if bootstrap_options.has_key?(:ip)
456
- return true
457
- end
458
- end
459
- end
460
- false
461
- end
462
-
463
- def remaining_wait_time(machine_spec, machine_options)
464
- if machine_spec.location['started_at']
465
- (machine_options[:start_timeout] || 600) -
466
- (Time.now.utc - Time.parse(machine_spec.location['started_at']))
467
- else
468
- (machine_options[:create_timeout] || 600) -
469
- (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
470
- end
471
- end
472
-
473
- def wait_until_ready(action_handler, machine_spec, machine_options, vm)
474
- if vm.guest.toolsRunningStatus != "guestToolsRunning"
475
- if action_handler.should_perform_actions
476
- action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
477
- until remaining_wait_time(machine_spec, machine_options) < 0 ||
478
- (vm.guest.toolsRunningStatus == "guestToolsRunning" && !vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
479
- print "."
480
- sleep 5
481
- end
482
- action_handler.report_progress "#{machine_spec.name} is now ready"
483
- end
484
- end
485
- end
486
-
487
- def vm_for(machine_spec)
488
- if machine_spec.location
489
- vsphere_helper.find_vm_by_id(machine_spec.location['server_id'])
490
- else
491
- nil
492
- end
493
- end
494
-
495
- def clone_vm(action_handler, bootstrap_options, machine_name)
496
- vm_template = vm_template_for(bootstrap_options)
497
-
498
- spec_builder = CloneSpecBuilder.new(vsphere_helper, action_handler)
499
- clone_spec = spec_builder.build(vm_template, machine_name, bootstrap_options)
500
- Chef::Log.debug("Clone spec: #{clone_spec.pretty_inspect}")
501
-
502
- vm_folder = vsphere_helper.find_folder(bootstrap_options[:vm_folder])
503
- vm_template.CloneVM_Task(
504
- name: machine_name,
505
- folder: vm_folder,
506
- spec: clone_spec
507
- ).wait_for_completion
508
-
509
- vm = vsphere_helper.find_vm(vm_folder, machine_name)
510
-
511
- additional_disk_size_gb = bootstrap_options[:additional_disk_size_gb]
512
- if !additional_disk_size_gb.is_a?(Array)
513
- additional_disk_size_gb = [additional_disk_size_gb]
514
- end
515
-
516
- additional_disk_size_gb.each do |size|
517
- size = size.to_i
518
- next if size == 0
519
- if bootstrap_options[:datastore].to_s.empty?
520
- raise ':datastore must be specified when adding a disk to a cloned vm'
521
- end
522
- task = vm.ReconfigVM_Task(
523
- spec: RbVmomi::VIM.VirtualMachineConfigSpec(
524
- deviceChange: [
525
- vsphere_helper.virtual_disk_for(
526
- vm,
527
- bootstrap_options[:datastore],
528
- size
529
- )
530
- ]
531
- )
532
- )
533
- task.wait_for_completion
534
- end
535
-
536
- vm
537
- end
538
-
539
- def vsphere_helper
540
- @vsphere_helper ||= VsphereHelper.new(
541
- connect_options,
542
- config[:machine_options][:bootstrap_options][:datacenter]
543
- )
544
- end
545
-
546
- def vm_template_for(bootstrap_options)
547
- template_folder = bootstrap_options[:template_folder]
548
- template_name = bootstrap_options[:template_name]
549
- vsphere_helper.find_vm(template_folder, template_name) ||
550
- raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
551
- end
552
-
553
- def machine_for(machine_spec, machine_options)
554
- if machine_spec.location.nil?
555
- raise "Server for node #{machine_spec.name} has not been created!"
556
- end
557
-
558
- transport = transport_for(
559
- machine_spec,
560
- machine_options[:bootstrap_options][:ssh]
561
- )
562
- strategy = convergence_strategy_for(machine_spec, machine_options)
563
-
564
- if machine_spec.location['is_windows']
565
- Chef::Provisioning::Machine::WindowsMachine.new(
566
- machine_spec, transport, strategy)
567
- else
568
- Chef::Provisioning::Machine::UnixMachine.new(
569
- machine_spec, transport, strategy)
570
- end
571
- end
572
-
573
- def is_windows?(vm)
574
- return false if vm.nil?
575
- vm.config.guestId.start_with?('win')
576
- end
577
-
578
- def convergence_strategy_for(machine_spec, machine_options)
579
- require 'chef/provisioning/convergence_strategy/install_msi'
580
- require 'chef/provisioning/convergence_strategy/install_cached'
581
- require 'chef/provisioning/convergence_strategy/no_converge'
582
-
583
- mopts = machine_options[:convergence_options].to_hash.dup
584
- if mopts[:chef_server]
585
- mopts[:chef_server] = mopts[:chef_server].to_hash.dup
586
- mopts[:chef_server][:options] = mopts[:chef_server][:options].to_hash.dup if mopts[:chef_server][:options]
587
- end
588
-
589
- if !machine_spec.location
590
- return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(
591
- mopts, config)
592
- end
593
-
594
- if machine_spec.location['is_windows']
595
- Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(
596
- mopts, config)
597
- else
598
- Chef::Provisioning::ConvergenceStrategy::InstallCached.new(
599
- mopts, config)
600
- end
601
- end
602
-
603
- def wait_for_transport(action_handler, machine_spec, machine_options, vm)
604
- transport = transport_for(
605
- machine_spec,
606
- machine_options[:bootstrap_options][:ssh]
607
- )
608
- if !transport.available?
609
- if action_handler.should_perform_actions
610
- action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
611
-
612
- until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
613
- print "."
614
- sleep 5
615
- end
616
-
617
- action_handler.report_progress "#{machine_spec.name} is now connectable"
618
- end
619
- end
620
- end
621
-
622
- def transport_for(
623
- machine_spec,
624
- remoting_options,
625
- ip = machine_spec.location['ipaddress']
626
- )
627
- if machine_spec.location['is_windows']
628
- create_winrm_transport(ip, remoting_options)
629
- else
630
- create_ssh_transport(ip, remoting_options)
631
- end
632
- end
633
-
634
- def create_winrm_transport(host, options)
635
- require 'chef/provisioning/transport/winrm'
636
- opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
637
- winrm_options = {
638
- user: "#{options[:user]}",
639
- pass: options[:password],
640
- opt => true
641
- }
642
-
643
- Chef::Provisioning::Transport::WinRM.new(
644
- "http://#{host}:5985/wsman",
645
- :plaintext,
646
- winrm_options,
647
- config
648
- )
649
- end
650
-
651
- def create_ssh_transport(host, options)
652
- require 'chef/provisioning/transport/ssh'
653
- ssh_user = options[:user]
654
- Chef::Provisioning::Transport::SSH.new(
655
- host,
656
- ssh_user,
657
- options,
658
- @config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
659
- config
660
- )
661
- end
662
-
663
- def ip_to_bootstrap(bootstrap_options, vm)
664
- if has_static_ip(bootstrap_options)
665
- bootstrap_options[:customization_spec][:ipsettings][:ip]
666
- else
667
- vm.guest.ipAddress
668
- end
669
- end
670
- end
671
- end
1
+ require 'chef'
2
+ require 'cheffish/merged_config'
3
+ require 'chef/provisioning/driver'
4
+ require 'chef/provisioning/machine/windows_machine'
5
+ require 'chef/provisioning/machine/unix_machine'
6
+ require 'chef/provisioning/vsphere_driver/clone_spec_builder'
7
+ require 'chef/provisioning/vsphere_driver/version'
8
+ require 'chef/provisioning/vsphere_driver/vsphere_helpers'
9
+ require 'chef/provisioning/vsphere_driver/vsphere_url'
10
+
11
+ module ChefProvisioningVsphere
12
+ # Provisions machines in vSphere.
13
+ class VsphereDriver < Chef::Provisioning::Driver
14
+ include Chef::Mixin::ShellOut
15
+
16
+ def self.from_url(driver_url, config)
17
+ VsphereDriver.new(driver_url, config)
18
+ end
19
+
20
+ # Create a new Vsphere provisioner.
21
+ #
22
+ # ## Parameters
23
+ # connect_options - hash of options to be passed to RbVmomi::VIM.connect
24
+ # :host - required - hostname of the vSphere API server
25
+ # :port - optional - port on the vSphere API server (default: 443)
26
+ # :path - optional - path on the vSphere API server (default: /sdk)
27
+ # :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
28
+ # :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
29
+ # :user - required - user name to use in connection to vSphere API server
30
+ # :password - required - password to use in connection to vSphere API server
31
+ def self.canonicalize_url(driver_url, config)
32
+ config = symbolize_keys(config)
33
+ [ driver_url || URI::VsphereUrl.from_config(config).to_s, config ]
34
+ end
35
+
36
+ def self.symbolize_keys(h)
37
+ Hash === h ?
38
+ Hash[
39
+ h.map do |k, v|
40
+ [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
41
+ end
42
+ ] : h
43
+ end
44
+
45
+ def initialize(driver_url, config)
46
+ super(driver_url, config)
47
+
48
+ uri = URI(driver_url)
49
+ @connect_options = {
50
+ provider: 'vsphere',
51
+ host: uri.host,
52
+ port: uri.port,
53
+ use_ssl: uri.use_ssl,
54
+ insecure: uri.insecure,
55
+ path: uri.path
56
+ }
57
+
58
+ if driver_options
59
+ @connect_options[:user] = driver_options[:user]
60
+ @connect_options[:password] = driver_options[:password]
61
+ end
62
+ end
63
+
64
+ attr_reader :connect_options
65
+
66
+ # Acquire a machine, generally by provisioning it. Returns a Machine
67
+ # object pointing at the machine, allowing useful actions like setup,
68
+ # converge, execute, file and directory. The Machine object will have a
69
+ # "node" property which must be saved to the server (if it is any
70
+ # different from the original node object).
71
+ #
72
+ # ## Parameters
73
+ # action_handler - the action_handler object that is calling this method; this
74
+ # is generally a action_handler, but could be anything that can support the
75
+ # ChefMetal::ActionHandler interface (i.e., in the case of the test
76
+ # kitchen metal driver for acquiring and destroying VMs; see the base
77
+ # class for what needs providing).
78
+ # node - node object (deserialized json) representing this machine. If
79
+ # the node has a provisioner_options hash in it, these will be used
80
+ # instead of options provided by the provisioner. TODO compare and
81
+ # fail if different?
82
+ # node will have node['normal']['provisioner_options'] in it with any options.
83
+ # It is a hash with this format:
84
+ #
85
+ # -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
86
+ # -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
87
+ # :datacenter
88
+ # :resource_pool
89
+ # :cluster
90
+ # :datastore
91
+ # :template_name
92
+ # :template_folder
93
+ # :vm_folder
94
+ # :winrm {...} (not yet implemented)
95
+ # :ssh {...}
96
+ #
97
+ # Example bootstrap_options for vSphere:
98
+ # TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
99
+ # 'bootstrap_options' => {
100
+ # 'template_name' =>'centos6.small',
101
+ # 'template_folder' =>'Templates',
102
+ # 'vm_folder' => 'MyApp'
103
+ # }
104
+ #
105
+ # node['normal']['provisioner_output'] will be populated with information
106
+ # about the created machine. For vSphere, it is a hash with this
107
+ # format:
108
+ #
109
+ # -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
110
+ # -- vm_folder: name of the vSphere folder containing the VM
111
+ #
112
+ def allocate_machine(action_handler, machine_spec, machine_options)
113
+ merge_options! machine_options
114
+
115
+ if machine_spec.location
116
+ Chef::Log.warn(
117
+ "Checking to see if #{machine_spec.location} has been created...")
118
+ vm = vm_for(machine_spec)
119
+ if vm
120
+ Chef::Log.warn 'returning existing machine'
121
+ return vm
122
+ else
123
+ Chef::Log.warn machine_msg(
124
+ machine_spec.name,
125
+ machine_spec.location['server_id'],
126
+ 'no longer exists. Recreating ...'
127
+ )
128
+ end
129
+ end
130
+ bootstrap_options = machine_options[:bootstrap_options]
131
+
132
+ action_handler.report_progress full_description(
133
+ machine_spec, bootstrap_options)
134
+
135
+ vm = find_or_create_vm(bootstrap_options, machine_spec, action_handler)
136
+
137
+ add_machine_spec_location(vm, machine_spec)
138
+
139
+ action_handler.performed_action(machine_msg(
140
+ machine_spec.name,
141
+ vm.config.instanceUuid,
142
+ 'created'
143
+ ))
144
+ vm
145
+ end
146
+
147
+ def merge_options!(machine_options)
148
+ @config = Cheffish::MergedConfig.new(
149
+ { machine_options: machine_options },
150
+ @config
151
+ )
152
+ end
153
+
154
+ def add_machine_spec_location(vm, machine_spec)
155
+ machine_spec.location = {
156
+ 'driver_url' => driver_url,
157
+ 'driver_version' => VERSION,
158
+ 'server_id' => vm.config.instanceUuid,
159
+ 'is_windows' => is_windows?(vm),
160
+ 'allocated_at' => Time.now.utc.to_s,
161
+ 'ipaddress' => vm.guest.ipAddress
162
+ }
163
+ end
164
+
165
+ def find_or_create_vm(bootstrap_options, machine_spec, action_handler)
166
+ vm = vsphere_helper.find_vm(
167
+ bootstrap_options[:vm_folder],
168
+ machine_spec.name
169
+ )
170
+ if vm
171
+ Chef::Log.info machine_msg(
172
+ machine_spec.name,
173
+ vm.config.instanceUuid,
174
+ 'already created'
175
+ )
176
+ else
177
+ vm = clone_vm(
178
+ action_handler,
179
+ bootstrap_options,
180
+ machine_spec.name
181
+ )
182
+ end
183
+ vm
184
+ end
185
+
186
+ def full_description(machine_spec, bootstrap_options)
187
+ description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
188
+ bootstrap_options.to_hash.each_pair do |key,value|
189
+ description << " #{key}: #{value.inspect}"
190
+ end
191
+ description
192
+ end
193
+
194
+ def machine_msg(name, id, action)
195
+ "Machine - #{action} - #{name} (#{id} on #{driver_url})"
196
+ end
197
+
198
+ def ready_machine(action_handler, machine_spec, machine_options)
199
+ merge_options! machine_options
200
+
201
+ vm = start_machine(action_handler, machine_spec, machine_options)
202
+ if vm.nil?
203
+ raise "Machine #{machine_spec.name} does not have a server "\
204
+ 'associated with it, or server does not exist.'
205
+ end
206
+
207
+ bootstrap_options = machine_options[:bootstrap_options]
208
+
209
+ transport_respond?(
210
+ machine_options,
211
+ vm,
212
+ action_handler,
213
+ machine_spec
214
+ )
215
+
216
+ machine = machine_for(machine_spec,machine_options)
217
+
218
+ setup_extra_nics(action_handler, bootstrap_options, vm, machine)
219
+
220
+ if has_static_ip(bootstrap_options) && !is_windows?(vm)
221
+ setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
222
+ end
223
+
224
+ machine
225
+ end
226
+
227
+ def setup_extra_nics(action_handler, bootstrap_options, vm, machine)
228
+ networks=bootstrap_options[:network_name]
229
+ if networks.kind_of?(String)
230
+ networks=[networks]
231
+ end
232
+ return if networks.nil? || networks.count < 2
233
+
234
+ new_nics = vsphere_helper.add_extra_nic(
235
+ action_handler,
236
+ vm_template_for(bootstrap_options),
237
+ bootstrap_options,
238
+ vm
239
+ )
240
+ if is_windows?(vm) && !new_nics.nil?
241
+ new_nics.each do |nic|
242
+ nic_label = nic.device.deviceInfo.label
243
+ machine.execute_always(
244
+ "Disable-Netadapter -Name '#{nic_label}' -Confirm:$false")
245
+ end
246
+ end
247
+ end
248
+
249
+ def transport_respond?(
250
+ machine_options,
251
+ vm,
252
+ action_handler,
253
+ machine_spec
254
+ )
255
+ bootstrap_options = machine_options[:bootstrap_options]
256
+
257
+ # this waits for vmware tools to start and the vm to presebnt an ip
258
+ # This may just be the ip of a newly cloned machine
259
+ # Customization below may change this to a valid ip
260
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
261
+
262
+ if !machine_spec.location['ipaddress'] || !has_ip?(machine_spec.location['ipaddress'], vm)
263
+ # find the ip we actually want
264
+ # this will be the static ip to assign
265
+ # or the ip reported back by the vm if using dhcp
266
+ # it *may* be nil if just cloned
267
+ vm_ip = ip_to_bootstrap(bootstrap_options, vm)
268
+ transport = nil
269
+ unless vm_ip.nil?
270
+ transport = transport_for(machine_spec, bootstrap_options[:ssh], vm_ip)
271
+ end
272
+
273
+ unless !transport.nil? && transport.available? && has_ip?(vm_ip, vm)
274
+ attempt_ip(machine_options, action_handler, vm, machine_spec)
275
+ end
276
+ machine_spec.location['ipaddress'] = vm.guest.ipAddress
277
+ action_handler.report_progress(
278
+ "IP address obtained: #{machine_spec.location['ipaddress']}")
279
+ end
280
+
281
+ wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
282
+
283
+ begin
284
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
285
+ rescue Timeout::Error
286
+ # Only ever reboot once, and only if it's been less than 10 minutes
287
+ # since we stopped waiting
288
+ if machine_spec.location['started_at'] ||
289
+ remaining_wait_time(machine_spec, machine_options) < -(10*60)
290
+ raise
291
+ else
292
+ Chef::Log.warn(machine_msg(
293
+ machine_spec.name,
294
+ vm.config.instanceUuid,
295
+ 'started but SSH did not come up. Rebooting...'
296
+ ))
297
+ restart_server(action_handler, machine_spec, machine_options)
298
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
299
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
300
+ end
301
+ end
302
+ end
303
+
304
+ def attempt_ip(machine_options, action_handler, vm, machine_spec)
305
+ vm_ip = ip_to_bootstrap(machine_options[:bootstrap_options], vm)
306
+
307
+ wait_for_ip(vm, machine_options, machine_spec, action_handler)
308
+
309
+ unless has_ip?(vm_ip, vm)
310
+ action_handler.report_progress "rebooting..."
311
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
312
+ msg = 'tools have stopped. current power state is '
313
+ msg << vm.runtime.powerState
314
+ msg << ' and tools state is '
315
+ msg << vm.guest.toolsRunningStatus
316
+ msg << '. powering up server...'
317
+ action_handler.report_progress(msg.join)
318
+ vsphere_helper.start_vm(vm)
319
+ else
320
+ restart_server(action_handler, machine_spec, machine_options)
321
+ end
322
+ wait_for_ip(vm, machine_options, machine_spec, action_handler)
323
+ end
324
+ end
325
+
326
+ def wait_for_domain(bootstrap_options, vm, machine_spec, action_handler)
327
+ return unless bootstrap_options[:customization_spec]
328
+ return unless bootstrap_options[:customization_spec][:domain]
329
+
330
+ domain = bootstrap_options[:customization_spec][:domain]
331
+ if is_windows?(vm) && domain != 'local'
332
+ start = Time.now.utc
333
+ trimmed_name = machine_spec.name.byteslice(0,15)
334
+ expected_name="#{trimmed_name}.#{domain}"
335
+ action_handler.report_progress(
336
+ "waiting to domain join and be named #{expected_name}")
337
+ until (Time.now.utc - start) > 30 ||
338
+ (vm.guest.hostName == expected_name) do
339
+ print '.'
340
+ sleep 5
341
+ end
342
+ end
343
+ end
344
+
345
+ def wait_for_ip(vm, machine_options, machine_spec, action_handler)
346
+ bootstrap_options = machine_options[:bootstrap_options]
347
+ vm_ip = ip_to_bootstrap(bootstrap_options, vm)
348
+ ready_timeout = machine_options[:ready_timeout] || 300
349
+ msg = "waiting up to #{ready_timeout} seconds for customization"
350
+ msg << " and find #{vm_ip}" unless vm_ip == vm.guest.ipAddress
351
+ action_handler.report_progress msg
352
+
353
+ start = Time.now.utc
354
+ connectable = false
355
+ until (Time.now.utc - start) > ready_timeout || connectable do
356
+ action_handler.report_progress(
357
+ "IP addresses found: #{all_ips_for(vm)}")
358
+ vm_ip ||= ip_to_bootstrap(bootstrap_options, vm)
359
+ if has_ip?(vm_ip, vm)
360
+ connectable = transport_for(
361
+ machine_spec,
362
+ machine_options[:bootstrap_options][:ssh],
363
+ ip
364
+ ).available?
365
+ end
366
+ sleep 5
367
+ end
368
+ end
369
+
370
+ def all_ips_for(vm)
371
+ vm.guest.net.map { |net| net.ipAddress}.flatten
372
+ end
373
+
374
+ def has_ip?(ip, vm)
375
+ all_ips_for(vm).include?(ip)
376
+ end
377
+
378
+ # Connect to machine without acquiring it
379
+ def connect_to_machine(machine_spec, machine_options)
380
+ merge_options! machine_options
381
+ machine_for(machine_spec, machine_options)
382
+ end
383
+
384
+ def destroy_machine(action_handler, machine_spec, machine_options)
385
+ merge_options! machine_options
386
+ vm = vm_for(machine_spec)
387
+ if vm
388
+ action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
389
+ begin
390
+ vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
391
+ vm.Destroy_Task.wait_for_completion
392
+ rescue RbVmomi::Fault => fault
393
+ raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
394
+ ensure
395
+ machine_spec.location = nil
396
+ end
397
+ end
398
+ end
399
+ strategy = convergence_strategy_for(machine_spec, machine_options)
400
+ strategy.cleanup_convergence(action_handler, machine_spec)
401
+ end
402
+
403
+ def stop_machine(action_handler, machine_spec, machine_options)
404
+ merge_options! machine_options
405
+ vm = vm_for(machine_spec)
406
+ if vm
407
+ action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
408
+ vsphere_helper.stop_vm(vm, machine_options[:stop_timeout])
409
+ end
410
+ end
411
+ end
412
+
413
+ def start_machine(action_handler, machine_spec, machine_options)
414
+ merge_options! machine_options
415
+ vm = vm_for(machine_spec)
416
+ if vm
417
+ action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
418
+ vsphere_helper.start_vm(vm, machine_options[:bootstrap_options][:ssh][:port])
419
+ end
420
+ end
421
+ vm
422
+ end
423
+
424
+ def restart_server(action_handler, machine_spec, machine_options)
425
+ action_handler.perform_action "restart machine #{machine_spec.name} (#{driver_url})" do
426
+ stop_machine(action_handler, machine_spec, machine_options)
427
+ start_machine(action_handler, machine_spec, machine_options)
428
+ machine_spec.location['started_at'] = Time.now.utc.to_s
429
+ end
430
+ end
431
+
432
+ protected
433
+
434
+ def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
435
+ host_lookup = machine.execute_always('host google.com')
436
+ if host_lookup.exitstatus != 0
437
+ if host_lookup.stdout.include?("setlocale: LC_ALL")
438
+ machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
439
+ end
440
+ distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
441
+ Chef::Log.info "Found distro:#{distro}"
442
+ if distro == 'Ubuntu'
443
+ distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
444
+ Chef::Log.info "Found distro version:#{distro_version}"
445
+ if distro_version>= 12.04
446
+ Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
447
+ interfaces_file = "/etc/network/interfaces"
448
+ nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
449
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
450
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
451
+ machine.execute_always('/etc/init.d/networking restart')
452
+ machine.execute_always('apt-get -qq update')
453
+ end
454
+ end
455
+ end
456
+ end
457
+
458
+ def has_static_ip(bootstrap_options)
459
+ if bootstrap_options.has_key?(:customization_spec)
460
+ bootstrap_options = bootstrap_options[:customization_spec]
461
+ if bootstrap_options.has_key?(:ipsettings)
462
+ bootstrap_options = bootstrap_options[:ipsettings]
463
+ if bootstrap_options.has_key?(:ip)
464
+ return true
465
+ end
466
+ end
467
+ end
468
+ false
469
+ end
470
+
471
+ def remaining_wait_time(machine_spec, machine_options)
472
+ if machine_spec.location['started_at']
473
+ (machine_options[:start_timeout] || 600) -
474
+ (Time.now.utc - Time.parse(machine_spec.location['started_at']))
475
+ else
476
+ (machine_options[:create_timeout] || 600) -
477
+ (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
478
+ end
479
+ end
480
+
481
+ def wait_until_ready(action_handler, machine_spec, machine_options, vm)
482
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
483
+ if action_handler.should_perform_actions
484
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
485
+ until remaining_wait_time(machine_spec, machine_options) < 0 ||
486
+ (vm.guest.toolsRunningStatus == "guestToolsRunning" && !vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
487
+ print "."
488
+ sleep 5
489
+ end
490
+ action_handler.report_progress "#{machine_spec.name} is now ready"
491
+ end
492
+ end
493
+ end
494
+
495
+ def vm_for(machine_spec)
496
+ if machine_spec.location
497
+ vsphere_helper.find_vm_by_id(machine_spec.location['server_id'])
498
+ else
499
+ nil
500
+ end
501
+ end
502
+
503
+ def clone_vm(action_handler, bootstrap_options, machine_name)
504
+ vm_template = vm_template_for(bootstrap_options)
505
+
506
+ spec_builder = CloneSpecBuilder.new(vsphere_helper, action_handler)
507
+ clone_spec = spec_builder.build(vm_template, machine_name, bootstrap_options)
508
+ Chef::Log.debug("Clone spec: #{clone_spec.pretty_inspect}")
509
+
510
+ vm_folder = vsphere_helper.find_folder(bootstrap_options[:vm_folder])
511
+ vm_template.CloneVM_Task(
512
+ name: machine_name,
513
+ folder: vm_folder,
514
+ spec: clone_spec
515
+ ).wait_for_completion
516
+
517
+ vm = vsphere_helper.find_vm(vm_folder, machine_name)
518
+
519
+ additional_disk_size_gb = bootstrap_options[:additional_disk_size_gb]
520
+ if !additional_disk_size_gb.is_a?(Array)
521
+ additional_disk_size_gb = [additional_disk_size_gb]
522
+ end
523
+
524
+ additional_disk_size_gb.each do |size|
525
+ size = size.to_i
526
+ next if size == 0
527
+ if bootstrap_options[:datastore].to_s.empty?
528
+ raise ':datastore must be specified when adding a disk to a cloned vm'
529
+ end
530
+ task = vm.ReconfigVM_Task(
531
+ spec: RbVmomi::VIM.VirtualMachineConfigSpec(
532
+ deviceChange: [
533
+ vsphere_helper.virtual_disk_for(
534
+ vm,
535
+ bootstrap_options[:datastore],
536
+ size
537
+ )
538
+ ]
539
+ )
540
+ )
541
+ task.wait_for_completion
542
+ end
543
+
544
+ vm
545
+ end
546
+
547
+ def vsphere_helper
548
+ @vsphere_helper ||= VsphereHelper.new(
549
+ connect_options,
550
+ config[:machine_options][:bootstrap_options][:datacenter]
551
+ )
552
+ end
553
+
554
+ def vm_template_for(bootstrap_options)
555
+ template_folder = bootstrap_options[:template_folder]
556
+ template_name = bootstrap_options[:template_name]
557
+ vsphere_helper.find_vm(template_folder, template_name) ||
558
+ raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
559
+ end
560
+
561
+ def machine_for(machine_spec, machine_options)
562
+ if machine_spec.location.nil?
563
+ raise "Server for node #{machine_spec.name} has not been created!"
564
+ end
565
+
566
+ transport = transport_for(
567
+ machine_spec,
568
+ machine_options[:bootstrap_options][:ssh]
569
+ )
570
+ strategy = convergence_strategy_for(machine_spec, machine_options)
571
+
572
+ if machine_spec.location['is_windows']
573
+ Chef::Provisioning::Machine::WindowsMachine.new(
574
+ machine_spec, transport, strategy)
575
+ else
576
+ Chef::Provisioning::Machine::UnixMachine.new(
577
+ machine_spec, transport, strategy)
578
+ end
579
+ end
580
+
581
+ def is_windows?(vm)
582
+ return false if vm.nil?
583
+ vm.config.guestId.start_with?('win')
584
+ end
585
+
586
+ def convergence_strategy_for(machine_spec, machine_options)
587
+ require 'chef/provisioning/convergence_strategy/install_msi'
588
+ require 'chef/provisioning/convergence_strategy/install_cached'
589
+ require 'chef/provisioning/convergence_strategy/no_converge'
590
+
591
+ mopts = machine_options[:convergence_options].to_hash.dup
592
+ if mopts[:chef_server]
593
+ mopts[:chef_server] = mopts[:chef_server].to_hash.dup
594
+ mopts[:chef_server][:options] = mopts[:chef_server][:options].to_hash.dup if mopts[:chef_server][:options]
595
+ end
596
+
597
+ if !machine_spec.location
598
+ return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(
599
+ mopts, config)
600
+ end
601
+
602
+ if machine_spec.location['is_windows']
603
+ Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(
604
+ mopts, config)
605
+ else
606
+ Chef::Provisioning::ConvergenceStrategy::InstallCached.new(
607
+ mopts, config)
608
+ end
609
+ end
610
+
611
+ def wait_for_transport(action_handler, machine_spec, machine_options, vm)
612
+ transport = transport_for(
613
+ machine_spec,
614
+ machine_options[:bootstrap_options][:ssh]
615
+ )
616
+ if !transport.available?
617
+ if action_handler.should_perform_actions
618
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
619
+
620
+ until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
621
+ print "."
622
+ sleep 5
623
+ end
624
+
625
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
626
+ end
627
+ end
628
+ end
629
+
630
+ def transport_for(
631
+ machine_spec,
632
+ remoting_options,
633
+ ip = machine_spec.location['ipaddress']
634
+ )
635
+ if machine_spec.location['is_windows']
636
+ create_winrm_transport(ip, remoting_options)
637
+ else
638
+ create_ssh_transport(ip, remoting_options)
639
+ end
640
+ end
641
+
642
+ def create_winrm_transport(host, options)
643
+ require 'chef/provisioning/transport/winrm'
644
+ opt = options[:user].include?("\\") ? :disable_sspi : :basic_auth_only
645
+ winrm_options = {
646
+ user: "#{options[:user]}",
647
+ pass: options[:password],
648
+ opt => true
649
+ }
650
+
651
+ Chef::Provisioning::Transport::WinRM.new(
652
+ "http://#{host}:5985/wsman",
653
+ :plaintext,
654
+ winrm_options,
655
+ config
656
+ )
657
+ end
658
+
659
+ def create_ssh_transport(host, options)
660
+ require 'chef/provisioning/transport/ssh'
661
+ ssh_user = options[:user]
662
+ Chef::Provisioning::Transport::SSH.new(
663
+ host,
664
+ ssh_user,
665
+ options,
666
+ @config[:machine_options][:sudo] ? {:prefix => 'sudo '} : {},
667
+ config
668
+ )
669
+ end
670
+
671
+ def ip_to_bootstrap(bootstrap_options, vm)
672
+ if has_static_ip(bootstrap_options)
673
+ bootstrap_options[:customization_spec][:ipsettings][:ip]
674
+ else
675
+ vm.guest.ipAddress
676
+ end
677
+ end
678
+ end
679
+ end