rvc 1.7.0 → 1.8.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.
@@ -0,0 +1,103 @@
1
+ # Copyright (c) 2013 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
+ require 'rvc/vim'
22
+
23
+ begin
24
+ require 'net/ssh'
25
+ rescue LoadError
26
+ end
27
+
28
+ opts :config_syslog do
29
+ summary "Configure Syslog"
30
+ arg :entity, nil, :lookup => [VIM, VIM::HostSystem, VIM::ComputeResource, VIM::ClusterComputeResource]
31
+ arg :ip, nil, :type => :string
32
+ opt :vc_root_pwd, "VC root password for SSH access", :default => "vmware"
33
+ end
34
+
35
+ def config_syslog entity, ip, opts
36
+ if entity.is_a?(VIM)
37
+ puts "#{Time.now}: Finding all Hosts inside VC"
38
+ $shell.fs.marks['vcrvc'] = entity
39
+ hosts = []
40
+ hosts += $shell.fs.lookup("~vcrvc/*/computers/*/host")
41
+ hosts += $shell.fs.lookup("~vcrvc/*/computers/*/hosts/*")
42
+ elsif entity.is_a?(VIM::ComputeResource)
43
+ hosts = entity.host
44
+ else
45
+ hosts = [entity]
46
+ end
47
+ if hosts.length == 0
48
+ err "No hosts found"
49
+ end
50
+ conn = hosts.first._connection
51
+ pc = conn.propertyCollector
52
+
53
+ lock = Mutex.new
54
+ hosts_props = pc.collectMultiple(hosts,
55
+ 'name',
56
+ 'runtime.connectionState',
57
+ )
58
+ connected_hosts = hosts_props.select do |k,v|
59
+ v['runtime.connectionState'] == 'connected'
60
+ end.keys
61
+ host = connected_hosts.first
62
+ if !connected_hosts.first
63
+ err "Couldn't find any connected hosts"
64
+ end
65
+
66
+ puts "#{Time.now}: Configuring all ESX hosts ..."
67
+ loghost = "udp://#{ip}:514"
68
+ hosts.map do |host|
69
+ Thread.new do
70
+ begin
71
+ c1 = conn.spawn_additional_connection
72
+ host = host.dup_on_conn(c1)
73
+ hostName = host.name
74
+ lock.synchronize do
75
+ puts "#{Time.now}: Configuring syslog on #{hostName}"
76
+ end
77
+ syslog = host.esxcli.system.syslog
78
+ syslog.config.set(:loghost => loghost)
79
+ syslog.reload
80
+ rescue Exception => ex
81
+ puts "#{Time.now}: #{host.name}: Got exception: #{ex.class}: #{ex.message}"
82
+ end
83
+ end
84
+ end.each{|t| t.join}
85
+ puts "#{Time.now}: Done configuring syslog on all hosts"
86
+
87
+ local = "#{File.dirname(__FILE__)}/configurevCloudSuiteSyslog.sh"
88
+ osType = conn.serviceContent.about.osType
89
+ if File.exists?(local) && osType == "linux-x64"
90
+ puts "#{Time.now}: Configuring VCVA ..."
91
+ Net::SSH.start(conn.host, "root", :password => opts[:vc_root_pwd],
92
+ :paranoid => false) do |ssh|
93
+ ssh.scp.upload!(local, "/tmp/configurevCloudSuiteSyslog.sh")
94
+ cmd = "sh /tmp/configurevCloudSuiteSyslog.sh vcsa #{ip}"
95
+ puts "#{Time.now}: Running '#{cmd}' on VCVA"
96
+ puts ssh.exec!(cmd)
97
+ end
98
+ puts "#{Time.now}: Done with VC"
99
+ else
100
+ puts "#{Time.now}: VC isn't Linux, skipping ..."
101
+ end
102
+ puts "#{Time.now}: Done"
103
+ end
@@ -636,10 +636,10 @@ end
636
636
  opts :create_vmknic do
637
637
  summary "Create a vmknic on vDS on one or more hosts. Always uses DHCP"
638
638
  arg :portgroup, nil, :lookup => VIM::Network
