knife-vsphere 0.9.8 → 0.9.9

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