knife-vsphere 0.1.7 → 0.1.8

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.
@@ -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