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 +9 -0
- data/TODO +5 -1
- data/VERSION +1 -1
- data/bin/rvc +7 -2
- data/lib/rvc/completion.rb +1 -1
- data/lib/rvc/extensions/ComputeResource.rb +5 -0
- data/lib/rvc/fs.rb +54 -29
- data/lib/rvc/modules/basic.rb +10 -6
- data/lib/rvc/modules/cluster.rb +63 -0
- data/lib/rvc/modules/datacenter.rb +30 -0
- data/lib/rvc/modules/datastore.rb +3 -3
- data/lib/rvc/modules/host.rb +27 -0
- data/lib/rvc/modules/vm.rb +44 -12
- data/lib/rvc/option_parser.rb +37 -21
- data/lib/rvc/shell.rb +3 -3
- data/lib/rvc/util.rb +21 -4
- data/test/test_fs.rb +57 -26
- metadata +4 -2
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
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
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
|
85
|
-
|
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
|
data/lib/rvc/completion.rb
CHANGED
@@ -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
|
-
|
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
|
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
|
-
|
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
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
103
|
-
|
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
|
-
|
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
|
data/lib/rvc/modules/basic.rb
CHANGED
@@ -42,7 +42,7 @@ rvc_alias :help
|
|
42
42
|
HELP_ORDER = %w(basic vm)
|
43
43
|
|
44
44
|
def help path
|
45
|
-
obj =
|
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
|
-
|
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
|
-
|
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
|
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
|
-
|
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 =
|
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 =
|
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 =
|
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 =
|
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]
|
data/lib/rvc/modules/host.rb
CHANGED
@@ -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
|
data/lib/rvc/modules/vm.rb
CHANGED
@@ -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, "
|
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
|
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 =
|
112
|
+
vmFolder, name = *dest
|
83
113
|
datastore_path = "[#{opts[:datastore].name}]"
|
84
114
|
config = {
|
85
|
-
: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 =>
|
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 =>
|
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 =>
|
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
|
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
|
614
|
-
dev = vm
|
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
|
654
|
+
vm.ReconfigVM_Task(:spec => spec).wait_for_completion
|
623
655
|
end
|
624
656
|
|
625
657
|
def vm_ip vm
|
data/lib/rvc/option_parser.rb
CHANGED
@@ -55,18 +55,21 @@ class OptionParser < Trollop::Parser
|
|
55
55
|
@has_options
|
56
56
|
end
|
57
57
|
|
58
|
-
def arg name, description,
|
59
|
-
|
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
|
64
|
-
|
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
|
67
|
-
|
68
|
-
@
|
69
|
-
|
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] =
|
80
|
+
opts[name] = lookup_single! path, klass
|
78
81
|
end
|
79
82
|
|
80
83
|
argv = leftovers
|
81
84
|
args = []
|
82
|
-
@args.each do |name,
|
83
|
-
if multi
|
84
|
-
err "missing argument '#{name}'" if required and argv.empty?
|
85
|
-
a = (argv.empty? ? default : argv.dup)
|
86
|
-
a.map
|
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
|
-
|
94
|
-
|
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,
|
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::
|
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
|
30
|
-
lookup(path)
|
31
|
-
|
32
|
-
|
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
|
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
|
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
|
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
|
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
|
69
|
+
loc = @context.lookup_loc('a/..')[0]
|
64
70
|
assert_equal [['', Root]], loc.stack
|
65
71
|
|
66
|
-
loc = @context.lookup_loc
|
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
|
80
|
+
loc = @context.lookup_loc('a/...')[0]
|
75
81
|
assert_equal [['', Root], ['a', NodeA], ['...', Root]], loc.stack
|
76
82
|
|
77
|
-
loc = @context.lookup_loc
|
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
|
88
|
+
b_loc = @context.lookup_loc('a/b')[0]
|
83
89
|
assert_not_nil b_loc
|
84
90
|
|
85
|
-
loc = @context.lookup_loc
|
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
|
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
|
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
|
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
|
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
|
-
|
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.
|
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-
|
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
|