rvc 1.1.0 → 1.2.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.
data/README.rdoc CHANGED
@@ -52,6 +52,15 @@ the "vm" module, but since it is a common operation it has been aliased to
52
52
  Commands and paths can be tab completed in the usual fashion. Whitespace must
53
53
  be escaped with a backslash.
54
54
 
55
+ === Wildcards
56
+
57
+ Many commands such as "vm.on" can operate on multiple objects at once. RVC
58
+ supports simple globbing using "*" as well as advanced regex syntax. To use a
59
+ regex, prefix the path element with "%". For example: "vm.on myvms/%^(linux|windows)"
60
+ will power on VMs whose names start with "linux" or "windows" in the "myvms" folder.
61
+ Note that it is necessary to explicitly anchor the pattern with "^" and "$" if you
62
+ wish to match the whole path element.
63
+
55
64
  === Marks
56
65
 
57
66
  192.168.1.105:/> mark a dc/vm/foo
data/TODO CHANGED
@@ -1,4 +1,8 @@
1
1
  add commands for more entities
2
2
  add fake subfolders for more entities
3
3
  add fastpass authentication
4
- globbing?
4
+
5
+ commands:
6
+ storage vmotion
7
+ edit clusters
8
+ add/remove hosts from clusters
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.1.0
1
+ 1.2.0
data/bin/rvc CHANGED
@@ -81,8 +81,13 @@ if $opts[:path]
81
81
  lookup(parent_path).CreateFolder(:name => File.basename($opts[:path]))
82
82
  CMD.basic.cd $opts[:path]
83
83
  end
84
- elsif ARGV.size == 1
85
- CMD.basic.cd $shell.connections.keys.first
84
+ elsif $shell.connections.size == 1
85
+ conn_name, conn = $shell.connections.first
86
+ if conn.serviceContent.about.apiType == 'VirtualCenter'
87
+ CMD.basic.cd conn_name
88
+ else
89
+ CMD.basic.cd "#{conn_name}/ha-datacenter/vm"
90
+ end
86
91
  end
87
92
 
88
93
  unless CMD.vmrc.find_vmrc
@@ -91,7 +91,7 @@ module Completion
91
91
  last = trailing_slash ? '' : (els.pop || '')
92
92
  els.map! { |x| x.gsub '\\', '' }
93
93
  base_loc = absolute ? Location.new($shell.fs.root) : $shell.fs.loc
94
- found_loc = $shell.fs.traverse(base_loc, els) or return []
94
+ found_loc = $shell.fs.traverse(base_loc, els).first or return []
95
95
  cur = found_loc.obj
96
96
  els.unshift '' if absolute
97
97
  children = Cache[cur, :children] rescue []
@@ -21,7 +21,12 @@
21
21
  class RbVmomi::VIM::ComputeResource
22
22
  # TODO expand, optimize
23
23
  def display_info
24
+ stats = self.stats
25
+ pct_cpu_used = 100.0*stats[:usedCPU]/stats[:totalCPU]
26
+ pct_mem_used = 100.0*stats[:usedMem]/stats[:totalMem]
24
27
  puts "name: #{name}"
28
+ puts "cpu #{stats[:totalCPU]} GHz (#{pct_cpu_used.to_i}% used)"
29
+ puts "memory #{stats[:totalMem]} GB (#{pct_mem_used.to_i}% used)"
25
30
  puts "hosts:"
26
31
  host.each do |h|
27
32
  puts " #{h.name}"
data/lib/rvc/fs.rb CHANGED
@@ -52,7 +52,9 @@ end
52
52
  class FS
53
53
  attr_reader :root, :loc, :marks
54
54
 
55
- MARK_REGEX = /^~(?:([\d\w]*|~|@))$/
55
+ MARK_PATTERN = /^~(?:([\d\w]*|~|@))$/
56
+ REGEX_PATTERN = /^%/
57
+ GLOB_PATTERN = /\*/
56
58
 
57
59
  def initialize root
58
60
  @root = root
@@ -68,15 +70,13 @@ class FS
68
70
  @loc.path * '/'
69
71
  end
70
72
 
71
- def lookup path
72
- (lookup_loc(path) || return).obj
73
- end
74
-
75
- def cd path
76
- new_loc = lookup_loc(path) or return false
73
+ def cd new_loc
77
74
  mark '~', @loc
78
75
  @loc = new_loc
79
- true
76
+ end
77
+
78
+ def lookup path
79
+ lookup_loc(path).map(&:obj)
80
80
  end
