Top Level Namespace

Defined Under Namespace

Modules: RVC Classes: ProgressStream

Constant Summary collapse

URI_REGEX =
%r{
  ^
  (?:
    ([^@:]+)
    (?::
     ([^@]*)
    )?
    @
  )?
  ([^@:]+)
  (?::(.*))?
  $
}x
VNC =

Copyright © 2011 VMware, Inc. All Rights Reserved.

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the “Software”), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

ENV['VNC'] || search_path('vinagre') || search_path('tightvnc')
VMRC_BASENAME =
"#{VMRC_NAME}.xpi"
VMRC_URL =
"http://cloud.github.com/downloads/vmware/rvc/#{VMRC_BASENAME}"
HELP_ORDER =
%w(basic vm)

Instance Method Summary collapse

Instance Method Details

#_add_device(vm, dev) ⇒ Object



476
477
478
479
480
481
482
483
# File 'lib/rvc/modules/vm.rb', line 476

def _add_device vm, dev
  spec = {
    :deviceChange => [
      { :operation => :add, :device => dev },
    ]
  }
  vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end

#_add_net_device(vm, klass, network) ⇒ Object



485
486
487
488
489
490
491
492
493
494
495
496
497
# File 'lib/rvc/modules/vm.rb', line 485

def _add_net_device vm, klass, network
  _add_device vm, klass.new(
    :key => -1,
    :deviceInfo => {
      :summary => network,
      :label => `uuidgen`.chomp
    },
    :backing => VIM.VirtualEthernetCardNetworkBackingInfo(
      :deviceName => network
    ),
    :addressType => 'generated'
  )
end

#_display_snapshot_tree(nodes, indent) ⇒ Object



542
543
544
545
546
547
# File 'lib/rvc/modules/vm.rb', line 542

def _display_snapshot_tree nodes, indent
  nodes.each do |node|
    puts "#{' '*indent}#{node.name} #{node.createTime}"
    _display_snapshot_tree node.childSnapshotList, (indent+1)
  end
end

#_extraConfig(vm, *regexes) ⇒ Object



364
365
366
367
368
369
370
371
# File 'lib/rvc/modules/vm.rb', line 364

def _extraConfig vm, *regexes
  vm.config.extraConfig.each do |h|
    if regexes.empty? or regexes.any? { |r| h[:key] =~ r }
      puts "#{h[:key]}: #{h[:value]}"
    end
  end
  nil
end

#_setExtraConfig(vm, hash) ⇒ Object



357
358
359
360
361
362
# File 'lib/rvc/modules/vm.rb', line 357

def _setExtraConfig vm, hash
  cfg = {
    :extraConfig => hash.map { |k,v| { :key => k, :value => v } },
  }
  vm.ReconfigVM_Task(:spec => cfg).wait_for_completion
end

#add_host(cluster, hostname, opts) ⇒ Object



40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
# File 'lib/rvc/modules/cluster.rb', line 40

def add_host cluster, hostname, opts
  sslThumbprint = nil
  while true
    spec = {
      :force => false,
      :hostName => hostname,
      :userName => opts[:username],
      :password => opts[:password],
      :sslThumbprint => sslThumbprint,
    }
    task = cluster.AddHost_Task :spec => spec,
                                :asConnected => false
    begin
      task.wait_for_completion
    rescue VIM::SSLVerifyFault
      puts "SSL thumbprint: #{$!.fault.thumbprint}"
      $stdout.write "Accept this thumbprint? (y/n) "
      $stdout.flush
      answer = $stdin.readline.chomp
      err "Aborted" unless answer == 'y' or answer == 'yes'
      sslThumbprint = $!.fault.thumbprint
    end
  end
end

#add_iscsi_target(hosts, opts) ⇒ Object



130
131
132
133
134
135
136
137
138
139
140
141
142
# File 'lib/rvc/modules/host.rb', line 130

def add_iscsi_target hosts, opts
  hosts.each do |host|
    puts "configuring host #{host.name}"
    storage = host.configManager.storageSystem
    storage.UpdateSoftwareInternetScsiEnabled(enabled: true)
    adapter = storage.storageDeviceInfo.hostBusAdapter.grep(VIM::HostInternetScsiHba)[0]
    storage.AddInternetScsiStaticTargets(
      iScsiHbaDevice: adapter.device,
      targets: [ VIM::HostInternetScsiHbaStaticTarget(address: opts[:address], iScsiName: opts[:iqn]) ]
    )
    storage.RescanAllHba
  end
end

#add_net_device(vm, opts) ⇒ Object



465
466
467
468
469
470
471
472
473
# File 'lib/rvc/modules/vm.rb', line 465

def add_net_device vm, opts
  case opts[:type]
  when 'e1000'
    _add_net_device vm, VIM::VirtualE1000, opts[:network]
  when 'vmxnet3'
    _add_net_device vm, VIM::VirtualVmxnet3, opts[:network]
  else err "unknown device"
  end
end

#add_privilege(name, privileges) ⇒ Object



93
94
95
96
97
98
99
100
# File 'lib/rvc/modules/role.rb', line 93

def add_privilege name, privileges
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  cur_auth_mgr.UpdateAuthorizationRole :roleId => role.roleId,
                                       :newName => role.name,
                                       :privIds => (role.privilege | privileges)

end

#annotate(vm, str) ⇒ Object



637
638
639
# File 'lib/rvc/modules/vm.rb', line 637

def annotate vm, str
  vm.ReconfigVM_Task(:spec => { :annotation => str }).wait_for_completion
end

#answer(vm, str) ⇒ Object



252
253
254
255
256
# File 'lib/rvc/modules/vm.rb', line 252

def answer vm, str
  choice = q.choice.choiceInfo.find { |x| x.label == str }
  err("invalid answer") unless choice
  vm.AnswerVM :questionid => q.path, :answerChoice => choice.key
