clc-chef-metal-vsphere 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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