rvc 1.0.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.
Files changed (43) hide show
  1. data/LICENSE +19 -0
  2. data/README.rdoc +120 -0
  3. data/Rakefile +39 -0
  4. data/TODO +4 -0
  5. data/VERSION +1 -0
  6. data/bin/rvc +104 -0
  7. data/lib/rvc.rb +31 -0
  8. data/lib/rvc/completion.rb +110 -0
  9. data/lib/rvc/extensions/ClusterComputeResource.rb +42 -0
  10. data/lib/rvc/extensions/ComputeResource.rb +47 -0
  11. data/lib/rvc/extensions/Datacenter.rb +36 -0
  12. data/lib/rvc/extensions/Datastore.rb +188 -0
  13. data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +38 -0
  14. data/lib/rvc/extensions/DistributedVirtualSwitch.rb +40 -0
  15. data/lib/rvc/extensions/Folder.rb +33 -0
  16. data/lib/rvc/extensions/HostSystem.rb +48 -0
  17. data/lib/rvc/extensions/ManagedEntity.rb +28 -0
  18. data/lib/rvc/extensions/Network.rb +28 -0
  19. data/lib/rvc/extensions/ResourcePool.rb +52 -0
  20. data/lib/rvc/extensions/VirtualMachine.rb +72 -0
  21. data/lib/rvc/fs.rb +123 -0
  22. data/lib/rvc/inventory.rb +125 -0
  23. data/lib/rvc/modules.rb +74 -0
  24. data/lib/rvc/modules/basic.rb +276 -0
  25. data/lib/rvc/modules/datastore.rb +63 -0
  26. data/lib/rvc/modules/host.rb +29 -0
  27. data/lib/rvc/modules/resource_pool.rb +95 -0
  28. data/lib/rvc/modules/vim.rb +128 -0
  29. data/lib/rvc/modules/vm.rb +607 -0
  30. data/lib/rvc/modules/vmrc.rb +72 -0
  31. data/lib/rvc/modules/vnc.rb +111 -0
  32. data/lib/rvc/option_parser.rb +114 -0
  33. data/lib/rvc/path.rb +37 -0
  34. data/lib/rvc/readline-ffi.rb +41 -0
  35. data/lib/rvc/shell.rb +220 -0
  36. data/lib/rvc/ttl_cache.rb +44 -0
  37. data/lib/rvc/util.rb +168 -0
  38. data/test/_test_completion.rb +27 -0
  39. data/test/_test_option_parser.rb +6 -0
  40. data/test/inventory_fixtures.rb +15 -0
  41. data/test/test_fs.rb +113 -0
  42. data/test/test_parse_path.rb +41 -0
  43. metadata +146 -0
