rvc 1.2.2 → 1.3.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -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.2.2
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
- lookup(parent_path).CreateFolder(:name => File.basename($opts[:path]))
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
@@ -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
- els, absolute, trailing_slash = Path.parse word
92
- last = trailing_slash ? '' : (els.pop || '')
93
- els.map! { |x| x.gsub '\\', '' }
94
- base_loc = absolute ? Location.new($shell.fs.root) : $shell.fs.loc
95
- found_loc = $shell.fs.traverse(base_loc, els).first or return []
96
- cur = found_loc.obj
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| (els+[k])*'/' }.
101
+ map { |k,v| (arcs+[k])*'/' }.
102
102
  map { |x| x.gsub ' ', '\\ ' }
103
103
  end
104
104
 
@@ -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, :loc, :marks
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
- @loc = Location.new root
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
- @loc.path * '/'
38
+ @cur.rvc_path.map { |arc,obj| arc } * '/'
71
39
  end
72
40
 
73
- def cd new_loc
74
- mark '~', @loc
75
- @loc = new_loc
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
- lookup_loc(path).map(&:obj)
48
+ arcs, absolute, trailing_slash = Path.parse path
49
+ base = absolute ? @root : @cur
50
+ traverse(base, arcs)
80
51
  end
81
52
 
82
- def lookup_loc path
83
- els, absolute, trailing_slash = Path.parse path
84
- base_loc = absolute ? Location.new(@root) : @loc
85
- traverse(base_loc, els)
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 loc, el, first
89
- case el
64
+ def traverse_one cur, arc, first
65
+ case arc
90
66
  when '.'
91
- [loc]
67
+ [cur]
92
68
  when '..'
93
- loc.pop unless loc.obj == @root
94
- [loc]
69
+ [cur.rvc_parent ? cur.rvc_parent : cur]
95
70
  when '...'
96
- loc.push(el, loc.obj.parent) unless loc.obj == @root
97
- [loc]
71
+ # XXX shouldnt be nil
72
+ [(cur.respond_to?(:parent) && cur.parent) ? cur.parent : (cur.rvc_parent || cur)]
98
73
  when MARK_PATTERN
99
- return unless first
100
- loc = @marks[$1] or return []
101
- [loc.dup]
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
- loc.obj.children.
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 el
109
- loc.obj.children.
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 el =~ /^\d+$/ and @marks.member? el
115
- loc = @marks[el].dup
87
+ if first and arc =~ /^\d+$/ and objs = @marks[arc]
88
+ objs
116
89
  else
117
- x = loc.obj.traverse_one(el) or return []
118
- loc.push el, x
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
- # 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!
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
- def mark key, loc
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", ".*")}$"
@@ -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 parent
55
- nil
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
@@ -113,48 +113,41 @@ end
113
113
 
114
114
  opts :cd do
115
115
  summary "Change directory"
116
- arg :path, "Directory to change to"
116
+ arg :obj, "Directory to change to", :lookup => Object
117
117
  end
118
118
 
119
119
  rvc_alias :cd
120
120
 
121
- def cd path
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)
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 find_ancestor_loc klass
131
- dc_loc = $shell.fs.loc.dup
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 :path, "Directory to list", :required => false, :default => '.'
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 path
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,obj|
155
- puts "#{i} #{name}#{obj.ls_text(nil)}"
156
- mark_loc = loc.dup.tap { |x| x.push name, obj }
157
- $shell.fs.mark i.to_s, mark_loc
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,obj|
167
- filterSpec.objectSet << { :obj => obj }
168
- filteredTypes << obj.class
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
- mark_loc = loc.dup.tap { |x| x.push name, r.obj }
187
- $shell.fs.mark i.to_s, mark_loc
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 :mark do
223
- summary "Save a path for later use"
224
- arg :key, "Name for this mark"
225
- arg :path, "Any object", :required => false, :default => '.'
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
- def mark key, path
232
- err "invalid mark name" unless key =~ /^\w+$/
233
- # XXX aggregate marks
234
- obj = $shell.fs.lookup_loc(path).first or err "Not found: #{path.inspect}"
235
- $shell.fs.mark key, obj
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
@@ -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 prompt_insecure
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
- system "stty -echo"
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 prompt_insecure
129
- answer = Readline.readline "SSL certificate verification failed. Connect anyway? (y/n) "
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
@@ -111,9 +111,12 @@ class OptionParser < Trollop::Parser
111
111
 