639
- arg :host, nil, :lookup => VIM::HostSystem, :multi => true
639
+ arg :hosts, nil, :lookup => VIM::HostSystem, :multi => true
640
640
  end
641
641
 
642
- def create_vmknic portgroup, hosts, opts
642
+ def create_vmknic portgroup, hosts
643
643
  if !portgroup.is_a?(VIM::DistributedVirtualPortgroup)
644
644
  err "Legacy switches not supported yet"
645
645
  end
@@ -660,3 +660,60 @@ def create_vmknic portgroup, hosts, opts
660
660
  puts "Host #{host.name}: Added vmknic #{vmknic_name}"
661
661
  end
662
662
  end
663
+
664
+ opts :migrate_vmknic do
665
+ summary "Migrate a vmknic from a standard vSwitch to a vDS."
666
+ arg :host, nil, :lookup => VIM::HostSystem
667
+ arg :vmknic, nil
668
+ arg :portgroup, nil, :lookup => VIM::Network
669
+ end
670
+
671
+ def migrate_vmknic host, vmknic, portgroup
672
+ if !portgroup.is_a?(VIM::DistributedVirtualPortgroup)
673
+ err "Only migration from standard to a distributed vSwitch is supported for now."
674
+ end
675
+ ns = host.configManager.networkSystem
676
+
677
+ # Find the VMkernel interface.
678
+ vmk_dev = nil
679
+ ns.networkInfo.vnic.each do |vnic|
680
+ if vnic.device == vmknic
681
+ vmk_dev = vnic
682
+ break
683
+ end
684
+ end
685
+
686
+ # Find the source portgroup.
687
+ src_pg = nil
688
+ ns.networkInfo.portgroup.each do |pg|
689
+ if pg.spec.name == vmk_dev.portgroup
690
+ src_pg = pg
691
+ break
692
+ end
693
+ end
694
+
695
+ ns.UpdateNetworkConfig(
696
+ changeMode: 'modify',
697
+ config: VIM::HostNetworkConfig(
698
+ portgroup: [
699
+ VIM::HostPortGroupConfig(
700
+ changeOperation: 'remove',
701
+ spec: src_pg.spec
702
+ )
703
+ ],
704
+ vnic: [
705
+ VIM::HostVirtualNicConfig(
706
+ changeOperation: 'edit',
707
+ device: vmknic,
708
+ portgroup: src_pg.spec.name,
709
+ spec: VIM::HostVirtualNicSpec(
710
+ distributedVirtualPort: VIM::DistributedVirtualSwitchPortConnection(
711
+ portgroupKey: portgroup.key,
712
+ switchUuid: portgroup.config.distributedVirtualSwitch.uuid
713
+ )
714
+ )
715
+ )
716
+ ]
717
+ )
718
+ )
719
+ end
@@ -81,7 +81,7 @@ def connect uri, opts
81
81
  if env_rev && env_rev.to_f == 0
82
82
  vim.rev = env_rev
83
83
  else
84
- vim.rev = [rev, env_rev || '5.1'].min
84
+ vim.rev = [rev, env_rev || '5.5'].min
85
85
  end
86
86
  end
87
87
 
@@ -103,7 +103,25 @@ opts :shutdown_guest do
103
103
  end
104
104
 
105
105
  def shutdown_guest vms, opts
106
- vms.each(&:ShutdownGuest)
106
+ conn = vms.first._connection
107
+ pc = conn.propertyCollector
108
+ vmsProps = pc.collectMultiple(vms, 'runtime.powerState',
109
+ 'guest.toolsRunningStatus', 'name')
110
+ vms.each do |vm|
111
+ if vmsProps[vm]['runtime.powerState'] != 'poweredOn'
112
+ puts "VM #{vmsProps[vm]['name']} not powered on, skipping"
113
+ next
114
+ end
115
+ if vmsProps[vm]['guest.toolsRunningStatus'] != 'guestToolsRunning'
116
+ puts "VM #{vmsProps[vm]['name']} doesn't have tools running, skipping"
117
+ next
118
+ end
119
+ begin
120
+ vm.ShutdownGuest
121
+ rescue Exception => ex
122
+ puts "VM #{vmsProps[vm]['name']}: #{ex.class}: #{ex.message}"
123
+ end
124
+ end
107
125
  wait_for_shutdown vms, opts unless opts[:timeout].nil?
