rvc 1.2.2 → 1.3.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 +10 -1
- data/VERSION +1 -1
- data/bin/rvc +8 -6
- data/lib/rvc/completion.rb +8 -8
- data/lib/rvc/fs.rb +46 -82
- data/lib/rvc/inventory.rb +21 -14
- data/lib/rvc/known_hosts.rb +46 -0
- data/lib/rvc/modules/basic.rb +40 -36
- data/lib/rvc/modules/datastore.rb +26 -0
- data/lib/rvc/modules/mark.rb +56 -0
- data/lib/rvc/modules/vim.rb +30 -13
- data/lib/rvc/option_parser.rb +5 -2
- data/lib/rvc/shell.rb +8 -6
- data/lib/rvc/util.rb +11 -1
- data/test/test_fs.rb +60 -59
- metadata +4 -2
data/README.rdoc
CHANGED
@@ -81,6 +81,13 @@ When the working directory is a descendant of a Datacenter object, the mark "~"
|
|
81
81
|
refers to the Datacenter. For example "~/datastore" is a convenient way to get
|
82
82
|
the datastore folder of the current datacenter.
|
83
83
|
|
84
|
+
=== Aggregate marks
|
85
|
+
|
86
|
+
More than one object can be given to the "mark" command. The resulting mark can
|
87
|
+
be used with any command that accepts multiple objects. The "mark.edit" command
|
88
|
+
opens up an editor showing the objects referenced by the given mark and allows
|
89
|
+
you to remove some or add more.
|
90
|
+
|
84
91
|
=== Ruby mode
|
85
92
|
|
86
93
|
Beginning an input line with "/" causes RVC to treat it as Ruby code and eval
|
@@ -89,7 +96,9 @@ it. This gives you direct access to the underlying RbVmomi library. If the line
|
|
89
96
|
|
90
97
|
Marks can be easily used in Ruby mode since there are magic variables with the
|
91
98
|
same names. Since some marks, like numeric ones, aren't valid variable names,
|
92
|
-
they also exist with a "_" prefix.
|
99
|
+
they also exist with a "_" prefix. By default only the first object in an
|
100
|
+
aggregate mark will be returned; to get an array of them all use the '!'
|
101
|
+
suffix.
|
93
102
|
|
94
103
|
The methods "this", "conn", and "dc" are provided in Ruby mode, returning the
|
95
104
|
current object, connection, and datacenter respectively. The connection object
|
data/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
1.
|
1
|
+
1.3.0
|
data/bin/rvc
CHANGED
@@ -19,6 +19,7 @@
|
|
19
19
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
20
20
|
# THE SOFTWARE.
|
21
21
|
require 'readline'
|
22
|
+
require "highline/import"
|
22
23
|
require 'pp'
|
23
24
|
require 'trollop'
|
24
25
|
require 'rbvmomi'
|
@@ -49,6 +50,7 @@ where [options] are:
|
|
49
50
|
EOS
|
50
51
|
|
51
52
|
opt :insecure, "don't verify ssl certificate", :short => 'k', :default => (ENV['RBVMOMI_INSECURE'] == '1')
|
53
|
+
opt :really_insecure, "don't use ~/.rvc/known_hosts", :short => 'K', :default => (ENV['RBVMOMI_REALLY_INSECURE'] == '1')
|
52
54
|
opt :path, "Initial directory", :short => :none, :default => ENV['RVC_PATH'], :type => :string
|
53
55
|
opt :create_directory, "Create the initial directory if it doesn't exist", :short => :none
|
54
56
|
opt :cmd, "command to evaluate", :short => 'c', :multi => true, :type => :string
|
@@ -63,7 +65,7 @@ $shell = RVC::Shell.new
|
|
63
65
|
ARGV.each do |uri|
|
64
66
|
begin
|
65
67
|
puts "Connecting to #{uri}..." if ARGV.size > 1
|
66
|
-
CMD.vim.connect uri, :insecure => $opts[:insecure]
|
68
|
+
CMD.vim.connect uri, :insecure => $opts[:insecure], :really_insecure => $opts[:really_insecure]
|
67
69
|
rescue UserError
|
68
70
|
puts "Failed to connect to #{uri}: #{$!.message}"
|
69
71
|
exit 1
|
@@ -81,15 +83,15 @@ if $opts[:path]
|
|
81
83
|
rescue UserError
|
82
84
|
raise unless $opts[:create_directory]
|
83
85
|
parent_path = File.dirname($opts[:path])
|
84
|
-
|
85
|
-
CMD.basic.cd $opts[:path]
|
86
|
+
lookup_single(parent_path).CreateFolder(:name => File.basename($opts[:path]))
|
87
|
+
CMD.basic.cd lookup_single($opts[:path])
|
86
88
|
end
|
87
89
|
elsif $shell.connections.size == 1
|
88
90
|
conn_name, conn = $shell.connections.first
|
89
91
|
if conn.serviceContent.about.apiType == 'VirtualCenter'
|
90
|
-
CMD.basic.cd conn_name
|
92
|
+
CMD.basic.cd lookup_single(conn_name)
|
91
93
|
else
|
92
|
-
CMD.basic.cd "#{conn_name}/ha-datacenter/vm"
|
94
|
+
CMD.basic.cd lookup_single("#{conn_name}/ha-datacenter/vm")
|
93
95
|
end
|
94
96
|
end
|
95
97
|
|
@@ -97,7 +99,7 @@ unless CMD.vmrc.find_vmrc
|
|
97
99
|
$stderr.puts "VMRC is not installed. You will be unable to view virtual machine consoles. Use the vmrc.install command to install it."
|
98
100
|
end
|
99
101
|
|
100
|
-
CMD.basic.ls '.'
|
102
|
+
CMD.basic.ls lookup_single('.')
|
101
103
|
|
102
104
|
while true
|
103
105
|
begin
|
data/lib/rvc/completion.rb
CHANGED
@@ -87,18 +87,18 @@ module Completion
|
|
87
87
|
ret.sort.select { |e| e.match(prefix_regex) }
|
88
88
|
end
|
89
89
|
|
90
|
+
# TODO convert to globbing
|
90
91
|
def self.child_candidates word
|
91
|
-
|
92
|
-
last = trailing_slash ? '' : (
|
93
|
-
|
94
|
-
|
95
|
-
|
96
|
-
|
97
|
-
els.unshift '' if absolute
|
92
|
+
arcs, absolute, trailing_slash = Path.parse word
|
93
|
+
last = trailing_slash ? '' : (arcs.pop || '')
|
94
|
+
arcs.map! { |x| x.gsub '\\', '' }
|
95
|
+
base = absolute ? $shell.fs.root : $shell.fs.cur
|
96
|
+
cur = $shell.fs.traverse(base, arcs).first or return []
|
97
|
+
arcs.unshift '' if absolute
|
98
98
|
children = Cache[cur, :children] rescue []
|
99
99
|
children.
|
100
100
|
select { |k,v| k.gsub(' ', '\\ ') =~ /^#{Regexp.escape(last)}/ }.
|
101
|
-
map { |k,v| (
|
101
|
+
map { |k,v| (arcs+[k])*'/' }.
|
102
102
|
map { |x| x.gsub ' ', '\\ ' }
|
103
103
|
end
|
104
104
|
|
data/lib/rvc/fs.rb
CHANGED
@@ -20,125 +20,89 @@
|
|
20
20
|
|
21
21
|
module RVC
|
22
22
|
|
23
|
-
class Location
|
24
|
-
attr_reader :stack
|
25
|
-
|
26
|
-
def initialize root
|
27
|
-
@stack = [['', root]]
|
28
|
-
end
|
29
|
-
|
30
|
-
def initialize_copy src
|
31
|
-
super
|
32
|
-
@stack = @stack.dup
|
33
|
-
end
|
34
|
-
|
35
|
-
def push name, obj
|
36
|
-
@stack << [name, obj]
|
37
|
-
end
|
38
|
-
|
39
|
-
def pop
|
40
|
-
@stack.pop
|
41
|
-
end
|
42
|
-
|
43
|
-
def obj
|
44
|
-
@stack.empty? ? nil : @stack[-1][1]
|
45
|
-
end
|
46
|
-
|
47
|
-
def path
|
48
|
-
@stack.map { |name,obj| name }
|
49
|
-
end
|
50
|
-
end
|
51
|
-
|
52
23
|
class FS
|
53
|
-
attr_reader :root, :
|
24
|
+
attr_reader :root, :cur, :marks
|
54
25
|
|
55
26
|
MARK_PATTERN = /^~(?:([\d\w]*|~|@))$/
|
56
27
|
REGEX_PATTERN = /^%/
|
57
28
|
GLOB_PATTERN = /\*/
|
58
29
|
|
59
30
|
def initialize root
|
31
|
+
fail unless root.is_a? RVC::InventoryObject
|
60
32
|
@root = root
|
61
|
-
@
|
33
|
+
@cur = root
|
62
34
|
@marks = {}
|
63
35
|
end
|
64
36
|
|
65
|
-
def cur
|
66
|
-
@loc.obj
|
67
|
-
end
|
68
|
-
|
69
37
|
def display_path
|
70
|
-
@
|
38
|
+
@cur.rvc_path.map { |arc,obj| arc } * '/'
|
71
39
|
end
|
72
40
|
|
73
|
-
def cd
|
74
|
-
|
75
|
-
|
41
|
+
def cd dst
|
42
|
+
fail unless dst.is_a? RVC::InventoryObject
|
43
|
+
mark '~', [@cur]
|
44
|
+
@cur = dst
|
76
45
|
end
|
77
46
|
|
78
47
|
def lookup path
|
79
|
-
|
48
|
+
arcs, absolute, trailing_slash = Path.parse path
|
49
|
+
base = absolute ? @root : @cur
|
50
|
+
traverse(base, arcs)
|
80
51
|
end
|
81
52
|
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
53
|
+
# Starting from base, traverse each path element in arcs. Since the path
|
54
|
+
# may contain wildcards, this function returns a list of matches.
|
55
|
+
def traverse base, arcs
|
56
|
+
objs = [base]
|
57
|
+
arcs.each_with_index do |arc,i|
|
58
|
+
objs.map! { |obj| traverse_one obj, arc, i==0 }
|
59
|
+
objs.flatten!
|
60
|
+
end
|
61
|
+
objs
|
86
62
|
end
|
87
63
|
|
88
|
-
def traverse_one
|
89
|
-
case
|
64
|
+
def traverse_one cur, arc, first
|
65
|
+
case arc
|
90
66
|
when '.'
|
91
|
-
[
|
67
|
+
[cur]
|
92
68
|
when '..'
|
93
|
-
|
94
|
-
[loc]
|
69
|
+
[cur.rvc_parent ? cur.rvc_parent : cur]
|
95
70
|
when '...'
|
96
|
-
|
97
|
-
[
|
71
|
+
# XXX shouldnt be nil
|
72
|
+
[(cur.respond_to?(:parent) && cur.parent) ? cur.parent : (cur.rvc_parent || cur)]
|
98
73
|
when MARK_PATTERN
|
99
|
-
|
100
|
-
|
101
|
-
|
74
|
+
if first and objs = @marks[$1]
|
75
|
+
objs
|
76
|
+
else
|
77
|
+
[]
|
78
|
+
end
|
102
79
|
when REGEX_PATTERN
|
103
80
|
regex = Regexp.new($')
|
104
|
-
|
105
|
-
select { |k,v| k =~ regex }.
|
106
|
-
map { |k,v| loc.dup.tap { |x| x.push(k, v) } }
|
81
|
+
cur.children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
|
107
82
|
when GLOB_PATTERN
|
108
|
-
regex = glob_to_regex
|
109
|
-
|
110
|
-
select { |k,v| k =~ regex }.
|
111
|
-
map { |k,v| loc.dup.tap { |x| x.push(k, v) } }
|
83
|
+
regex = glob_to_regex arc
|
84
|
+
cur.children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
|
112
85
|
else
|
113
86
|
# XXX check for ambiguous child
|
114
|
-
if first and
|
115
|
-
|
87
|
+
if first and arc =~ /^\d+$/ and objs = @marks[arc]
|
88
|
+
objs
|
116
89
|
else
|
117
|
-
|
118
|
-
|
90
|
+
if child = cur.traverse_one(arc)
|
91
|
+
child.rvc_link cur, arc
|
92
|
+
[child]
|
93
|
+
else
|
94
|
+
[]
|
95
|
+
end
|
119
96
|
end
|
120
|
-
[loc]
|
121
97
|
end
|
122
98
|
end
|
123
99
|
|
124
|
-
|
125
|
-
|
126
|
-
|
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!
|
131
|
-
end
|
132
|
-
locs
|
100
|
+
def mark key, objs
|
101
|
+
fail "not an array" unless objs.is_a? Array
|
102
|
+
@marks[key] = objs
|
133
103
|
end
|
134
104
|
|
135
|
-
|
136
|
-
if loc == nil
|
137
|
-
@marks.delete key
|
138
|
-
else
|
139
|
-
@marks[key] = loc
|
140
|
-
end
|
141
|
-
end
|
105
|
+
private
|
142
106
|
|
143
107
|
def glob_to_regex str
|
144
108
|
Regexp.new "^#{Regexp.escape(str.gsub('*', "\0")).gsub("\0", ".*")}$"
|
data/lib/rvc/inventory.rb
CHANGED
@@ -35,6 +35,8 @@ module InventoryObject
|
|
35
35
|
m.extend ClassMethods
|
36
36
|
end
|
37
37
|
|
38
|
+
attr_reader :rvc_parent, :rvc_arc
|
39
|
+
|
38
40
|
def display_info
|
39
41
|
puts "class: #{self.class.name}"
|
40
42
|
end
|
@@ -51,8 +53,25 @@ module InventoryObject
|
|
51
53
|
{}
|
52
54
|
end
|
53
55
|
|
54
|
-
def
|
55
|
-
|
56
|
+
def rvc_path
|
57
|
+
[].tap do |a|
|
58
|
+
cur = self
|
59
|
+
while cur != nil
|
60
|
+
a << [cur.rvc_arc, cur]
|
61
|
+
cur = cur.rvc_parent
|
62
|
+
end
|
63
|
+
a.reverse!
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
def rvc_path_str
|
68
|
+
rvc_path.map { |k,v| k } * '/'
|
69
|
+
end
|
70
|
+
|
71
|
+
def rvc_link parent, arc
|
72
|
+
return if @rvc_parent
|
73
|
+
@rvc_parent = parent
|
74
|
+
@rvc_arc = arc
|
56
75
|
end
|
57
76
|
end
|
58
77
|
|
@@ -72,10 +91,6 @@ class FakeFolder
|
|
72
91
|
true
|
73
92
|
end
|
74
93
|
|
75
|
-
def parent
|
76
|
-
@target
|
77
|
-
end
|
78
|
-
|
79
94
|
def eql? x
|
80
95
|
@target == x.instance_variable_get(:@target) &&
|
81
96
|
@method == x.instance_variable_get(:@method)
|
@@ -93,10 +108,6 @@ class RootNode
|
|
93
108
|
$shell.connections
|
94
109
|
end
|
95
110
|
|
96
|
-
def parent
|
97
|
-
nil
|
98
|
-
end
|
99
|
-
|
100
111
|
def self.folder?
|
101
112
|
true
|
102
113
|
end
|
@@ -115,10 +126,6 @@ class RbVmomi::VIM
|
|
115
126
|
rootFolder.children
|
116
127
|
end
|
117
128
|
|
118
|
-
def parent
|
119
|
-
$shell.fs.root
|
120
|
-
end
|
121
|
-
|
122
129
|
def self.folder?
|
123
130
|
true
|
124
131
|
end
|
@@ -0,0 +1,46 @@
|
|
1
|
+
require 'digest/sha2'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module RVC
|
5
|
+
|
6
|
+
class KnownHosts
|
7
|
+
def filename
|
8
|
+
File.join(ENV['HOME'], ".rvc", "known_hosts");
|
9
|
+
end
|
10
|
+
|
11
|
+
def hash_host protocol, hostname
|
12
|
+
Digest::SHA2.hexdigest([protocol, hostname] * "\0")
|
13
|
+
end
|
14
|
+
|
15
|
+
def hash_public_key public_key
|
16
|
+
Digest::SHA2.hexdigest(public_key)
|
17
|
+
end
|
18
|
+
|
19
|
+
def verify protocol, hostname, public_key
|
20
|
+
expected_hashed_host = hash_host protocol, hostname
|
21
|
+
expected_hashed_public_key = hash_public_key public_key
|
22
|
+
if File.exists? filename
|
23
|
+
fail "bad permissions on known_hosts, expected 0600" unless File.stat(filename).mode & 0666 == 0600
|
24
|
+
File.readlines(filename).each_with_index do |l,i|
|
25
|
+
hashed_host, hashed_public_key = l.split
|
26
|
+
next unless hashed_host == expected_hashed_host
|
27
|
+
if hashed_public_key == expected_hashed_public_key
|
28
|
+
return :ok
|
29
|
+
else
|
30
|
+
return :mismatch, i
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
return :not_found, expected_hashed_public_key
|
35
|
+
end
|
36
|
+
|
37
|
+
def add protocol, hostname, public_key
|
38
|
+
FileUtils.mkdir_p File.dirname(filename)
|
39
|
+
File.open(filename, 'a') do |io|
|
40
|
+
io.chmod 0600
|
41
|
+
io.write "#{hash_host protocol, hostname} #{hash_public_key public_key}\n"
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
end
|
data/lib/rvc/modules/basic.rb
CHANGED
@@ -113,48 +113,41 @@ end
|
|
113
113
|
|
114
114
|
opts :cd do
|
115
115
|
summary "Change directory"
|
116
|
-
arg :
|
116
|
+
arg :obj, "Directory to change to", :lookup => Object
|
117
117
|
end
|
118
118
|
|
119
119
|
rvc_alias :cd
|
120
120
|
|
121
|
-
def cd
|
122
|
-
|
123
|
-
|
124
|
-
$shell.fs.
|
125
|
-
$shell.fs.mark '', find_ancestor_loc(RbVmomi::VIM::Datacenter)
|
126
|
-
$shell.fs.mark '@', find_ancestor_loc(RbVmomi::VIM)
|
121
|
+
def cd obj
|
122
|
+
$shell.fs.cd(obj)
|
123
|
+
$shell.fs.mark '', [find_ancestor(RbVmomi::VIM::Datacenter)].compact
|
124
|
+
$shell.fs.mark '@', [find_ancestor(RbVmomi::VIM)].compact
|
127
125
|
$shell.fs.marks.delete_if { |k,v| k =~ /^\d+$/ }
|
128
126
|
end
|
129
127
|
|
130
|
-
def
|
131
|
-
|
132
|
-
dc_loc.pop while dc_loc.obj and not dc_loc.obj.is_a? klass
|
133
|
-
dc_loc.obj ? dc_loc : nil
|
128
|
+
def find_ancestor klass
|
129
|
+
$shell.fs.cur.rvc_path.map { |k,v| v }.reverse.find { |x| x.is_a? klass }
|
134
130
|
end
|
135
131
|
|
136
132
|
|
137
133
|
opts :ls do
|
138
134
|
summary "List objects in a directory"
|
139
|
-
arg :
|
135
|
+
arg :obj, "Directory to list", :required => false, :default => '.', :lookup => Object
|
140
136
|
end
|
141
137
|
|
142
138
|
rvc_alias :ls
|
143
139
|
rvc_alias :ls, :l
|
144
140
|
|
145
|
-
def ls
|
146
|
-
# XXX check for multiple matches
|
147
|
-
loc = $shell.fs.lookup_loc(path).first or err "Not found: #{path.inspect}"
|
148
|
-
obj = loc.obj
|
141
|
+
def ls obj
|
149
142
|
children = obj.children
|
150
143
|
name_map = children.invert
|
151
144
|
children, fake_children = children.partition { |k,v| v.is_a? VIM::ManagedEntity }
|
152
145
|
i = 0
|
153
146
|
|
154
|
-
fake_children.each do |name,
|
155
|
-
puts "#{i} #{name}#{
|
156
|
-
|
157
|
-
$shell.fs.mark i.to_s,
|
147
|
+
fake_children.each do |name,child|
|
148
|
+
puts "#{i} #{name}#{child.ls_text(nil)}"
|
149
|
+
child.rvc_link obj, name
|
150
|
+
$shell.fs.mark i.to_s, [child]
|
158
151
|
i += 1
|
159
152
|
end
|
160
153
|
|
@@ -163,9 +156,9 @@ def ls path
|
|
163
156
|
filterSpec = VIM.PropertyFilterSpec(:objectSet => [], :propSet => [])
|
164
157
|
filteredTypes = Set.new
|
165
158
|
|
166
|
-
children.each do |name,
|
167
|
-
filterSpec.objectSet << { :obj =>
|
168
|
-
filteredTypes <<
|
159
|
+
children.each do |name,child|
|
160
|
+
filterSpec.objectSet << { :obj => child }
|
161
|
+
filteredTypes << child.class
|
169
162
|
end
|
170
163
|
|
171
164
|
filteredTypes.each do |x|
|
@@ -183,8 +176,8 @@ def ls path
|
|
183
176
|
text = r.obj.ls_text(r) rescue " (error)"
|
184
177
|
realname = r['name'] if name != r['name']
|
185
178
|
puts "#{i} #{name}#{realname && " [#{realname}]"}#{text}"
|
186
|
-
|
187
|
-
$shell.fs.mark i.to_s,
|
179
|
+
r.obj.rvc_link obj, name
|
180
|
+
$shell.fs.mark i.to_s, [r.obj]
|
188
181
|
i += 1
|
189
182
|
end
|
190
183
|
end
|
@@ -199,6 +192,7 @@ rvc_alias :info
|
|
199
192
|
rvc_alias :info, :i
|
200
193
|
|
201
194
|
def info obj
|
195
|
+
puts "path: #{obj.rvc_path_str}"
|
202
196
|
if obj.respond_to? :display_info
|
203
197
|
obj.display_info
|
204
198
|
else
|
@@ -219,20 +213,30 @@ def destroy objs
|
|
219
213
|
end
|
220
214
|
|
221
215
|
|
222
|
-
opts :
|
223
|
-
summary "
|
224
|
-
arg :
|
225
|
-
|
216
|
+
opts :reload_entity do
|
217
|
+
summary "Synchronize management server state"
|
218
|
+
arg :obj, nil, :lookup => VIM::ManagedEntity, :multi => true
|
219
|
+
end
|
220
|
+
|
221
|
+
rvc_alias :reload_entity
|
222
|
+
|
223
|
+
def reload_entity objs
|
224
|
+
objs.each(&:Reload)
|
226
225
|
end
|
227
226
|
|
228
|
-
rvc_alias :mark
|
229
|
-
rvc_alias :mark, :m
|
230
227
|
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
228
|
+
opts :show do
|
229
|
+
summary "Basic information about the given objects"
|
230
|
+
arg :obj, nil, :multi => true, :required => false, :lookup => Object
|
231
|
+
end
|
232
|
+
|
233
|
+
rvc_alias :show
|
234
|
+
rvc_alias :show, :w
|
235
|
+
|
236
|
+
def show objs
|
237
|
+
objs.each do |obj|
|
238
|
+
puts "#{obj.rvc_path_str}: #{obj.class}"
|
239
|
+
end
|
236
240
|
end
|
237
241
|
|
238
242
|
|
@@ -30,6 +30,7 @@ def download datastore_path, local_path
|
|
30
30
|
file.datastore.download file.path, local_path
|
31
31
|
end
|
32
32
|
|
33
|
+
|
33
34
|
opts :upload do
|
34
35
|
summary "Upload a file to a datastore"
|
35
36
|
arg 'local-path', "Filename on the local machine"
|
@@ -45,6 +46,7 @@ def upload local_path, datastore_path
|
|
45
46
|
dir.datastore.upload real_datastore_path, local_path
|
46
47
|
end
|
47
48
|
|
49
|
+
|
48
50
|
opts :mkdir do
|
49
51
|
summary "Create a directory on a datastore"
|
50
52
|
arg 'path', "Directory to create on the datastore"
|
@@ -61,3 +63,27 @@ def mkdir datastore_path
|
|
61
63
|
:datacenter => dc,
|
62
64
|
:createParentDirectories => false
|
63
65
|
end
|
66
|
+
|
67
|
+
|
68
|
+
opts :edit do
|
69
|
+
summary "Edit a file"
|
70
|
+
arg "file", nil, :lookup => VIM::Datastore::FakeDatastoreFile
|
71
|
+
end
|
72
|
+
|
73
|
+
rvc_alias :edit, :vi
|
74
|
+
|
75
|
+
def edit file
|
76
|
+
editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
|
77
|
+
filename = File.join(Dir.tmpdir, "rvc.#{Time.now.to_i}.#{rand(65536)}")
|
78
|
+
file.datastore.download(file.path, filename) rescue err("download failed")
|
79
|
+
begin
|
80
|
+
pre_stat = File.stat filename
|
81
|
+
system("#{editor} #{filename}")
|
82
|
+
post_stat = File.stat filename
|
83
|
+
if pre_stat != post_stat
|
84
|
+
file.datastore.upload(file.path, filename) rescue err("upload failed")
|
85
|
+
end
|
86
|
+
ensure
|
87
|
+
File.unlink filename
|
88
|
+
end
|
89
|
+
end
|
@@ -0,0 +1,56 @@
|
|
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 :mark do
|
22
|
+
summary "Save an object for later use"
|
23
|
+
arg :key, "Name for this mark"
|
24
|
+
arg :obj, "Any objects", :required => false, :default => ['.'], :multi => true, :lookup => Object
|
25
|
+
end
|
26
|
+
|
27
|
+
rvc_alias :mark
|
28
|
+
rvc_alias :mark, :m
|
29
|
+
|
30
|
+
def mark key, objs
|
31
|
+
err "invalid mark name" unless key =~ /^\w+$/
|
32
|
+
$shell.fs.mark key, objs
|
33
|
+
end
|
34
|
+
|
35
|
+
|
36
|
+
opts :edit do
|
37
|
+
summary "Edit objects referenced by a mark"
|
38
|
+
arg :key, "Name of mark"
|
39
|
+
end
|
40
|
+
|
41
|
+
rvc_alias :edit, :me
|
42
|
+
|
43
|
+
def edit key
|
44
|
+
editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
|
45
|
+
objs = $shell.fs.marks[key] or err "no such mark #{key.inspect}"
|
46
|
+
filename = File.join(Dir.tmpdir, "rvc.#{Time.now.to_i}.#{rand(65536)}")
|
47
|
+
File.open(filename, 'w') { |io| objs.each { |obj| io.puts(obj.rvc_path_str) } }
|
48
|
+
begin
|
49
|
+
system("#{editor} #{filename}")
|
50
|
+
new_paths = File.readlines(filename).map(&:chomp) rescue return
|
51
|
+
new_objs = new_paths.map { |path| lookup(path) }.inject([], &:+)
|
52
|
+
mark key, new_objs
|
53
|
+
ensure
|
54
|
+
File.unlink filename
|
55
|
+
end
|
56
|
+
end
|
data/lib/rvc/modules/vim.rb
CHANGED
@@ -18,6 +18,8 @@
|
|
18
18
|
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
19
|
# THE SOFTWARE.
|
20
20
|
|
21
|
+
require 'rvc/known_hosts'
|
22
|
+
|
21
23
|
URI_REGEX = %r{
|
22
24
|
^
|
23
25
|
(?:
|
@@ -63,13 +65,34 @@ def connect uri, opts
|
|
63
65
|
:insecure => insecure
|
64
66
|
break
|
65
67
|
rescue OpenSSL::SSL::SSLError
|
66
|
-
err "Connection failed" unless
|
68
|
+
err "Connection failed" unless prompt_cert_insecure
|
67
69
|
insecure = true
|
68
70
|
rescue Errno::EHOSTUNREACH, SocketError
|
69
71
|
err $!.message
|
70
72
|
end
|
71
73
|
end
|
72
74
|
|
75
|
+
if opts[:really_insecure]
|
76
|
+
result = :ok
|
77
|
+
else
|
78
|
+
peer_public_key = vim.http.peer_cert.public_key
|
79
|
+
known_hosts = RVC::KnownHosts.new
|
80
|
+
result, arg = known_hosts.verify 'vim', host, peer_public_key.to_s
|
81
|
+
end
|
82
|
+
|
83
|
+
if result == :not_found
|
84
|
+
puts "The authenticity of host '#{host}' can't be established."
|
85
|
+
puts "Public key fingerprint is #{arg}."
|
86
|
+
err "Connection failed" unless prompt_cert_unknown
|
87
|
+
puts "Warning: Permanently added '#{host}' (vim) to the list of known hosts"
|
88
|
+
known_hosts.add 'vim', host, peer_public_key.to_s
|
89
|
+
elsif result == :mismatch
|
90
|
+
err "Public key fingerprint for host '#{host}' does not match #{known_hosts.filename}:#{arg}."
|
91
|
+
elsif result == :ok
|
92
|
+
else
|
93
|
+
err "Unexpected result from known_hosts check"
|
94
|
+
end
|
95
|
+
|
73
96
|
unless opts[:rev]
|
74
97
|
# negotiate API version
|
75
98
|
rev = vim.serviceContent.about.apiVersion
|
@@ -114,19 +137,13 @@ def connect uri, opts
|
|
114
137
|
end
|
115
138
|
|
116
139
|
def prompt_password
|
117
|
-
|
118
|
-
$stdout.write "password: "
|
119
|
-
$stdout.flush
|
120
|
-
begin
|
121
|
-
($stdin.gets||exit(1)).chomp
|
122
|
-
ensure
|
123
|
-
system "stty echo"
|
124
|
-
puts
|
125
|
-
end
|
140
|
+
ask("password: ") { |q| q.echo = false }
|
126
141
|
end
|
127
142
|
|
128
|
-
def
|
129
|
-
|
130
|
-
answer == 'yes' or answer == 'y'
|
143
|
+
def prompt_cert_insecure
|
144
|
+
agree("SSL certificate verification failed. Connect anyway (y/n)? ", true)
|
131
145
|
end
|
132
146
|
|
147
|
+
def prompt_cert_unknown
|
148
|
+
agree("Are you sure you want to continue connecting (y/n)? ", true)
|
149
|
+
end
|
data/lib/rvc/option_parser.rb
CHANGED
@@ -111,9 +111,12 @@ class OptionParser < Trollop::Parser
|
|
111
111
|
|
112
112
|
def postprocess_arg x, spec
|
113
113
|
if spec[:lookup]
|
114
|
-
lookup!
|
114
|
+
lookup!(x, spec[:lookup]).
|
115
|
+
tap { |a| err "no matches for #{x.inspect}" if a.empty? }
|
115
116
|
elsif spec[:lookup_parent]
|
116
|
-
lookup!(File.dirname(x), spec[:lookup_parent]).
|
117
|
+
lookup!(File.dirname(x), spec[:lookup_parent]).
|
118
|
+
map { |y| [y, File.basename(x)] }.
|
119
|
+
tap { |a| err "no matches for #{File.dirname(x).inspect}" if a.empty? }
|
117
120
|
else
|
118
121
|
[x]
|
119
122
|
end
|
data/lib/rvc/shell.rb
CHANGED
@@ -81,7 +81,7 @@ class Shell
|
|
81
81
|
err "invalid command" unless cmd.is_a? String
|
82
82
|
case cmd
|
83
83
|
when RVC::FS::MARK_PATTERN
|
84
|
-
CMD.basic.cd cmd
|
84
|
+
CMD.basic.cd lookup_single(cmd)
|
85
85
|
else
|
86
86
|
if cmd.include? '.'
|
87
87
|
module_name, cmd, = cmd.split '.'
|
@@ -123,7 +123,7 @@ class Shell
|
|
123
123
|
end
|
124
124
|
|
125
125
|
def prompt
|
126
|
-
"#{@fs.display_path}#{@persist_ruby ? '~' : '>'} "
|
126
|
+
"#{@fs.display_path}#{$terminal.color(@persist_ruby ? '~' : '>', :yellow)} "
|
127
127
|
end
|
128
128
|
|
129
129
|
def introspect_object obj
|
@@ -204,10 +204,12 @@ class RubyEvaluator
|
|
204
204
|
if a.empty?
|
205
205
|
if MODULES.member? str
|
206
206
|
MODULES[str]
|
207
|
-
elsif @fs.marks
|
208
|
-
|
209
|
-
|
210
|
-
|
207
|
+
elsif str =~ /_?([\w\d]+)(!?)/ && objs = @fs.marks[$1]
|
208
|
+
if $2 == '!'
|
209
|
+
objs
|
210
|
+
else
|
211
|
+
objs.first
|
212
|
+
end
|
211
213
|
else
|
212
214
|
super
|
213
215
|
end
|
data/lib/rvc/util.rb
CHANGED
@@ -93,6 +93,16 @@ module Util
|
|
93
93
|
progress(objs.map { |obj| obj._call :"#{sym}_Task", args })
|
94
94
|
end
|
95
95
|
|
96
|
+
if ENV['LANG'] =~ /UTF/ and RUBY_VERSION >= '1.9.1'
|
97
|
+
PROGRESS_BAR_LEFT = "\u2772"
|
98
|
+
PROGRESS_BAR_MIDDLE = "\u25AC"
|
99
|
+
PROGRESS_BAR_RIGHT = "\u2773"
|
100
|
+
else
|
101
|
+
PROGRESS_BAR_LEFT = "["
|
102
|
+
PROGRESS_BAR_MIDDLE = "="
|
103
|
+
PROGRESS_BAR_RIGHT = "]"
|
104
|
+
end
|
105
|
+
|
96
106
|
def progress tasks
|
97
107
|
interested = %w(info.progress info.state info.entityName info.error info.name)
|
98
108
|
connection = single_connection tasks
|
@@ -106,7 +116,7 @@ module Util
|
|
106
116
|
progress = props['info.progress']
|
107
117
|
barlen = terminal_columns - text.size - 2
|
108
118
|
progresslen = ((progress||0)*barlen)/100
|
109
|
-
progress_bar = "
|
119
|
+
progress_bar = "#{PROGRESS_BAR_LEFT}#{PROGRESS_BAR_MIDDLE * progresslen}#{' ' * (barlen-progresslen)}#{PROGRESS_BAR_RIGHT}"
|
110
120
|
$stdout.write "\e[K#{text}#{progress_bar}\n"
|
111
121
|
elsif state == 'error'
|
112
122
|
error = props['info.error']
|
data/test/test_fs.rb
CHANGED
@@ -14,6 +14,8 @@ class FSTest < Test::Unit::TestCase
|
|
14
14
|
NodeA = FixtureNode.new('A', 'b' => NodeB, 'c' => NodeC)
|
15
15
|
Root = FixtureNode.new('ROOT', 'a' => NodeA, 'd' => NodeD)
|
16
16
|
|
17
|
+
Root.rvc_link nil, ''
|
18
|
+
|
17
19
|
def setup
|
18
20
|
@context = RVC::FS.new Root
|
19
21
|
end
|
@@ -26,8 +28,7 @@ class FSTest < Test::Unit::TestCase
|
|
26
28
|
assert_equal Root, @context.cur
|
27
29
|
assert_equal "", @context.display_path
|
28
30
|
assert_equal 0, @context.marks.size
|
29
|
-
assert_equal [''], @context.
|
30
|
-
assert_equal [['', Root]], @context.loc.stack
|
31
|
+
assert_equal [['', Root]], @context.cur.rvc_path
|
31
32
|
end
|
32
33
|
|
33
34
|
def test_lookup_simple
|
@@ -41,79 +42,79 @@ class FSTest < Test::Unit::TestCase
|
|
41
42
|
assert_equal [NodeC], @context.lookup('a/b/.../c')
|
42
43
|
end
|
43
44
|
|
44
|
-
def
|
45
|
-
|
46
|
-
assert_equal [],
|
45
|
+
def test_lookup_nonexistent
|
46
|
+
objs = @context.lookup 'nonexistent'
|
47
|
+
assert_equal [], objs
|
47
48
|
end
|
48
49
|
|
49
|
-
def
|
50
|
+
def test_lookup_simple_path
|
50
51
|
%w(a /a ./a ./a/.).each do |path|
|
51
|
-
|
52
|
-
assert_equal NodeA,
|
53
|
-
assert_equal ['', 'a'],
|
54
|
-
assert_equal [['', Root], ['a', NodeA]], loc.stack
|
52
|
+
obj = @context.lookup(path)[0]
|
53
|
+
assert_equal NodeA, obj
|
54
|
+
assert_equal [['', Root], ['a', NodeA]], obj.rvc_path
|
55
55
|
end
|
56
56
|
|
57
57
|
%w(a/b /a/b ./a/b /a/b/.).each do |path|
|
58
|
-
|
59
|
-
assert_equal NodeB,
|
60
|
-
assert_equal ['', 'a', 'b'],
|
61
|
-
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], loc.stack
|
58
|
+
obj = @context.lookup(path)[0]
|
59
|
+
assert_equal NodeB, obj
|
60
|
+
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
|
62
61
|
end
|
63
62
|
end
|
64
63
|
|
65
|
-
def
|
66
|
-
|
67
|
-
assert_equal [['', Root]],
|
64
|
+
def test_lookup_parent
|
65
|
+
obj = @context.lookup('..')[0]
|
66
|
+
assert_equal [['', Root]], obj.rvc_path
|
68
67
|
|
69
|
-
|
70
|
-
assert_equal [['', Root]],
|
68
|
+
obj = @context.lookup('a/..')[0]
|
69
|
+
assert_equal [['', Root]], obj.rvc_path
|
71
70
|
|
72
|
-
|
73
|
-
assert_equal [['', Root], ['a', NodeA]],
|
71
|
+
obj = @context.lookup('a/b/..')[0]
|
72
|
+
assert_equal [['', Root], ['a', NodeA]], obj.rvc_path
|
74
73
|
end
|
75
74
|
|
75
|
+
=begin
|
76
76
|
def test_lookup_loc_realparent
|
77
|
-
|
78
|
-
assert_equal [['', Root]],
|
77
|
+
obj = @context.lookup('...')[0]
|
78
|
+
assert_equal [['', Root]], obj.rvc_path
|
79
79
|
|
80
|
-
|
81
|
-
assert_equal [['', Root], ['a', NodeA], ['...', Root]],
|
80
|
+
obj = @context.lookup('a/...')[0]
|
81
|
+
assert_equal [['', Root], ['a', NodeA], ['...', Root]], obj.rvc_path
|
82
82
|
|
83
|
-
|
84
|
-
assert_equal [['', Root], ['a', NodeA], ['b', NodeB], ['...', NodeA]],
|
83
|
+
obj = @context.lookup('a/b/...')[0]
|
84
|
+
assert_equal [['', Root], ['a', NodeA], ['b', NodeB], ['...', NodeA]], obj.rvc_path
|
85
85
|
end
|
86
|
+
=end
|
86
87
|
|
87
|
-
def
|
88
|
-
|
89
|
-
assert_not_nil
|
88
|
+
def test_lookup_mark
|
89
|
+
b_obj = @context.lookup('a/b')[0]
|
90
|
+
assert_not_nil b_obj
|
90
91
|
|
91
|
-
|
92
|
-
assert_equal nil,
|
92
|
+
obj = @context.lookup('~foo')[0]
|
93
|
+
assert_equal nil, obj
|
93
94
|
|
94
95
|
['foo', '~', '7', ''].each do |mark|
|
95
|
-
@context.mark mark,
|
96
|
-
|
97
|
-
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]],
|
96
|
+
@context.mark mark, [b_obj]
|
97
|
+
obj = @context.lookup("~#{mark}")[0]
|
98
|
+
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
|
98
99
|
|
99
|
-
@context.mark mark,
|
100
|
-
|
101
|
-
assert_equal nil,
|
100
|
+
@context.mark mark, []
|
101
|
+
obj = @context.lookup("~#{mark}")[0]
|
102
|
+
assert_equal nil, obj
|
102
103
|
end
|
103
104
|
|
104
|
-
@context.mark '7',
|
105
|
-
|
106
|
-
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]],
|
105
|
+
@context.mark '7', [b_obj]
|
106
|
+
obj = @context.lookup("7")[0]
|
107
|
+
assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], obj.rvc_path
|
107
108
|
|
108
|
-
@context.mark '7',
|
109
|
-
|
110
|
-
assert_equal nil,
|
109
|
+
@context.mark '7', []
|
110
|
+
obj = @context.lookup("7")[0]
|
111
|
+
assert_equal nil, obj
|
111
112
|
end
|
112
113
|
|
113
114
|
def test_cd
|
114
|
-
assert_equal [['', Root]], @context.
|
115
|
-
@context.cd(@context.
|
116
|
-
assert_equal [['', Root], ['a', NodeA]], @context.
|
115
|
+
assert_equal [['', Root]], @context.cur.rvc_path
|
116
|
+
@context.cd(@context.lookup("a")[0])
|
117
|
+
assert_equal [['', Root], ['a', NodeA]], @context.cur.rvc_path
|
117
118
|
end
|
118
119
|
|
119
120
|
def test_regex
|
@@ -121,12 +122,12 @@ class FSTest < Test::Unit::TestCase
|
|
121
122
|
dab = [['', Root], ['d', NodeD], ['dab', NodeDab]]
|
122
123
|
dabc = [['', Root], ['d', NodeD], ['dabc', NodeDabc]]
|
123
124
|
dac = [['', Root], ['d', NodeD], ['dac', NodeDac]]
|
124
|
-
|
125
|
-
assert_equal [daa],
|
126
|
-
|
127
|
-
assert_equal [daa],
|
128
|
-
|
129
|
-
assert_equal [dabc, dac],
|
125
|
+
objs = @context.lookup '/d/%^daa'
|
126
|
+
assert_equal [daa], objs.map(&:rvc_path)
|
127
|
+
objs = @context.lookup '/d/%^daa.*'
|
128
|
+
assert_equal [daa], objs.map(&:rvc_path)
|
129
|
+
objs = @context.lookup '/d/%^da.*c'
|
130
|
+
assert_equal [dabc, dac], objs.map(&:rvc_path)
|
130
131
|
end
|
131
132
|
|
132
133
|
def test_glob
|
@@ -134,11 +135,11 @@ class FSTest < Test::Unit::TestCase
|
|
134
135
|
dab = [['', Root], ['d', NodeD], ['dab', NodeDab]]
|
135
136
|
dabc = [['', Root], ['d', NodeD], ['dabc', NodeDabc]]
|
136
137
|
dac = [['', Root], ['d', NodeD], ['dac', NodeDac]]
|
137
|
-
|
138
|
-
assert_equal [daa],
|
139
|
-
|
140
|
-
assert_equal [daa],
|
141
|
-
|
142
|
-
assert_equal [dabc, dac],
|
138
|
+
objs = @context.lookup '/d/*daa*'
|
139
|
+
assert_equal [daa], objs.map(&:rvc_path)
|
140
|
+
objs = @context.lookup '/d/d*a'
|
141
|
+
assert_equal [daa], objs.map(&:rvc_path)
|
142
|
+
objs = @context.lookup '/d/da*c'
|
143
|
+
assert_equal [dabc, dac], objs.map(&:rvc_path)
|
143
144
|
end
|
144
145
|
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.3.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-19 00:00:00 -07:00
|
14
14
|
default_executable: rvc
|
15
15
|
dependencies:
|
16
16
|
- !ruby/object:Gem::Dependency
|
@@ -79,12 +79,14 @@ files:
|
|
79
79
|
- lib/rvc/extensions/VirtualMachine.rb
|
80
80
|
- lib/rvc/fs.rb
|
81
81
|
- lib/rvc/inventory.rb
|
82
|
+
- lib/rvc/known_hosts.rb
|
82
83
|
- lib/rvc/modules.rb
|
83
84
|
- lib/rvc/modules/basic.rb
|
84
85
|
- lib/rvc/modules/cluster.rb
|
85
86
|
- lib/rvc/modules/datacenter.rb
|
86
87
|
- lib/rvc/modules/datastore.rb
|
87
88
|
- lib/rvc/modules/host.rb
|
89
|
+
- lib/rvc/modules/mark.rb
|
88
90
|
- lib/rvc/modules/resource_pool.rb
|
89
91
|
- lib/rvc/modules/vim.rb
|
90
92
|
- lib/rvc/modules/vm.rb
|