112
112
  def postprocess_arg x, spec
113
113
  if spec[:lookup]
114
- lookup! x, spec[: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]).map { |y| [y, File.basename(x)] }
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
@@ -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.member?(str)
208
- @fs.marks[str].obj
209
- elsif str[0..0] == '_' && @fs.marks.member?(str[1..-1])
210
- @fs.marks[str[1..-1]].obj
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
@@ -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 = "[#{'=' * progresslen}#{' ' * (barlen-progresslen)}]"
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']
@@ -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.loc.path
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 test_lookup_loc_nonexistent
45
- loc = @context.lookup_loc 'nonexistent'
46
- assert_equal [], loc
45
+ def test_lookup_nonexistent
46
+ objs = @context.lookup 'nonexistent'
47
+ assert_equal [], objs
47
48
  end
48
49
 
49
- def test_lookup_loc_simple
50
+ def test_lookup_simple_path
50
51
  %w(a /a ./a ./a/.).each do |path|
51
- loc = @context.lookup_loc(path)[0]
52
- assert_equal NodeA, loc.obj
53
- assert_equal ['', 'a'], loc.path
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
- loc = @context.lookup_loc(path)[0]
59
- assert_equal NodeB, loc.obj
60
- assert_equal ['', 'a', 'b'], loc.path
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 test_lookup_loc_parent
66
- loc = @context.lookup_loc('..')[0]
67
- assert_equal [['', Root]], loc.stack
64
+ def test_lookup_parent
65
+ obj = @context.lookup('..')[0]
66
+ assert_equal [['', Root]], obj.rvc_path
68
67
 
69
- loc = @context.lookup_loc('a/..')[0]
70
- assert_equal [['', Root]], loc.stack
68
+ obj = @context.lookup('a/..')[0]
69
+ assert_equal [['', Root]], obj.rvc_path
71
70
 
72
- loc = @context.lookup_loc('a/b/..')[0]
73
- assert_equal [['', Root], ['a', NodeA]], loc.stack
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
- loc = @context.lookup_loc('...')[0]
78
- assert_equal [['', Root]], loc.stack
77
+ obj = @context.lookup('...')[0]
78
+ assert_equal [['', Root]], obj.rvc_path
79
79
 
80
- loc = @context.lookup_loc('a/...')[0]
81
- assert_equal [['', Root], ['a', NodeA], ['...', Root]], loc.stack
80
+ obj = @context.lookup('a/...')[0]
81
+ assert_equal [['', Root], ['a', NodeA], ['...', Root]], obj.rvc_path
82
82
 
83
- loc = @context.lookup_loc('a/b/...')[0]
84
- assert_equal [['', Root], ['a', NodeA], ['b', NodeB], ['...', NodeA]], loc.stack
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 test_lookup_loc_mark
88
- b_loc = @context.lookup_loc('a/b')[0]
89
- assert_not_nil b_loc
88
+ def test_lookup_mark
89
+ b_obj = @context.lookup('a/b')[0]
90
+ assert_not_nil b_obj
90
91
 
91
- loc = @context.lookup_loc('~foo')[0]
92
- assert_equal nil, loc
92
+ obj = @context.lookup('~foo')[0]
93
+ assert_equal nil, obj
93
94
 
94
95
  ['foo', '~', '7', ''].each do |mark|
95
- @context.mark mark, b_loc
96
- loc = @context.lookup_loc("~#{mark}")[0]
97
- assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], loc.stack
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, nil
100
- loc = @context.lookup_loc("~#{mark}")[0]
101
- assert_equal nil, loc
100
+ @context.mark mark, []
101
+ obj = @context.lookup("~#{mark}")[0]
102
+ assert_equal nil, obj
102
103
  end
103
104
 
104
- @context.mark '7', b_loc
105
- loc = @context.lookup_loc("7")[0]
106
- assert_equal [['', Root], ['a', NodeA], ['b', NodeB]], loc.stack
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', nil
109
- loc = @context.lookup_loc("7")[0]
110
- assert_equal nil, loc
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.loc.stack
115
- @context.cd(@context.lookup_loc("a")[0])
116
- assert_equal [['', Root], ['a', NodeA]], @context.loc.stack
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
- 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)
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
- 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)
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.2.2
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-14 00:00:00 -07:00
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