clc-chef-metal-vsphere 0.3.0

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.
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: 634a2fe8b1d2735ef8ec84b818d05126732212b5
4
+ data.tar.gz: a0f9a967f9d358c8d8ad0fddee9de3f90dafb0ef
5
+ SHA512:
6
+ metadata.gz: 48acfe4b1a2317fdfa6c4496f292b085ba0c6a35d459ecf5d0934dcda28184497f65f3e2ec3979fbc95785af59af2bab2a1668db1b7a4184ccdac5f593e1d389
7
+ data.tar.gz: ceb714b3b94168d2eb98cb14a0b7c5c1502b30de47a5005cc5eb2b2ed377a0596f923c5e649bedb51c4516d74a1cc8247635e5270db3daa4b01747c39bd26c31
@@ -0,0 +1,90 @@
1
+ chef-metal-vsphere
2
+ ==================
3
+
4
+ This is a [chef-metal](https://github.com/opscode/chef-metal) provisioner for [VMware vSphere](http://www.vmware.com/products/vsphere).
5
+
6
+ Currently, chef-metal-vsphere supports provisioning Unix/ssh guest VMs.
7
+
8
+ Try It Out
9
+ ----------
10
+
11
+ ### vSphere VM Template
12
+
13
+ Create or obtain a unix/linux VM template. The VM template must:
14
+
15
+ - be capable of installing Chef 11.8 or newer
16
+ - run vmware-tools on system boot (provides visiblity to ip address of the running VM)
17
+ - provide access via ssh
18
+ - provide a user account with NOPASSWD sudo
19
+
20
+ ### Example recipe
21
+ require 'chef_metal_vsphere'
22
+
23
+ with_vsphere_provisioner vsphere_host: 'vcenter-host-name',
24
+ vsphere_insecure: true,
25
+ vsphere_user: 'you_user_name',
26
+ vsphere_password: 'your_mothers_maiden_name' # consider using a chef-vault
27
+
28
+ with_provisioner_options('bootstrap_options' => {
29
+ datacenter: 'datacenter_name',
30
+ cluster: 'cluster_name',
31
+ resource_pool: 'resource_pool_name', # often the same as the cluster_name
32
+ datastore: 'datastore_name',
33
+ template_name: 'name_of_template_vm', # may be a VM or a VM Template
34
+ template_folder: 'folder_containing_template_vm',
35
+ vm_folder: 'folder_to_clone_vms_into',
36
+ customization_spec: 'standard-config', # optional
37
+
38
+ ssh: { # net-ssh start() options
39
+ user: 'username_on_vm', # must have nopasswd sudo
40
+ password: 'name_of_your_first_pet', # consider using a chef-vault
41
+ port: 22,
42
+ auth_methods: ['password'],
43
+ user_known_hosts_file: '/dev/null', # don't do this in production
44
+ paranoid: false, # don't do this in production, either
45
+ keys: [ ], # consider using a chef-vault
46
+ keys_only: false
47
+ }
48
+ })
49
+
50
+ 1.upto 2 do |n|
51
+ machine "metal_#{n}" do
52
+ action [:create]
53
+
54
+ ## optionally add options per-machine customizations
55
+ add_provisioner_options('bootstrap_options' => {
56
+ num_cpus: n,
57
+ memory_mb: 1024 * n,
58
+ annotation: "metal_#{n} created by chef-metal-vsphere"
59
+ })
60
+ end
61
+
62
+ machine_file "/tmp/metal_#{n}.txt" do
63
+ machine "metal_#{n}"
64
+ content "Hello machine #{n}!"
65
+ end
66
+
67
+ machine "metal_#{n}" do
68
+ action [:stop]
69
+ end
70
+
71
+ machine "metal_#{n}" do
72
+ # note: no need to :stop before :delete
73
+ action [:delete]
74
+ end
75
+
76
+ end
77
+
78
+ This will clone your VM template to create two VMware Virtual Machines, "metal_1" and "metal_2", in the vSphere Folder specified by vm_folder, bootstrapped to an empty runlist. It will then stop (guest shutdown) and delete the vms.
79
+
80
+ Roadmap
81
+ -------
82
+
83
+ Check out [TODO.md](TODO.md)
84
+
85
+ Bugs and Contact
86
+ ----------------
87
+
88
+ Please submit bugs at [chef-metal-vpshere](https://github.com/RallySoftware-cookbooks/chef-metal-vsphere), contact Brian Dupras on Twitter at @briandupras, email at rallysoftware-cookbooks@rallydev.com.
89
+
90
+ *Warning* if you get an rbvmomi error regarding VMODL::AnyType, add `gem 'nokogiri', '1.5.5'` to your dependencies.
@@ -0,0 +1,30 @@
1
+ require 'bundler'
2
+ require 'bundler/gem_tasks'
3
+
4
+ def gem_server
5
+ @gem_server ||= ENV['GEM_SERVER'] || 'rubygems.org'
6
+ end
7
+
8
+ module Bundler
9
+ class GemHelper
10
+ unless gem_server == 'rubygems.org'
11
+ unless method_defined?(:rubygem_push)
12
+ raise NoMethodError, "Monkey patching Bundler::GemHelper#rubygem_push failed: did the Bundler API change???"
13
+ end
14
+
15
+ def rubygem_push(path)
16
+ print "Username: "
17
+ username = STDIN.gets.chomp
18
+ print "Password: "
19
+ password = STDIN.gets.chomp
20
+
21
+ gem_server_url = "https://#{username}:#{password}@#{gem_server}/"
22
+ sh %{gem push #{path} --host #{gem_server_url}}
23
+
24
+ Bundler.ui.confirm "Pushed #{name} #{version} to #{gem_server}"
25
+ end
26
+
27
+ puts "Monkey patched Bundler::GemHelper#rubygem_push to push to #{gem_server}."
28
+ end
29
+ end
30
+ end
@@ -0,0 +1,3 @@
1
+ require 'chef_metal_vsphere/vsphere_driver'
2
+
3
+ ChefMetal.register_driver_class('vsphere', ChefMetalVsphere::VsphereDriver)
@@ -0,0 +1,13 @@
1
+ require 'chef_metal'
2
+ require 'chef_metal_vsphere/vsphere_driver'
3
+
4
+ class Chef
5
+ module DSL
6
+ module Recipe
7
+ def with_vsphere_driver(driver_options, &block)
8
+ url, config = ChefMetalVsphere::VsphereDriver.canonicalize_url(nil, {:driver_options => driver_options})
9
+ with_driver url, driver_options, &block
10
+ end
11
+ end
12
+ end
13
+ end
@@ -0,0 +1,3 @@
1
+ module ChefMetalVsphere
2
+ VERSION = '0.3.0'
3
+ end
@@ -0,0 +1,464 @@
1
+ require 'chef'
2
+ require 'chef_metal/driver'
3
+ require 'cheffish/merged_config'
4
+ require 'chef_metal/machine/windows_machine'
5
+ require 'chef_metal/machine/unix_machine'
6
+ require 'chef_metal_vsphere/version'
7
+ require 'chef_metal_vsphere/vsphere_helpers'
8
+ require 'chef_metal_vsphere/vsphere_url'
9
+
10
+ module ChefMetalVsphere
11
+ # Provisions machines in vSphere.
12
+ class VsphereDriver < ChefMetal::Driver
13
+ include Chef::Mixin::ShellOut
14
+ include ChefMetalVsphere::Helpers
15
+
16
+ def self.from_url(driver_url, config)
17
+ VsphereDriver.new(driver_url, config)
18
+ end
19
+
20
+ def self.canonicalize_url(driver_url, config)
21
+ config = symbolize_keys(config)
22
+ new_defaults = {
23
+ :driver_options => { :connect_options => { :port => 443,
24
+ :use_ssl => true,
25
+ :insecure => false,
26
+ :path => '/sdk'
27
+ } },
28
+ :machine_options => { :start_timeout => 600,
29
+ :create_timeout => 600,
30
+ :bootstrap_options => { :ssh => { :port => 22,
31
+ :user => 'root' },
32
+ :key_name => 'metal_default',
33
+ :tags => {} } }
34
+ }
35
+
36
+ new_connect_options = {}
37
+ new_connect_options[:provider] = 'vsphere'
38
+ if !driver_url.nil?
39
+ uri = URI(driver_url)
40
+ new_connect_options[:host] = uri.host
41
+ new_connect_options[:port] = uri.port
42
+ if uri.path && uri.path.length > 0
43
+ new_connect_options[:path] = uri.path
44
+ end
45
+ new_connect_options[:use_ssl] = uri.use_ssl
46
+ new_connect_options[:insecure] = uri.insecure
47
+ end
48
+ new_connect_options = new_connect_options.merge(config[:driver_options])
49
+
50
+ new_config = { :driver_options => { :connect_options => new_connect_options }}
51
+ config = Cheffish::MergedConfig.new(new_config, config, new_defaults)
52
+
53
+ required_options = [:host, :user, :password]
54
+ missing_options = []
55
+ required_options.each do |opt|
56
+ missing_options << opt unless config[:driver_options][:connect_options].has_key?(opt)
57
+ end
58
+ unless missing_options.empty?
59
+ raise "missing required options: #{missing_options.join(', ')}"
60
+ end
61
+
62
+ url = URI::VsphereUrl.from_config(config[:driver_options][:connect_options]).to_s
63
+ [ url, config ]
64
+ end
65
+
66
+ def self.symbolize_keys(h)
67
+ Hash === h ?
68
+ Hash[
69
+ h.map do |k, v|
70
+ [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
71
+ end
72
+ ] : h
73
+ end
74
+
75
+ # Create a new Vsphere provisioner.
76
+ #
77
+ # ## Parameters
78
+ # connect_options - hash of options to be passed to RbVmomi::VIM.connect
79
+ # :host - required - hostname of the vSphere API server
80
+ # :port - optional - port on the vSphere API server (default: 443)
81
+ # :path - optional - path on the vSphere API server (default: /sdk)
82
+ # :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
83
+ # :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
84
+ # :user - required - user name to use in connection to vSphere API server
85
+ # :password - required - password to use in connection to vSphere API server
86
+ # :proxy_host - optional - http proxy host to use in connection to vSphere API server (default: none)
87
+ # :proxy_port - optional - http proxy port to use in connection to vSphere API server (default: none)
88
+ def initialize(driver_url, config)
89
+ super(driver_url, config)
90
+ @connect_options = config[:driver_options][:connect_options].to_hash
91
+ end
92
+
93
+ attr_reader :connect_options
94
+
95
+ # Acquire a machine, generally by provisioning it. Returns a Machine
96
+ # object pointing at the machine, allowing useful actions like setup,
97
+ # converge, execute, file and directory. The Machine object will have a
98
+ # "node" property which must be saved to the server (if it is any
99
+ # different from the original node object).
100
+ #
101
+ # ## Parameters
102
+ # action_handler - the action_handler object that is calling this method; this
103
+ # is generally a action_handler, but could be anything that can support the
104
+ # ChefMetal::ActionHandler interface (i.e., in the case of the test
105
+ # kitchen metal driver for acquiring and destroying VMs; see the base
106
+ # class for what needs providing).
107
+ # node - node object (deserialized json) representing this machine. If
108
+ # the node has a provisioner_options hash in it, these will be used
109
+ # instead of options provided by the provisioner. TODO compare and
110
+ # fail if different?
111
+ # node will have node['normal']['provisioner_options'] in it with any options.
112
+ # It is a hash with this format:
113
+ #
114
+ # -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
115
+ # -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
116
+ # :datacenter
117
+ # :resource_pool
118
+ # :cluster
119
+ # :datastore
120
+ # :template_name
121
+ # :template_folder
122
+ # :vm_folder
123
+ # :winrm {...} (not yet implemented)
124
+ # :ssh {...}
125
+ #
126
+ # Example bootstrap_options for vSphere:
127
+ # TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
128
+ # 'bootstrap_options' => {
129
+ # 'template_name' =>'centos6.small',
130
+ # 'template_folder' =>'Templates',
131
+ # 'vm_folder' => 'MyApp'
132
+ # }
133
+ #
134
+ # node['normal']['provisioner_output'] will be populated with information
135
+ # about the created machine. For vSphere, it is a hash with this
136
+ # format:
137
+ #
138
+ # -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
139
+ # -- vm_folder: name of the vSphere folder containing the VM
140
+ #
141
+ def allocate_machine(action_handler, machine_spec, machine_options)
142
+ if machine_spec.location
143
+ Chef::Log.warn "Checking to see if #{machine_spec.location} has been created..."
144
+ vm = vm_for(machine_spec)
145
+ if vm
146
+ Chef::Log.warn "returning existing machine"
147
+ return vm
148
+ else
149
+ Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['server_id']} on #{driver_url}) no longer exists. Recreating ..."
150
+ end
151
+ end
152
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
153
+ vm = nil
154
+
155
+ if bootstrap_options[:ssh]
156
+ wait_on_port = bootstrap_options[:ssh][:port]
157
+ raise "Must specify bootstrap_options[:ssh][:port]" if wait_on_port.nil?
158
+ else
159
+ raise 'bootstrapping is currently supported for ssh only'
160
+ # wait_on_port = bootstrap_options['winrm']['port']
161
+ end
162
+
163
+ description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
164
+ bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
165
+ action_handler.report_progress description
166
+
167
+ # TODO compare new options to existing and fail if we cannot change it
168
+ # over (perhaps introduce a boolean that will force a delete and recreate
169
+ # in such a case)
170
+
171
+ vm = clone_vm(bootstrap_options)
172
+
173
+ machine_spec.location = {
174
+ 'driver_url' => driver_url,
175
+ 'driver_version' => VERSION,
176
+ 'server_id' => vm.config.instanceUuid,
177
+ 'is_windows' => is_windows?(vm),
178
+ 'allocated_at' => Time.now.utc.to_s
179
+ }
180
+ machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
181
+ %w(ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
182
+ machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
183
+ end
184
+
185
+ action_handler.performed_action "machine #{machine_spec.name} created as #{machine_spec.location['server_id']} on #{driver_url}"
186
+ vm
187
+ end
188
+
189
+ def ready_machine(action_handler, machine_spec, machine_options)
190
+ start_machine(action_handler, machine_spec, machine_options)
191
+ vm = vm_for(machine_spec)
192
+ if vm.nil?
193
+ raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
194
+ end
195
+
196
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
197
+
198
+
199
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
200
+ is_static = false
201
+ if has_static_ip(bootstrap_options)
202
+ is_static = true
203
+ transport = transport_for(machine_spec, machine_options, vm)
204
+ if !transport.available?
205
+ Chef::Log.info "waiting for customizations to complete"
206
+ now = Time.now.utc
207
+ until (Time.now.utc - now) > 45 || (!vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
208
+ print "-"
209
+ sleep 5
210
+ end
211
+ if vm.guest.ipAddress.nil? || vm.guest.ipAddress.length == 0
212
+ Chef::Log.info "rebooting..."
213
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
214
+ Chef::Log.info "tools have stopped. current power state is #{vm.runtime.powerState} and tools state is #{vm.toolsRunningStatus}. powering up server..."
215
+ start_vm(vm)
216
+ else
217
+ restart_server(action_handler, machine_spec, vm)
218
+ end
219
+ now = Time.now.utc
220
+ until (Time.now.utc - now) > 60 || (!vm.guest.ipAddress.nil? && vm.guest.ipAddress.length > 0) do
221
+ print "-"
222
+ sleep 5
223
+ end
224
+ end
225
+ end
226
+ end
227
+
228
+ begin
229
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
230
+ rescue Timeout::Error
231
+ # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
232
+ if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
233
+ raise
234
+ else
235
+ Chef::Log.warn "Machine #{machine_spec.name} (#{server.config.instanceUuid} on #{driver_url}) was started but SSH did not come up. Rebooting machine in an attempt to unstick it ..."
236
+ restart_server(action_handler, machine_spec, vm)
237
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
238
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
239
+ end
240
+ end
241
+
242
+ machine = machine_for(machine_spec, machine_options, vm)
243
+
244
+ if is_static
245
+ if machine.execute_always('host google.com').exitstatus != 0
246
+ distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID:\t//g'").stdout.strip
247
+ if distro == 'Ubuntu'
248
+ distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
249
+ if distro_version>= 12.04
250
+ Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
251
+ interfaces_file = "/etc/network/interfaces"
252
+ nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
253
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{machine_spec.name}' >> #{interfaces_file} ; fi")
254
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
255
+ machine.execute_always('/etc/init.d/networking restart')
256
+ machine.execute_always('echo "ACTION=="add", SUBSYSTEM=="cpu", ATTR{online}="1"" > /etc/udev/rules.d/99-vmware-cpuhotplug-udev.rules')
257
+ machine.execute_always('apt-get -qq update')
258
+ end
259
+ end
260
+ end
261
+ end
262
+
263
+ machine
264
+ end
265
+
266
+ # Connect to machine without acquiring it
267
+ def connect_to_machine(machine_spec, machine_options)
268
+ machine_for(machine_spec, machine_options)
269
+ end
270
+
271
+ def destroy_machine(action_handler, machine_spec, machine_options)
272
+ vm = vm_for(machine_spec)
273
+ if vm
274
+ action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
275
+ vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == 'poweredOff'
276
+ vm.Destroy_Task.wait_for_completion
277
+ machine_spec.location = nil
278
+ end
279
+ end
280
+ strategy = convergence_strategy_for(machine_spec, machine_options)
281
+ begin
282
+ strategy.cleanup_convergence(action_handler, machine_spec)
283
+ rescue URI::InvalidURIError
284
+ raise unless Chef::Config.local_mode
285
+ end
286
+ end
287
+
288
+ def stop_machine(action_handler, machine_spec, machine_options)
289
+ vm = vm_for(machine_spec)
290
+ if vm
291
+ action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
292
+ stop_vm(vm)
293
+ end
294
+ end
295
+ end
296
+
297
+ def start_machine(action_handler, machine_spec, machine_options)
298
+ vm = vm_for(machine_spec)
299
+ if vm
300
+ action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
301
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
302
+ start_vm(vm, bootstrap_options[:ssh][:port])
303
+ end
304
+ end
305
+ end
306
+
307
+ def restart_server(action_handler, machine_spec, vm)
308
+ action_handler.perform_action "restart machine #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url})" do
309
+ stop_machine(action_handler, machine_spec, vm)
310
+ start_vm(vm)
311
+ machine_spec.location['started_at'] = Time.now.utc.to_s
312
+ end
313
+ end
314
+
315
+ protected
316
+
317
+ def has_static_ip(bootstrap_options)
318
+ if bootstrap_options.has_key?(:customization_spec)
319
+ bootstrap_options = bootstrap_options[:customization_spec]
320
+ if bootstrap_options.has_key?(:ipsettings)
321
+ bootstrap_options = bootstrap_options[:ipsettings]
322
+ if bootstrap_options.has_key?(:ip)
323
+ return true
324
+ end
325
+ end
326
+ end
327
+ false
328
+ end
329
+
330
+ def remaining_wait_time(machine_spec, machine_options)
331
+ if machine_spec.location['started_at']
332
+ machine_options[:start_timeout] - (Time.now.utc - Time.parse(machine_spec.location['started_at']))
333
+ else
334
+ machine_options[:create_timeout] - (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
335
+ end
336
+ end
337
+
338
+ def wait_until_ready(action_handler, machine_spec, machine_options, vm)
339
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
340
+ perform_action = true
341
+ if action_handler.should_perform_actions
342
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
343
+ until remaining_wait_time(machine_spec, machine_options) < 0 || (vm.guest.toolsRunningStatus == "guestToolsRunning" && (vm.guest.ipAddress.nil? || vm.guest.ipAddress.length > 0)) do
344
+ print "."
345
+ sleep 5
346
+ end
347
+ action_handler.report_progress "#{machine_spec.name} is now ready"
348
+ end
349
+ end
350
+ end
351
+
352
+ def vm_for(machine_spec)
353
+ if machine_spec.location
354
+ find_vm_by_id(machine_spec.location['server_id'])
355
+ else
356
+ nil
357
+ end
358
+ end
359
+
360
+ def bootstrap_options_for(machine_spec, machine_options)
361
+ bootstrap_options = machine_options[:bootstrap_options] || {}
362
+ bootstrap_options = bootstrap_options.to_hash
363
+ tags = {
364
+ 'Name' => machine_spec.name,
365
+ 'BootstrapId' => machine_spec.id,
366
+ 'BootstrapHost' => Socket.gethostname,
367
+ 'BootstrapUser' => Etc.getlogin
368
+ }
369
+ # User-defined tags override the ones we set
370
+ tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
371
+ bootstrap_options.merge!({ :tags => tags })
372
+ bootstrap_options[:name] ||= machine_spec.name
373
+ bootstrap_options
374
+ end
375
+
376
+ def clone_vm(bootstrap_options)
377
+ vm_name = bootstrap_options[:name]
378
+ datacenter = bootstrap_options[:datacenter]
379
+ template_folder = bootstrap_options[:template_folder]
380
+ template_name = bootstrap_options[:template_name]
381
+
382
+ vm = find_vm(datacenter, bootstrap_options[:vm_folder], vm_name)
383
+ return vm if vm
384
+
385
+ vm_template = find_vm(datacenter, template_folder, template_name) or raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
386
+
387
+ do_vm_clone(datacenter, vm_template, vm_name, bootstrap_options)
388
+ end
389
+
390
+ def machine_for(machine_spec, machine_options, vm = nil)
391
+ vm ||= vm_for(machine_spec)
392
+ if !vm
393
+ raise "Server for node #{machine_spec.name} has not been created!"
394
+ end
395
+
396
+ if machine_spec.location['is_windows']
397
+ ChefMetal::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
398
+ else
399
+ ChefMetal::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
400
+ end
401
+ end
402
+
403
+ def is_windows?(vm)
404
+ return false if vm.nil?
405
+ vm.guest.guestFamily == 'windowsGuest'
406
+ end
407
+
408
+ def convergence_strategy_for(machine_spec, machine_options)
409
+ require 'chef_metal/convergence_strategy/install_msi'
410
+ require 'chef_metal/convergence_strategy/install_cached'
411
+ require 'chef_metal/convergence_strategy/no_converge'
412
+ # Defaults
413
+ if !machine_spec.location
414
+ return ChefMetal::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
415
+ end
416
+
417
+ if machine_spec.location['is_windows']
418
+ ChefMetal::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
419
+ else
420
+ ChefMetal::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
421
+ end
422
+ end
423
+
424
+ def wait_for_transport(action_handler, machine_spec, machine_options, vm)
425
+ transport = transport_for(machine_spec, machine_options, vm)
426
+ if !transport.available?
427
+ if action_handler.should_perform_actions
428
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
429
+
430
+ _self = self
431
+
432
+ until remaining_wait_time(machine_spec, machine_options) || transport.available? do
433
+ print "."
434
+ sleep 5
435
+ end
436
+
437
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
438
+ end
439
+ end
440
+ end
441
+
442
+ def transport_for(machine_spec, machine_options, vm)
443
+ if is_windows?(vm)
444
+ create_winrm_transport(machine_spec, machine_options, vm)
445
+ else
446
+ create_ssh_transport(machine_spec, machine_options, vm)
447
+ end
448
+ end
449
+
450
+ def create_winrm_transport(machine_spec, machine_options, vm)
451
+ raise 'Windows guest VMs are not yet supported'
452
+ end
453
+
454
+ def create_ssh_transport(machine_spec, machine_options, vm)
455
+ require 'chef_metal/transport/ssh'
456
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
457
+ ssh_options = bootstrap_options[:ssh]
458
+ ssh_user = ssh_options[:user]
459
+ remote_host = has_static_ip(bootstrap_options) ? bootstrap_options[:customization_spec][:ipsettings][:ip] : vm.guest.ipaddress
460
+
461
+ ChefMetal::Transport::SSH.new(remote_host, ssh_user, ssh_options, {}, config)
462
+ end
463
+ end
464
+ end