rvc 1.1.0 → 1.2.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|