end

#cd(obj) ⇒ Object



133
134
135
136
137
138
# File 'lib/rvc/modules/basic.rb', line 133

def cd obj
  $shell.fs.cd(obj)
  $shell.session.set_mark '', [find_ancestor(RbVmomi::VIM::Datacenter)].compact
  $shell.session.set_mark '@', [find_ancestor(RbVmomi::VIM)].compact
  $shell.delete_numeric_marks
end

#change_device_connectivity(vm, label, connected) ⇒ Object



664
665
666
667
668
669
670
671
672
673
674
# File 'lib/rvc/modules/vm.rb', line 664

def change_device_connectivity vm, label, connected
  dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
  err "no such device" unless dev
  dev.connectable.connected = connected
  spec = {
    :deviceChange => [
      { :operation => :edit, :device => dev },
    ]
  }
  vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end

#check_known_hosts(host, peer_public_key) ⇒ Object



178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
# File 'lib/rvc/modules/vim.rb', line 178

def check_known_hosts host, peer_public_key
  known_hosts = RVC::KnownHosts.new
  result, arg = known_hosts.verify 'vim', host, peer_public_key.to_s

  if result == :not_found
    puts "The authenticity of host '#{host}' can't be established."
    puts "Public key fingerprint is #{arg}."
    err "Connection failed" unless agree("Are you sure you want to continue connecting (y/n)? ", true)
    puts "Warning: Permanently added '#{host}' (vim) to the list of known hosts"
    known_hosts.add 'vim', host, peer_public_key.to_s
  elsif result == :mismatch
    err "Public key fingerprint for host '#{host}' does not match #{known_hosts.filename}:#{arg}."
  elsif result == :ok
  else
    err "Unexpected result from known_hosts check"
  end
end

#clone(src, dst, opts) ⇒ Object



585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
# File 'lib/rvc/modules/vm.rb', line 585

def clone src, dst, opts
  folder, name = *dst
  diskMoveType = nil

  if opts[:linked]
    deltaize_disks src
    diskMoveType = :moveChildMostDiskBacking
  end

  task = src.CloneVM_Task(:folder => folder,
                          :name => name,
                          :spec => {
                            :location => {
                              :diskMoveType => diskMoveType,
                              :host => opts[:host],
                              :pool => opts[:pool],
                            },
                            :template => opts[:template],
                            :powerOn => opts[:powerOn],
                          })
  progress [task]
end

#connect(uri, opts) ⇒ Object



292
293
294
# File 'lib/rvc/modules/vm.rb', line 292

def connect vm, label
  change_device_connectivity vm, label, true
end

#create(name, parent, opts) ⇒ Object



112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
# File 'lib/rvc/modules/vm.rb', line 112

def create dest, opts
  err "must specify resource pool (--pool)" unless opts[:pool]
  err "must specify datastore (--datastore)" unless opts[:datastore]
  vmFolder, name = *dest
  datastore_path = "[#{opts[:datastore].name}]"
  config = {
    :name => name,
    :guestId => 'otherGuest',
    :files => { :vmPathName => datastore_path },
    :numCPUs => opts[:cpucount],
    :memoryMB => opts[:memory],
    :deviceChange => [
      {
        :operation => :add,
        :device => VIM.VirtualLsiLogicController(
          :key => 1000,
          :busNumber => 0,
          :sharedBus => :noSharing
        )
      }, {
        :operation => :add,
        :fileOperation => :create,
        :device => VIM.VirtualDisk(
          :key => -1,
          :backing => VIM.VirtualDiskFlatVer2BackingInfo(
            :fileName => datastore_path,
            :diskMode => :persistent,
            :thinProvisioned => true
          ),
          :controllerKey => 1000,
          :unitNumber => 0,
          :capacityInKB => opts[:disksize]
        )
      }, {
        :operation => :add,
        :device => VIM.VirtualCdrom(
          :key => -2,
          :connectable => {
            :allowGuestControl => true,
            :connected => true,
            :startConnected => true,
          },
          :backing => VIM.VirtualCdromIsoBackingInfo(
            :fileName => datastore_path
          ),
          :controllerKey => 200,
          :unitNumber => 0
        )
      }, {
        :operation => :add,
        :device => VIM.VirtualE1000(
          :key => -3,
          :deviceInfo => {
            :label => 'Network Adapter 1',
            :summary => 'VM Network'
          },
          :backing => VIM.VirtualEthernetCardNetworkBackingInfo(
            :deviceName => 'VM Network'
          ),
          :addressType => 'generated'
        )
      }
    ],
  }
  vmFolder.CreateVM_Task(:config => config,
                         :pool => opts[:pool],
                         :host => opts[:host]).wait_for_completion
end

#cur_auth_mgrObject



1
2
3
4
# File 'lib/rvc/modules/role.rb', line 1

def cur_auth_mgr
  conn = $shell.fs.cur._connection
  conn.serviceContent.authorizationManager
end

#debugObject



94
95
96
97
98
99
# File 'lib/rvc/modules/basic.rb', line 94

def debug
  debug = $shell.debug = !$shell.debug
  $shell.connections.each do |name,conn|
    conn.debug = debug
  end
end

#delete(name, opts) ⇒ Object



65
66
67
68
69
# File 'lib/rvc/modules/role.rb', line 65

def delete name, opts
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{role_name.inspect}" unless role
  cur_auth_mgr.RemoveAuthorizationRole :roleId => role.roleId, :failIfUsed => opts[:force]
end

#deltaize_disks(vm) ⇒ Object



609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
# File 'lib/rvc/modules/vm.rb', line 609