@@ -0,0 +1,128 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ URI_REGEX = %r{
22
+ ^
23
+ (?:
24
+ ([^@:]+)
25
+ (?::
26
+ ([^@]*)
27
+ )?
28
+ @
29
+ )?
30
+ ([^@:]+)
31
+ (?::(.*))?
32
+ $
33
+ }x
34
+
35
+ opts :connect do
36
+ summary 'Open a connection to ESX/VC'
37
+ arg :uri, "Host to connect to"
38
+ opt :insecure, "don't verify ssl certificate", :short => 'k', :default => (ENV['RBVMOMI_INSECURE'] == '1')
39
+ end
40
+
41
+ rvc_alias :connect
42
+
43
+ def connect uri, opts
44
+ match = URI_REGEX.match uri
45
+ Trollop.die "invalid hostname" unless match
46
+
47
+ username = match[1] || ENV['RBVMOMI_USER']
48
+ password = match[2] || ENV['RBVMOMI_PASSWORD']
49
+ host = match[3]
50
+ path = match[4]
51
+ insecure = opts[:insecure]
52
+
53
+ vim = nil
54
+ loop do
55
+ begin
56
+ vim = RbVmomi::VIM.new :host => host,
57
+ :port => 443,
58
+ :path => '/sdk',
59
+ :ns => 'urn:vim25',
60
+ :rev => '4.0',
61
+ :ssl => true,
62
+ :insecure => insecure
63
+ break
64
+ rescue OpenSSL::SSL::SSLError
65
+ err "Connection failed" unless prompt_insecure
66
+ insecure = true
67
+ rescue Errno::EHOSTUNREACH, SocketError
68
+ err $!.message
69
+ end
70
+ end
71
+
72
+ # negotiate API version
73
+ rev = vim.serviceContent.about.apiVersion
74
+ vim.rev = [rev, '4.1'].min
75
+ isVC = vim.serviceContent.about.apiType == "VirtualCenter"
76
+
77
+ # authenticate
78
+ if username == nil
79
+ username = isVC ? 'Administrator' : 'root'
80
+ puts "Using default username #{username.inspect}."
81
+ end
82
+
83
+ password_given = password != nil
84
+ loop do
85
+ begin
86
+ password = prompt_password unless password_given
87
+ vim.serviceContent.sessionManager.Login :userName => username,
88
+ :password => password
89
+ break
90
+ rescue RbVmomi::VIM::InvalidLogin
91
+ err $!.message if password_given
92
+ end
93
+ end
94
+
95
+ Thread.new do
96
+ while true
97
+ sleep 600
98
+ vim.serviceInstance.RetrieveServiceContent
99
+ end
100
+ end
101
+
102
+ # Stash the address we used to connect so VMRC can use it.
103
+ vim.define_singleton_method(:_host) { host }
104
+
105
+ conn_name = host.dup
106
+ conn_name = "#{conn_name}:1" if $shell.connections.member? conn_name
107
+ conn_name.succ! while $shell.connections.member? conn_name
108
+
109
+ $shell.connections[conn_name] = vim
110
+ end
111
+
112
+ def prompt_password
113
+ system "stty -echo"
114
+ $stdout.write "password: "
115
+ $stdout.flush
116
+ begin
117
+ ($stdin.gets||exit(1)).chomp
118
+ ensure
119
+ system "stty echo"
120
+ puts
121
+ end
122
+ end
123
+
124
+ def prompt_insecure
125
+ answer = Readline.readline "SSL certificate verification failed. Connect anyway? (y/n) "
126
+ answer == 'yes' or answer == 'y'
127
+ end
128
+
@@ -0,0 +1,607 @@
1
+ # Copyright (c) 2011 VMware, Inc. All Rights Reserved.
2
+ #
3
+ # Permission is hereby granted, free of charge, to any person obtaining a copy
4
+ # of this software and associated documentation files (the "Software"), to deal
5
+ # in the Software without restriction, including without limitation the rights
6
+ # to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
7
+ # copies of the Software, and to permit persons to whom the Software is
8
+ # furnished to do so, subject to the following conditions:
9
+ #
10
+ # The above copyright notice and this permission notice shall be included in
11
+ # all copies or substantial portions of the Software.
12
+ #
13
+ # THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
14
+ # IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
15
+ # FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
16
+ # AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
17
+ # LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
18
+ # OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
19
+ # THE SOFTWARE.
20
+
21
+ opts :on do
22
+ summary "Power on VMs"
23
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
24
+ end
25
+
26
+ rvc_alias :on
27
+
28
+ def on vms
29
+ tasks vms, :PowerOnVM
30
+ end
31
+
32
+
33
+ opts :off do
34
+ summary "Power off VMs"
35
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
36
+ end
37
+
38
+ rvc_alias :off
39
+
40
+ def off vms
41
+ tasks vms, :PowerOffVM
42
+ end
43
+
44
+
45
+ opts :reset do
46
+ summary "Reset VMs"
47
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
48
+ end
49
+
50
+ rvc_alias :reset
51
+ rvc_alias :reset, :r
52
+
53
+ def reset vms
54
+ tasks vms, :ResetVM
55
+ end
56
+
57
+
58
+ opts :suspend do
59
+ summary "Suspend VMs"
60
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
61
+ end
62
+
63
+ rvc_alias :suspend
64
+ rvc_alias :suspend, :s
65
+
66
+ def suspend vms
67
+ tasks vms, :SuspendVM
68
+ end
69
+
70
+
71
+ opts :create do
72
+ summary "Create a new VM"
73
+ arg :name, "Name"
74
+ opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
75
+ opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
76
+ opt :datastore, "Datastore", :short => 'd', :type => :string, :lookup => VIM::Datastore
77
+ end
78
+
79
+ def create name, opts
80
+ err "must specify resource pool (--pool)" unless opts[:pool]
81
+ err "must specify datastore (--datastore)" unless opts[:datastore]
82
+ vmFolder = lookup!(File.dirname(name), VIM::Folder)
83
+ datastore_path = "[#{opts[:datastore].name}]"
84
+ config = {
85
+ :name => File.basename(name),
86
+ :guestId => 'otherGuest',
87
+ :files => { :vmPathName => datastore_path },
88
+ :numCPUs => 1,
89
+ :memoryMB => 128,
90
+ :deviceChange => [
91
+ {
92
+ :operation => :add,
93
+ :device => VIM.VirtualLsiLogicController(
94
+ :key => 1000,
95
+ :busNumber => 0,
96
+ :sharedBus => :noSharing
97
+ )
98
+ }, {
99
+ :operation => :add,
100
+ :fileOperation => :create,
101
+ :device => VIM.VirtualDisk(
102
+ :key => 0,
103
+ :backing => VIM.VirtualDiskFlatVer2BackingInfo(
104
+ :fileName => datastore_path,
105
+ :diskMode => :persistent,
106
+ :thinProvisioned => true
107
+ ),
108
+ :controllerKey => 1000,
109
+ :unitNumber => 0,
110
+ :capacityInKB => 4000000
111
+ )
112
+ }, {
113
+ :operation => :add,
114
+ :device => VIM.VirtualCdrom(
115
+ :key => 0,
116
+ :connectable => {
117
+ :allowGuestControl => true,
118
+ :connected => true,
119
+ :startConnected => true,
120
+ },
121
+ :backing => VIM.VirtualCdromIsoBackingInfo(
122
+ :fileName => datastore_path
123
+ ),
124
+ :controllerKey => 200,
125
+ :unitNumber => 0
126
+ )
127
+ }, {
128
+ :operation => :add,
129
+ :device => VIM.VirtualE1000(
130
+ :key => 0,
131
+ :deviceInfo => {
132
+ :label => 'Network Adapter 1',
133
+ :summary => 'VM Network'
134
+ },
135
+ :backing => VIM.VirtualEthernetCardNetworkBackingInfo(
136
+ :deviceName => 'VM Network'
137
+ ),
138
+ :addressType => 'generated'
139
+ )
140
+ }
141
+ ],
142
+ }
143
+ vmFolder.CreateVM_Task(:config => config,
144
+ :pool => opts[:pool],
145
+ :host => opts[:host]).wait_for_completion
146
+ end
147
+
148
+
149
+ opts :insert_cdrom do
150
+ summary "Put a disc in a virtual CDROM drive"
151
+ arg :vm, nil, :lookup => VIM::VirtualMachine
152
+ arg :iso, "Path to the ISO image on a datastore", :lookup => VIM::Datastore::FakeDatastoreFile
153
+ end
154
+
155
+ def insert_cdrom vm, iso
156
+ device = vm.config.hardware.device.grep(VIM::VirtualCdrom)[0]
157
+ err "No virtual CDROM drive found" unless device
158
+
159
+ device.backing = VIM.VirtualCdromIsoBackingInfo(:fileName => iso.datastore_path)
160
+
161
+ spec = {
162
+ :deviceChange => [
163
+ {
164
+ :operation => :edit,
165
+ :device => device
166
+ }
167
+ ]
168
+ }
169
+
170
+ vm.ReconfigVM_Task(:spec => spec)
171
+ end
172
+
173
+ opts :register do
174
+ summary "Register a VM already in a datastore"
175
+ arg :file, "RVC path to the VMX file", :lookup => VIM::Datastore::FakeDatastoreFile
176
+ opt :resource_pool, 'Resource pool', :short => 'R', :type => :string, :lookup => VIM::ResourcePool
177
+ opt :folder, 'VM Folder', :short => 'F', :default => ".", :lookup => VIM::Folder
178
+ end
179
+
180
+ def register vmx_file, opts
181
+ rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool
182
+ vm = opts[:folder].RegisterVM_Task(:path => vmx_file.datastore_path,
183
+ :asTemplate => false,
184
+ :pool => rp).wait_for_completion
185
+ end
186
+
187
+
188
+ opts :unregister do
189
+ summary "Unregister a VM"
190
+ arg :vm, nil, :lookup => VIM::VirtualMachine
191
+ end
192
+
193
+ def unregister vm
194
+ vm.UnregisterVM
195
+ end
196
+
197
+
198
+ opts :kill do
199
+ summary "Power off and destroy VMs"
200
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
201
+ end
202
+
203
+ rvc_alias :kill
204
+ rvc_alias :kill, :k
205
+
206
+ def kill vms
207
+ on_vms = vms.select { |x| x.summary.runtime.powerState == 'poweredOn' }
208
+ off on_vms unless on_vms.empty?
209
+ CMD.basic.destroy vms unless vms.empty?
210
+ end
211
+
212
+
213
+ opts :answer do
214
+ summary "Answer a VM question"
215
+ arg :vm, nil, :lookup => VIM::VirtualMachine
216
+ arg :choice, "Answer ID"
217
+ end
218
+
219
+ def answer vm, str
220
+ choice = q.choice.choiceInfo.find { |x| x.label == str }
221
+ err("invalid answer") unless choice
222
+ vm.AnswerVM :questionid => q.path, :answerChoice => choice.key
223
+ end
224
+
225
+
226
+ opts :layout do
227
+ summary "Display info about VM files"
228
+ arg :vm, nil, :lookup => VIM::VirtualMachine
229
+ end
230
+
231
+ def layout vm
232
+ vm.layoutEx.file.each do |f|
233
+ puts "#{f.type}: #{f.name}"
234
+ end
235
+ end
236
+
237
+
238
+ opts :devices do
239
+ summary "Display info about VM devices"
240
+ arg :vm, nil, :lookup => VIM::VirtualMachine
241
+ end
242
+
243
+ def devices vm
244
+ devs = vm.config.hardware.device
245
+ devs.each do |dev|
246
+ tags = []
247
+ tags << (dev.connectable.connected ? :connected : :disconnected) if dev.props.member? :connectable
248
+ puts "#{dev.deviceInfo.label} (#{dev.class}): #{dev.deviceInfo.summary}; #{tags * ' '}"
249
+ end
250
+ end
251
+
252
+
253
+ opts :connect do
254
+ summary "Connect a virtual device"
255
+ arg :vm, nil, :lookup => VIM::VirtualMachine
256
+ arg :label, "Device label"
257
+ end
258
+
259
+ def connect vm, label
260
+ change_device_connectivity vm, label, true
261
+ end
262
+
263
+
264
+ opts :disconnect do
265
+ summary "Disconnect a virtual device"
266
+ arg :vm, nil, :lookup => VIM::VirtualMachine
267
+ arg :label, "Device label"
268
+ end
269
+
270
+ def disconnect vm, label
271
+ change_device_connectivity vm, label, false
272
+ end
273
+
274
+
275
+ opts :find do
276
+ summary "Display a menu of VMX files to register"
277
+ arg :datastore, nil, :lookup => VIM::Datastore
278
+ opt :resource_pool, "Resource pool", :short => 'R', :type => :string, :lookup => VIM::ResourcePool
279
+ opt :folder, "Folder to register in", :short => 'F', :type => :string, :default => ".", :lookup => VIM::Folder
280
+ end
281
+
282
+ def find ds, opts
283
+ folder = opts[:folder]
284
+ rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool
285
+
286
+ paths = find_vmx_files(ds)
287
+ if paths.empty?
288
+ puts "no VMX files found"
289
+ return
290
+ end
291
+
292
+ puts "Select a VMX file"
293
+ path = menu(paths) or return
294
+
295
+ folder.RegisterVM_Task(:path => path,
296
+ :asTemplate => false,
297
+ :pool => rp).wait_for_completion
298
+ end
299
+
300
+
301
+ opts :extraConfig do
302
+ summary "Display extraConfig options"
303
+ arg :vm, nil, :lookup => VIM::VirtualMachine
304
+ arg :regex, "Regexes to filter keys", :multi => true, :required => false
305
+ end
306
+
307
+ def extraConfig vm, regexes
308
+ _extraConfig(vm, *regexes.map { |x| /#{x}/ })
309
+ end
310
+
311
+
312
+ opts :setExtraConfig do
313
+ summary "Set extraConfig options"
314
+ arg :vm, nil, :lookup => VIM::VirtualMachine
315
+ arg 'key=value', "extraConfig key/value pairs", :multi => true
316
+ end
317
+
318
+ def setExtraConfig vm, pairs
319
+ h = Hash[pairs.map { |x| x.split('=', 2).tap { |a| a << '' if a.size == 1 } }]
320
+ _setExtraConfig vm, h
321
+ end
322
+
323
+
324
+ def _setExtraConfig vm, hash
325
+ cfg = {
326
+ :extraConfig => hash.map { |k,v| { :key => k, :value => v } },
327
+ }
328
+ vm.ReconfigVM_Task(:spec => cfg).wait_for_completion
329
+ end
330
+
331
+ def _extraConfig vm, *regexes
332
+ vm.config.extraConfig.each do |h|
333
+ if regexes.empty? or regexes.any? { |r| h[:key] =~ r }
334
+ puts "#{h[:key]}: #{h[:value]}"
335
+ end
336
+ end
337
+ nil
338
+ end
339
+
340
+
341
+ opts :ssh do
342
+ summary "SSH to a VM"
343
+ arg :vm, nil, :lookup => VIM::VirtualMachine
344
+ end
345
+
346
+ rvc_alias :ssh
347
+
348
+ def ssh vm
349
+ ip = vm_ip vm
350
+ ssh_cmd = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{ip}"
351
+ system_fg(ssh_cmd)
352
+ end
353
+
354
+
355
+ opts :rvc do
356
+ summary "RVC to a VM"
357
+ arg :vm, nil, :lookup => VIM::VirtualMachine
358
+ end
359
+
360
+ rvc_alias :rvc
361
+
362
+ def rvc vm
363
+ ip = vm_ip vm
364
+
365
+ env = Hash[%w(RBVMOMI_PASSWORD RBVMOMI_HOST RBVMOMI_USER RBVMOMI_SSL RBVMOMI_PORT
366
+ RBVMOMI_FOLDER RBVMOMI_DATASTORE RBVMOMI_PATH RBVMOMI_DATACENTER
367
+ RBVMOMI_COMPUTER).map { |k| [k,nil] }]
368
+ cmd = "rvc #{ip}"
369
+ system_fg(cmd, env)
370
+ end
371
+
372
+
373
+ opts :ping do
374
+ summary "Ping a VM"
375
+ arg :vm, nil, :lookup => VIM::VirtualMachine
376
+ end
377
+
378
+ rvc_alias :ping
379
+
380
+ def ping vm
381
+ ip = vm_ip vm
382
+ system_fg "ping #{ip}"
383
+ end
384
+
385
+
386
+ opts :ip do
387
+ summary "Wait for and display VM IP addresses"
388
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
389
+ end
390
+
391
+ def ip vms
392
+ props = %w(summary.runtime.powerState summary.guest.ipAddress summary.config.annotation)
393
+ connection = single_connection vms
394
+
395
+ filters = vms.map do |vm|
396
+ connection.propertyCollector.CreateFilter :spec => {
397
+ :propSet => [{ :type => 'VirtualMachine', :all => false, :pathSet => props }],
398
+ :objectSet => [{ :obj => vm }],
399
+ }, :partialUpdates => false
400
+ end
401
+
402
+ ver = ''
403
+ while not vms.empty?
404
+ result = connection.propertyCollector.WaitForUpdates(:version => ver)
405
+ ver = result.version
406
+
407
+ vms.reject! do |vm|
408
+ begin
409
+ ip = vm_ip(vm)
410
+ puts "#{vm.name}: #{ip}"
411
+ true
412
+ rescue UserError
413
+ false
414
+ end
415
+ end
416
+ end
417
+ ensure
418
+ filters.each(&:DestroyPropertyFilter) if filters
419
+ end
420
+
421
+
422
+ opts :add_net_device do
423
+ summary "Add a network adapter to a virtual machine"
424
+ arg :vm, nil, :lookup => VIM::VirtualMachine
425
+ opt :type, "Adapter type", :default => 'e1000'
426
+ opt :network, "Network to connect to", :default => 'VM Network'
427
+ end
428
+
429
+ def add_net_device vm, opts
430
+ case opts[:type]
431
+ when 'e1000'
432
+ _add_net_device vm, VIM::VirtualE1000, opts[:network]
433
+ when 'vmxnet3'
434
+ _add_net_device vm, VIM::VirtualVmxnet3, opts[:network]
435
+ else err "unknown device"
436
+ end
437
+ end
438
+
439
+
440
+ def _add_device vm, dev
441
+ spec = {
442
+ :deviceChange => [
443
+ { :operation => :add, :device => dev },
444
+ ]
445
+ }
446
+ vm.ReconfigVM_Task(:spec => spec).wait_for_completion
447
+ end
448
+
449
+ def _add_net_device vm, klass, network
450
+ _add_device vm, klass.new(
451
+ :key => -1,
452
+ :deviceInfo => {
453
+ :summary => network,
454
+ :label => `uuidgen`.chomp
455
+ },
456
+ :backing => VIM.VirtualEthernetCardNetworkBackingInfo(
457
+ :deviceName => network
458
+ ),
459
+ :addressType => 'generated'
460
+ )
461
+ end
462
+
463
+
464
+ opts :remove_device do
465
+ summary "Remove a virtual device"
466
+ arg :vm, nil, :lookup => VIM::VirtualMachine
467
+ arg :label, "Device label"
468
+ end
469
+
470
+ def remove_device vm, label
471
+ dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
472
+ err "no such device" unless dev
473
+ spec = {
474
+ :deviceChange => [
475
+ { :operation => :remove, :device => dev },
476
+ ]
477
+ }
478
+ vm.ReconfigVM_Task(:spec => spec).wait_for_completion
479
+ end
480
+
481
+
482
+ opts :snapshot do
483
+ summary "Snapshot a VM"
484
+ arg :vm, nil, :lookup => VIM::VirtualMachine
485
+ arg :name, "Name of new snapshot"
486
+ end
487
+
488
+ def snapshot vm, name
489
+ tasks [vm], :CreateSnapshot, :memory => true, :name => name, :quiesce => false
490
+ end
491
+
492
+
493
+ # TODO make fake folder
494
+ opts :snapshots do
495
+ summary "Display VM snapshot tree"
496
+ arg :vm, nil, :lookup => VIM::VirtualMachine
497
+ end
498
+
499
+ def snapshots vm
500
+ _display_snapshot_tree vm.snapshot.rootSnapshotList, 0
501
+ end
502
+
503
+ def _display_snapshot_tree nodes, indent
504
+ nodes.each do |node|
505
+ puts "#{' '*indent}#{node.name} #{node.createTime}"
506
+ _display_snapshot_tree node.childSnapshotList, (indent+1)
507
+ end
508
+ end
509
+
510
+
511
+ opts :revert do
512
+ summary "Revert a VM to its current snapshot"
513
+ arg :vm, nil, :lookup => VIM::VirtualMachine
514
+ end
515
+
516
+ def revert vm
517
+ tasks [vm], :RevertToCurrentSnapshot
518
+ end
519
+
520
+
521
+ opts :migrate do
522
+ summary "Migrate a VM"
523
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
524
+ opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
525
+ opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
526
+ end
527
+
528
+ def migrate vms, opts
529
+ tasks vms, :MigrateVM, :pool => opts[:pool],
530
+ :host => opts[:host],
531
+ :priority => :defaultPriority
532
+ end
533
+
534
+
535
+ opts :clone do
536
+ summary "Clone a VM"
537
+ arg :src, nil, :lookup => VIM::VirtualMachine
538
+ arg :dst, "Path to new VM"
539
+ opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
540
+ opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
541
+ opt :template, "Create a template"
542
+ opt :powerOn, "Power on VM after clone"
543
+ end
544
+
545
+ def clone src, dst, opts
546
+ folder = lookup! File.dirname(dst), VIM::Folder
547
+ task = src.CloneVM_Task(:folder => folder,
548
+ :name => File.basename(dst),
549
+ :spec => {
550
+ :location => {
551
+ :host => opts[:host],
552
+ :pool => opts[:pool],
553
+ },
554
+ :template => opts[:template],
555
+ :powerOn => opts[:powerOn],
556
+ })
557
+ progress [task]
558
+ end
559
+
560
+
561
+ def find_vmx_files ds
562
+ datastorePath = "[#{ds.name}] /"
563
+ searchSpec = {
564
+ :details => { :fileOwner => false, :fileSize => false, :fileType => true, :modification => false },
565
+ :query => [
566
+ VIM::VmConfigFileQuery()
567
+ ]
568
+ }
569
+ task = ds.browser.SearchDatastoreSubFolders_Task(:datastorePath => datastorePath, :searchSpec => searchSpec)
570
+
571
+ results = task.wait_for_completion
572
+
573
+ files = []
574
+ results.each do |result|
575
+ result.file.each do |file|
576
+ files << result.folderPath + file.path
577
+ end
578
+ end
579
+
580
+ files
581
+ end
582
+
583
+ def change_device_connectivity id, label, connected
584
+ dev = vm(id).config.hardware.device.find { |x| x.deviceInfo.label == label }
585
+ err "no such device" unless dev
586
+ dev.connectable.connected = connected
587
+ spec = {
588
+ :deviceChange => [
589
+ { :operation => :edit, :device => dev },
590
+ ]
591
+ }
592
+ vm(id).ReconfigVM_Task(:spec => spec).wait_for_completion
593
+ end
594
+
595
+ def vm_ip vm
596
+ summary = vm.summary
597
+
598
+ err "VM is not powered on" unless summary.runtime.powerState == 'poweredOn'
599
+
600
+ ip = if summary.guest.ipAddress and summary.guest.ipAddress != '127.0.0.1'
601
+ summary.guest.ipAddress
602
+ elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note.member? 'ip'
603
+ note['ip']
604
+ else
605
+ err "no IP known for this VM"
606
+ end
607
+ end