knife-vsphere 0.1.7 → 0.1.8

Sign up to get free protection for your applications and to get access to all the features.
@@ -1,32 +1,32 @@
1
- #
2
- # Author:: Ezra Pagel (<ezra@cpan.org>)
3
- # License:: Apache License, Version 2.0
4
- #
5
-
6
- require 'chef/knife'
7
- require 'chef/knife/BaseVsphereCommand'
8
-
9
- # Lists all known VM templates in the configured datacenter
10
- class Chef::Knife::VsphereTemplateList < Chef::Knife::BaseVsphereCommand
11
-
12
- banner "knife vsphere template list"
13
-
14
- get_common_options
15
-
16
- def run
17
-
18
- $stdout.sync = true
19
- $stderr.sync = true
20
-
21
- vim = get_vim_connection
22
-
23
- baseFolder = find_folder(config[:folder]);
24
-
25
- vms = find_all_in_folder(baseFolder, RbVmomi::VIM::VirtualMachine).
26
- select {|v| !v.config.nil? && v.config.template == true }
27
-
28
- vms.each do |vm|
29
- puts "#{ui.color("Template Name", :cyan)}: #{vm.name}"
30
- end
31
- end
32
- end
1
+ #
2
+ # Author:: Ezra Pagel (<ezra@cpan.org>)
3
+ # License:: Apache License, Version 2.0
4
+ #
5
+
6
+ require 'chef/knife'
7
+ require 'chef/knife/BaseVsphereCommand'
8
+
9
+ # Lists all known VM templates in the configured datacenter
10
+ class Chef::Knife::VsphereTemplateList < Chef::Knife::BaseVsphereCommand
11
+
12
+ banner "knife vsphere template list"
13
+
14
+ get_common_options
15
+
16
+ def run
17
+
18
+ $stdout.sync = true
19
+ $stderr.sync = true
20
+
21
+ vim = get_vim_connection
22
+
23
+ baseFolder = find_folder(config[:folder]);
24
+
25
+ vms = find_all_in_folder(baseFolder, RbVmomi::VIM::VirtualMachine).
26
+ select {|v| !v.config.nil? && v.config.template == true }
27
+
28
+ vms.each do |vm|
29
+ puts "#{ui.color("Template Name", :cyan)}: #{vm.name}"
30
+ end
31
+ end
32
+ end
@@ -1,373 +1,397 @@
1
- #
2
- # Author:: Ezra Pagel (<ezra@cpan.org>)
3
- # Contributor:: Jesse Campbell (<hikeit@gmail.com>)
4
- # License:: Apache License, Version 2.0
5
- #
6
-
7
- require 'chef/knife'
8
- require 'chef/knife/BaseVsphereCommand'
9
- require 'rbvmomi'
10
- require 'netaddr'
11
-
12
- # Clone an existing template into a new VM, optionally applying a customization specification.
13
- #
14
- # usage:
15
- # knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
16
- # --cips 192.168.0.99/24,192.168.1.99/24 \
17
- # --chostname NODENAME --cdomain NODEDOMAIN
18
- class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
19
-
20
- banner "knife vsphere vm clone VMNAME (options)"
21
-
22
- get_common_options
23
-
24
- option :dest_folder,
25
- :long => "--dest-folder FOLDER",
26
- :description => "The folder into which to put the cloned VM"
27
-
28
- option :datastore,
29
- :long => "--datastore STORE",
30
- :description => "The datastore into which to put the cloned VM"
31
-
32
- option :resource_pool,
33
- :long => "--resource-pool POOL",
34
- :description => "The resource pool into which to put the cloned VM"
35
-
36
- option :source_vm,
37
- :long => "--template TEMPLATE",
38
- :description => "The source VM / Template to clone from",
39
- :required => true
40
-
41
- option :customization_spec,
42
- :long => "--cspec CUST_SPEC",
43
- :description => "The name of any customization specification to apply"
44
-
45
- option :customization_vlan,
46
- :long => "--cvlan CUST_VLAN",
47
- :description => "VLAN name for network adapter to join"
48
-
49
- option :customization_ips,
50
- :long => "--cips CUST_IPS",
51
- :description => "Comma-delimited list of CIDR IPs for customization"
52
-
53
- option :customization_gw,
54
- :long => "--cgw CUST_GW",
55
- :description => "CIDR IP of gateway for customization"
56
-
57
- option :customization_hostname,
58
- :long => "--chostname CUST_HOSTNAME",
59
- :description => "Unqualified hostname for customization"
60
-
61
- option :customization_domain,
62
- :long => "--cdomain CUST_DOMAIN",
63
- :description => "Domain name for customization"
64
-
65
- option :customization_tz,
66
- :long => "--ctz CUST_TIMEZONE",
67
- :description => "Timezone invalid 'Area/Location' format"
68
-
69
- option :customization_cpucount,
70
- :long => "--ccpu CUST_CPU_COUNT",
71
- :description => "Number of CPUs"
72
-
73
- option :customization_memory,
74
- :long => "--cram CUST_MEMORY_GB",
75
- :description => "Gigabytes of RAM"
76
-
77
- option :power,
78
- :long => "--start STARTVM",
79
- :description => "Indicates whether to start the VM after a successful clone",
80
- :default => false
81
-
82
- option :bootstrap,
83
- :long => "--bootstrap FALSE",
84
- :description => "Indicates whether to bootstrap the VM",
85
- :default => false
86
-
87
- option :fqdn,
88
- :long => "--fqdn SERVER_FQDN",
89
- :description => "Fully qualified hostname for bootstrapping"
90
-
91
- option :ssh_user,
92
- :short => "-x USERNAME",
93
- :long => "--ssh-user USERNAME",
94
- :description => "The ssh username",
95
- :default => "root"
96
-
97
- option :ssh_password,
98
- :short => "-P PASSWORD",
99
- :long => "--ssh-password PASSWORD",
100
- :description => "The ssh password"
101
-
102
- option :ssh_port,
103
- :short => "-p PORT",
104
- :long => "--ssh-port PORT",
105
- :description => "The ssh port",
106
- :default => "22",
107
- :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
108
-
109
- option :identity_file,
110
- :short => "-i IDENTITY_FILE",
111
- :long => "--identity-file IDENTITY_FILE",
112
- :description => "The SSH identity file used for authentication"
113
-
114
- option :chef_node_name,
115
- :short => "-N NAME",
116
- :long => "--node-name NAME",
117
- :description => "The Chef node name for your new node"
118
-
119
- option :prerelease,
120
- :long => "--prerelease",
121
- :description => "Install the pre-release chef gems"
122
-
123
- option :bootstrap_version,
124
- :long => "--bootstrap-version VERSION",
125
- :description => "The version of Chef to install",
126
- :proc => lambda { |v| Chef::Config[:knife][:bootstrap_version] = v }
127
-
128
- option :bootstrap_proxy,
129
- :long => "--bootstrap-proxy PROXY_URL",
130
- :description => "The proxy server for the node being bootstrapped",
131
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
132
-
133
- option :distro,
134
- :short => "-d DISTRO",
135
- :long => "--distro DISTRO",
136
- :description => "Bootstrap a distro using a template",
137
- :default => "ubuntu10.04-gems"
138
-
139
- option :template_file,
140
- :long => "--template-file TEMPLATE",
141
- :description => "Full path to location of template to use",
142
- :default => false
143
-
144
- option :run_list,
145
- :short => "-r RUN_LIST",
146
- :long => "--run-list RUN_LIST",
147
- :description => "Comma separated list of roles/recipes to apply",
148
- :proc => lambda { |o| o.split(/[\s,]+/) },
149
- :default => []
150
-
151
- option :no_host_key_verify,
152
- :long => "--no-host-key-verify",
153
- :description => "Disable host key verification",
154
- :boolean => true,
155
- :default => false
156
-
157
- def run
158
- $stdout.sync = true
159
-
160
- vmname = @name_args[0]
161
- if vmname.nil?
162
- show_usage
163
- fatal_exit("You must specify a virtual machine name")
164
- end
165
- config[:fqdn] = vmname unless config[:fqdn]
166
- config[:chef_node_name] = vmname unless config[:chef_node_name]
167
-
168
- vim = get_vim_connection
169
-
170
- dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
171
- dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
172
-
173
- src_folder = find_folder(config[:folder]) || dc.vmFolder
174
-
175
- src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
176
- abort "VM/Template not found"
177
-
178
- clone_spec = generate_clone_spec(src_vm.config)
179
-
180
- cust_folder = config[:dest_folder] || config[:folder]
181
-
182
- dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
183
-
184
- task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
185
- puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
186
- task.wait_for_completion
187
- puts "Finished creating virtual machine #{vmname}"
188
-
189
- if config[:power] || config[:bootstrap]
190
- vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
191
- fatal_exit("VM #{vmname} not found")
192
- vm.PowerOnVM_Task.wait_for_completion
193
- puts "Powered on virtual machine #{vmname}"
194
- end
195
-
196
- if config[:bootstrap]
197
- print "Waiting for sshd..."
198
- print "." until tcp_test_ssh(config[:fqdn])
199
- puts "done"
200
-
201
- bootstrap_for_node.run
202
- end
203
- end
204
-
205
- # Builds a CloneSpec
206
- def generate_clone_spec (src_config)
207
-
208
- rspec = nil
209
- if config[:resource_pool]
210
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(config[:resource_pool]))
211
- else
212
- dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
213
- dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
214
- hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
215
- rp = hosts.first.resourcePool
216
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
217
- end
218
-
219
- if config[:datastore]
220
- rspec.datastore = find_datastore(config[:datastore])
221
- end
222
-
223
- clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
224
- :powerOn => false,
225
- :template => false)
226
-
227
- clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
228
-
229
- if config[:customization_cpucount]
230
- clone_spec.config.numCPUs = config[:customization_cpucount]
231
- end
232
-
233
- if config[:customization_memory]
234
- clone_spec.config.memoryMB = Integer(config[:customization_memory]) * 1024
235
- end
236
-
237
- if config[:customization_vlan]
238
- network = find_network(config[:customization_vlan])
239
- switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid ,:portgroupKey => network.key)
240
- card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" } or
241
- abort "Can't find source network card to customize"
242
- card.backing.port = switch_port
243
- dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
244
- clone_spec.config.deviceChange.push dev_spec
245
- end
246
-
247
- if config[:customization_spec]
248
- csi = find_customization(config[:customization_spec]) or
249
- fatal_exit("failed to find customization specification named #{config[:customization_spec]}")
250
-
251
- if csi.info.type != "Linux"
252
- fatal_exit("Only Linux customization specifications are currently supported")
253
- end
254
-
255
- if config[:customization_ips]
256
- if config[:customization_gw]
257
- csi.spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i,config[:customization_gw]) }
258
- else
259
- csi.spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
260
- end
261
- end
262
-
263
- use_ident = !config[:customization_hostname].nil? || !config[:customization_domain].nil?
264
-
265
- if use_ident
266
- # TODO - verify that we're deploying a linux spec, at least warn
267
- ident = RbVmomi::VIM.CustomizationLinuxPrep
268
-
269
- if config[:customization_hostname]
270
- ident.hostName = RbVmomi::VIM.CustomizationFixedName
271
- ident.hostName.name = config[:customization_hostname]
272
- else
273
- ident.hostName = RbVmomi::VIM.CustomizationFixedName
274
- ident.hostName.name = config[:customization_domain]
275
- end
276
-
277
- if config[:customization_domain]
278
- ident.domain = config[:customization_domain]
279
- end
280
-
281
- csi.spec.identity = ident
282
- end
283
-
284
- clone_spec.customization = csi.spec
285
- end
286
- clone_spec
287
- end
288
-
289
- # Retrieves a CustomizationSpecItem that matches the supplied name
290
- # @param vim [Connection] VI Connection to use
291
- # @param name [String] name of customization
292
- # @return [RbVmomi::VIM::CustomizationSpecItem]
293
- def find_customization(name)
294
- csm = config[:vim].serviceContent.customizationSpecManager
295
- csm.GetCustomizationSpec(:name => name)
296
- end
297
-
298
- # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
299
- # @param ip [String] Any static IP address to use, otherwise DHCP
300
- # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
301
- # @return [RbVmomi::VIM::CustomizationIPSettings]
302
- def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
303
-
304
- settings = RbVmomi::VIM.CustomizationIPSettings
305
-
306
- if ip.nil?
307
- settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
308
- else
309
- cidr_ip = NetAddr::CIDR.create(ip)
310
- settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
311
- settings.subnetMask = cidr_ip.netmask_ext
312
-
313
- # TODO - want to confirm gw/ip are in same subnet?
314
- # Only set gateway on first IP.
315
- if config[:customization_ips].split(',').first == ip
316
- if gw.nil?
317
- settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
318
- else
319
- gw_cidr = NetAddr::CIDR.create(gw)
320
- settings.gateway = [gw_cidr.ip]
321
- end
322
- end
323
- end
324
-
325
- adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
326
- adapter_map.adapter = settings
327
- adapter_map
328
- end
329
-
330
- def bootstrap_for_node()
331
- Chef::Knife::Bootstrap.load_deps
332
- bootstrap = Chef::Knife::Bootstrap.new
333
- bootstrap.name_args = [config[:fqdn]]
334
- bootstrap.config[:run_list] = config[:run_list]
335
- bootstrap.config[:ssh_user] = config[:ssh_user]
336
- bootstrap.config[:ssh_password] = config[:ssh_password]
337
- bootstrap.config[:ssh_port] = config[:ssh_port]
338
- bootstrap.config[:identity_file] = config[:identity_file]
339
- bootstrap.config[:chef_node_name] = config[:chef_node_name]
340
- bootstrap.config[:prerelease] = config[:prerelease]
341
- bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
342
- bootstrap.config[:distro] = locate_config_value(:distro)
343
- bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
344
- bootstrap.config[:template_file] = locate_config_value(:template_file)
345
- bootstrap.config[:environment] = config[:environment]
346
- # may be needed for vpc_mode
347
- bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
348
- bootstrap
349
- end
350
-
351
- def tcp_test_ssh(hostname)
352
- tcp_socket = TCPSocket.new(hostname, 22)
353
- readable = IO.select([tcp_socket], nil, nil, 5)
354
- if readable
355
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
356
- true
357
- else
358
- false
359
- end
360
- rescue Errno::ETIMEDOUT
361
- false
362
- rescue Errno::EPERM
363
- false
364
- rescue Errno::ECONNREFUSED
365
- sleep 2
366
- false
367
- rescue Errno::EHOSTUNREACH
368
- sleep 2
369
- false
370
- ensure
371
- tcp_socket && tcp_socket.close
372
- end
373
- end
1
+ #
2
+ # Author:: Ezra Pagel (<ezra@cpan.org>)
3
+ # Contributor:: Jesse Campbell (<hikeit@gmail.com>)
4
+ # License:: Apache License, Version 2.0
5
+ #
6
+
7
+ require 'chef/knife'
8
+ require 'chef/knife/BaseVsphereCommand'
9
+ require 'rbvmomi'
10
+ require 'netaddr'
11
+
12
+ # Clone an existing template into a new VM, optionally applying a customization specification.
13
+ #
14
+ # usage:
15
+ # knife vsphere vm clone NewNode UbuntuTemplate --cspec StaticSpec \
16
+ # --cips 192.168.0.99/24,192.168.1.99/24 \
17
+ # --chostname NODENAME --cdomain NODEDOMAIN
18
+ class Chef::Knife::VsphereVmClone < Chef::Knife::BaseVsphereCommand
19
+
20
+ banner "knife vsphere vm clone VMNAME (options)"
21
+
22
+ get_common_options
23
+
24
+ option :dest_folder,
25
+ :long => "--dest-folder FOLDER",
26
+ :description => "The folder into which to put the cloned VM"
27
+
28
+ option :datastore,
29
+ :long => "--datastore STORE",
30
+ :description => "The datastore into which to put the cloned VM"
31
+
32
+ option :resource_pool,
33
+ :long => "--resource-pool POOL",
34
+ :description => "The resource pool into which to put the cloned VM"
35
+
36
+ option :source_vm,
37
+ :long => "--template TEMPLATE",
38
+ :description => "The source VM / Template to clone from",
39
+ :required => true
40
+
41
+ option :customization_spec,
42
+ :long => "--cspec CUST_SPEC",
43
+ :description => "The name of any customization specification to apply"
44
+
45
+ option :customization_vlan,
46
+ :long => "--cvlan CUST_VLAN",
47
+ :description => "VLAN name for network adapter to join"
48
+
49
+ option :customization_ips,
50
+ :long => "--cips CUST_IPS",
51
+ :description => "Comma-delimited list of CIDR IPs for customization"
52
+
53
+ option :customization_dns_ips,
54
+ :long => "--cdnsips CUST_DNS_IPS",
55
+ :description => "Comma-delimited list of DNS IP addresses"
56
+
57
+ option :customization_dns_suffixes,
58
+ :long => "--cdnssuffix CUST_DNS_SUFFIXES",
59
+ :description => "Comma-delimited list of DNS search suffixes"
60
+
61
+ option :customization_gw,
62
+ :long => "--cgw CUST_GW",
63
+ :description => "CIDR IP of gateway for customization"
64
+
65
+ option :customization_hostname,
66
+ :long => "--chostname CUST_HOSTNAME",
67
+ :description => "Unqualified hostname for customization"
68
+
69
+ option :customization_domain,
70
+ :long => "--cdomain CUST_DOMAIN",
71
+ :description => "Domain name for customization"
72
+
73
+ option :customization_tz,
74
+ :long => "--ctz CUST_TIMEZONE",
75
+ :description => "Timezone invalid 'Area/Location' format"
76
+
77
+ option :customization_cpucount,
78
+ :long => "--ccpu CUST_CPU_COUNT",
79
+ :description => "Number of CPUs"
80
+
81
+ option :customization_memory,
82
+ :long => "--cram CUST_MEMORY_GB",
83
+ :description => "Gigabytes of RAM"
84
+
85
+ option :power,
86
+ :long => "--start STARTVM",
87
+ :description => "Indicates whether to start the VM after a successful clone",
88
+ :default => false
89
+
90
+ option :bootstrap,
91
+ :long => "--bootstrap FALSE",
92
+ :description => "Indicates whether to bootstrap the VM",
93
+ :default => false
94
+
95
+ option :fqdn,
96
+ :long => "--fqdn SERVER_FQDN",
97
+ :description => "Fully qualified hostname for bootstrapping"
98
+
99
+ option :ssh_user,
100
+ :short => "-x USERNAME",
101
+ :long => "--ssh-user USERNAME",
102
+ :description => "The ssh username",
103
+ :default => "root"
104
+
105
+ option :ssh_password,
106
+ :short => "-P PASSWORD",
107
+ :long => "--ssh-password PASSWORD",
108
+ :description => "The ssh password"
109
+
110
+ option :ssh_port,
111
+ :short => "-p PORT",
112
+ :long => "--ssh-port PORT",
113
+ :description => "The ssh port",
114
+ :default => "22",
115
+ :proc => Proc.new { |key| Chef::Config[:knife][:ssh_port] = key }
116
+
117
+ option :identity_file,
118
+ :short => "-i IDENTITY_FILE",
119
+ :long => "--identity-file IDENTITY_FILE",
120
+ :description => "The SSH identity file used for authentication"
121
+
122
+ option :chef_node_name,
123
+ :short => "-N NAME",
124
+ :long => "--node-name NAME",
125
+ :description => "The Chef node name for your new node"
126
+
127
+ option :prerelease,
128
+ :long => "--prerelease",
129
+ :description => "Install the pre-release chef gems"
130
+
131
+ option :bootstrap_version,
132
+ :long => "--bootstrap-version VERSION",
133
+ :description => "The version of Chef to install",
134
+ :proc => lambda { |v| Chef::Config[:knife][:bootstrap_version] = v }
135
+
136
+ option :bootstrap_proxy,
137
+ :long => "--bootstrap-proxy PROXY_URL",
138
+ :description => "The proxy server for the node being bootstrapped",
139
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
140
+
141
+ option :distro,
142
+ :short => "-d DISTRO",
143
+ :long => "--distro DISTRO",
144
+ :description => "Bootstrap a distro using a template",
145
+ :default => "ubuntu10.04-gems"
146
+
147
+ option :template_file,
148
+ :long => "--template-file TEMPLATE",
149
+ :description => "Full path to location of template to use",
150
+ :default => false
151
+
152
+ option :run_list,
153
+ :short => "-r RUN_LIST",
154
+ :long => "--run-list RUN_LIST",
155
+ :description => "Comma separated list of roles/recipes to apply",
156
+ :proc => lambda { |o| o.split(/[\s,]+/) },
157
+ :default => []
158
+
159
+ option :no_host_key_verify,
160
+ :long => "--no-host-key-verify",
161
+ :description => "Disable host key verification",
162
+ :boolean => true,
163
+ :default => false
164
+
165
+ def run
166
+ $stdout.sync = true
167
+
168
+ vmname = @name_args[0]
169
+ if vmname.nil?
170
+ show_usage
171
+ fatal_exit("You must specify a virtual machine name")
172
+ end
173
+ config[:fqdn] = vmname unless config[:fqdn]
174
+ config[:chef_node_name] = vmname unless config[:chef_node_name]
175
+ config[:vmname] = vmname
176
+
177
+ vim = get_vim_connection
178
+
179
+ dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
180
+ dc = vim.serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
181
+
182
+ src_folder = find_folder(config[:folder]) || dc.vmFolder
183
+
184
+ src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
185
+ abort "VM/Template not found"
186
+
187
+ clone_spec = generate_clone_spec(src_vm.config)
188
+
189
+ cust_folder = config[:dest_folder] || config[:folder]
190
+
191
+ dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
192
+
193
+ task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
194
+ puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
195
+ task.wait_for_completion
196
+ puts "Finished creating virtual machine #{vmname}"
197
+
198
+ if config[:power] || config[:bootstrap]
199
+ vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
200
+ fatal_exit("VM #{vmname} not found")
201
+ vm.PowerOnVM_Task.wait_for_completion
202
+ puts "Powered on virtual machine #{vmname}"
203
+ end
204
+
205
+ if config[:bootstrap]
206
+ print "Waiting for sshd..."
207
+ print "." until tcp_test_ssh(config[:fqdn])
208
+ puts "done"
209
+
210
+ bootstrap_for_node.run
211
+ end
212
+ end
213
+
214
+ # Builds a CloneSpec
215
+ def generate_clone_spec (src_config)
216
+
217
+ rspec = nil
218
+ if config[:resource_pool]
219
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(config[:resource_pool]))
220
+ else
221
+ dcname = config[:vsphere_dc] || Chef::Config[:knife][:vsphere_dc]
222
+ dc = config[:vim].serviceInstance.find_datacenter(dcname) or abort "datacenter not found"
223
+ hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
224
+ rp = hosts.first.resourcePool
225
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
226
+ end
227
+
228
+ if config[:datastore]
229
+ rspec.datastore = find_datastore(config[:datastore])
230
+ end
231
+
232
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
233
+ :powerOn => false,
234
+ :template => false)
235
+
236
+ clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
237
+
238
+ if config[:customization_cpucount]
239
+ clone_spec.config.numCPUs = config[:customization_cpucount]
240
+ end
241
+
242
+ if config[:customization_memory]
243
+ clone_spec.config.memoryMB = Integer(config[:customization_memory]) * 1024
244
+ end
245
+
246
+ if config[:customization_vlan]
247
+ network = find_network(config[:customization_vlan])
248
+ switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid ,:portgroupKey => network.key)
249
+ card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" } or
250
+ abort "Can't find source network card to customize"
251
+ card.backing.port = switch_port
252
+ dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
253
+ clone_spec.config.deviceChange.push dev_spec
254
+ end
255
+
256
+ if config[:customization_spec]
257
+ csi = find_customization(config[:customization_spec]) or
258
+ fatal_exit("failed to find customization specification named #{config[:customization_spec]}")
259
+
260
+ if csi.info.type != "Linux"
261
+ fatal_exit("Only Linux customization specifications are currently supported")
262
+ end
263
+ cust_spec = csi.spec
264
+ else
265
+ global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
266
+ cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
267
+ end
268
+
269
+ if config[:customization_dns_ips]
270
+ cust_spec.globalIPSettings.dnsServerList = config[:customization_dns_ips].split(',')
271
+ end
272
+
273
+ if config[:customization_dns_suffixes]
274
+ cust_spec.globalIPSettings.dnsSuffixList = config[:customization_dns_suffixes].split(',')
275
+ end
276
+
277
+ if config[:customization_ips]
278
+ if config[:customization_gw]
279
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i,config[:customization_gw]) }
280
+ else
281
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
282
+ end
283
+ end
284
+
285
+ use_ident = !config[:customization_hostname].nil? || !config[:customization_domain].nil? || cust_spec.identity.nil?
286
+
287
+ if use_ident
288
+ # TODO - verify that we're deploying a linux spec, at least warn
289
+ ident = RbVmomi::VIM.CustomizationLinuxPrep
290
+
291
+ ident.hostName = RbVmomi::VIM.CustomizationFixedName
292
+ if config[:customization_hostname]
293
+ ident.hostName.name = config[:customization_hostname]
294
+ elsif config[:customization_domain]
295
+ ident.hostName.name = config[:customization_domain]
296
+ else
297
+ ident.hostName.name = config[:vmname]
298
+ end
299
+
300
+ if config[:customization_domain]
301
+ ident.domain = config[:customization_domain]
302
+ else
303
+ ident.domain = ''
304
+ end
305
+
306
+ cust_spec.identity = ident
307
+ end
308
+
309
+ clone_spec.customization = cust_spec
310
+ clone_spec
311
+ end
312
+
313
+ # Retrieves a CustomizationSpecItem that matches the supplied name
314
+ # @param vim [Connection] VI Connection to use
315
+ # @param name [String] name of customization
316
+ # @return [RbVmomi::VIM::CustomizationSpecItem]
317
+ def find_customization(name)
318
+ csm = config[:vim].serviceContent.customizationSpecManager
319
+ csm.GetCustomizationSpec(:name => name)
320
+ end
321
+
322
+ # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
323
+ # @param ip [String] Any static IP address to use, otherwise DHCP
324
+ # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
325
+ # @return [RbVmomi::VIM::CustomizationIPSettings]
326
+ def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
327
+
328
+ settings = RbVmomi::VIM.CustomizationIPSettings
329
+
330
+ if ip.nil?
331
+ settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
332
+ else
333
+ cidr_ip = NetAddr::CIDR.create(ip)
334
+ settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
335
+ settings.subnetMask = cidr_ip.netmask_ext
336
+
337
+ # TODO - want to confirm gw/ip are in same subnet?
338
+ # Only set gateway on first IP.
339
+ if config[:customization_ips].split(',').first == ip
340
+ if gw.nil?
341
+ settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
342
+ else
343
+ gw_cidr = NetAddr::CIDR.create(gw)
344
+ settings.gateway = [gw_cidr.ip]
345
+ end
346
+ end
347
+ end
348
+
349
+ adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
350
+ adapter_map.adapter = settings
351
+ adapter_map
352
+ end
353
+
354
+ def bootstrap_for_node()
355
+ Chef::Knife::Bootstrap.load_deps
356
+ bootstrap = Chef::Knife::Bootstrap.new
357
+ bootstrap.name_args = [config[:fqdn]]
358
+ bootstrap.config[:run_list] = config[:run_list]
359
+ bootstrap.config[:ssh_user] = config[:ssh_user]
360
+ bootstrap.config[:ssh_password] = config[:ssh_password]
361
+ bootstrap.config[:ssh_port] = config[:ssh_port]
362
+ bootstrap.config[:identity_file] = config[:identity_file]
363
+ bootstrap.config[:chef_node_name] = config[:chef_node_name]
364
+ bootstrap.config[:prerelease] = config[:prerelease]
365
+ bootstrap.config[:bootstrap_version] = locate_config_value(:bootstrap_version)
366
+ bootstrap.config[:distro] = locate_config_value(:distro)
367
+ bootstrap.config[:use_sudo] = true unless config[:ssh_user] == 'root'
368
+ bootstrap.config[:template_file] = locate_config_value(:template_file)
369
+ bootstrap.config[:environment] = config[:environment]
370
+ # may be needed for vpc_mode
371
+ bootstrap.config[:no_host_key_verify] = config[:no_host_key_verify]
372
+ bootstrap
373
+ end
374
+
375
+ def tcp_test_ssh(hostname)
376
+ tcp_socket = TCPSocket.new(hostname, 22)
377
+ readable = IO.select([tcp_socket], nil, nil, 5)
378
+ if readable
379
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
380
+ true
381
+ else
382
+ false
383
+ end
384
+ rescue Errno::ETIMEDOUT
385
+ false
386
+ rescue Errno::EPERM
387
+ false
388
+ rescue Errno::ECONNREFUSED
389
+ sleep 2
390
+ false
391
+ rescue Errno::EHOSTUNREACH
392
+ sleep 2
393
+ false
394
+ ensure
395
+ tcp_socket && tcp_socket.close
396
+ end
397
+ end