108
126
  end
109
127
 
@@ -274,7 +292,16 @@ rvc_alias :kill
274
292
  rvc_alias :kill, :k
275
293
 
276
294
  def kill vms
277
- on_vms = vms.select { |x| x.summary.runtime.powerState == 'poweredOn' }
295
+ on_vms = []
296
+ vms.each do |vm|
297
+ begin
298
+ if vm.summary.runtime.powerState == 'poweredOn'
299
+ on_vms.push(vm)
300
+ end
301
+ rescue Exception => ex
302
+ puts "vm.rb:kill: Skipping current object #{vm} due to Exception: #{ex.message}"
303
+ end
304
+ end
278
305
  off on_vms unless on_vms.empty?
279
306
  shell.cmds.basic.destroy vms unless vms.empty?
280
307
  end
@@ -516,6 +543,7 @@ opts :clone do
516
543
  opt :host, "Host", :short => 'o', :type => :string, :lookup => VIM::HostSystem
517
544
  opt :template, "Create a template", :short => 't'
518
545
  opt :linked, "Create a linked clone", :short => 'l'
546
+ opt :customizationspecname, "Customization spec", :type => :string, :short => 's'
519
547
  opt :power_on, "Power on VM after clone"
520
548
  end
521
549
 
@@ -528,9 +556,19 @@ def clone src, dst, opts
528
556
  diskMoveType = :moveChildMostDiskBacking
529
557
  end
530
558
 