def deltaize_disks vm
  real_disks = vm.config.hardware.device.grep(VIM::VirtualDisk).select { |x| x.backing.parent == nil }
  unless real_disks.empty?
    puts "Reconfiguring source VM to use delta disks..."
    deviceChange = []
    real_disks.each do |disk|
      deviceChange << { :operation => :remove, :device => disk }
      deviceChange << {
        :operation => :add,
        :fileOperation => :create,
        :device => disk.dup.tap { |x|
          x.backing = x.backing.dup
          x.backing.fileName = "[#{disk.backing.datastore.name}]"
          x.backing.parent = disk.backing
        }
      }
    end
    progress [vm.ReconfigVM_Task(:spec => { :deviceChange => deviceChange })]
  end
end

#destroy(objs) ⇒ Object



223
224
225
# File 'lib/rvc/modules/basic.rb', line 223

def destroy objs
  tasks objs, :Destroy
end

#devices(vm) ⇒ Object



276
277
278
279
280
281
282
283
# File 'lib/rvc/modules/vm.rb', line 276

def devices vm
  devs = vm.config.hardware.device
  devs.each do |dev|
    tags = []
    tags << (dev.connectable.connected ? :connected : :disconnected) if dev.props.member? :connectable
    puts "#{dev.deviceInfo.label} (#{dev.class}): #{dev.deviceInfo.summary}; #{tags * ' '}"
  end
end

#disconnect(connection) ⇒ Object



303
304
305
# File 'lib/rvc/modules/vm.rb', line 303

def disconnect vm, label
  change_device_connectivity vm, label, false
end

#download(file, local_path) ⇒ Object



119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
# File 'lib/rvc/modules/vmrc.rb', line 119

def download url_str, dest
  puts "Downloading VMRC..."

  url = URI.parse(url_str)

  http = if ENV['http_proxy']
    proxy_uri = URI.parse(ENV['http_proxy'])
    proxy_user, proxy_pass = proxy_uri.userinfo.split(/:/) if proxy_uri.userinfo
    Net::HTTP::Proxy(proxy_uri.host, proxy_uri.port, proxy_user, proxy_pass)
  else
    Net::HTTP
  end

  begin
    File.open(dest, 'wb') do |io|
      res = http.start(url.host, url.port) do |http|
        http.get(url.path) do |segment|
          io.write segment
        end
      end
    end
  rescue Exception
    err "Error downloading VMRC: #{$!.class}: #{$!.message}"
  end
end

#edit(file) ⇒ Object



43
44
45
46
47
48
49
50
51
52
53
54
55
56
# File 'lib/rvc/modules/mark.rb', line 43

def edit key
  editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
  objs = $shell.session.get_mark(key) or err "no such mark #{key.inspect}"
  filename = File.join(Dir.tmpdir, "rvc.#{Time.now.to_i}.#{rand(65536)}")
  File.open(filename, 'w') { |io| objs.each { |obj| io.puts(obj.rvc_path_str) } }
  begin
    system("#{editor} #{filename}")
    new_paths = File.readlines(filename).map(&:chomp) rescue return
    new_objs = new_paths.map { |path| lookup(path) }.inject([], &:+)
    mark key, new_objs
  ensure
    File.unlink filename
  end
end

#enter_maintenance_mode(hosts, opts) ⇒ Object



81
82
83
# File 'lib/rvc/modules/host.rb', line 81

def enter_maintenance_mode hosts, opts
  tasks hosts, :EnterMaintenanceMode, :timeout => opts[:timeout]
end

#evacuate(src, dsts, opts) ⇒ Object



39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
# File 'lib/rvc/modules/host.rb', line 39

def evacuate src, dsts, opts
  vim = src._connection
  vms = src.vm
  dst_hosts = dsts.map(&:host).flatten
  checks = ['cpu', 'software']

  dst_hosts.reject! { |host| host == src ||
                             host.runtime.connectionState != 'connected' ||
                             host.runtime.inMaintenanceMode }

  candidates = {}
  vms.each do |vm|
    required_datastores = vm.datastore
    result = vim.serviceInstance.QueryVMotionCompatibility(:vm => vm,
                                                           :host => dst_hosts,
                                                           :compatibility => checks)
    result.reject! { |x| x.compatibility != checks ||
                         x.host.datastore & required_datastores != required_datastores }
    candidates[vm] = result.map { |x| x.host }
  end

  if candidates.any? { |vm,hosts| hosts.empty? }
    puts "The following VMs have no compatible vMotion destination:"
    candidates.select { |vm,hosts| hosts.empty? }.each { |vm,hosts| puts " #{vm.name}" }
    return
  end

  tasks = candidates.map do |vm,hosts|
    host = hosts[rand(hosts.size)]
    vm.MigrateVM_Task(:host => host, :priority => :defaultPriority)
  end

  progress tasks
end

#events(obj, opts) ⇒ Object



309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
# File 'lib/rvc/modules/basic.rb', line 309

def events obj, opts
  err "'events' not supported at this level" unless obj.respond_to?(:_connection)
  manager = obj._connection.serviceContent.eventManager
  @event_details ||= Hash[manager.collect("description.eventInfo").first.collect { |d| [d.key, d] }]

  spec = VIM::EventFilterSpec(:entity => VIM::EventFilterSpecByEntity(:entity => obj, :recursion => "all"))

  collector = manager.CreateCollectorForEvents(:filter => spec)
  collector.SetCollectorPageSize(:maxCount => opts[:lines])
  collector.latestPage.reverse.each do |event|
    time = event.createdTime.localtime.strftime("%m/%d/%Y %I:%M %p")
    category = @event_details[event.class.to_s].category
    puts "[#{time}] [#{category}] #{event.fullFormattedMessage.strip}"
  end
ensure
  collector.DestroyCollector if collector
end

#exit_maintenance_mode(hosts, opts) ⇒ Object



92
93
94
# File 'lib/rvc/modules/host.rb', line 92

def exit_maintenance_mode hosts, opts
  tasks hosts, :ExitMaintenanceMode, :timeout => opts[:timeout]
