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