81
81
 
82
82
  def lookup_loc path
@@ -85,30 +85,51 @@ class FS
85
85
  traverse(base_loc, els)
86
86
  end
87
87
 
88
- def traverse base_loc, els
89
- loc = base_loc.dup
90
- els.each_with_index do |el,i|
91
- case el
92
- when '.'
93
- when '..'
94
- loc.pop unless loc.obj == @root
95
- when '...'
96
- loc.push(el, loc.obj.parent) unless loc.obj == @root
97
- when MARK_REGEX
98
- return unless i == 0
99
- loc = @marks[$1] or return
100
- loc = loc.dup
88
+ def traverse_one loc, el, first
89
+ case el
90
+ when '.'
91
+ [loc]
92
+ when '..'
93
+ loc.pop unless loc.obj == @root
94
+ [loc]
95
+ when '...'
96
+ loc.push(el, loc.obj.parent) unless loc.obj == @root
97
+ [loc]
98
+ when MARK_PATTERN
99
+ return unless first
100
+ loc = @marks[$1] or return []
101
+ [loc.dup]
102
+ when REGEX_PATTERN
103
+ regex = Regexp.new($')
104
+ loc.obj.children.
105
+ select { |k,v| k =~ regex }.
106
+ map { |k,v| loc.dup.tap { |x| x.push(k, v) } }
107
+ when GLOB_PATTERN
108
+ regex = glob_to_regex el
109
+ loc.obj.children.
110
+ select { |k,v| k =~ regex }.
111
+ map { |k,v| loc.dup.tap { |x| x.push(k, v) } }
112
+ else
113
+ # XXX check for ambiguous child
114
+ if first and el =~ /^\d+$/ and @marks.member? el
115
+ loc = @marks[el].dup
101
116
  else
102
- # XXX check for ambiguous child
103
- if i == 0 and el =~ /^\d+$/ and @marks.member? el
104
- loc = @marks[el].dup
105
- else
106
- x = loc.obj.traverse_one(el) or return
107
- loc.push el, x
108
- end
117
+ x = loc.obj.traverse_one(el) or return []
118
+ loc.push el, x
109
119
  end
120
+ [loc]
121
+ end
122
+ end
123
+
124
+ # Starting from base_loc, traverse each path element in els. Since the path
125
+ # may contain wildcards, this function returns a list of matches.
126
+ def traverse base_loc, els
127
+ locs = [base_loc.dup]
128
+ els.each_with_index do |el,i|
129
+ locs.map! { |loc| traverse_one loc, el, i==0 }
130
+ locs.flatten!
110
131
  end
111
- loc
132
+ locs
112
133
  end
113
134
 
114
135
  def mark key, loc
@@ -118,6 +139,10 @@ class FS
118
139
  @marks[key] = loc
119
140
  end
120
141
  end
142
+
143
+ def glob_to_regex str
144
+ Regexp.new "^#{Regexp.escape(str.gsub('*', "\0")).gsub("\0", ".*")}$"
145
+ end
121
146
  end
122
147
 
123
148
  end
@@ -42,7 +42,7 @@ rvc_alias :help
42
42
  HELP_ORDER = %w(basic vm)
43
43
 
44
44
  def help path
45
- obj = lookup(path) if path
45
+ obj = lookup_single(path) if path
46
46
 
47
47
  if obj
48
48
  puts "Relevant commands for #{obj.class}:"
@@ -119,7 +119,9 @@ end
119
119
  rvc_alias :cd
120
120
 
121
121
  def cd path
122
- $shell.fs.cd(path) or err "Not found: #{path.inspect}"
122
+ # XXX check for multiple matches
123
+ new_loc = $shell.fs.lookup_loc(path).first or err "Not found: #{path.inspect}"
124
+ $shell.fs.cd(new_loc)
123
125
  $shell.fs.mark '', find_ancestor_loc(RbVmomi::VIM::Datacenter)
124
126
  $shell.fs.mark '@', find_ancestor_loc(RbVmomi::VIM)
125
127
  $shell.fs.marks.delete_if { |k,v| k =~ /^\d+$/ }
@@ -141,7 +143,8 @@ rvc_alias :ls
141
143
  rvc_alias :ls, :l
142
144
 
143
145
  def ls path
144
- loc = $shell.fs.lookup_loc(path) or err "Not found: #{path.inspect}"
146
+ # XXX check for multiple matches
147
+ loc = $shell.fs.lookup_loc(path).first or err "Not found: #{path.inspect}"
145
148
  obj = loc.obj
146
149
  children = obj.children
147
150
  name_map = children.invert
@@ -177,7 +180,7 @@ def ls path
177
180
 
178
181
  results.each do |r|
179
182
  name = name_map[r.obj]
180
- text = r.obj.ls_text r
183
+ text = r.obj.ls_text(r) rescue " (error)"
181
184
  realname = r['name'] if name != r['name']
182
185
  puts "#{i} #{name}#{realname && " [#{realname}]"}#{text}"
183
186
  mark_loc = loc.dup.tap { |x| x.push name, r.obj }
@@ -227,7 +230,8 @@ rvc_alias :mark, :m
227
230
 
228
231
  def mark key, path
229
232
  err "invalid mark name" unless key =~ /^\w+$/
230
- obj = $shell.fs.lookup_loc(path) or err "Not found: #{path.inspect}"
233
+ # XXX aggregate marks
234
+ obj = $shell.fs.lookup_loc(path).first or err "Not found: #{path.inspect}"
231
235
  $shell.fs.mark key, obj
232
236
  end
233
237
 
@@ -271,6 +275,6 @@ rvc_alias :mkdir
271
275
 
272
276
  # TODO dispatch to datastore.mkdir if path is in a datastore
273
277
  def mkdir path
274
- parent = lookup! File.dirname(path), RbVmomi::VIM::Folder
278
+ parent = lookup_single! File.dirname(path), RbVmomi::VIM::Folder
275
279
  parent.CreateFolder(:name => File.basename(path))
276
280
  end
@@ -0,0 +1,63 @@
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 :create do
22
+ summary "Create a cluster"
23
+ arg :dest, nil, :lookup_parent => VIM::Folder
24
+ end
25
+
26
+ def create dest
27
+ folder, name = *dest
28
+ folder.CreateClusterEx(:name => name, :spec => {})
29
+ end
30
+
31
+
32
+ opts :add_host do
33
+ summary "Add a host to a cluster"
34
+ arg :cluster, nil, :lookup => VIM::ClusterComputeResource
35
+ arg :hostname, nil
36
+ opt :username, "Username", :short => 'u', :default => 'root'
37
+ opt :password, "Password", :short => 'p', :default => ''
38
+ end
39
+
40
+ def add_host cluster, hostname, opts
41
+ sslThumbprint = nil
42
+ while true
43
+ spec = {
44
+ :force => false,
45
+ :hostName => hostname,
46
+ :userName => opts[:username],
47
+ :password => opts[:password],
48
+ :sslThumbprint => sslThumbprint,
49
+ }
50
+ task = cluster.AddHost_Task :spec => spec,
51
+ :asConnected => false
52
+ begin
53
+ task.wait_for_completion
54
+ rescue VIM::SSLVerifyFault
55
+ puts "SSL thumbprint: #{$!.fault.thumbprint}"
56
+ $stdout.write "Accept this thumbprint? (y/n) "
57
+ $stdout.flush
58
+ answer = $stdin.readline.chomp
59
+ err "Aborted" unless answer == 'y' or answer == 'yes'
60
+ sslThumbprint = $!.fault.thumbprint
61
+ end
62
+ end
63
+ end
@@ -0,0 +1,30 @@
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 :create do
22
+ summary "Create a datacenter"
23
+ arg :dest, nil, :lookup_parent => [VIM::Folder, VIM]
24
+ end
25
+
26
+ def create dest
27
+ folder, name = *dest
28
+ folder = folder.rootFolder if folder.is_a? VIM
29
+ folder.CreateDatacenter(:name => name)
30
+ end
@@ -25,7 +25,7 @@ opts :download do
25
25
  end
26
26
 
27
27
  def download datastore_path, local_path
28
- file = lookup(datastore_path)
28
+ file = lookup_single(datastore_path)
29
29
  err "not a datastore file" unless file.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFile
30
30
  file.datastore.download file.path, local_path
31
31
  end
@@ -38,7 +38,7 @@ end
38
38
 
39
39
  def upload local_path, datastore_path
40
40
  datastore_dir_path = File.dirname datastore_path
41
- dir = lookup(datastore_dir_path)
41
+ dir = lookup_single(datastore_dir_path)
42
42
  err "datastore directory does not exist" unless dir.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFolder
43
43
  err "local file does not exist" unless File.exists? local_path
44
44
  real_datastore_path = "#{dir.path}/#{File.basename(datastore_path)}"
@@ -52,7 +52,7 @@ end
52
52
 
53
53
  def mkdir datastore_path
54
54
  datastore_dir_path = File.dirname datastore_path
55
- dir = lookup(datastore_dir_path)
55
+ dir = lookup_single(datastore_dir_path)
56
56
  err "datastore directory does not exist" unless dir.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFolder
57
57
  ds = dir.datastore
58
58
  dc = ds.path.find { |o,x| o.is_a? RbVmomi::VIM::Datacenter }[0]
@@ -92,3 +92,30 @@ end
92
92
  def exit_maintenance_mode hosts, opts
93
93
  tasks hosts, :ExitMaintenanceMode, :timeout => opts[:timeout]
94
94
  end
95
+
96
+
97
+ opts :disconnect do
98
+ summary "Disconnect a host"
99
+ arg :host, nil, :lookup => VIM::HostSystem, :multi => true
100
+ end
101
+
102
+ def disconnect hosts
103
+ tasks hosts, :DisconnectHost
104
+ end
105
+
106
+
107
+ opts :reconnect do
108
+ summary "Reconnect a host"
109
+ arg :host, nil, :lookup => VIM::HostSystem, :multi => true
110
+ opt :username, "Username", :short => 'u', :default => 'root'
111
+ opt :password, "Password", :short => 'p', :default => ''
112
+ end
113
+
114
+ def reconnect hosts, opts
115
+ spec = {
116
+ :force => false,
117
+ :userName => opts[:username],
118
+ :password => opts[:password],
119
+ }
120
+ tasks hosts, :ReconnectHost
121
+ end
@@ -68,21 +68,51 @@ def suspend vms
68
68
  end
69
69
 
70
70
 
71
+ opts :shutdown_guest do
72
+ summary "Shut down guest OS"
73
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
74
+ end
75
+
76
+ def shutdown_guest vms
77
+ vms.each(&:ShutdownGuest)
78
+ end
79
+
80
+
81
+ opts :standby_guest do
82
+ summary "Suspend guest OS"
83
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
84
+ end
85
+
86
+ def standby_guest vms
87
+ vms.each(&:StandbyGuest)
88
+ end
89
+
90
+
91
+ opts :reboot_guest do
92
+ summary "Reboot guest OS"
93
+ arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
94
+ end
95
+
96
+ def reboot_guest vms
97
+ vms.each(&:RebootGuest)
98
+ end
99
+
100
+
71
101
  opts :create do
72
102
  summary "Create a new VM"
73
- arg :name, "Name"
103
+ arg :name, "Destination", :lookup_parent => VIM::Folder
74
104
  opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
75
105
  opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
76
106
  opt :datastore, "Datastore", :short => 'd', :type => :string, :lookup => VIM::Datastore
77
107
  end
78
108
 
79
- def create name, opts
109
+ def create dest, opts
80
110
  err "must specify resource pool (--pool)" unless opts[:pool]
81
111
  err "must specify datastore (--datastore)" unless opts[:datastore]
82
- vmFolder = lookup!(File.dirname(name), VIM::Folder)
112
+ vmFolder, name = *dest
83
113
  datastore_path = "[#{opts[:datastore].name}]"
84
114
  config = {
85
- :name => File.basename(name),
115
+ :name => name,
86
116
  :guestId => 'otherGuest',
87
117
  :files => { :vmPathName => datastore_path },
88
118
  :numCPUs => 1,
@@ -99,7 +129,7 @@ def create name, opts
99
129
  :operation => :add,
100
130
  :fileOperation => :create,
101
131
  :device => VIM.VirtualDisk(
102
- :key => 0,
132
+ :key => -1,
103
133
  :backing => VIM.VirtualDiskFlatVer2BackingInfo(
104
134
  :fileName => datastore_path,
105
135
  :diskMode => :persistent,
@@ -112,7 +142,7 @@ def create name, opts
112
142
  }, {
113
143
  :operation => :add,
114
144
  :device => VIM.VirtualCdrom(
115
- :key => 0,
145
+ :key => -2,
116
146
  :connectable => {
117
147
  :allowGuestControl => true,
118
148
  :connected => true,
@@ -127,7 +157,7 @@ def create name, opts
127
157
  }, {
128
158
  :operation => :add,
129
159
  :device => VIM.VirtualE1000(
130
- :key => 0,
160
+ :key => -3,
131
161
  :deviceInfo => {
132
162
  :label => 'Network Adapter 1',
133
163
  :summary => 'VM Network'
@@ -341,13 +371,15 @@ end
341
371
  opts :ssh do
342
372
  summary "SSH to a VM"
343
373
  arg :vm, nil, :lookup => VIM::VirtualMachine
374
+ arg :cmd, "Optional command", :required => false, :default => nil
375
+ opt :login, "Username", :short => 'l', :default => 'root'
344
376
  end
345
377
 
346
378
  rvc_alias :ssh
347
379
 
348
- def ssh vm
380
+ def ssh vm, cmd, opts
349
381
  ip = vm_ip vm
350
- ssh_cmd = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{ip}"
382
+ ssh_cmd = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no -l #{opts[:login]} #{ip} #{cmd}"
351
383
  system_fg(ssh_cmd)
352
384
  end
353
385
 
@@ -610,8 +642,8 @@ def find_vmx_files ds
610
642
  files
611
643
  end
612
644
 
613
- def change_device_connectivity id, label, connected
614
- dev = vm(id).config.hardware.device.find { |x| x.deviceInfo.label == label }
645
+ def change_device_connectivity vm, label, connected
646
+ dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
615
647
  err "no such device" unless dev
616
648
  dev.connectable.connected = connected
617
649
  spec = {
@@ -619,7 +651,7 @@ def change_device_connectivity id, label, connected
619
651
  { :operation => :edit, :device => dev },
620
652
  ]
621
653
  }
622
- vm(id).ReconfigVM_Task(:spec => spec).wait_for_completion
654
+ vm.ReconfigVM_Task(:spec => spec).wait_for_completion
623
655
  end
624
656
 
625
657
  def vm_ip vm
@@ -55,18 +55,21 @@ class OptionParser < Trollop::Parser
55
55
  @has_options
56
56
  end
57
57
 
58
- def arg name, description, opts={}
59
- opts = {
58
+ def arg name, description, spec={}
59
+ spec = {
60
+ :description => description,
60
61
  :required => true,
61
62
  :default => nil,
62
63
  :multi => false,
63
- }.merge opts
64
- opts[:default] = [] if opts[:multi] and opts[:default].nil?
64
+ }.merge spec
65
+ spec[:default] = [] if spec[:multi] and spec[:default].nil?
65
66
  fail "Multi argument must be the last one" if @seen_multi
66
- fail "Can't have required argument after optional ones" if opts[:required] and @seen_not_required
67
- @applicable << opts[:lookup] if opts[:lookup]
68
- @args << [name, description, opts[:required], opts[:default], opts[:multi], opts[:lookup]]
69
- text " #{name}: " + [description, opts[:lookup]].compact.join(' ')
67
+ fail "Can't have required argument after optional ones" if spec[:required] and @seen_not_required
68
+ fail "lookup and lookup_parent are mutually exclusive" if spec[:lookup] and spec[:lookup_parent]
69
+ @applicable << spec[:lookup] if spec[:lookup]
70
+ @applicable << spec[:lookup_parent] if spec[:lookup_parent]
71
+ @args << [name,spec]
72
+ text " #{name}: " + [description, spec[:lookup], spec[:lookup_parent]].compact.join(' ')
70
73
  end
71
74
 
72
75
  def parse argv
@@ -74,35 +77,48 @@ class OptionParser < Trollop::Parser
74
77
 
75
78
  @specs.each do |name,spec|
76
79
  next unless klass = spec[:lookup] and path = opts[name]
77
- opts[name] = lookup! path, klass
80
+ opts[name] = lookup_single! path, klass
78
81
  end
79
82
 
80
83
  argv = leftovers
81
84
  args = []
82
- @args.each do |name,desc,required,default,multi,lookup_klass|
83
- if multi
84
- err "missing argument '#{name}'" if required and argv.empty?
85
- a = (argv.empty? ? default : argv.dup)
86
- a.map! { |x| lookup! x, lookup_klass } if lookup_klass
85
+ @args.each do |name,spec|
86
+ if spec[:multi]
87
+ err "missing argument '#{name}'" if spec[:required] and argv.empty?
88
+ a = (argv.empty? ? spec[:default] : argv.dup)
89
+ a = a.map { |x| postprocess_arg x, spec }.inject(:+)
90
+ err "no matches for '#{name}'" if spec[:required] and a.empty?
87
91
  args << a
88
92
  argv.clear
89
93
  else
90
94
  x = argv.shift
91
- err "missing argument '#{name}'" if required and x.nil?
92
- x = default if x.nil?
93
- x = lookup! x, lookup_klass if lookup_klass
94
- args << x
95
+ err "missing argument '#{name}'" if spec[:required] and x.nil?
96
+ x = spec[:default] if x.nil?
97
+ a = x.nil? ? [] : postprocess_arg(x, spec)
98
+ err "more than one match for #{name}" if a.size > 1
99
+ err "no match for '#{name}'" if spec[:required] and a.empty?
100
+ args << a.first
95
101
  end
96
102
  end
97
103
  err "too many arguments" unless argv.empty?
98
104
  return args, opts
99
105
  end
100
106
 
107
+ def postprocess_arg x, spec
108
+ if spec[:lookup]
109
+ lookup! x, spec[:lookup]
110
+ elsif spec[:lookup_parent]
111
+ lookup!(File.dirname(x), spec[:lookup_parent]).map { |y| [y, File.basename(x)] }
112
+ else
113
+ [x]
114
+ end
115
+ end
116
+
101
117
  def educate
102
- arg_texts = @args.map do |name,desc,required,default,multi,lookup_klass|
118
+ arg_texts = @args.map do |name,spec|
103
119
  text = name
104
- text = "[#{text}]" if not required
105
- text = "#{text}..." if multi
120
+ text = "[#{text}]" if not spec[:required]
121
+ text = "#{text}..." if spec[:multi]
106
122
  text
107
123
  end
108
124
  arg_texts.unshift "[opts]" if has_options?
data/lib/rvc/shell.rb CHANGED
@@ -80,7 +80,7 @@ class Shell
80
80
  return unless cmd
81
81
  err "invalid command" unless cmd.is_a? String
82
82
  case cmd
83
- when RVC::FS::MARK_REGEX
83
+ when RVC::FS::MARK_PATTERN
84
84
  CMD.basic.cd cmd
85
85
  else
86
86
  if cmd.include? '.'
@@ -192,11 +192,11 @@ class RubyEvaluator
192
192
  end
193
193
 
194
194
  def dc
195
- @fs.lookup("~")
195
+ @fs.lookup("~").first
196
196
  end
197
197
 
198
198
  def conn
199
- @fs.lookup("~@")
199
+ @fs.lookup("~@").first
200
200
  end
201
201
 
202
202
  def method_missing sym, *a
data/lib/rvc/util.rb CHANGED
@@ -22,17 +22,34 @@ module RVC
22
22
  module Util
23
23
  extend self
24
24
 
25
+ # XXX just use an optional argument for type
25
26
  def lookup path
26
27
  $shell.fs.lookup path
27
28
  end
28
29
 
29
- def lookup! path, type
30
- lookup(path).tap do |obj|
31
- err "Not found: #{path.inspect}" unless obj
32
- err "Expected #{type} but got #{obj.class} at #{path.inspect}" unless obj.is_a? type
30
+ def lookup_single path
31
+ objs = lookup(path, type)
32
+ err "Not found: #{path.inspect}" if objs.empty?
33
+ err "More than one match for #{path.inspect}" if objs.size > 1
34
+ objs.first
35
+ end
36
+
37
+ def lookup! path, types
38
+ types = [types] unless types.is_a? Enumerable
39
+ lookup(path).tap do |objs|
40
+ objs.each do |obj|
41
+ err "Expected #{types*'/'} but got #{obj.class} at #{path.inspect}" unless types.any? { |type| obj.is_a? type }
42
+ end
33
43
  end
34
44
  end
35
45
 
46
+ def lookup_single! path, type
47
+ objs = lookup!(path, type)
48
+ err "Not found: #{path.inspect}" if objs.empty?
49
+ err "More than one match for #{path.inspect}" if objs.size > 1
50
+ objs.first
51
+ end
52
+
36
53
  def menu items
37
54
  items.each_with_index { |x, i| puts "#{i} #{x}" }
38
55
  input = Readline.readline("? ", false)
data/test/test_fs.rb CHANGED
@@ -3,10 +3,16 @@ require 'rvc'
3
3
  require 'inventory_fixtures'
4
4
 
5
5
  class FSTest < Test::Unit::TestCase
6
+ NodeDaa = FixtureNode.new 'Daa'
7
+ NodeDab = FixtureNode.new 'Dab'
8
+ NodeDabc = FixtureNode.new 'Dabc'
9
+ NodeDac = FixtureNode.new 'Dac'
6
10
  NodeB = FixtureNode.new 'B'
7
11
  NodeC = FixtureNode.new 'C'
12
+ NodeD = FixtureNode.new('D', 'daa' => NodeDaa, 'dab' => NodeDab,
13
+ 'dabc' => NodeDabc, 'dac' => NodeDac)
8
14
  NodeA = FixtureNode.new('A', 'b' => NodeB, 'c' => NodeC)
9
- Root = FixtureNode.new('ROOT', 'a' => NodeA)
15
+ Root = FixtureNode.new('ROOT', 'a' => NodeA, 'd' => NodeD)
10
16
 
11
17
  def setup
12
18
  @context = RVC::FS.new Root
@@ -25,31 +31,31 @@ class FSTest < Test::Unit::TestCase
25
31
  end
26
32
 
27
33
  def test_lookup_simple
28
- assert_equal nil, @context.lookup('nonexistent')
29
- assert_equal Root, @context.lookup('.')
30
- assert_equal Root, @context.lookup('..')
31
- assert_equal Root, @context.lookup('...')
32
- assert_equal NodeA, @context.lookup('a')
33
- assert_equal NodeB, @context.lookup('a/b')
34
- assert_equal NodeC, @context.lookup('a/b/../c')
35
- assert_equal NodeC, @context.lookup('a/b/.../c')
34
+ assert_equal [], @context.lookup('nonexistent')
35
+ assert_equal [Root], @context.lookup('.')
36
+ assert_equal [Root], @context.lookup('..')
37
+ assert_equal [Root], @context.lookup('...')
38
+ assert_equal [NodeA], @context.lookup('a')
39
+ assert_equal [NodeB], @context.lookup('a/b')
40
+ assert_equal [NodeC], @context.lookup('a/b/../c')
41
+ assert_equal [NodeC], @context.lookup('a/b/.../c')
36
42
  end
37
43
 
38
44
  def test_lookup_loc_nonexistent
39
45
  loc = @context.lookup_loc 'nonexistent'
40
- assert_equal nil, loc
46
+ assert_equal [], loc
41
47
  end
42
48
 
43
49
  def test_lookup_loc_simple
44
50
  %w(a /a ./a ./a/.).each do |path|
45
- loc = @context.lookup_loc path
51
+ loc = @context.lookup_loc(path)[0]
46
52
  assert_equal NodeA, loc.obj
47
53
  assert_equal ['', 'a'], loc.path
48
54
  assert_equal [['', Root], ['a', NodeA]], loc.stack
49
55
  end
50
56
 
51
57
  %w(a/b /a/b ./a/b /a/b/.).each do |path|
52
- loc = @context.lookup_loc path
58
+ loc = @context.lookup_loc(path)[0]
53
59
  assert_equal NodeB, loc.obj
54
60
  assert_equal ['', 'a', 'b'], loc.path
55
61
  assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], loc.stack
@@ -57,57 +63,82 @@ class FSTest < Test::Unit::TestCase
57
63
  end
58
64
 
59
65
  def test_lookup_loc_parent
60
- loc = @context.lookup_loc '..'
66
+ loc = @context.lookup_loc('..')[0]
61
67
  assert_equal [['', Root]], loc.stack
62
68
 
63
- loc = @context.lookup_loc 'a/..'
69
+ loc = @context.lookup_loc('a/..')[0]
64
70
  assert_equal [['', Root]], loc.stack
65
71
 
66
- loc = @context.lookup_loc 'a/b/..'
72
+ loc = @context.lookup_loc('a/b/..')[0]
67
73
  assert_equal [['', Root], ['a', NodeA]], loc.stack
68
74
  end
69
75
 
70
76
  def test_lookup_loc_realparent
71
- loc = @context.lookup_loc '...'
77
+ loc = @context.lookup_loc('...')[0]
72
78
  assert_equal [['', Root]], loc.stack
73
79
 
74
- loc = @context.lookup_loc 'a/...'
80
+ loc = @context.lookup_loc('a/...')[0]
75
81
  assert_equal [['', Root], ['a', NodeA], ['...', Root]], loc.stack
76
82
 
77
- loc = @context.lookup_loc 'a/b/...'
83
+ loc = @context.lookup_loc('a/b/...')[0]
78
84
  assert_equal [['', Root], ['a', NodeA], ['b', NodeB], ['...', NodeA]], loc.stack
79
85
  end
80
86
 
81
87
  def test_lookup_loc_mark
82
- b_loc = @context.lookup_loc 'a/b'
88
+ b_loc = @context.lookup_loc('a/b')[0]
83
89
  assert_not_nil b_loc
84
90
 
85
- loc = @context.lookup_loc '~foo'
91
+ loc = @context.lookup_loc('~foo')[0]
86
92
  assert_equal nil, loc
87
93
 
88
94
  ['foo', '~', '7', ''].each do |mark|
89
95
  @context.mark mark, b_loc
90
- loc = @context.lookup_loc "~#{mark}"
96
+ loc = @context.lookup_loc("~#{mark}")[0]
91
97
  assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], loc.stack
92
98
 
93
99
  @context.mark mark, nil
94
- loc = @context.lookup_loc "~#{mark}"
100
+ loc = @context.lookup_loc("~#{mark}")[0]
95
101
  assert_equal nil, loc
96
102
  end
97
103
 
98
104
  @context.mark '7', b_loc
99
- loc = @context.lookup_loc "7"
105
+ loc = @context.lookup_loc("7")[0]
100
106
  assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], loc.stack
101
107
 
102
108
  @context.mark '7', nil
103
- loc = @context.lookup_loc "7"
109
+ loc = @context.lookup_loc("7")[0]
104
110
  assert_equal nil, loc
105
111
  end
106
112
 
107
113
  def test_cd
108
- assert_equal false, @context.cd("nonexistent")
109
114
  assert_equal [['', Root]], @context.loc.stack
110
- assert_equal true, @context.cd("a")
115
+ @context.cd(@context.lookup_loc("a")[0])
111
116
  assert_equal [['', Root], ['a', NodeA]], @context.loc.stack
112
117
  end
118
+
119
+ def test_regex
120
+ daa = [['', Root], ['d', NodeD], ['daa', NodeDaa]]
121
+ dab = [['', Root], ['d', NodeD], ['dab', NodeDab]]
122
+ dabc = [['', Root], ['d', NodeD], ['dabc', NodeDabc]]
123
+ dac = [['', Root], ['d', NodeD], ['dac', NodeDac]]
124
+ locs = @context.lookup_loc '/d/%^daa'
125
+ assert_equal [daa], locs.map(&:stack)
126
+ locs = @context.lookup_loc '/d/%^daa.*'
127
+ assert_equal [daa], locs.map(&:stack)
128
+ locs = @context.lookup_loc '/d/%^da.*c'
129
+ assert_equal [dabc, dac], locs.map(&:stack)
130
+ end
131
+
132
+ def test_glob
133
+ daa = [['', Root], ['d', NodeD], ['daa', NodeDaa]]
134
+ dab = [['', Root], ['d', NodeD], ['dab', NodeDab]]
135
+ dabc = [['', Root], ['d', NodeD], ['dabc', NodeDabc]]
136
+ dac = [['', Root], ['d', NodeD], ['dac', NodeDac]]
137
+ locs = @context.lookup_loc '/d/*daa*'
138
+ assert_equal [daa], locs.map(&:stack)
139
+ locs = @context.lookup_loc '/d/d*a'
140
+ assert_equal [daa], locs.map(&:stack)
141
+ locs = @context.lookup_loc '/d/da*c'
142
+ assert_equal [dabc, dac], locs.map(&:stack)
143
+ end
113
144
  end
metadata CHANGED
@@ -2,7 +2,7 @@
2
2
  name: rvc
3
3
  version: !ruby/object:Gem::Version
4
4
  prerelease:
5
- version: 1.1.0
5
+ version: 1.2.0
6
6
  platform: ruby
7
7
  authors:
8
8
  - Rich Lane
@@ -10,7 +10,7 @@ autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
12
 
13
- date: 2011-04-05 00:00:00 -07:00
13
+ date: 2011-04-12 00:00:00 -07:00
14
14
  default_executable: rvc
15
15
  dependencies:
16
16
  - !ruby/object:Gem::Dependency
@@ -81,6 +81,8 @@ files:
81
81
  - lib/rvc/inventory.rb
82
82
  - lib/rvc/modules.rb
83
83
  - lib/rvc/modules/basic.rb
84
+ - lib/rvc/modules/cluster.rb
85
+ - lib/rvc/modules/datacenter.rb
84
86
  - lib/rvc/modules/datastore.rb
85
87
  - lib/rvc/modules/host.rb
86
88
  - lib/rvc/modules/resource_pool.rb