end

#extraConfig(vm, regexes) ⇒ Object



340
341
342
# File 'lib/rvc/modules/vm.rb', line 340

def extraConfig vm, regexes
  _extraConfig(vm, *regexes.map { |x| /#{x}/ })
end

#extract(src, dst) ⇒ Object



151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
# File 'lib/rvc/modules/vmrc.rb', line 151

def extract src, dst
  puts "Installing VMRC..."
  FileUtils.mkdir_p dst
  Zip::ZipFile.open(src) do |zf|
    zf.each do |e|
      dst_filename = File.join(dst, e.name)
      case e.ftype
      when :file
        FileUtils.mkdir_p File.dirname(dst_filename)
        zf.extract e.name, dst_filename
        File.chmod(e.unix_perms, dst_filename) if e.unix_perms
      when :directory
        FileUtils.mkdir_p dst_filename
      else
        $stderr.puts "unknown file type #{e.ftype}"
      end
    end
  end
end

#find(ds, opts) ⇒ Object



315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
# File 'lib/rvc/modules/vm.rb', line 315

def find ds, opts
  folder = opts[:folder]
  rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool

  paths = find_vmx_files(ds)
  if paths.empty?
    puts "no VMX files found"
    return
  end

  puts "Select a VMX file"
  path = menu(paths) or return

  folder.RegisterVM_Task(:path => path,
                         :asTemplate => false,
                         :pool => rp).wait_for_completion
end

#find_ancestor(klass) ⇒ Object



140
141
142
# File 'lib/rvc/modules/basic.rb', line 140

def find_ancestor klass
  $shell.fs.cur.rvc_path.map { |k,v| v }.reverse.find { |x| x.is_a? klass }
end

#find_local_vmrcObject



45
46
47
48
49
# File 'lib/rvc/modules/vmrc.rb', line 45

def find_local_vmrc
  return nil if VMRC_NAME.nil?
  path = File.join(Dir.tmpdir, VMRC_NAME, 'plugins', VMRC_BIN)
  File.exists?(path) && path
end

#find_vmrcObject



51
52
53
# File 'lib/rvc/modules/vmrc.rb', line 51

def find_vmrc
  find_local_vmrc || search_path('vmrc')
end

#find_vmx_files(ds) ⇒ Object



642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
# File 'lib/rvc/modules/vm.rb', line 642

def find_vmx_files ds
  datastorePath = "[#{ds.name}] /"
  searchSpec = {
    :details => { :fileOwner => false, :fileSize => false, :fileType => true, :modification => false  },
    :query => [
      VIM::VmConfigFileQuery()
    ]
  }
  task = ds.browser.SearchDatastoreSubFolders_Task(:datastorePath => datastorePath, :searchSpec => searchSpec)

  results = task.wait_for_completion

  files = []
  results.each do |result|
    result.file.each do |file|
      files << "#{result.folderPath}/#{file.path}"
    end
  end

  files
end

#get(objs) ⇒ Object



22
23
24
25
26
27
28
# File 'lib/rvc/modules/role.rb', line 22

def get name
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{role_name.inspect}" unless role
  puts "label: #{role.info.label}"
  puts "summary: #{role.info.summary}"
  puts "privileges: #{role.privilege.sort * ' '}"
end

#help(path) ⇒ Object



44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
# File 'lib/rvc/modules/basic.rb', line 44

def help path
  if tgt = RVC::ALIASES[path]
    fail unless tgt =~ /^(.+)\.(.+)$/
    opts_block = RVC::MODULES[$1].opts_for($2.to_sym)
    RVC::OptionParser.new(tgt, &opts_block).educate
    return
  elsif path =~ /^(.+)\.(.+)$/ and
        mod = RVC::MODULES[$1] and
        opts_block = mod.opts_for($2.to_sym)
    RVC::OptionParser.new(path, &opts_block).educate
    return
  end

  obj = lookup_single(path) if path

  if obj
    puts "Relevant commands for #{obj.class}:"
  else
    puts "All commands:"
  end

  MODULES.sort_by do |mod_name,mod|
    HELP_ORDER.index(mod_name) || HELP_ORDER.size
  end.each do |mod_name,mod|
    opts = mod.instance_variable_get(:@opts)
    opts.each do |method_name,method_opts|
      parser = RVC::OptionParser.new method_name, &method_opts
      next unless obj.nil? or parser.applicable.any? { |x| obj.is_a? x }
      aliases = ALIASES.select { |k,v| v == "#{mod_name}.#{method_name}" }.map(&:first)
      aliases_text = aliases.empty? ? '' : " (#{aliases*', '})"
      puts "#{mod_name}.#{method_name}#{aliases_text}: #{parser.summary?}" if parser.summary?
    end
  end

  if not obj
    puts (<<-EOS)

To see detailed help for a command, use its --help option.
To show only commands relevant to a specific object, use "help /path/to/object".
    EOS
  end
end

#http_path(dc_name, ds_name, path) ⇒ Object



163
164
165
# File 'lib/rvc/modules/datastore.rb', line 163

def http_path dc_name, ds_name, path
  "/folder/#{URI.escape path}?dcPath=#{URI.escape dc_name}&dsName=#{URI.escape ds_name}"
end

#info(obj) ⇒ Object



206
207
208
209
210
211
212
213
# File 'lib/rvc/modules/basic.rb', line 206

def info obj
  puts "path: #{obj.rvc_path_str}"
  if obj.respond_to? :display_info
    obj.display_info
  else
    puts "class: #{obj.class.name}"
  end
end

#insert_cdrom(vm, iso) ⇒ Object



188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
# File 'lib/rvc/modules/vm.rb', line 188

def insert_cdrom vm, iso
  device = vm.config.hardware.device.grep(VIM::VirtualCdrom)[0]
  err "No virtual CDROM drive found" unless device

  device.backing = VIM.VirtualCdromIsoBackingInfo(:fileName => iso.datastore_path)

  spec = {
    :deviceChange => [
      {
        :operation => :edit,
        :device => device
      }
    ]
  }
  
  vm.ReconfigVM_Task(:spec => spec)
end

#installObject



111
112
113
114
115
116
117
# File 'lib/rvc/modules/vmrc.rb', line 111

def install
  zip_filename = File.join(Dir.tmpdir, VMRC_BASENAME)
  download VMRC_URL, zip_filename
  verify zip_filename, VMRC_SHA256
  extract zip_filename, File.join(Dir.tmpdir, VMRC_NAME)
  puts "VMRC was installed successfully."
end

#ip(vms) ⇒ Object



427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
# File 'lib/rvc/modules/vm.rb', line 427

def ip vms
  props = %w(summary.runtime.powerState summary.guest.ipAddress summary.config.annotation)
  connection = single_connection vms

  filters = vms.map do |vm|
    connection.propertyCollector.CreateFilter :spec => {
      :propSet => [{ :type => 'VirtualMachine', :all => false, :pathSet => props }],
      :objectSet => [{ :obj => vm }],
    }, :partialUpdates => false
  end

  ver = ''
  while not vms.empty?
    result = connection.propertyCollector.WaitForUpdates(:version => ver)
    ver = result.version

    vms.reject! do |vm|
      begin
        ip = vm_ip(vm)
        puts "#{vm.name}: #{ip}"
        true
      rescue UserError
        false
      end
    end
  end
ensure
  filters.each(&:DestroyPropertyFilter) if filters
end

#keychain_password(username, hostname) ⇒ Object



144
145
146
147
148
149
150
151
152
153
154
155
156
# File 'lib/rvc/modules/vim.rb', line 144

def keychain_password username , hostname
   return nil unless RbConfig::CONFIG['host_os'] =~ /^darwin10/

  begin
    require 'osx_keychain'
  rescue LoadError
    return nil
  end

  keychain = OSXKeychain.new
  return keychain["rvc", "#{username}@#{hostname}" ]

end

#kill(vms) ⇒ Object



239
240
241
242
243
# File 'lib/rvc/modules/vm.rb', line 239

def kill vms
  on_vms = vms.select { |x| x.summary.runtime.powerState == 'poweredOn' }
  off on_vms unless on_vms.empty?
  CMD.basic.destroy vms unless vms.empty?
end

#layout(vm) ⇒ Object



264
265
266
267
268
# File 'lib/rvc/modules/vm.rb', line 264

def layout vm
  vm.layoutEx.file.each do |f|
    puts "#{f.type}: #{f.name}"
  end
end

#listObject



63
64
65
# File 'lib/rvc/modules/mark.rb', line 63

def list
  $shell.session.marks.each { |x| puts x }
end

#ls(obj) ⇒ Object



153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
# File 'lib/rvc/modules/basic.rb', line 153

def ls obj
  children = obj.children
  name_map = children.invert
  children, fake_children = children.partition { |k,v| v.is_a? VIM::ManagedEntity }
  i = 0

  fake_children.each do |name,child|
    puts "#{i} #{name}#{child.ls_text(nil)}"
    child.rvc_link obj, name
    CMD.mark.mark i.to_s, [child]
    i += 1
  end

  return if children.empty?

  filterSpec = VIM.PropertyFilterSpec(:objectSet => [], :propSet => [])
  filteredTypes = Set.new

  children.each do |name,child|
    filterSpec.objectSet << { :obj => child }
    filteredTypes << child.class
  end

  filteredTypes.each do |x|
    filterSpec.propSet << {
      :type => x.wsdl_name,
      :pathSet => x.ls_properties+%w(name),
    }
  end

  connection = single_connection(children.map { |k,v| v })
  results = connection.propertyCollector.RetrieveProperties(:specSet => [filterSpec])

  results.each do |r|
    name = name_map[r.obj]
    text = r.obj.ls_text(r) rescue " (error)"
    realname = r['name'] if name != r['name']
    puts "#{i} #{name}#{realname && " [#{realname}]"}#{text}"
    r.obj.rvc_link obj, name
    CMD.mark.mark i.to_s, [r.obj]
    i += 1
  end
end

#mark(key, objs) ⇒ Object



30
31
32
33
# File 'lib/rvc/modules/mark.rb', line 30

def mark key, objs
  err "invalid mark name" unless key =~ /^\w+$/
  $shell.session.set_mark key, objs
end

#migrate(vms, opts) ⇒ Object



567
568
569
570
571
# File 'lib/rvc/modules/vm.rb', line 567

def migrate vms, opts
  tasks vms, :MigrateVM, :pool => opts[:pool],
                         :host => opts[:host],
                         :priority => :defaultPriority
end

#mkdir(datastore_path) ⇒ Object

TODO dispatch to datastore.mkdir if path is in a datastore



295
296
297
298
# File 'lib/rvc/modules/basic.rb', line 295

def mkdir path
  parent = lookup_single! File.dirname(path), RbVmomi::VIM::Folder
  parent.CreateFolder(:name => File.basename(path))
end

#mv(src, dst) ⇒ Object



263
264
265
266
267
268
269
270
# File 'lib/rvc/modules/basic.rb', line 263

def mv src, dst
  src_dir = File.dirname(src)
  dst_dir = File.dirname(dst)
  err "cross-directory mv not yet supported" unless src_dir == dst_dir
  dst_name = File.basename(dst)
  obj = lookup(src)
  obj.Rename_Task(:newName => dst_name).wait_for_completion
end

#off(vm) ⇒ Object



40
41
42
# File 'lib/rvc/modules/vm.rb', line 40

def off vms
  tasks vms, :PowerOffVM
end

#on(vms) ⇒ Object



28
29
30
# File 'lib/rvc/modules/vm.rb', line 28

def on vms
  tasks vms, :PowerOnVM
end

#permissions(name) ⇒ Object



36
37
38
39
40
41
42
43
44
45
# File 'lib/rvc/modules/role.rb', line 36

def permissions name
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{role_name.inspect}" unless role
  cur_auth_mgr.RetrieveRolePermissions(roleId: role.roleId).each do |perm|
    flags = []
    flags << 'group' if perm[:group]
    flags << 'propagate' if perm[:propagate]
    puts " #{perm[:principal]}#{flags.empty? ? '' : " (#{flags * ', '})"}: #{perm.entity.name}"
  end
end

#ping(vm) ⇒ Object



416
417
418
419
# File 'lib/rvc/modules/vm.rb', line 416

def ping vm
  ip = vm_ip vm
  system_fg "ping #{Shellwords.escape ip}"
end

#prompt_passwordObject



140
141
142
# File 'lib/rvc/modules/vim.rb', line 140

def prompt_password
  ask("password: ") { |q| q.echo = false }
end

#quitObject



110
111
112
# File 'lib/rvc/modules/basic.rb', line 110

def quit
  exit
end

#reachable_ip(host) ⇒ Object



70
71
72
73
74
75
76
77
78
79
# File 'lib/rvc/modules/vnc.rb', line 70

def reachable_ip host
  ips = host.config.network.vnic.map { |x| x.spec.ip.ipAddress } # TODO optimize
  ips.find do |x|
    begin
      Timeout.timeout(1) { TCPSocket.new(x, 443).close; true }
    rescue
      false
    end
  end or err("could not find IP for server #{host.name}")
end

#reboot(hosts, opts) ⇒ Object



27
28
29
# File 'lib/rvc/modules/host.rb', line 27

def reboot hosts, opts
  tasks hosts, :RebootHost, :force => opts[:force]
end

#reboot_guest(vms) ⇒ Object



96
97
98
# File 'lib/rvc/modules/vm.rb', line 96

def reboot_guest vms
  vms.each(&:RebootGuest)
end

#reconnect(hosts, opts) ⇒ Object



114
115
116
117
118
119
120
121
# File 'lib/rvc/modules/host.rb', line 114

def reconnect hosts, opts
  spec = {
    :force => false,
    :userName => opts[:username],
    :password => opts[:password],
  }
  tasks hosts, :ReconnectHost
end

#register(vmx_file, opts) ⇒ Object



213
214
215
216
217
218
# File 'lib/rvc/modules/vm.rb', line 213

def register vmx_file, opts
  rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool
  vm = opts[:folder].RegisterVM_Task(:path => vmx_file.datastore_path,
                                     :asTemplate => false,
                                     :pool => rp).wait_for_completion
end

#reloadObject



121
122
123
# File 'lib/rvc/modules/basic.rb', line 121

def reload
  RVC.reload_modules
end

#reload_entity(objs) ⇒ Object



235
236
237
# File 'lib/rvc/modules/basic.rb', line 235

def reload_entity objs
  objs.each(&:Reload)
end

#remove(objs, opts) ⇒ Object



54
55
56
57
58
59
60
61
62
# File 'lib/rvc/modules/permissions.rb', line 54

def remove objs, opts
  conn = single_connection objs
  authMgr = conn.serviceContent.authorizationManager
  objs.each do |obj|
    authMgr.RemoveEntityPermission :entity => obj,
                                   :user => opts[:principal],
                                   :isGroup => opts[:group]
  end
end

#remove_device(vm, label) ⇒ Object



506
507
508
509
510
511
512
513
514
515
# File 'lib/rvc/modules/vm.rb', line 506

def remove_device vm, label
  dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
  err "no such device" unless dev
  spec = {
    :deviceChange => [
      { :operation => :remove, :device => dev },
    ]
  }
  vm.ReconfigVM_Task(:spec => spec).wait_for_completion
end

#remove_privilege(name, privileges) ⇒ Object



109
110
111
112
113
114
115
116
# File 'lib/rvc/modules/role.rb', line 109

def remove_privilege name, privileges
  role = cur_auth_mgr.roleList.find { |x| x.name == name }
  err "no such role #{name.inspect}" unless role
  cur_auth_mgr.UpdateAuthorizationRole :roleId => role.roleId,
                                       :newName => role.name,
                                       :privIds => (role.privilege - privileges)

end

#rename(old, new) ⇒ Object



78
79
80
81
82
83
84
# File 'lib/rvc/modules/role.rb', line 78

def rename old, new
  role = cur_auth_mgr.roleList.find { |x| x.name == old }
  err "no such role #{old.inspect}" unless role
  cur_auth_mgr.UpdateAuthorizationRole :roleId => role.roleId,
                                       :newName => new,
                                       :privIds => role.privilege
end

#reset(vms) ⇒ Object



53
54
55
# File 'lib/rvc/modules/vm.rb', line 53

def reset vms
  tasks vms, :ResetVM
end

#revert(vm) ⇒ Object



555
556
557
# File 'lib/rvc/modules/vm.rb', line 555

def revert vm
  tasks [vm], :RevertToCurrentSnapshot
end

#rvc(vm) ⇒ Object



398
399
400
401
402
403
404
405
406
# File 'lib/rvc/modules/vm.rb', line 398

def rvc vm
  ip = vm_ip vm

  env = Hash[%w(RBVMOMI_PASSWORD RBVMOMI_HOST RBVMOMI_USER RBVMOMI_SSL RBVMOMI_PORT
                RBVMOMI_FOLDER RBVMOMI_DATASTORE RBVMOMI_PATH RBVMOMI_DATACENTER
                RBVMOMI_COMPUTER).map { |k| [k,nil] }]
  cmd = "rvc #{Shellwords.escape ip}"
  system_fg(cmd, env)
end

#save_keychain_password(username, password, hostname) ⇒ Object



158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
# File 'lib/rvc/modules/vim.rb', line 158

def save_keychain_password username , password , hostname
  # only works for OSX at the minute.
  return false unless RbConfig::CONFIG['host_os'] =~ /^darwin10/

  # check we already managed to load that gem.
  if defined? OSXKeychain::VERSION

    if agree("Save password for connection (y/n)? ", true)
      keychain = OSXKeychain.new

      # update the keychain, unless it's already set to that.
      keychain.set("rvc", "#{username}@#{hostname}" , password ) unless 
        keychain["rvc", "#{username}@#{hostname}" ] == password
    end
  else
    return false
  end
end

#set(objs, opts) ⇒ Object



32
33
34
35
36
37
38
39
40
41
42
43
44
# File 'lib/rvc/modules/permissions.rb', line 32

def set objs, opts
  conn = single_connection objs
  authMgr = conn.serviceContent.authorizationManager
  role = authMgr.roleList.find { |x| x.name == opts[:role] }
  err "no such role #{role.inspect}" unless role
  perm = { :roleId => role.roleId,
           :principal => opts[:principal],
           :group => opts[:group],
           :propagate => opts[:propagate] }
  objs.each do |obj|
    authMgr.SetEntityPermissions(:entity => obj, :permission => [perm])
  end
end

#setExtraConfig(vm, pairs) ⇒ Object



351
352
353
354
# File 'lib/rvc/modules/vm.rb', line 351

def setExtraConfig vm, pairs
  h = Hash[pairs.map { |x| x.split('=', 2).tap { |a| a << '' if a.size == 1 } }]
  _setExtraConfig vm, h
end

#shares_from_string(str) ⇒ Object



35
36
37
38
39
40
41
42
43
44
# File 'lib/rvc/modules/resource_pool.rb', line 35

def shares_from_string str
  case str
  when 'normal', 'low', 'high'
    { :level => str, :shares => 0 }
  when /^\d+$/
    { :level => 'custom', :shares => str.to_i }
  else
    err "Invalid shares argument #{str.inspect}"
  end
end

#show(objs) ⇒ Object



248
249
250
251
252
# File 'lib/rvc/modules/basic.rb', line 248

def show objs
  objs.each do |obj|
    puts "#{obj.rvc_path_str}: #{obj.class}"
  end
end

#shutdown_guest(vms) ⇒ Object



76
77
78
# File 'lib/rvc/modules/vm.rb', line 76

def shutdown_guest vms
  vms.each(&:ShutdownGuest)
end

#snapshot(vm, name, opts) ⇒ Object



527
528
529
# File 'lib/rvc/modules/vm.rb', line 527

def snapshot vm, name, opts
  tasks [vm], :CreateSnapshot, :description => opts[:description], :memory => opts[:memory], :name => name, :quiesce => opts[:quiesce]
end

#snapshots(vm) ⇒ Object



538
539
540
# File 'lib/rvc/modules/vm.rb', line 538

def snapshots vm
  _display_snapshot_tree vm.snapshot.rootSnapshotList, 0
end

#ssh(vm, cmd, opts) ⇒ Object



383
384
385
386
387
388
# File 'lib/rvc/modules/vm.rb', line 383

def ssh vm, cmd, opts
  ip = vm_ip vm
  cmd_arg = cmd ? Shellwords.escape(cmd) : ""
  ssh_cmd = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l #{Shellwords.escape opts[:login]} #{Shellwords.escape ip} #{cmd_arg}"
  system_fg(ssh_cmd)
end

#standby_guest(vms) ⇒ Object



86
87
88
# File 'lib/rvc/modules/vm.rb', line 86

def standby_guest vms
  vms.each(&:StandbyGuest)
end

#suspend(vms) ⇒ Object



66
67
68
# File 'lib/rvc/modules/vm.rb', line 66

def suspend vms
  tasks vms, :SuspendVM
end

#tasksObject



211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
# File 'lib/rvc/modules/vim.rb', line 211

def tasks
  conn = single_connection [$shell.fs.cur]

  begin
    view = conn.serviceContent.viewManager.CreateListView

    collector = conn.serviceContent.taskManager.CreateCollectorForTasks(:filter => {
      :time => {
        :beginTime => conn.serviceInstance.CurrentTime.to_datetime, # XXX
        :timeType => :queuedTime
      }
    })
    collector.SetCollectorPageSize :maxCount => 1

    filter_spec = {
      :objectSet => [
        {
          :obj => view,
          :skip => true,
          :selectSet => [
            VIM::TraversalSpec(:path => 'view', :type => view.class.wsdl_name)
          ]
        },
        { :obj => collector },
      ],
      :propSet => [
        { :type => 'Task', :pathSet => %w(info.state) },
        { :type => 'TaskHistoryCollector', :pathSet => %w(latestPage) },
      ]
    }
    filter = conn.propertyCollector.CreateFilter(:partialUpdates => false, :spec => filter_spec)

    ver = ''
    loop do
      result = conn.propertyCollector.WaitForUpdates(:version => ver)
      ver = result.version
      result.filterSet[0].objectSet.each do |r|
        remove = []
        case r.obj
        when VIM::TaskHistoryCollector
          infos = collector.ReadNextTasks :maxCount => 100
          view.ModifyListView :add => infos.map(&:task)
        when VIM::Task
          puts "#{Time.now} #{r.obj.info.name} #{r.obj.info.entityName} #{r['info.state']}" unless r['info.state'] == nil
          remove << r.obj if %w(error success).member? r['info.state']
        end
        view.ModifyListView :remove => remove unless remove.empty?
      end
    end
  rescue Interrupt
  ensure
    filter.DestroyPropertyFilter if filter
    collector.DestroyCollector if collector
    view.DestroyView if view
  end
end

#type(name) ⇒ Object



28
29
30
31
32
# File 'lib/rvc/modules/basic.rb', line 28

def type name
  klass = RbVmomi::VIM.type(name) rescue err("#{name.inspect} is not a VMODL type.")
  $shell.introspect_class klass
  nil
end

#unregister(vm) ⇒ Object



226
227
228
# File 'lib/rvc/modules/vm.rb', line 226

def unregister vm
  vm.UnregisterVM
end

#unused_vnc_port(ip) ⇒ Object



81
82
83
84
85
86
87
88
# File 'lib/rvc/modules/vnc.rb', line 81

def unused_vnc_port ip
  10.times do
    port = 5901 + rand(64)
    unused = (TCPSocket.connect(ip, port).close rescue true)
    return port if unused
  end
  err "no unused port found"
end

#update(pool, opts) ⇒ Object



79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
# File 'lib/rvc/modules/resource_pool.rb', line 79

def update pool, opts
  spec = {
    :cpuAllocation => {
      :limit => opts[:cpu_limit],
      :reservation => opts[:cpu_reservation],
      :expandableReservation => opts[:cpu_expandable],
      :shares => shares_from_string(opts[:cpu_shares]),
    },
    :memoryAllocation => {
      :limit => opts[:mem_limit],
      :reservation => opts[:mem_reservation],
      :expandableReservation => opts[:mem_expandable],
      :shares => shares_from_string(opts[:mem_shares]),
    },
  }
  pool.UpdateConfig(:name => opts[:name], :spec => spec)
end

#upload(local_path, dest) ⇒ Object



65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
# File 'lib/rvc/modules/datastore.rb', line 65

def upload local_path, dest
  dir, datastore_filename = *dest
  err "local file does not exist" unless File.exists? local_path
  real_datastore_path = "#{dir.path}/#{datastore_filename}"

  main_http = dir.datastore._connection.http
  http = Net::HTTP.new(main_http.address, main_http.port)
  http.use_ssl = true
  http.verify_mode = OpenSSL::SSL::VERIFY_NONE
  #http.set_debug_output $stderr
  http.start
  err "certificate mismatch" unless main_http.peer_cert.to_der == http.peer_cert.to_der

  File.open(local_path, 'rb') do |io|
    stream = ProgressStream.new(io, io.stat.size) do |s|
      $stdout.write "\e[0G\e[Kuploading #{s.count}/#{s.len} bytes (#{(s.count*100)/s.len}%)"
      $stdout.flush
    end

    headers = {
      'cookie' => dir.datastore._connection.cookie,
      'content-length' => io.stat.size.to_s,
      'Content-Type' => 'application/octet-stream',
    }
    path = http_path dir.datastore.send(:datacenter).name, dir.datastore.name, real_datastore_path
    request = Net::HTTP::Put.new path, headers
    request.body_stream = stream
    res = http.request(request)
    $stdout.puts
    case res
    when Net::HTTPOK
    else
      err "upload failed: #{res.message}"
    end
  end
end

#verify(filename, expected_hash) ⇒ Object



145
146
147
148
149
# File 'lib/rvc/modules/vmrc.rb', line 145

def verify filename, expected_hash
  puts "Checking integrity..."
  hexdigest = Digest::SHA256.file(filename).hexdigest
  err "Hash mismatch" if hexdigest != VMRC_SHA256
end

#view(vms, opts) ⇒ Object



31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
# File 'lib/rvc/modules/vnc.rb', line 31

def view vm
  ip = reachable_ip vm.runtime.host
  extraConfig = vm.config.extraConfig
  already_enabled = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.enabled' && x.value.downcase == 'true' }
  if already_enabled
    puts "VNC already enabled"
    port = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.port' }.value
    password = extraConfig.find { |x| x.key == 'RemoteDisplay.vnc.password' }.value
  else
    port = unused_vnc_port ip
    password = vnc_password
    vm.ReconfigVM_Task(:spec => {
      :extraConfig => [
        { :key => 'RemoteDisplay.vnc.enabled', :value => 'true' },
        { :key => 'RemoteDisplay.vnc.password', :value => password },
        { :key => 'RemoteDisplay.vnc.port', :value => port.to_s }
      ]
    }).wait_for_completion
  end
  vnc_client ip, port, password
end

#vm_ip(vm) ⇒ Object



676
677
678
679
680
681
682
683
684
685
686
687
688
# File 'lib/rvc/modules/vm.rb', line 676

def vm_ip vm
  summary = vm.summary

  err "VM is not powered on" unless summary.runtime.powerState == 'poweredOn'

  ip = if summary.guest.ipAddress and summary.guest.ipAddress != '127.0.0.1'
    summary.guest.ipAddress
  elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note.member? 'ip'
    note['ip']
  else
    err "no IP known for this VM"
  end
end

#vnc_client(ip, port, password) ⇒ Object

Override this to spawn a VNC client differently



98
99
100
101
102
103
104
105
106
107
108
109
110
111
# File 'lib/rvc/modules/vnc.rb', line 98

def vnc_client ip, port, password
  if VNC
    fork do
      $stderr.reopen("#{ENV['HOME']||'.'}/.rvc-vmrc.log", "w")
      Process.setpgrp
      exec VNC, "#{ip}:#{port}"
    end
    puts "spawning #{VNC}"
    puts "#{ip}:#{port} password: #{password}"
  else
    puts "no VNC client configured"
    puts "#{ip}:#{port} password: #{password}"
  end
end

#vnc_passwordObject

Override this if you don’t want a random password



91
92
93
94
95
# File 'lib/rvc/modules/vnc.rb', line 91

def vnc_password
  n = 8
  chars = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'
  (0...n).map { chars[rand(chars.length)].chr }.join
end