clc-chef-provisioning-vsphere 0.4.0

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