knife-vsphere 0.9.8 → 0.9.9

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.
@@ -42,7 +42,7 @@ class Chef
42
42
  :description => "The vsphere host"
43
43
 
44
44
  option :vsphere_dc,
45
- :short => "-d DATACENTER",
45
+ :short => "-D DATACENTER",
46
46
  :long => "--vsdc DATACENTER",
47
47
  :description => "The Datacenter for vsphere"
48
48
 
@@ -39,7 +39,7 @@ end
39
39
  # Lists all known data store cluster in datacenter with sizes
40
40
  class Chef::Knife::VsphereDatastoreclusterList < Chef::Knife::BaseVsphereCommand
41
41
 
42
- banner "knife vsphere datastore list"
42
+ banner "knife vsphere datastorecluster list"
43
43
 
44
44
  get_common_options
45
45
 
@@ -1,484 +1,521 @@
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/base_vsphere_command'
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 :annotation,
43
- :long => "--annotation TEXT",
44
- :description => "Add TEXT in Notes field from annotation"
45
-
46
- option :customization_spec,
47
- :long => "--cspec CUST_SPEC",
48
- :description => "The name of any customization specification to apply"
49
-
50
- option :customization_plugin,
51
- :long => "--cplugin CUST_PLUGIN_PATH",
52
- :description => "Path to plugin that implements KnifeVspherePlugin.customize_clone_spec and/or KnifeVspherePlugin.reconfig_vm"
53
-
54
- option :customization_plugin_data,
55
- :long => "--cplugin-data CUST_PLUGIN_DATA",
56
- :description => "String of data to pass to the plugin. Use any format you wish."
57
-
58
- option :customization_vlan,
59
- :long => "--cvlan CUST_VLAN",
60
- :description => "VLAN name for network adapter to join"
61
-
62
- option :customization_ips,
63
- :long => "--cips CUST_IPS",
64
- :description => "Comma-delimited list of CIDR IPs for customization"
65
-
66
- option :customization_dns_ips,
67
- :long => "--cdnsips CUST_DNS_IPS",
68
- :description => "Comma-delimited list of DNS IP addresses"
69
-
70
- option :customization_dns_suffixes,
71
- :long => "--cdnssuffix CUST_DNS_SUFFIXES",
72
- :description => "Comma-delimited list of DNS search suffixes"
73
-
74
- option :customization_gw,
75
- :long => "--cgw CUST_GW",
76
- :description => "CIDR IP of gateway for customization"
77
-
78
- option :customization_hostname,
79
- :long => "--chostname CUST_HOSTNAME",
80
- :description => "Unqualified hostname for customization"
81
-
82
- option :customization_domain,
83
- :long => "--cdomain CUST_DOMAIN",
84
- :description => "Domain name for customization"
85
-
86
- option :customization_tz,
87
- :long => "--ctz CUST_TIMEZONE",
88
- :description => "Timezone invalid 'Area/Location' format"
89
-
90
- option :customization_cpucount,
91
- :long => "--ccpu CUST_CPU_COUNT",
92
- :description => "Number of CPUs"
93
-
94
- option :customization_memory,
95
- :long => "--cram CUST_MEMORY_GB",
96
- :description => "Gigabytes of RAM"
97
-
98
- option :power,
99
- :long => "--start",
100
- :description => "Indicates whether to start the VM after a successful clone",
101
- :boolean => false
102
-
103
- option :bootstrap,
104
- :long => "--bootstrap",
105
- :description => "Indicates whether to bootstrap the VM",
106
- :boolean => false
107
-
108
- option :fqdn,
109
- :long => "--fqdn SERVER_FQDN",
110
- :description => "Fully qualified hostname for bootstrapping"
111
-
112
- option :ssh_user,
113
- :short => "-x USERNAME",
114
- :long => "--ssh-user USERNAME",
115
- :description => "The ssh username"
116
- $default[:ssh_user] = "root"
117
-
118
- option :ssh_password,
119
- :short => "-P PASSWORD",
120
- :long => "--ssh-password PASSWORD",
121
- :description => "The ssh password"
122
-
123
- option :ssh_port,
124
- :short => "-p PORT",
125
- :long => "--ssh-port PORT",
126
- :description => "The ssh port"
127
- $default[:ssh_port] = 22
128
-
129
- option :identity_file,
130
- :short => "-i IDENTITY_FILE",
131
- :long => "--identity-file IDENTITY_FILE",
132
- :description => "The SSH identity file used for authentication"
133
-
134
- option :chef_node_name,
135
- :short => "-N NAME",
136
- :long => "--node-name NAME",
137
- :description => "The Chef node name for your new node"
138
-
139
- option :prerelease,
140
- :long => "--prerelease",
141
- :description => "Install the pre-release chef gems",
142
- :boolean => false
143
-
144
- option :bootstrap_version,
145
- :long => "--bootstrap-version VERSION",
146
- :description => "The version of Chef to install",
147
- :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
148
-
149
- option :bootstrap_proxy,
150
- :long => "--bootstrap-proxy PROXY_URL",
151
- :description => "The proxy server for the node being bootstrapped",
152
- :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
153
-
154
- option :distro,
155
- :short => "-d DISTRO",
156
- :long => "--distro DISTRO",
157
- :description => "Bootstrap a distro using a template"
158
-
159
- option :template_file,
160
- :long => "--template-file TEMPLATE",
161
- :description => "Full path to location of template to use"
162
-
163
- option :run_list,
164
- :short => "-r RUN_LIST",
165
- :long => "--run-list RUN_LIST",
166
- :description => "Comma separated list of roles/recipes to apply"
167
- $default[:run_list] = ''
168
-
169
- option :secret_file,
170
- :long => "--secret-file SECRET_FILE",
171
- :description => "A file containing the secret key to use to encrypt data bag item values"
172
- $default[:secret_file] = ''
173
-
174
- option :no_host_key_verify,
175
- :long => "--no-host-key-verify",
176
- :description => "Disable host key verification",
177
- :boolean => true
178
-
179
- option :first_boot_attributes,
180
- :short => "-j JSON_ATTRIBS",
181
- :long => "--json-attributes",
182
- :description => "A JSON string to be added to the first run of chef-client",
183
- :proc => lambda { |o| JSON.parse(o) },
184
- :default => {}
185
-
186
- option :disable_customization,
187
- :long => "--disable-customization",
188
- :description => "Disable default customization",
189
- :boolean => true,
190
- :default => false
191
-
192
- option :log_level,
193
- :short => "-l LEVEL",
194
- :long => "--log_level",
195
- :description => "Set the log level (debug, info, warn, error, fatal) for chef-client",
196
- :proc => lambda { |l| l.to_sym }
197
-
198
- def run
199
- $stdout.sync = true
200
-
201
- vmname = @name_args[0]
202
- if vmname.nil?
203
- show_usage
204
- fatal_exit("You must specify a virtual machine name")
205
- end
206
- config[:chef_node_name] = vmname unless config[:chef_node_name]
207
- config[:vmname] = vmname
208
-
209
- if get_config(:bootstrap) && get_config(:distro) && !@@chef_config_dir
210
- fatal_exit("Can't find .chef for bootstrap files. chdir to a location with a .chef directory and try again")
211
- end
212
-
213
- vim = get_vim_connection
214
-
215
- dc = get_datacenter
216
-
217
- src_folder = find_folder(get_config(:folder)) || dc.vmFolder
218
-
219
- src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
220
- abort "VM/Template not found"
221
-
222
- clone_spec = generate_clone_spec(src_vm.config)
223
-
224
- cust_folder = config[:dest_folder] || get_config(:folder)
225
-
226
- dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
227
-
228
- task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
229
- puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
230
- task.wait_for_completion
231
- puts "Finished creating virtual machine #{vmname}"
232
-
233
- if customization_plugin && customization_plugin.respond_to?(:reconfig_vm)
234
- target_vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or abort "VM could not be found in #{dest_folder}"
235
- customization_plugin.reconfig_vm(target_vm)
236
- end
237
-
238
- if get_config(:power) || get_config(:bootstrap)
239
- vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
240
- fatal_exit("VM #{vmname} not found")
241
- vm.PowerOnVM_Task.wait_for_completion
242
- puts "Powered on virtual machine #{vmname}"
243
- end
244
-
245
- if get_config(:bootstrap)
246
- sleep 2 until vm.guest.ipAddress
247
- config[:fqdn] = vm.guest.ipAddress unless config[:fqdn]
248
- print "Waiting for sshd..."
249
- print "." until tcp_test_ssh(config[:fqdn])
250
- puts "done"
251
-
252
- bootstrap_for_node.run
253
- end
254
- end
255
-
256
- # Builds a CloneSpec
257
- def generate_clone_spec (src_config)
258
-
259
- rspec = nil
260
- if get_config(:resource_pool)
261
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
262
- else
263
- dc = get_datacenter
264
- hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
265
- rp = hosts.first.resourcePool
266
- rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
267
- end
268
-
269
- if get_config(:datastore)
270
- rspec.datastore = find_datastore(get_config(:datastore))
271
- end
272
-
273
- clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
274
- :powerOn => false,
275
- :template => false)
276
-
277
- clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
278
-
279
- if get_config(:annotation)
280
- clone_spec.config.annotation = get_config(:annotation)
281
- end
282
-
283
- if get_config(:customization_cpucount)
284
- clone_spec.config.numCPUs = get_config(:customization_cpucount)
285
- end
286
-
287
- if get_config(:customization_memory)
288
- clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
289
- end
290
-
291
- if get_config(:customization_vlan)
292
- network = find_network(get_config(:customization_vlan))
293
- card = src_config.hardware.device.find { |d| d.deviceInfo.label == "Network adapter 1" } or
294
- abort "Can't find source network card to customize"
295
- begin
296
- switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid, :portgroupKey => network.key)
297
- card.backing.port = switch_port
298
- rescue
299
- # not connected to a distibuted switch?
300
- card.backing.deviceName = network.name
301
- end
302
- dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
303
- clone_spec.config.deviceChange.push dev_spec
304
- end
305
-
306
- if get_config(:customization_spec)
307
- csi = find_customization(get_config(:customization_spec)) or
308
- fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
309
-
310
- if csi.info.type != "Linux"
311
- fatal_exit("Only Linux customization specifications are currently supported")
312
- end
313
- cust_spec = csi.spec
314
- else
315
- global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
316
- cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
317
- end
318
-
319
- if get_config(:customization_dns_ips)
320
- cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
321
- end
322
-
323
- if get_config(:customization_dns_suffixes)
324
- cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
325
- end
326
-
327
- if config[:customization_ips]
328
- if get_config(:customization_gw)
329
- cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i, get_config(:customization_gw)) }
330
- else
331
- cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
332
- end
333
- end
334
-
335
- unless get_config(:disable_customization)
336
- use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
337
-
338
-
339
- if use_ident
340
- # TODO - verify that we're deploying a linux spec, at least warn
341
- ident = RbVmomi::VIM.CustomizationLinuxPrep
342
-
343
- ident.hostName = RbVmomi::VIM.CustomizationFixedName
344
- if config[:customization_hostname]
345
- ident.hostName.name = config[:customization_hostname]
346
- else
347
- ident.hostName.name = config[:vmname]
348
- end
349
-
350
- if get_config(:customization_domain)
351
- ident.domain = get_config(:customization_domain)
352
- else
353
- ident.domain = ''
354
- end
355
-
356
- cust_spec.identity = ident
357
- end
358
-
359
- if customization_plugin && customization_plugin.respond_to?(:customize_clone_spec)
360
- clone_spec = customization_plugin.customize_clone_spec(src_config, clone_spec)
361
- end
362
-
363
- clone_spec.customization = cust_spec
364
- end
365
- clone_spec
366
- end
367
-
368
- # Loads the customization plugin if one was specified
369
- # @return [KnifeVspherePlugin] the loaded and initialized plugin or nil
370
- def customization_plugin
371
- if @customization_plugin.nil?
372
- if cplugin_path = get_config(:customization_plugin)
373
- if File.exists? cplugin_path
374
- require cplugin_path
375
- else
376
- abort "Customization plugin could not be found at #{cplugin_path}"
377
- end
378
-
379
- if Object.const_defined? 'KnifeVspherePlugin'
380
- @customization_plugin = Object.const_get('KnifeVspherePlugin').new
381
- if cplugin_data = get_config(:customization_plugin_data)
382
- if @customization_plugin.respond_to?(:data=)
383
- @customization_plugin.data = cplugin_data
384
- else
385
- abort "Customization plugin has no :data= accessor to receive the --cplugin-data argument. Define both or neither."
386
- end
387
- end
388
- else
389
- abort "KnifeVspherePlugin class is not defined in #{cplugin_path}"
390
- end
391
- end
392
- end
393
-
394
- @customization_plugin
395
- end
396
-
397
- # Retrieves a CustomizationSpecItem that matches the supplied name
398
- # @param vim [Connection] VI Connection to use
399
- # @param name [String] name of customization
400
- # @return [RbVmomi::VIM::CustomizationSpecItem]
401
- def find_customization(name)
402
- csm = config[:vim].serviceContent.customizationSpecManager
403
- csm.GetCustomizationSpec(:name => name)
404
- end
405
-
406
- # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
407
- # @param ip [String] Any static IP address to use, otherwise DHCP
408
- # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
409
- # @return [RbVmomi::VIM::CustomizationIPSettings]
410
- def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
411
-
412
- settings = RbVmomi::VIM.CustomizationIPSettings
413
-
414
- if ip.nil?
415
- settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
416
- else
417
- cidr_ip = NetAddr::CIDR.create(ip)
418
- settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
419
- settings.subnetMask = cidr_ip.netmask_ext
420
-
421
- # TODO - want to confirm gw/ip are in same subnet?
422
- # Only set gateway on first IP.
423
- if config[:customization_ips].split(',').first == ip
424
- if gw.nil?
425
- settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
426
- else
427
- gw_cidr = NetAddr::CIDR.create(gw)
428
- settings.gateway = [gw_cidr.ip]
429
- end
430
- end
431
- end
432
-
433
- adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
434
- adapter_map.adapter = settings
435
- adapter_map
436
- end
437
-
438
- def bootstrap_for_node()
439
- Chef::Knife::Bootstrap.load_deps
440
- bootstrap = Chef::Knife::Bootstrap.new
441
- bootstrap.name_args = [config[:fqdn]]
442
- bootstrap.config[:run_list] = get_config(:run_list).split(/[\s,]+/)
443
- bootstrap.config[:secret_file] = get_config(:secret_file)
444
- bootstrap.config[:ssh_user] = get_config(:ssh_user)
445
- bootstrap.config[:ssh_password] = get_config(:ssh_password)
446
- bootstrap.config[:ssh_port] = get_config(:ssh_port)
447
- bootstrap.config[:identity_file] = get_config(:identity_file)
448
- bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
449
- bootstrap.config[:prerelease] = get_config(:prerelease)
450
- bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
451
- bootstrap.config[:distro] = get_config(:distro)
452
- bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
453
- bootstrap.config[:template_file] = get_config(:template_file)
454
- bootstrap.config[:environment] = get_config(:environment)
455
- bootstrap.config[:first_boot_attributes] = get_config(:first_boot_attributes)
456
- bootstrap.config[:log_level] = get_config(:log_level)
457
- # may be needed for vpc_mode
458
- bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
459
- bootstrap
460
- end
461
-
462
- def tcp_test_ssh(hostname)
463
- tcp_socket = TCPSocket.new(hostname, get_config(:ssh_port))
464
- readable = IO.select([tcp_socket], nil, nil, 5)
465
- if readable
466
- Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
467
- true
468
- else
469
- false
470
- end
471
- rescue Errno::ETIMEDOUT
472
- false
473
- rescue Errno::EPERM
474
- false
475
- rescue Errno::ECONNREFUSED
476
- sleep 2
477
- false
478
- rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
479
- sleep 2
480
- false
481
- ensure
482
- tcp_socket && tcp_socket.close
483
- end
484
- 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/base_vsphere_command'
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 :linked_clone,
43
+ :long => "--linked-clone",
44
+ :description => "Indicates whether to use linked clones.",
45
+ :boolean => false
46
+
47
+ option :annotation,
48
+ :long => "--annotation TEXT",
49
+ :description => "Add TEXT in Notes field from annotation"
50
+
51
+ option :customization_spec,
52
+ :long => "--cspec CUST_SPEC",
53
+ :description => "The name of any customization specification to apply"
54
+
55
+ option :customization_plugin,
56
+ :long => "--cplugin CUST_PLUGIN_PATH",
57
+ :description => "Path to plugin that implements KnifeVspherePlugin.customize_clone_spec and/or KnifeVspherePlugin.reconfig_vm"
58
+
59
+ option :customization_plugin_data,
60
+ :long => "--cplugin-data CUST_PLUGIN_DATA",
61
+ :description => "String of data to pass to the plugin. Use any format you wish."
62
+
63
+ option :customization_vlan,
64
+ :long => "--cvlan CUST_VLAN",
65
+ :description => "VLAN name for network adapter to join"
66
+
67
+ option :customization_ips,
68
+ :long => "--cips CUST_IPS",
69
+ :description => "Comma-delimited list of CIDR IPs for customization"
70
+
71
+ option :customization_dns_ips,
72
+ :long => "--cdnsips CUST_DNS_IPS",
73
+ :description => "Comma-delimited list of DNS IP addresses"
74
+
75
+ option :customization_dns_suffixes,
76
+ :long => "--cdnssuffix CUST_DNS_SUFFIXES",
77
+ :description => "Comma-delimited list of DNS search suffixes"
78
+
79
+ option :customization_gw,
80
+ :long => "--cgw CUST_GW",
81
+ :description => "CIDR IP of gateway for customization"
82
+
83
+ option :customization_hostname,
84
+ :long => "--chostname CUST_HOSTNAME",
85
+ :description => "Unqualified hostname for customization"
86
+
87
+ option :customization_domain,
88
+ :long => "--cdomain CUST_DOMAIN",
89
+ :description => "Domain name for customization"
90
+
91
+ option :customization_tz,
92
+ :long => "--ctz CUST_TIMEZONE",
93
+ :description => "Timezone invalid 'Area/Location' format"
94
+
95
+ option :customization_cpucount,
96
+ :long => "--ccpu CUST_CPU_COUNT",
97
+ :description => "Number of CPUs"
98
+
99
+ option :customization_memory,
100
+ :long => "--cram CUST_MEMORY_GB",
101
+ :description => "Gigabytes of RAM"
102
+
103
+ option :power,
104
+ :long => "--start",
105
+ :description => "Indicates whether to start the VM after a successful clone",
106
+ :boolean => false
107
+
108
+ option :bootstrap,
109
+ :long => "--bootstrap",
110
+ :description => "Indicates whether to bootstrap the VM",
111
+ :boolean => false
112
+
113
+ option :fqdn,
114
+ :long => "--fqdn SERVER_FQDN",
115
+ :description => "Fully qualified hostname for bootstrapping"
116
+
117
+ option :ssh_user,
118
+ :short => "-x USERNAME",
119
+ :long => "--ssh-user USERNAME",
120
+ :description => "The ssh username"
121
+ $default[:ssh_user] = "root"
122
+
123
+ option :ssh_password,
124
+ :short => "-P PASSWORD",
125
+ :long => "--ssh-password PASSWORD",
126
+ :description => "The ssh password"
127
+
128
+ option :ssh_port,
129
+ :short => "-p PORT",
130
+ :long => "--ssh-port PORT",
131
+ :description => "The ssh port"
132
+ $default[:ssh_port] = 22
133
+
134
+ option :identity_file,
135
+ :short => "-i IDENTITY_FILE",
136
+ :long => "--identity-file IDENTITY_FILE",
137
+ :description => "The SSH identity file used for authentication"
138
+
139
+ option :chef_node_name,
140
+ :short => "-N NAME",
141
+ :long => "--node-name NAME",
142
+ :description => "The Chef node name for your new node"
143
+
144
+ option :prerelease,
145
+ :long => "--prerelease",
146
+ :description => "Install the pre-release chef gems",
147
+ :boolean => false
148
+
149
+ option :bootstrap_version,
150
+ :long => "--bootstrap-version VERSION",
151
+ :description => "The version of Chef to install",
152
+ :proc => Proc.new { |v| Chef::Config[:knife][:bootstrap_version] = v }
153
+
154
+ option :bootstrap_proxy,
155
+ :long => "--bootstrap-proxy PROXY_URL",
156
+ :description => "The proxy server for the node being bootstrapped",
157
+ :proc => Proc.new { |p| Chef::Config[:knife][:bootstrap_proxy] = p }
158
+
159
+ option :distro,
160
+ :short => "-d DISTRO",
161
+ :long => "--distro DISTRO",
162
+ :description => "Bootstrap a distro using a template"
163
+
164
+ option :template_file,
165
+ :long => "--template-file TEMPLATE",
166
+ :description => "Full path to location of template to use"
167
+
168
+ option :run_list,
169
+ :short => "-r RUN_LIST",
170
+ :long => "--run-list RUN_LIST",
171
+ :description => "Comma separated list of roles/recipes to apply"
172
+ $default[:run_list] = ''
173
+
174
+ option :secret_file,
175
+ :long => "--secret-file SECRET_FILE",
176
+ :description => "A file containing the secret key to use to encrypt data bag item values"
177
+ $default[:secret_file] = ''
178
+
179
+ option :no_host_key_verify,
180
+ :long => "--no-host-key-verify",
181
+ :description => "Disable host key verification",
182
+ :boolean => true
183
+
184
+ option :first_boot_attributes,
185
+ :short => "-j JSON_ATTRIBS",
186
+ :long => "--json-attributes",
187
+ :description => "A JSON string to be added to the first run of chef-client",
188
+ :proc => lambda { |o| JSON.parse(o) },
189
+ :default => {}
190
+
191
+ option :disable_customization,
192
+ :long => "--disable-customization",
193
+ :description => "Disable default customization",
194
+ :boolean => true,
195
+ :default => false
196
+
197
+ option :log_level,
198
+ :short => "-l LEVEL",
199
+ :long => "--log_level",
200
+ :description => "Set the log level (debug, info, warn, error, fatal) for chef-client",
201
+ :proc => lambda { |l| l.to_sym }
202
+
203
+ def run
204
+ $stdout.sync = true
205
+
206
+ vmname = @name_args[0]
207
+ if vmname.nil?
208
+ show_usage
209
+ fatal_exit("You must specify a virtual machine name")
210
+ end
211
+ config[:chef_node_name] = vmname unless config[:chef_node_name]
212
+ config[:vmname] = vmname
213
+
214
+ if get_config(:bootstrap) && get_config(:distro) && !@@chef_config_dir
215
+ fatal_exit("Can't find .chef for bootstrap files. chdir to a location with a .chef directory and try again")
216
+ end
217
+
218
+ vim = get_vim_connection
219
+
220
+ dc = get_datacenter
221
+
222
+ src_folder = find_folder(get_config(:folder)) || dc.vmFolder
223
+
224
+ src_vm = find_in_folder(src_folder, RbVmomi::VIM::VirtualMachine, config[:source_vm]) or
225
+ abort "VM/Template not found"
226
+
227
+ if get_config(:linked_clone)
228
+ create_delta_disk(src_vm)
229
+ end
230
+
231
+ clone_spec = generate_clone_spec(src_vm.config)
232
+
233
+ cust_folder = config[:dest_folder] || get_config(:folder)
234
+
235
+ dest_folder = cust_folder.nil? ? src_vm.vmFolder : find_folder(cust_folder)
236
+
237
+ task = src_vm.CloneVM_Task(:folder => dest_folder, :name => vmname, :spec => clone_spec)
238
+ puts "Cloning template #{config[:source_vm]} to new VM #{vmname}"
239
+ task.wait_for_completion
240
+ puts "Finished creating virtual machine #{vmname}"
241
+
242
+ if customization_plugin && customization_plugin.respond_to?(:reconfig_vm)
243
+ target_vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or abort "VM could not be found in #{dest_folder}"
244
+ customization_plugin.reconfig_vm(target_vm)
245
+ end
246
+
247
+ if get_config(:power) || get_config(:bootstrap)
248
+ vm = find_in_folder(dest_folder, RbVmomi::VIM::VirtualMachine, vmname) or
249
+ fatal_exit("VM #{vmname} not found")
250
+ vm.PowerOnVM_Task.wait_for_completion
251
+ puts "Powered on virtual machine #{vmname}"
252
+ end
253
+
254
+ if get_config(:bootstrap)
255
+ sleep 2 until vm.guest.ipAddress
256
+ config[:fqdn] = vm.guest.ipAddress unless config[:fqdn]
257
+ print "Waiting for sshd..."
258
+ print "." until tcp_test_ssh(config[:fqdn])
259
+ puts "done"
260
+
261
+ bootstrap_for_node.run
262
+ end
263
+ end
264
+
265
+ def create_delta_disk(src_vm)
266
+ disks = src_vm.config.hardware.device.grep(RbVmomi::VIM::VirtualDisk)
267
+ disks.select { |disk| disk.backing.parent == nil }.each do |disk|
268
+ spec = {
269
+ :deviceChange => [
270
+ {
271
+ :operation => :remove,
272
+ :device => disk
273
+ },
274
+ {
275
+ :operation => :add,
276
+ :fileOperation => :create,
277
+ :device => disk.dup.tap { |new_disk|
278
+ new_disk.backing = new_disk.backing.dup
279
+ new_disk.backing.fileName = "[#{disk.backing.datastore.name}]"
280
+ new_disk.backing.parent = disk.backing
281
+ },
282
+ }
283
+ ]
284
+ }
285
+ src_vm.ReconfigVM_Task(:spec => spec).wait_for_completion
286
+ end
287
+ end
288
+
289
+ # Builds a CloneSpec
290
+ def generate_clone_spec (src_config)
291
+
292
+ rspec = nil
293
+ if get_config(:resource_pool)
294
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => find_pool(get_config(:resource_pool)))
295
+ else
296
+ dc = get_datacenter
297
+ hosts = find_all_in_folder(dc.hostFolder, RbVmomi::VIM::ComputeResource)
298
+ rp = hosts.first.resourcePool
299
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:pool => rp)
300
+ end
301
+
302
+ if get_config(:linked_clone)
303
+ rspec = RbVmomi::VIM.VirtualMachineRelocateSpec(:diskMoveType => :moveChildMostDiskBacking)
304
+ end
305
+
306
+ if get_config(:datastore)
307
+ rspec.datastore = find_datastore(get_config(:datastore))
308
+ end
309
+
310
+ clone_spec = RbVmomi::VIM.VirtualMachineCloneSpec(:location => rspec,
311
+ :powerOn => false,
312
+ :template => false)
313
+
314
+ clone_spec.config = RbVmomi::VIM.VirtualMachineConfigSpec(:deviceChange => Array.new)
315
+
316
+ if get_config(:annotation)
317
+ clone_spec.config.annotation = get_config(:annotation)
318
+ end
319
+
320
+ if get_config(:customization_cpucount)
321
+ clone_spec.config.numCPUs = get_config(:customization_cpucount)
322
+ end
323
+
324
+ if get_config(:customization_memory)
325
+ clone_spec.config.memoryMB = Integer(get_config(:customization_memory)) * 1024
326
+ end
327
+
328
+ if get_config(:customization_vlan)
329
+ network = find_network(get_config(:customization_vlan))
330
+ card = src_config.hardware.device.grep(RbVmomi::VIM::VirtualEthernetCard).first or
331
+ abort "Can't find source network card to customize"
332
+ begin
333
+ switch_port = RbVmomi::VIM.DistributedVirtualSwitchPortConnection(:switchUuid => network.config.distributedVirtualSwitch.uuid, :portgroupKey => network.key)
334
+ card.backing.port = switch_port
335
+ rescue
336
+ # not connected to a distibuted switch?
337
+ card.backing.deviceName = network.name
338
+ end
339
+ dev_spec = RbVmomi::VIM.VirtualDeviceConfigSpec(:device => card, :operation => "edit")
340
+ clone_spec.config.deviceChange.push dev_spec
341
+ end
342
+
343
+ if get_config(:customization_spec)
344
+ csi = find_customization(get_config(:customization_spec)) or
345
+ fatal_exit("failed to find customization specification named #{get_config(:customization_spec)}")
346
+
347
+ if csi.info.type != "Linux"
348
+ fatal_exit("Only Linux customization specifications are currently supported")
349
+ end
350
+ cust_spec = csi.spec
351
+ else
352
+ global_ipset = RbVmomi::VIM.CustomizationGlobalIPSettings
353
+ cust_spec = RbVmomi::VIM.CustomizationSpec(:globalIPSettings => global_ipset)
354
+ end
355
+
356
+ if get_config(:customization_dns_ips)
357
+ cust_spec.globalIPSettings.dnsServerList = get_config(:customization_dns_ips).split(',')
358
+ end
359
+
360
+ if get_config(:customization_dns_suffixes)
361
+ cust_spec.globalIPSettings.dnsSuffixList = get_config(:customization_dns_suffixes).split(',')
362
+ end
363
+
364
+ if config[:customization_ips]
365
+ if get_config(:customization_gw)
366
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i, get_config(:customization_gw)) }
367
+ else
368
+ cust_spec.nicSettingMap = config[:customization_ips].split(',').map { |i| generate_adapter_map(i) }
369
+ end
370
+ end
371
+
372
+ unless get_config(:disable_customization)
373
+ use_ident = !config[:customization_hostname].nil? || !get_config(:customization_domain).nil? || cust_spec.identity.nil?
374
+
375
+
376
+ if use_ident
377
+ # TODO - verify that we're deploying a linux spec, at least warn
378
+ ident = RbVmomi::VIM.CustomizationLinuxPrep
379
+
380
+ ident.hostName = RbVmomi::VIM.CustomizationFixedName
381
+ if config[:customization_hostname]
382
+ ident.hostName.name = config[:customization_hostname]
383
+ else
384
+ ident.hostName.name = config[:vmname]
385
+ end
386
+
387
+ if get_config(:customization_domain)
388
+ ident.domain = get_config(:customization_domain)
389
+ else
390
+ ident.domain = ''
391
+ end
392
+
393
+ cust_spec.identity = ident
394
+ end
395
+
396
+ if customization_plugin && customization_plugin.respond_to?(:customize_clone_spec)
397
+ clone_spec = customization_plugin.customize_clone_spec(src_config, clone_spec)
398
+ end
399
+
400
+ clone_spec.customization = cust_spec
401
+ end
402
+ clone_spec
403
+ end
404
+
405
+ # Loads the customization plugin if one was specified
406
+ # @return [KnifeVspherePlugin] the loaded and initialized plugin or nil
407
+ def customization_plugin
408
+ if @customization_plugin.nil?
409
+ if cplugin_path = get_config(:customization_plugin)
410
+ if File.exists? cplugin_path
411
+ require cplugin_path
412
+ else
413
+ abort "Customization plugin could not be found at #{cplugin_path}"
414
+ end
415
+
416
+ if Object.const_defined? 'KnifeVspherePlugin'
417
+ @customization_plugin = Object.const_get('KnifeVspherePlugin').new
418
+ if cplugin_data = get_config(:customization_plugin_data)
419
+ if @customization_plugin.respond_to?(:data=)
420
+ @customization_plugin.data = cplugin_data
421
+ else
422
+ abort "Customization plugin has no :data= accessor to receive the --cplugin-data argument. Define both or neither."
423
+ end
424
+ end
425
+ else
426
+ abort "KnifeVspherePlugin class is not defined in #{cplugin_path}"
427
+ end
428
+ end
429
+ end
430
+
431
+ @customization_plugin
432
+ end
433
+
434
+ # Retrieves a CustomizationSpecItem that matches the supplied name
435
+ # @param vim [Connection] VI Connection to use
436
+ # @param name [String] name of customization
437
+ # @return [RbVmomi::VIM::CustomizationSpecItem]
438
+ def find_customization(name)
439
+ csm = config[:vim].serviceContent.customizationSpecManager
440
+ csm.GetCustomizationSpec(:name => name)
441
+ end
442
+
443
+ # Generates a CustomizationAdapterMapping (currently only single IPv4 address) object
444
+ # @param ip [String] Any static IP address to use, otherwise DHCP
445
+ # @param gw [String] If static, the gateway for the interface, otherwise network address + 1 will be used
446
+ # @return [RbVmomi::VIM::CustomizationIPSettings]
447
+ def generate_adapter_map (ip=nil, gw=nil, dns1=nil, dns2=nil, domain=nil)
448
+
449
+ settings = RbVmomi::VIM.CustomizationIPSettings
450
+
451
+ if ip.nil?
452
+ settings.ip = RbVmomi::VIM::CustomizationDhcpIpGenerator
453
+ else
454
+ cidr_ip = NetAddr::CIDR.create(ip)
455
+ settings.ip = RbVmomi::VIM::CustomizationFixedIp(:ipAddress => cidr_ip.ip)
456
+ settings.subnetMask = cidr_ip.netmask_ext
457
+
458
+ # TODO - want to confirm gw/ip are in same subnet?
459
+ # Only set gateway on first IP.
460
+ if config[:customization_ips].split(',').first == ip
461
+ if gw.nil?
462
+ settings.gateway = [cidr_ip.network(:Objectify => true).next_ip]
463
+ else
464
+ gw_cidr = NetAddr::CIDR.create(gw)
465
+ settings.gateway = [gw_cidr.ip]
466
+ end
467
+ end
468
+ end
469
+
470
+ adapter_map = RbVmomi::VIM.CustomizationAdapterMapping
471
+ adapter_map.adapter = settings
472
+ adapter_map
473
+ end
474
+
475
+ def bootstrap_for_node()
476
+ Chef::Knife::Bootstrap.load_deps
477
+ bootstrap = Chef::Knife::Bootstrap.new
478
+ bootstrap.name_args = [config[:fqdn]]
479
+ bootstrap.config[:run_list] = get_config(:run_list).split(/[\s,]+/)
480
+ bootstrap.config[:secret_file] = get_config(:secret_file)
481
+ bootstrap.config[:ssh_user] = get_config(:ssh_user)
482
+ bootstrap.config[:ssh_password] = get_config(:ssh_password)
483
+ bootstrap.config[:ssh_port] = get_config(:ssh_port)
484
+ bootstrap.config[:identity_file] = get_config(:identity_file)
485
+ bootstrap.config[:chef_node_name] = get_config(:chef_node_name)
486
+ bootstrap.config[:prerelease] = get_config(:prerelease)
487
+ bootstrap.config[:bootstrap_version] = get_config(:bootstrap_version)
488
+ bootstrap.config[:distro] = get_config(:distro)
489
+ bootstrap.config[:use_sudo] = true unless get_config(:ssh_user) == 'root'
490
+ bootstrap.config[:template_file] = get_config(:template_file)
491
+ bootstrap.config[:environment] = get_config(:environment)
492
+ bootstrap.config[:first_boot_attributes] = get_config(:first_boot_attributes)
493
+ bootstrap.config[:log_level] = get_config(:log_level)
494
+ # may be needed for vpc_mode
495
+ bootstrap.config[:no_host_key_verify] = get_config(:no_host_key_verify)
496
+ bootstrap
497
+ end
498
+
499
+ def tcp_test_ssh(hostname)
500
+ tcp_socket = TCPSocket.new(hostname, get_config(:ssh_port))
501
+ readable = IO.select([tcp_socket], nil, nil, 5)
502
+ if readable
503
+ Chef::Log.debug("sshd accepting connections on #{hostname}, banner is #{tcp_socket.gets}")
504
+ true
505
+ else
506
+ false
507
+ end
508
+ rescue Errno::ETIMEDOUT
509
+ false
510
+ rescue Errno::EPERM
511
+ false
512
+ rescue Errno::ECONNREFUSED
513
+ sleep 2
514
+ false
515
+ rescue Errno::EHOSTUNREACH, Errno::ENETUNREACH
516
+ sleep 2
517
+ false
518
+ ensure
519
+ tcp_socket && tcp_socket.close
520
+ end
521
+ end
@@ -1,47 +1,46 @@
1
- # Author:: Brian Dupras (<bdupras@rallydev.com>)
2
- # License:: Apache License, Version 2.0
3
-
4
- require 'chef/knife'
5
- require 'chef/knife/base_vsphere_command'
6
- require 'rbvmomi'
7
- require 'netaddr'
8
-
9
- class Chef::Knife::VsphereVmConfig < Chef::Knife::BaseVsphereCommand
10
- banner "knife vsphere vm config VMNAME PROPERTY VALUE. See \"http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.ConfigSpec.html\" for allowed ATTRIBUTE values (any property of type xs:string is supported)."
11
-
12
- get_common_options
13
-
14
- def run
15
- $stdout.sync = true
16
- vmname = @name_args[0]
17
- if vmname.nil?
18
- show_usage
19
- fatal_exit("You must specify a virtual machine name")
20
- end
21
-
22
- property_name = @name_args[1]
23
- if property_name.nil?
24
- show_usage
25
- fatal_exit("You must specify a PROPERTY name (e.g. annotation)")
26
- end
27
- property_name = property_name.to_sym
28
-
29
- property_value = @name_args[2]
30
- if property_value.nil?
31
- show_usage
32
- fatal_exit("You must specify a PROPERTY value")
33
- end
34
-
35
- vim = get_vim_connection
36
-
37
- dc = get_datacenter
38
- folder = find_folder(get_config(:folder)) || dc.vmFolder
39
-
40
- vm = find_in_folder(folder, RbVmomi::VIM::VirtualMachine, vmname) or
41
- abort "VM #{vmname} not found"
42
-
43
- properties = {}
44
- properties[property_name] = property_value
45
- vm.ReconfigVM_Task(:spec => RbVmomi::VIM.VirtualMachineConfigSpec(properties)).wait_for_completion
46
- end
47
- end
1
+ # Author:: Brian Dupras (<bdupras@rallydev.com>)
2
+ # License:: Apache License, Version 2.0
3
+
4
+ require 'chef/knife'
5
+ require 'chef/knife/base_vsphere_command'
6
+ require 'rbvmomi'
7
+ require 'netaddr'
8
+
9
+ class Chef::Knife::VsphereVmConfig < Chef::Knife::BaseVsphereCommand
10
+ banner "knife vsphere vm config VMNAME PROPERTY VALUE. See \"http://pubs.vmware.com/vi3/sdk/ReferenceGuide/vim.vm.ConfigSpec.html\" for allowed ATTRIBUTE values (any property of type xs:string is supported)."
11
+
12
+ get_common_options
13
+
14
+ def run
15
+ $stdout.sync = true
16
+ vmname = @name_args[0]
17
+ if vmname.nil?
18
+ show_usage
19
+ fatal_exit("You must specify a virtual machine name")
20
+ end
21
+
22
+ property_name = @name_args[1]
23
+ if property_name.nil?
24
+ show_usage
25
+ fatal_exit("You must specify a PROPERTY name (e.g. annotation)")
26
+ end
27
+ property_name = property_name.to_sym
28
+
29
+ property_value = @name_args[2]
30
+ if property_value.nil?
31
+ show_usage
32
+ fatal_exit("You must specify a PROPERTY value")
33
+ end
34
+
35
+ vim = get_vim_connection
36
+
37
+ dc = get_datacenter
38
+ folder = find_folder(get_config(:folder)) || dc.vmFolder
39
+
40
+ vm = traverse_folders_for_vm(folder, vmname) or abort "VM #{vmname} not found"
41
+
42
+ properties = {}
43
+ properties[property_name] = property_value
44
+ vm.ReconfigVM_Task(:spec => RbVmomi::VIM.VirtualMachineConfigSpec(properties)).wait_for_completion
45
+ end
46
+ end
@@ -1,4 +1,4 @@
1
1
  module KnifeVsphere
2
- VERSION = "0.9.8"
2
+ VERSION = "0.9.9"
3
3
  end
4
4
 
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: knife-vsphere
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 0.9.8
5
+ version: 0.9.9
6
6
  platform: ruby
7
7
  authors:
8
8
  - Ezra Pagel
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2014-01-30 00:00:00 -06:00
13
+ date: 2014-03-27 00:00:00 -05:00
14
14
  default_executable:
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency