clc-chef-provisioning-vsphere 0.4.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.
checksums.yaml ADDED
@@ -0,0 +1,7 @@
1
+ ---
2
+ SHA1:
3
+ metadata.gz: ee129d486f718866a0155190131925bb583bb694
4
+ data.tar.gz: 6c9785aeeddd9172ed6474ff5b8ffa844021084c
5
+ SHA512:
6
+ metadata.gz: 118c1e9179b0eedb57ff4556ce7397eb853bf70feb3ab6b833896395aafbab51c5e25c920ad4dafce05c5051fffb1765c192314da49dbd29b0d02ed67cc4d7d9
7
+ data.tar.gz: ba0cd2103bfa3e699bd4cb26a451ee0888fdd5dae7cb7175eb6e1df93ce1a80a0abd4f48f30aa677630f995326f7d9b4840d48b591c9b7ca1b19145351d89ffe
data/.gitignore ADDED
@@ -0,0 +1,3 @@
1
+ pkg/
2
+ Gemfile.lock
3
+ *.gem
data/Gemfile ADDED
@@ -0,0 +1,5 @@
1
+ # -*- encoding: utf-8 -*-
2
+ source "https://rubygems.org"
3
+ gemspec
4
+
5
+ gem "chef", "~> 12.1"
data/README.md ADDED
@@ -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.
data/Rakefile ADDED
@@ -0,0 +1,17 @@
1
+ require 'bundler/gem_tasks'
2
+ require 'rspec/core/rake_task'
3
+
4
+ $:.unshift(File.dirname(__FILE__) + '/lib')
5
+ require 'chef/provisioning/vsphere_driver/version'
6
+
7
+ RSpec::Core::RakeTask.new(:unit) do |task|
8
+ task.pattern = 'spec/unit_tests/*_spec.rb'
9
+ task.rspec_opts = ['--color', '-f documentation']
10
+ end
11
+
12
+ RSpec::Core::RakeTask.new(:integration) do |task|
13
+ task.pattern = 'spec/integration_tests/*_spec.rb'
14
+ task.rspec_opts = ['--color', '-f documentation']
15
+ end
16
+
17
+ task :default => [:unit]
@@ -0,0 +1,28 @@
1
+ $:.unshift(File.dirname(__FILE__) + '/lib')
2
+ require 'chef/provisioning/vsphere_driver/version'
3
+
4
+ Gem::Specification.new do |s|
5
+ s.name = 'clc-chef-provisioning-vsphere'
6
+ s.version = ChefProvisioningVsphere::VERSION
7
+ s.platform = Gem::Platform::RUBY
8
+ s.extra_rdoc_files = ['README.md']
9
+ s.summary = 'Provisioner for creating vSphere VM instances in Chef Provisioning.'
10
+ s.description = s.summary
11
+ s.authors = ['CenturyLink Cloud']
12
+ s.email = 'matt.wrock@CenturyLinkCloud.com'
13
+ s.homepage = 'https://github.com/tier3/chef-provisioning-vsphere'
14
+ s.license = 'Apache 2.0'
15
+
16
+ s.bindir = 'bin'
17
+ s.executables = %w( )
18
+
19
+ s.require_path = 'lib'
20
+ s.files = `git ls-files -z`.split("\x0")
21
+ s.test_files = s.files.grep(%r{^(test|spec|features)/})
22
+
23
+ s.add_dependency 'rbvmomi', '~> 1.8.0', '>= 1.8.2'
24
+ s.add_dependency 'chef-provisioning', '~>1.1'
25
+
26
+ s.add_development_dependency 'rspec'
27
+ s.add_development_dependency 'rake'
28
+ end
@@ -0,0 +1,3 @@
1
+ require 'chef/provisioning/vsphere_driver'
2
+
3
+ Chef::Provisioning.register_driver_class('vsphere', ChefProvisioningVsphere::VsphereDriver)
@@ -0,0 +1,13 @@
1
+ require 'chef/provisioning'
2
+ require 'chef/provisioning/vsphere_driver/driver'
3
+
4
+ class Chef
5
+ module DSL
6
+ module Recipe
7
+ def with_vsphere_driver(driver_options, &block)
8
+ url, config = ChefProvisioningVsphere::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,526 @@
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/version'
7
+ require 'chef/provisioning/vsphere_driver/vsphere_helpers'
8
+ require 'chef/provisioning/vsphere_driver/vsphere_url'
9
+
10
+ module ChefProvisioningVsphere
11
+ # Provisions machines in vSphere.
12
+ class VsphereDriver < Chef::Provisioning::Driver
13
+ include Chef::Mixin::ShellOut
14
+ include ChefProvisioningVsphere::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
+ :ready_timeout => 300,
31
+ :bootstrap_options => { :ssh => { :port => 22,
32
+ :user => 'root' },
33
+ :key_name => 'metal_default',
34
+ :tags => {} } }
35
+ }
36
+
37
+ new_connect_options = {}
38
+ new_connect_options[:provider] = 'vsphere'
39
+ if !driver_url.nil?
40
+ uri = URI(driver_url)
41
+ new_connect_options[:host] = uri.host
42
+ new_connect_options[:port] = uri.port
43
+ if uri.path && uri.path.length > 0
44
+ new_connect_options[:path] = uri.path
45
+ end
46
+ new_connect_options[:use_ssl] = uri.use_ssl
47
+ new_connect_options[:insecure] = uri.insecure
48
+ end
49
+ new_connect_options = new_connect_options.merge(config[:driver_options])
50
+
51
+ new_config = { :driver_options => { :connect_options => new_connect_options }}
52
+ config = Cheffish::MergedConfig.new(new_config, config, new_defaults)
53
+
54
+ required_options = [:host, :user, :password]
55
+ missing_options = []
56
+ required_options.each do |opt|
57
+ missing_options << opt unless config[:driver_options][:connect_options].has_key?(opt)
58
+ end
59
+ unless missing_options.empty?
60
+ raise "missing required options: #{missing_options.join(', ')}"
61
+ end
62
+
63
+ url = URI::VsphereUrl.from_config(config[:driver_options][:connect_options]).to_s
64
+ [ url, config ]
65
+ end
66
+
67
+ def self.symbolize_keys(h)
68
+ Hash === h ?
69
+ Hash[
70
+ h.map do |k, v|
71
+ [k.respond_to?(:to_sym) ? k.to_sym : k, symbolize_keys(v)]
72
+ end
73
+ ] : h
74
+ end
75
+
76
+ # Create a new Vsphere provisioner.
77
+ #
78
+ # ## Parameters
79
+ # connect_options - hash of options to be passed to RbVmomi::VIM.connect
80
+ # :host - required - hostname of the vSphere API server
81
+ # :port - optional - port on the vSphere API server (default: 443)
82
+ # :path - optional - path on the vSphere API server (default: /sdk)
83
+ # :use_ssl - optional - true to use ssl in connection to vSphere API server (default: true)
84
+ # :insecure - optional - true to ignore ssl certificate validation errors in connection to vSphere API server (default: false)
85
+ # :user - required - user name to use in connection to vSphere API server
86
+ # :password - required - password to use in connection to vSphere API server
87
+ # :proxy_host - optional - http proxy host to use in connection to vSphere API server (default: none)
88
+ # :proxy_port - optional - http proxy port to use in connection to vSphere API server (default: none)
89
+ def initialize(driver_url, config)
90
+ super(driver_url, config)
91
+ @connect_options = config[:driver_options][:connect_options].to_hash
92
+ end
93
+
94
+ attr_reader :connect_options
95
+
96
+ # Acquire a machine, generally by provisioning it. Returns a Machine
97
+ # object pointing at the machine, allowing useful actions like setup,
98
+ # converge, execute, file and directory. The Machine object will have a
99
+ # "node" property which must be saved to the server (if it is any
100
+ # different from the original node object).
101
+ #
102
+ # ## Parameters
103
+ # action_handler - the action_handler object that is calling this method; this
104
+ # is generally a action_handler, but could be anything that can support the
105
+ # ChefMetal::ActionHandler interface (i.e., in the case of the test
106
+ # kitchen metal driver for acquiring and destroying VMs; see the base
107
+ # class for what needs providing).
108
+ # node - node object (deserialized json) representing this machine. If
109
+ # the node has a provisioner_options hash in it, these will be used
110
+ # instead of options provided by the provisioner. TODO compare and
111
+ # fail if different?
112
+ # node will have node['normal']['provisioner_options'] in it with any options.
113
+ # It is a hash with this format:
114
+ #
115
+ # -- provisioner_url: vsphere://host:port?ssl=[true|false]&insecure=[true|false]
116
+ # -- bootstrap_options: hash of options to pass to RbVmomi::VIM::VirtualMachine::CloneTask()
117
+ # :datacenter
118
+ # :resource_pool
119
+ # :cluster
120
+ # :datastore
121
+ # :template_name
122
+ # :template_folder
123
+ # :vm_folder
124
+ # :winrm {...} (not yet implemented)
125
+ # :ssh {...}
126
+ #
127
+ # Example bootstrap_options for vSphere:
128
+ # TODO: add other CloneTask params, e.g.: datastore, annotation, resource_pool, ...
129
+ # 'bootstrap_options' => {
130
+ # 'template_name' =>'centos6.small',
131
+ # 'template_folder' =>'Templates',
132
+ # 'vm_folder' => 'MyApp'
133
+ # }
134
+ #
135
+ # node['normal']['provisioner_output'] will be populated with information
136
+ # about the created machine. For vSphere, it is a hash with this
137
+ # format:
138
+ #
139
+ # -- provisioner_url: vsphere:host:port?ssl=[true|false]&insecure=[true|false]
140
+ # -- vm_folder: name of the vSphere folder containing the VM
141
+ #
142
+ def allocate_machine(action_handler, machine_spec, machine_options)
143
+ if machine_spec.location
144
+ Chef::Log.warn "Checking to see if #{machine_spec.location} has been created..."
145
+ vm = vm_for(machine_spec)
146
+ if vm
147
+ Chef::Log.warn "returning existing machine"
148
+ return vm
149
+ else
150
+ Chef::Log.warn "Machine #{machine_spec.name} (#{machine_spec.location['server_id']} on #{driver_url}) no longer exists. Recreating ..."
151
+ end
152
+ end
153
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
154
+ vm = nil
155
+
156
+ if bootstrap_options[:ssh]
157
+ wait_on_port = bootstrap_options[:ssh][:port]
158
+ raise "Must specify bootstrap_options[:ssh][:port]" if wait_on_port.nil?
159
+ else
160
+ raise 'bootstrapping is currently supported for ssh only'
161
+ # wait_on_port = bootstrap_options['winrm']['port']
162
+ end
163
+
164
+ description = [ "creating machine #{machine_spec.name} on #{driver_url}" ]
165
+ bootstrap_options.each_pair { |key,value| description << " #{key}: #{value.inspect}" }
166
+ action_handler.report_progress description
167
+
168
+ vm = find_vm(bootstrap_options[:datacenter], bootstrap_options[:vm_folder], machine_spec.name)
169
+ server_id = nil
170
+ if vm
171
+ Chef::Log.info "machine already created: #{bootstrap_options[:vm_folder]}/#{machine_spec.name}"
172
+ else
173
+ vm = clone_vm(action_handler, bootstrap_options)
174
+ end
175
+
176
+ machine_spec.location = {
177
+ 'driver_url' => driver_url,
178
+ 'driver_version' => VERSION,
179
+ 'server_id' => vm.config.instanceUuid,
180
+ 'is_windows' => is_windows?(vm),
181
+ 'allocated_at' => Time.now.utc.to_s,
182
+ 'ipaddress' => vm.guest.ipAddress
183
+ }
184
+ machine_spec.location['key_name'] = bootstrap_options[:key_name] if bootstrap_options[:key_name]
185
+ %w(ssh_username sudo use_private_ip_for_ssh ssh_gateway).each do |key|
186
+ machine_spec.location[key] = machine_options[key.to_sym] if machine_options[key.to_sym]
187
+ end
188
+
189
+ action_handler.performed_action "machine #{machine_spec.name} created as #{machine_spec.location['server_id']} on #{driver_url}"
190
+ vm
191
+ end
192
+
193
+ def ready_machine(action_handler, machine_spec, machine_options)
194
+ start_machine(action_handler, machine_spec, machine_options)
195
+ vm = vm_for(machine_spec)
196
+ if vm.nil?
197
+ raise "Machine #{machine_spec.name} does not have a server associated with it, or server does not exist."
198
+ end
199
+
200
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
201
+
202
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
203
+
204
+ transport = nil
205
+ vm_ip = ip_for(bootstrap_options, vm)
206
+ if !vm_ip.nil?
207
+ transport = transport_for(machine_spec, machine_options, vm)
208
+ end
209
+
210
+ if transport.nil? || !transport.available? || !(vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip)
211
+ action_handler.report_progress "waiting up to #{machine_options[:ready_timeout]} seconds for customizations to complete and find #{vm_ip}"
212
+ now = Time.now.utc
213
+
214
+ until (Time.now.utc - now) > machine_options[:ready_timeout] || (vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip) do
215
+ action_handler.report_progress "IP addresses on #{machine_spec.name} are #{vm.guest.net.map { |net| net.ipAddress}.flatten}"
216
+ vm_ip = ip_for(bootstrap_options, vm) if vm_ip.nil?
217
+ sleep 5
218
+ end
219
+ if !(vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip)
220
+ action_handler.report_progress "rebooting..."
221
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
222
+ action_handler.report_progress "tools have stopped. current power state is #{vm.runtime.powerState} and tools state is #{vm.guest.toolsRunningStatus}. powering up server..."
223
+ start_vm(vm)
224
+ else
225
+ restart_server(action_handler, machine_spec, vm)
226
+ end
227
+ now = Time.now.utc
228
+ until (Time.now.utc - now) > 90 || (vm.guest.net.map { |net| net.ipAddress}.flatten).include?(vm_ip) do
229
+ vm_ip = ip_for(bootstrap_options, vm) if vm_ip.nil?
230
+ print "-"
231
+ sleep 5
232
+ end
233
+ end
234
+ machine_spec.location['ipaddress'] = vm.guest.ipAddress
235
+ action_handler.report_progress "IP address obtained: #{machine_spec.location['ipaddress']}"
236
+ end
237
+
238
+ domain = bootstrap_options[:customization_spec][:domain]
239
+ if vm.config.guestId.start_with?('win') && domain != 'local'
240
+ now = Time.now.utc
241
+ trimmed_name = machine_spec.name.byteslice(0,15)
242
+ expected_name="#{trimmed_name}.#{domain}"
243
+ action_handler.report_progress "waiting to domain join and be named #{expected_name}"
244
+ until (Time.now.utc - now) > 30 || (vm.guest.hostName == expected_name) do
245
+ print "."
246
+ sleep 5
247
+ end
248
+ end
249
+
250
+ begin
251
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
252
+ rescue Timeout::Error
253
+ # Only ever reboot once, and only if it's been less than 10 minutes since we stopped waiting
254
+ if machine_spec.location['started_at'] || remaining_wait_time(machine_spec, machine_options) < -(10*60)
255
+ raise
256
+ else
257
+ 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 ..."
258
+ restart_server(action_handler, machine_spec, vm)
259
+ wait_until_ready(action_handler, machine_spec, machine_options, vm)
260
+ wait_for_transport(action_handler, machine_spec, machine_options, vm)
261
+ end
262
+ end
263
+
264
+ machine = machine_for(machine_spec, machine_options, vm)
265
+
266
+ new_nics = add_extra_nic(action_handler, vm_template_for(bootstrap_options), bootstrap_options, vm)
267
+ if is_windows?(vm) && !new_nics.nil?
268
+ new_nics.each do |nic|
269
+ machine.execute_always("Disable-Netadapter -Name '#{nic.device.deviceInfo.label}' -Confirm:$false")
270
+ end
271
+ end
272
+
273
+ if has_static_ip(bootstrap_options) && !is_windows?(vm)
274
+ setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
275
+ end
276
+
277
+ machine
278
+ end
279
+
280
+ # Connect to machine without acquiring it
281
+ def connect_to_machine(machine_spec, machine_options)
282
+ machine_for(machine_spec, machine_options)
283
+ end
284
+
285
+ def destroy_machine(action_handler, machine_spec, machine_options)
286
+ vm = vm_for(machine_spec)
287
+ if vm
288
+ action_handler.perform_action "Delete VM [#{vm.parent.name}/#{vm.name}]" do
289
+ begin
290
+ vm.PowerOffVM_Task.wait_for_completion unless vm.runtime.powerState == 'poweredOff'
291
+ vm.Destroy_Task.wait_for_completion
292
+ rescue RbVmomi::Fault => fault
293
+ raise fault unless fault.fault.class.wsdl_name == "ManagedObjectNotFound"
294
+ ensure
295
+ machine_spec.location = nil
296
+ end
297
+ end
298
+ end
299
+ strategy = convergence_strategy_for(machine_spec, machine_options)
300
+ strategy.cleanup_convergence(action_handler, machine_spec)
301
+ end
302
+
303
+ def stop_machine(action_handler, machine_spec, machine_options)
304
+ vm = vm_for(machine_spec)
305
+ if vm
306
+ action_handler.perform_action "Shutdown guest OS and power off VM [#{vm.parent.name}/#{vm.name}]" do
307
+ stop_vm(vm)
308
+ end
309
+ end
310
+ end
311
+
312
+ def start_machine(action_handler, machine_spec, machine_options)
313
+ vm = vm_for(machine_spec)
314
+ if vm
315
+ action_handler.perform_action "Power on VM [#{vm.parent.name}/#{vm.name}]" do
316
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
317
+ start_vm(vm, bootstrap_options[:ssh][:port])
318
+ end
319
+ end
320
+ end
321
+
322
+ def restart_server(action_handler, machine_spec, vm)
323
+ action_handler.perform_action "restart machine #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url})" do
324
+ stop_machine(action_handler, machine_spec, vm)
325
+ start_vm(vm)
326
+ machine_spec.location['started_at'] = Time.now.utc.to_s
327
+ end
328
+ end
329
+
330
+ protected
331
+
332
+ def setup_ubuntu_dns(machine, bootstrap_options, machine_spec)
333
+ host_lookup = machine.execute_always('host google.com')
334
+ if host_lookup.exitstatus != 0
335
+ if host_lookup.stdout.include?("setlocale: LC_ALL")
336
+ machine.execute_always('locale-gen en_US && update-locale LANG=en_US')
337
+ end
338
+ distro = machine.execute_always("lsb_release -i | sed -e 's/Distributor ID://g'").stdout.strip
339
+ Chef::Log.info "Found distro:#{distro}"
340
+ if distro == 'Ubuntu'
341
+ distro_version = (machine.execute_always("lsb_release -r | sed -e s/[^0-9.]//g")).stdout.strip.to_f
342
+ Chef::Log.info "Found distro version:#{distro_version}"
343
+ if distro_version>= 12.04
344
+ Chef::Log.info "Ubuntu version 12.04 or greater. Need to patch DNS."
345
+ interfaces_file = "/etc/network/interfaces"
346
+ nameservers = bootstrap_options[:customization_spec][:ipsettings][:dnsServerList].join(' ')
347
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-search ; then echo 'dns-search #{bootstrap_options[:customization_spec][:domain]}' >> #{interfaces_file} ; fi")
348
+ machine.execute_always("if ! cat #{interfaces_file} | grep -q dns-nameservers ; then echo 'dns-nameservers #{nameservers}' >> #{interfaces_file} ; fi")
349
+ machine.execute_always('/etc/init.d/networking restart')
350
+ machine.execute_always('apt-get -qq update')
351
+ end
352
+ end
353
+ end
354
+ end
355
+
356
+ def has_static_ip(bootstrap_options)
357
+ if bootstrap_options.has_key?(:customization_spec)
358
+ bootstrap_options = bootstrap_options[:customization_spec]
359
+ if bootstrap_options.has_key?(:ipsettings)
360
+ bootstrap_options = bootstrap_options[:ipsettings]
361
+ if bootstrap_options.has_key?(:ip)
362
+ return true
363
+ end
364
+ end
365
+ end
366
+ false
367
+ end
368
+
369
+ def remaining_wait_time(machine_spec, machine_options)
370
+ if machine_spec.location['started_at']
371
+ machine_options[:start_timeout] - (Time.now.utc - Time.parse(machine_spec.location['started_at']))
372
+ else
373
+ machine_options[:create_timeout] - (Time.now.utc - Time.parse(machine_spec.location['allocated_at']))
374
+ end
375
+ end
376
+
377
+ def wait_until_ready(action_handler, machine_spec, machine_options, vm)
378
+ if vm.guest.toolsRunningStatus != "guestToolsRunning"
379
+ perform_action = true
380
+ if action_handler.should_perform_actions
381
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be ready ..."
382
+ until remaining_wait_time(machine_spec, machine_options) < 0 || (vm.guest.toolsRunningStatus == "guestToolsRunning" && (vm.guest.ipAddress.nil? || vm.guest.ipAddress.length > 0)) do
383
+ print "."
384
+ sleep 5
385
+ end
386
+ action_handler.report_progress "#{machine_spec.name} is now ready"
387
+ end
388
+ end
389
+ end
390
+
391
+ def vm_for(machine_spec)
392
+ if machine_spec.location
393
+ find_vm_by_id(machine_spec.location['server_id'])
394
+ else
395
+ nil
396
+ end
397
+ end
398
+
399
+ def bootstrap_options_for(machine_spec, machine_options)
400
+ bootstrap_options = machine_options[:bootstrap_options] || {}
401
+ bootstrap_options = bootstrap_options.to_hash
402
+ tags = {
403
+ 'Name' => machine_spec.name,
404
+ 'BootstrapId' => machine_spec.id,
405
+ 'BootstrapHost' => Socket.gethostname,
406
+ 'BootstrapUser' => Etc.getlogin
407
+ }
408
+ # User-defined tags override the ones we set
409
+ tags.merge!(bootstrap_options[:tags]) if bootstrap_options[:tags]
410
+ bootstrap_options.merge!({ :tags => tags })
411
+ bootstrap_options[:name] ||= machine_spec.name
412
+ bootstrap_options
413
+ end
414
+
415
+ def clone_vm(action_handler, bootstrap_options)
416
+ vm_name = bootstrap_options[:name]
417
+ datacenter = bootstrap_options[:datacenter]
418
+
419
+ vm = find_vm(datacenter, bootstrap_options[:vm_folder], vm_name)
420
+ return vm if vm
421
+
422
+ vm_template = vm_template_for(bootstrap_options)
423
+
424
+ do_vm_clone(action_handler, datacenter, vm_template, vm_name, bootstrap_options)
425
+ end
426
+
427
+ def vm_template_for(bootstrap_options)
428
+ datacenter = bootstrap_options[:datacenter]
429
+ template_folder = bootstrap_options[:template_folder]
430
+ template_name = bootstrap_options[:template_name]
431
+ find_vm(datacenter, template_folder, template_name) or raise("vSphere VM Template not found [#{template_folder}/#{template_name}]")
432
+ end
433
+
434
+ def machine_for(machine_spec, machine_options, vm = nil)
435
+ vm ||= vm_for(machine_spec)
436
+ if !vm
437
+ raise "Server for node #{machine_spec.name} has not been created!"
438
+ end
439
+
440
+ if machine_spec.location['is_windows']
441
+ Chef::Provisioning::Machine::WindowsMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
442
+ else
443
+ Chef::Provisioning::Machine::UnixMachine.new(machine_spec, transport_for(machine_spec, machine_options, vm), convergence_strategy_for(machine_spec, machine_options))
444
+ end
445
+ end
446
+
447
+ def is_windows?(vm)
448
+ return false if vm.nil?
449
+ vm.config.guestId.start_with?('win')
450
+ end
451
+
452
+ def convergence_strategy_for(machine_spec, machine_options)
453
+ require 'chef/provisioning/convergence_strategy/install_msi'
454
+ require 'chef/provisioning/convergence_strategy/install_cached'
455
+ require 'chef/provisioning/convergence_strategy/no_converge'
456
+ # Defaults
457
+ if !machine_spec.location
458
+ return Chef::Provisioning::ConvergenceStrategy::NoConverge.new(machine_options[:convergence_options], config)
459
+ end
460
+
461
+ if machine_spec.location['is_windows']
462
+ Chef::Provisioning::ConvergenceStrategy::InstallMsi.new(machine_options[:convergence_options], config)
463
+ else
464
+ Chef::Provisioning::ConvergenceStrategy::InstallCached.new(machine_options[:convergence_options], config)
465
+ end
466
+ end
467
+
468
+ def wait_for_transport(action_handler, machine_spec, machine_options, vm)
469
+ transport = transport_for(machine_spec, machine_options, vm)
470
+ if !transport.available?
471
+ if action_handler.should_perform_actions
472
+ action_handler.report_progress "waiting for #{machine_spec.name} (#{vm.config.instanceUuid} on #{driver_url}) to be connectable (transport up and running) ..."
473
+
474
+ until remaining_wait_time(machine_spec, machine_options) < 0 || transport.available? do
475
+ print "."
476
+ sleep 5
477
+ end
478
+
479
+ action_handler.report_progress "#{machine_spec.name} is now connectable"
480
+ end
481
+ end
482
+ end
483
+
484
+ def transport_for(machine_spec, machine_options, vm)
485
+ if is_windows?(vm)
486
+ create_winrm_transport(machine_spec, machine_options, vm)
487
+ else
488
+ create_ssh_transport(machine_spec, machine_options, vm)
489
+ end
490
+ end
491
+
492
+ def create_winrm_transport(machine_spec, machine_options, vm)
493
+ require 'chef/provisioning/transport/winrm'
494
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
495
+ ssh_options = bootstrap_options[:ssh]
496
+ remote_host = machine_spec.location['ipaddress'] || ip_for(bootstrap_options, vm)
497
+
498
+ winrm_options = {:user => "#{ssh_options[:user]}", :pass => ssh_options[:password]}
499
+ if ssh_options[:user].include?("\\")
500
+ winrm_options[:disable_sspi] = true
501
+ else
502
+ winrm_options[:basic_auth_only] = true
503
+ end
504
+
505
+ Chef::Provisioning::Transport::WinRM.new("http://#{remote_host}:5985/wsman", :plaintext, winrm_options, config)
506
+ end
507
+
508
+ def create_ssh_transport(machine_spec, machine_options, vm)
509
+ require 'chef/provisioning/transport/ssh'
510
+ bootstrap_options = bootstrap_options_for(machine_spec, machine_options)
511
+ ssh_options = bootstrap_options[:ssh]
512
+ ssh_user = ssh_options[:user]
513
+ remote_host = machine_spec.location['ipaddress'] || ip_for(bootstrap_options, vm)
514
+
515
+ Chef::Provisioning::Transport::SSH.new(remote_host, ssh_user, ssh_options, {}, config)
516
+ end
517
+
518
+ def ip_for(bootstrap_options, vm)
519
+ if has_static_ip(bootstrap_options)
520
+ bootstrap_options[:customization_spec][:ipsettings][:ip]
521
+ else
522
+ vm.guest.ipAddress
523
+ end
524
+ end
525
+ end
526
+ end