559
+ if opts[:customizationspecname]
560
+ begin
561
+ spec = src._connection.serviceContent.customizationSpecManager.GetCustomizationSpec(:name => opts[:customizationspecname]).spec
562
+ rescue
563
+ print "Customization Spec '#{opts[:customizationspecname]}' not found\n"
564
+ return
565
+ end
566
+ end
567
+
531
568
  task = src.CloneVM_Task(:folder => folder,
532
569
  :name => name,
533
570
  :spec => {
571
+ :customization => spec,
534
572
  :location => {
535
573
  :diskMoveType => diskMoveType,
536
574
  :host => opts[:host],
@@ -574,15 +612,26 @@ def annotate vm, str
574
612
  vm.ReconfigVM_Task(:spec => { :annotation => str }).wait_for_completion
575
613
  end
576
614
 
577
-
578
615
  opts :modify_cpu do
579
616
  summary "Change CPU configuration"
580
617
  arg :vm, nil, :lookup => VIM::VirtualMachine
581
618
  opt :num, "New number of CPUs", :type => :int, :required => true
619
+ opt :reservation, "Reservation in Mhz", :type => :int
620
+ opt :limit, "Limit in Mhz. -1 means no limit", :type => :int
582
621
  end
583
622
 
584
623
  def modify_cpu vm, opts
585
- spec = { :numCPUs => opts[:num] }
624
+ alloc, = vm.collect 'config.cpuAllocation'
625
+ if opts[:reservation]
626
+ alloc.reservation = opts[:reservation]
627
+ end
628
+ if opts[:limit]
629
+ alloc.limit = opts[:limit]
630
+ end
631
+ spec = {
632
+ :numCPUs => opts[:num],
633
+ :cpuAllocation => alloc,
634
+ }
586
635
  tasks [vm], :ReconfigVM, :spec => spec
587
636
  end
588
637
 
@@ -591,12 +640,26 @@ opts :modify_memory do
591
640
  summary "Change memory configuration"
592
641
  arg :vm, nil, :lookup => VIM::VirtualMachine
593
642
  opt :size, "New memory size in MB", :type => :int, :required => true
643
+ opt :reservation, "Reservation in MB", :type => :int
644
+ opt :limit, "Limit in MB. -1 means no limit", :type => :int
594
645
  end
595
646
 
596
647
  def modify_memory vm, opts
597
- err "VM needs to be off" unless vm.summary.runtime.powerState == 'poweredOff'
598
648
  err "memory must be a multiple of 4MB" unless ( opts[:size] % 4 ) == 0
599
- spec = { :memoryMB => opts[:size] }
649
+
650
+ #alloc, = vm.collect 'config.memoryAllocation'
651
+ alloc = VIM::ResourceAllocationInfo()
652
+ if opts[:reservation]
653
+ alloc.reservation = opts[:reservation]
654
+ end
655
+ if opts[:limit]
656
+ alloc.limit = opts[:limit]
657
+ end
658
+
659
+ spec = {
660
+ :memoryMB => opts[:size],
661
+ :memoryAllocation => alloc,
662
+ }
600
663
  tasks [vm], :ReconfigVM, :spec => spec
601
664
  end
602
665
 
@@ -630,7 +693,7 @@ def vm_ip vm
630
693
 
631
694
  ip = if summary.guest.ipAddress and summary.guest.ipAddress != '127.0.0.1'
632
695
  summary.guest.ipAddress
633
- elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note.member? 'ip'
696
+ elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note['ip']
634
697
  note['ip']
635
698
  else
636
699
  err "no IP known for this VM"
@@ -1,30 +1,54 @@
1
+ # Copyright (c) 2013 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
+ require 'rvc/vim'
22
+
1
23
  opts :authenticate do
2
24
  summary "Authenticate within guest"
3
- arg :vm, nil, :lookup => VIM::VirtualMachine
25
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
4
26
  opt :interactive_session, "Allow command to interact with desktop", :default => false, :type => :bool
5
27
  opt :password, "Password in guest", :type => :string
6
28
  opt :username, "Username in guest", :default => "root", :type => :string
7
29
  end
8
30
 
9
- def authenticate vm, opts
10
- auth = ((@auths ||= {})[vm] ||= {})[opts[:username]]
11
-
12
- if opts[:password].nil? or opts[:password].empty?
13
- opts[:password] = ask("password: ") { |q| q.echo = false }
14
- end
15
-
16
- auth = VIM.NamePasswordAuthentication(
17
- :username => opts[:username],
18
- :password => opts[:password],
19
- :interactiveSession => opts[:interactive_session]
20
- )
21
-
22
- @auths[vm][opts[:username]] = auth
23
- begin
24
- check_auth vm, opts
25
- rescue
26
- clear_auth vm, opts
27
- err "Could not authenticate: #{$!}"
31
+ def authenticate vms, opts
32
+ vms.each do |vm|
33
+ auth = ((@auths ||= {})[vm] ||= {})[opts[:username]]
34
+
35
+ if opts[:password].nil? or opts[:password].empty?
36
+ opts[:password] = ask("password: ") { |q| q.echo = false }
37
+ end
38
+
39
+ auth = VIM.NamePasswordAuthentication(
40
+ :username => opts[:username],
41
+ :password => opts[:password],
42
+ :interactiveSession => opts[:interactive_session]
43
+ )
44
+
45
+ @auths[vm][opts[:username]] = auth
46
+ begin
47
+ check_auth vm, opts
48
+ rescue
49
+ clear_auth vm, opts
50
+ err "Could not authenticate: #{$!}"
51
+ end
28
52
  end
29
53
  end
30
54
 
@@ -240,6 +264,67 @@ def rmfile vm, opts
240
264
  )
241
265
  end
242
266
 
267
+ def generic_http_download uri, local_path
268
+ http = Net::HTTP.new(uri.host, uri.port)
269
+ http.use_ssl = true
270
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
271
+ #http.set_debug_output $stderr
272
+ http.start
273
+
274
+ # headers = { 'cookie' => connection.cookie }
275
+ headers = {}
276
+ http_path = "#{uri.path}?#{uri.query}"
277
+ http.request_get(http_path, headers) do |res|
278
+ case res
279
+ when Net::HTTPOK
280
+ len = res.content_length
281
+ count = 0
282
+ File.open(local_path, 'wb') do |io|
283
+ res.read_body do |segment|
284
+ count += segment.length
285
+ io.write segment
286
+ $stdout.write "\e[0G\e[Kdownloading #{count}/#{len} bytes (#{(count*100)/len}%)"
287
+ $stdout.flush
288
+ end
289
+ end
290
+ $stdout.puts
291
+ else
292
+ err "download failed: #{res.message}"
293
+ end
294
+ end
295
+ end
296
+
297
+ def generic_http_upload local_path, uri, size = nil
298
+ http = Net::HTTP.new(uri.host, uri.port)
299
+ http.use_ssl = true
300
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
301
+ #http.set_debug_output $stderr
302
+ http.start
303
+
304
+
305
+ open(local_path, 'rb') do |io|
306
+ stream = ProgressStream.new(io, size || io.stat.size) do |s|
307
+ $stdout.write "\e[0G\e[Kuploading #{s.count}/#{s.len} bytes (#{(s.count*100)/s.len}%)"
308
+ $stdout.flush
309
+ end
310
+
311
+ headers = {
312
+ 'content-length' => (size || io.stat.size).to_s,
313
+ 'Content-Type' => 'application/octet-stream',
314
+ }
315
+ http_path = "#{uri.path}?#{uri.query}"
316
+
317
+ request = Net::HTTP::Put.new http_path, headers
318
+ request.body_stream = stream
319
+ res = http.request(request)
320
+ $stdout.puts
321
+ case res
322
+ when Net::HTTPOK
323
+ else
324
+ err "upload failed: #{res.message}"
325
+ end
326
+ end
327
+ end
243
328
 
244
329
  opts :download_file do
245
330
  summary "Download file from guest"
@@ -265,14 +350,14 @@ def download_file vm, opts
265
350
 
266
351
  download_uri = URI.parse(download_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
267
352
  download_path = "#{download_uri.path}?#{download_uri.query}"
268
-
269
- http_download vm._connection, download_path, opts[:local_path]
353
+
354
+ generic_http_download download_uri, opts[:local_path]
270
355
  end
271
356
 
272
357
 
273
358
  opts :upload_file do
274
359
  summary "Upload file to guest"
275
- arg :vm, nil, :lookup => VIM::VirtualMachine
360
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
276
361
  opt :group_id, "Group ID of file", :type => :int
277
362
  opt :guest_path, "Path in guest to upload to", :required => true, :type => :string
278
363
  opt :local_path, "Local file to upload", :required => true, :type => :string
@@ -282,35 +367,45 @@ opts :upload_file do
282
367
  opt :username, "Username in guest", :default => "root", :type => :string
283
368
  end
284
369
 
285
- def upload_file vm, opts
286
- guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
287
- err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
288
- fileManager = guestOperationsManager.fileManager
289
-
290
- opts[:permissions] = opts[:permissions].to_i(8) if opts[:permissions]
291
-
292
- auth = get_auth vm, opts
293
-
294
- file = File.new(opts[:local_path], 'rb')
295
-
296
- upload_url = fileManager.
297
- InitiateFileTransferToGuest(
298
- :vm => vm,
299
- :auth => auth,
300
- :guestFilePath => opts[:guest_path],
301
- :fileAttributes => VIM.GuestPosixFileAttributes(
302
- :groupId => opts[:group_id],
303
- :ownerId => opts[:owner_id],
304
- :permissions => opts[:permissions]
305
- ),
306
- :fileSize => file.size,
307
- :overwrite => opts[:overwrite]
308
- )
309
-
310
- upload_uri = URI.parse(upload_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
311
- upload_path = "#{upload_uri.path}?#{upload_uri.query}"
312
-
313
- http_upload vm._connection, opts[:local_path], upload_path
370
+ def upload_file vms, opts
371
+ vms.each do |vm|
372
+ guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
373
+ err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :fileManager
374
+ fileManager = guestOperationsManager.fileManager
375
+
376
+ opts[:permissions] = opts[:permissions].to_i(8) if opts[:permissions]
377
+
378
+ auth = get_auth vm, opts
379
+
380
+ file_size = nil
381
+ if opts[:local_path] =~ /^http:/
382
+ # XXX: Not acceptable for big files
383
+ file_size = open(opts[:local_path], 'rb').read.length
384
+ else
385
+ err "local file does not exist" unless File.exists? local_path
386
+ file = File.new(opts[:local_path], 'rb')
387
+ file_size = file.size
388
+ end
389
+
390
+ upload_url = fileManager.
391
+ InitiateFileTransferToGuest(
392
+ :vm => vm,
393
+ :auth => auth,
394
+ :guestFilePath => opts[:guest_path],
395
+ :fileAttributes => VIM.GuestPosixFileAttributes(
396
+ :groupId => opts[:group_id],
397
+ :ownerId => opts[:owner_id],
398
+ :permissions => opts[:permissions]
399
+ ),
400
+ :fileSize => file_size,
401
+ :overwrite => opts[:overwrite]
402
+ )
403
+
404
+ upload_uri = URI.parse(upload_url.gsub /http(s?):\/\/\*:[0-9]*/, "")
405
+ upload_path = "#{upload_uri.path}?#{upload_uri.query}"
406
+
407
+ generic_http_upload opts[:local_path], upload_uri, file_size
408
+ end
314
409
  end
315
410
 
316
411
 
@@ -431,7 +526,7 @@ end
431
526
  # Process commands
432
527
  opts :start_program do
433
528
  summary "Run program in guest"
434
- arg :vm, nil, :lookup => VIM::VirtualMachine
529
+ arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
435
530
  opt :arguments, "Arguments of command", :default => "", :type => :string
436
531
  opt :background, "Don't wait for process to finish", :default => false, :type => :bool
437
532
  opt :delay, "Interval in seconds", :type => :float, :default => 5.0
@@ -444,47 +539,51 @@ opts :start_program do
444
539
  conflicts :background, :delay
445
540
  end
446
541
 
447
- def start_program vm, opts
448
- guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
449
- err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :processManager
450
- processManager = guestOperationsManager.processManager
451
-
452
- auth = get_auth vm, opts
453
-
454
- pid = processManager.
455
- StartProgramInGuest(
456
- :vm => vm,
457
- :auth => auth,
458
- :spec => VIM.GuestProgramSpec(
459
- :arguments => opts[:arguments],
460
- :programPath => opts[:program_path],
461
- :envVariables => opts[:env],
462
- :workingDirectory => opts[:working_directory]
463
- )
464
- )
465
-
466
- Timeout.timeout opts[:timeout] do
467
- while true
468
- processes = processManager.
469
- ListProcessesInGuest(
470
- :vm => vm,
471
- :auth => auth,
472
- :pids => [pid]
542
+ def start_program vms, opts
543
+ vms.each do |vm|
544
+ guestOperationsManager = vm._connection.serviceContent.guestOperationsManager
545
+ err "This command requires vSphere 5 or greater" unless guestOperationsManager.respond_to? :processManager
546
+ processManager = guestOperationsManager.processManager
547
+
548
+ auth = get_auth vm, opts
549
+
550
+ pid = processManager.
551
+ StartProgramInGuest(
552
+ :vm => vm,
553
+ :auth => auth,
554
+ :spec => VIM.GuestProgramSpec(
555
+ :arguments => opts[:arguments],
556
+ :programPath => opts[:program_path],
557
+ :envVariables => opts[:env],
558
+ :workingDirectory => opts[:working_directory]
473
559
  )
474
- process = processes.first
475
-
476
- if !process.endTime.nil?
477
- if process.exitCode != 0
478
- err "Process failed with exit code #{process.exitCode}"
560
+ )
561
+
562
+ begin
563
+ Timeout.timeout opts[:timeout] do
564
+ while true
565
+ processes = processManager.
566
+ ListProcessesInGuest(
567
+ :vm => vm,
568
+ :auth => auth,
569
+ :pids => [pid]
570
+ )
571
+ process = processes.first
572
+
573
+ if !process.endTime.nil?
574
+ if process.exitCode != 0
575
+ err "Process failed with exit code #{process.exitCode}"
576
+ end
577
+ break
578
+ elsif opts[:background]
579
+ break
580
+ end
581
+
582
+ sleep opts[:delay]
479
583
  end
480
- break
481
- elsif opts[:background]
482
- break
483
584
  end
484
-
485
- sleep opts[:delay]
585
+ rescue Timeout::Error
586
+ err "Timed out waiting for process to finish."
486
587
  end
487
588
  end
488
- rescue Timeout::Error
489
- err "Timed out waiting for process to finish."
490
589
  end