rvc 1.3.6 → 1.4.0

Sign up to get free protection for your applications and to get access to all the features.
data/README.rdoc CHANGED
@@ -47,6 +47,14 @@ example, the command to power off a VM is actually "vm.off", since it exists in
47
47
  the "vm" module, but since it is a common operation it has been aliased to
48
48
  "off".
49
49
 
50
+ == Paths
51
+
52
+ All vm operations need a full path, or relative path, rather than just the
53
+ name of the resource. So to create a VM you would use:
54
+
55
+ /> vm.create -p /vm1/ha/host/hosta/resourcePool/pools/dev/ -d
56
+ /vm1/ha/datastore/datastore1 /vm1/ha/vms/newvm
57
+
50
58
  == Features
51
59
 
52
60
  === Tab-completion
data/Rakefile CHANGED
@@ -39,3 +39,15 @@ begin
39
39
  rescue LoadError
40
40
  puts "Rcov not available. Install it with: gem install rcov"
41
41
  end
42
+
43
+ begin
44
+ # HACK rvc needs to be installed as a gem
45
+ require 'rvc'
46
+ require 'ocra'
47
+ desc 'Compile into a win32 executable'
48
+ task :exe do
49
+ sh "ocra bin/rvc"
50
+ end
51
+ rescue LoadError
52
+ puts "OCRA not available. Install it with: gem install ocra"
53
+ end
data/TODO CHANGED
@@ -1,8 +1,12 @@
1
1
  add commands for more entities
2
2
  add fake subfolders for more entities
3
- add fastpass authentication
3
+ add fastpass and SSPI authentication
4
4
 
5
5
  commands:
6
6
  storage vmotion
7
7
  edit clusters
8
8
  add/remove hosts from clusters
9
+ watch tasks
10
+ tail -f logs
11
+ show virtual disk tree
12
+ consolidate delta disks
data/VERSION CHANGED
@@ -1 +1 @@
1
- 1.3.6
1
+ 1.4.0
data/bin/rvc CHANGED
@@ -30,7 +30,6 @@ require 'backports'
30
30
  require 'rvc'
31
31
 
32
32
  VIM = RbVmomi::VIM
33
- include RVC::Util
34
33
 
35
34
  Thread.abort_on_exception = true
36
35
 
@@ -51,8 +50,6 @@ Usage:
51
50
  where [options] are:
52
51
  EOS
53
52
 
54
- opt :insecure, "don't verify ssl certificate", :short => 'k', :default => (ENV['RBVMOMI_INSECURE'] == '1')
55
- opt :really_insecure, "don't use ~/.rvc/known_hosts", :short => 'K', :default => (ENV['RBVMOMI_REALLY_INSECURE'] == '1')
56
53
  opt :path, "Initial directory", :short => :none, :default => ENV['RVC_PATH'], :type => :string
57
54
  opt :create_directory, "Create the initial directory if it doesn't exist", :short => :none
58
55
  opt :cmd, "command to evaluate", :short => 'c', :multi => true, :type => :string
@@ -60,13 +57,36 @@ end
60
57
 
61
58
  RVC.reload_modules false
62
59
 
63
- $shell = RVC::Shell.new
60
+ session = if ENV['RVC_SESSION'] and not ENV['RVC_SESSION'].empty?
61
+ RVC::FilesystemSession.new ENV['RVC_SESSION']
62
+ else
63
+ RVC::MemorySession.new
64
+ end
65
+
66
+ $shell = RVC::Shell.new session
67
+
68
+ # Prompt for hostname if none given. Useful on win32.
69
+ if $shell.session.connections.empty? and ARGV.empty?
70
+ ARGV << Readline.readline("Host to connect to (user@host): ")
71
+ end
72
+
73
+ $shell.session.connections.each do |key|
74
+ conn = $shell.session.get_connection(key) or fail "no such connection #{key.inspect}"
75
+ uri = "#{conn['username']}@#{conn['host']}"
76
+ begin
77
+ puts "Connecting to #{uri}..."
78
+ CMD.vim.connect uri, {}
79
+ rescue RVC::Util::UserError
80
+ puts "Failed to connect to #{uri}: #{$!.message}"
81
+ exit 1
82
+ end
83
+ end
64
84
 
65
85
  ARGV.each do |uri|
66
86
  begin
67
- puts "Connecting to #{uri}..." if ARGV.size > 1
68
- CMD.vim.connect uri, :insecure => $opts[:insecure], :really_insecure => $opts[:really_insecure]
69
- rescue UserError
87
+ puts "Connecting to #{uri}..." if (ARGV+$shell.session.connections).size > 1
88
+ CMD.vim.connect uri, {}
89
+ rescue RVC::Util::UserError
70
90
  puts "Failed to connect to #{uri}: #{$!.message}"
71
91
  exit 1
72
92
  end
@@ -79,19 +99,19 @@ history = File.open(history_fn, 'a')
79
99
 
80
100
  if $opts[:path]
81
101
  begin
82
- CMD.basic.cd lookup_single($opts[:path])
83
- rescue UserError
102
+ CMD.basic.cd RVC::Util.lookup_single($opts[:path])
103
+ rescue RVC::Util::UserError
84
104
  raise unless $opts[:create_directory]
85
105
  parent_path = File.dirname($opts[:path])
86
- lookup_single(parent_path).CreateFolder(:name => File.basename($opts[:path]))
87
- CMD.basic.cd lookup_single($opts[:path])
106
+ RVC::Util.lookup_single(parent_path).CreateFolder(:name => File.basename($opts[:path]))
107
+ CMD.basic.cd RVC::Util.lookup_single($opts[:path])
88
108
  end
89
109
  elsif $shell.connections.size == 1
90
110
  conn_name, conn = $shell.connections.first
91
111
  if conn.serviceContent.about.apiType == 'VirtualCenter'
92
- CMD.basic.cd lookup_single(conn_name)
112
+ CMD.basic.cd RVC::Util.lookup_single(conn_name)
93
113
  else
94
- CMD.basic.cd lookup_single("#{conn_name}/ha-datacenter/vm")
114
+ CMD.basic.cd RVC::Util.lookup_single("#{conn_name}/ha-datacenter/vm")
95
115
  end
96
116
  end
97
117
 
@@ -109,7 +129,7 @@ if $shell.connections.empty?
109
129
  $stderr.puts "Use the 'connect' command to connect to an ESX or VC server."
110
130
  end
111
131
 
112
- CMD.basic.ls lookup_single('.')
132
+ CMD.basic.ls RVC::Util.lookup_single('.')
113
133
 
114
134
  while true
115
135
  begin
@@ -107,7 +107,7 @@ module Completion
107
107
  def self.mark_candidates word
108
108
  return [] unless word.empty? || word[0..0] == '~'
109
109
  prefix_regex = /^#{Regexp.escape(word[1..-1] || '')}/
110
- $shell.fs.marks.keys.sort.grep(prefix_regex).map { |x| "~#{x}" }
110
+ $shell.session.marks.sort.grep(prefix_regex).map { |x| "~#{x}" }
111
111
  end
112
112
  end
113
113
  end
@@ -21,7 +21,7 @@
21
21
  class RbVmomi::VIM::VirtualMachine
22
22
  def display_info
23
23
  config, runtime, guest = collect :config, :runtime, :guest
24
- err "Information currently unavailable" unless config and runtime and guest
24
+ RVC::Util.err "Information currently unavailable" unless config and runtime and guest
25
25
 
26
26
  puts "name: #{config.name}"
27
27
  puts "note: #{config.annotation}" if config.annotation and !config.annotation.empty?
@@ -68,6 +68,31 @@ class RbVmomi::VIM::VirtualMachine
68
68
  {
69
69
  'host' => host,
70
70
  'resourcePool' => resourcePool,
71
+ 'datastores' => RVC::FakeFolder.new(self, :rvc_children_datastores),
72
+ 'networks' => RVC::FakeFolder.new(self, :rvc_children_networks),
73
+ 'files' => RVC::FakeFolder.new(self, :rvc_children_files),
71
74
  }
72
75
  end
76
+
77
+ def rvc_children_datastores
78
+ RVC::Util.collect_children self, :datastore
79
+ end
80
+
81
+ def rvc_children_networks
82
+ RVC::Util.collect_children self, :network
83
+ end
84
+
85
+ def rvc_children_files
86
+ files = layoutEx.file
87
+ datastore_map = RVC::Util.collect_children self, :datastore
88
+ Hash[files.map do |file|
89
+ file.name =~ /^\[(.+)\] (.+)$/ or fail "invalid datastore path"
90
+ ds = datastore_map[$1] or fail "datastore #{$1.inspect} not found"
91
+ arcs, = RVC::Path.parse $2
92
+ arcs.unshift 'files'
93
+ objs = $shell.fs.traverse ds, arcs
94
+ fail unless objs.size == 1
95
+ [File.basename(file.name), objs.first]
96
+ end]
97
+ end
73
98
  end
@@ -0,0 +1,81 @@
1
+ module RVC
2
+
3
+ class FilesystemSession
4
+ def initialize name
5
+ fail "invalid session name" unless name =~ /^[\w-]+$/
6
+ @dir = File.join(ENV['HOME'], '.rvc', 'sessions', name)
7
+ prev_umask = File.umask 077
8
+ FileUtils.mkdir_p @dir
9
+ FileUtils.mkdir_p mark_dir
10
+ FileUtils.mkdir_p connection_dir
11
+ File.umask prev_umask
12
+ @priv = {}
13
+ end
14
+
15
+ def marks
16
+ Dir.entries(mark_dir).reject { |x| x == '.' || x == '..' } + @priv.keys
17
+ end
18
+
19
+ def get_mark key
20
+ if is_private_mark? key
21
+ @priv[key]
22
+ else
23
+ return nil unless File.exists? mark_fn(key)
24
+ File.readlines(mark_fn(key)).
25
+ map { |path| RVC::Util.lookup(path.chomp) }.
26
+ inject([], &:+)
27
+ end
28
+ end
29
+
30
+ def set_mark key, objs
31
+ if is_private_mark? key
32
+ if objs == nil
33
+ @priv.delete key
34
+ else
35
+ @priv[key] = objs
36
+ end
37
+ else
38
+ if objs == nil
39
+ File.unlink mark_fn(key)
40
+ else
41
+ File.open(mark_fn(key), 'w') do |io|
42
+ objs.each { |obj| io.puts obj.rvc_path_str }
43
+ end
44
+ end
45
+ end
46
+ end
47
+
48
+ def connections
49
+ Dir.entries(connection_dir).reject { |x| x == '.' || x == '..' }
50
+ end
51
+
52
+ def get_connection key
53
+ return nil unless File.exists? connection_fn(key)
54
+ File.open(connection_fn(key)) { |io| YAML.load io }
55
+ end
56
+
57
+ def set_connection key, conn
58
+ if conn == nil
59
+ File.unlink(connection_fn(key))
60
+ else
61
+ File.open(connection_fn(key), 'w') { |io| YAML.dump conn, io }
62
+ end
63
+ end
64
+
65
+ private
66
+
67
+ def is_private_mark? key
68
+ return key == '' ||
69
+ key == '~' ||
70
+ key == '@' ||
71
+ key =~ /^\d+$/
72
+ end
73
+
74
+ def mark_dir; File.join(@dir, 'marks') end
75
+ def mark_fn(key); File.join(mark_dir, key) end
76
+ def connection_dir; File.join(@dir, 'connections') end
77
+ def connection_fn(key); File.join(connection_dir, key) end
78
+ end
79
+
80
+ end
81
+
data/lib/rvc/fs.rb CHANGED
@@ -21,7 +21,7 @@
21
21
  module RVC
22
22
 
23
23
  class FS
24
- attr_reader :root, :cur, :marks
24
+ attr_reader :root, :cur
25
25
 
26
26
  MARK_PATTERN = /^~(?:([\d\w]*|~|@))$/
27
27
  REGEX_PATTERN = /^%/
@@ -31,7 +31,6 @@ class FS
31
31
  fail unless root.is_a? RVC::InventoryObject
32
32
  @root = root
33
33
  @cur = root
34
- @marks = {}
35
34
  end
36
35
 
37
36
  def display_path
@@ -40,7 +39,7 @@ class FS
40
39
 
41
40
  def cd dst
42
41
  fail unless dst.is_a? RVC::InventoryObject
43
- mark '~', [@cur]
42
+ $shell.session.set_mark '~', [@cur]
44
43
  @cur = dst
45
44
  end
46
45
 
@@ -71,7 +70,7 @@ class FS
71
70
  # XXX shouldnt be nil
72
71
  [(cur.respond_to?(:parent) && cur.parent) ? cur.parent : (cur.rvc_parent || cur)]
73
72
  when MARK_PATTERN
74
- if first and objs = @marks[$1]
73
+ if first and objs = $shell.session.get_mark($1)
75
74
  objs
76
75
  else
77
76
  []
@@ -84,7 +83,7 @@ class FS
84
83
  cur.children.select { |k,v| k =~ regex }.map { |k,v| v.rvc_link(cur, k); v }
85
84
  else
86
85
  # XXX check for ambiguous child
87
- if first and arc =~ /^\d+$/ and objs = @marks[arc]
86
+ if first and arc =~ /^\d+$/ and objs = $shell.session.get_mark(arc)
88
87
  objs
89
88
  else
90
89
  if child = cur.traverse_one(arc)
@@ -97,11 +96,6 @@ class FS
97
96
  end
98
97
  end
99
98
 
100
- def mark key, objs
101
- fail "not an array" unless objs.is_a? Array
102
- @marks[key] = objs
103
- end
104
-
105
99
  private
106
100
 
107
101
  def glob_to_regex str
@@ -0,0 +1,39 @@
1
+ module RVC
2
+
3
+ class MemorySession
4
+ def initialize
5
+ @marks = {}
6
+ @connections = {}
7
+ end
8
+
9
+ def marks
10
+ @marks.keys
11
+ end
12
+
13
+ def get_mark key
14
+ @marks[key]
15
+ end
16
+
17
+ def set_mark key, objs
18
+ if objs == nil
19
+ @marks.delete key
20
+ else
21
+ fail "not an array" unless objs.is_a? Array
22
+ @marks[key] = objs
23
+ end
24
+ end
25
+
26
+ def connections
27
+ @connections.keys
28
+ end
29
+
30
+ def get_connection key
31
+ @connections[key]
32
+ end
33
+
34
+ def set_connection key, conn
35
+ @connections[key] = conn
36
+ end
37
+ end
38
+
39
+ end
@@ -132,9 +132,9 @@ rvc_alias :cd
132
132
 
133
133
  def cd obj
134
134
  $shell.fs.cd(obj)
135
- $shell.fs.mark '', [find_ancestor(RbVmomi::VIM::Datacenter)].compact
136
- $shell.fs.mark '@', [find_ancestor(RbVmomi::VIM)].compact
137
- $shell.fs.marks.delete_if { |k,v| k =~ /^\d+$/ }
135
+ $shell.session.set_mark '', [find_ancestor(RbVmomi::VIM::Datacenter)].compact
136
+ $shell.session.set_mark '@', [find_ancestor(RbVmomi::VIM)].compact
137
+ $shell.delete_numeric_marks
138
138
  end
139
139
 
140
140
  def find_ancestor klass
@@ -159,7 +159,7 @@ def ls obj
159
159
  fake_children.each do |name,child|
160
160
  puts "#{i} #{name}#{child.ls_text(nil)}"
161
161
  child.rvc_link obj, name
162
- $shell.fs.mark i.to_s, [child]
162
+ CMD.mark.mark i.to_s, [child]
163
163
  i += 1
164
164
  end
165
165
 
@@ -189,7 +189,7 @@ def ls obj
189
189
  realname = r['name'] if name != r['name']
190
190
  puts "#{i} #{name}#{realname && " [#{realname}]"}#{text}"
191
191
  r.obj.rvc_link obj, name
192
- $shell.fs.mark i.to_s, [r.obj]
192
+ CMD.mark.mark i.to_s, [r.obj]
193
193
  i += 1
194
194
  end
195
195
  end
@@ -278,7 +278,9 @@ end
278
278
  rvc_alias :disconnect
279
279
 
280
280
  def disconnect connection
281
- $shell.connections.delete_if { |k,v| v == connection }
281
+ k, = $shell.connections.find { |k,v| v == connection }
282
+ $shell.connections.delete k
283
+ $shell.session.set_connection k, nil
282
284
  end
283
285
 
284
286
 
@@ -294,3 +296,30 @@ def mkdir path
294
296
  parent = lookup_single! File.dirname(path), RbVmomi::VIM::Folder
295
297
  parent.CreateFolder(:name => File.basename(path))
296
298
  end
299
+
300
+
301
+ opts :events do
302
+ summary "Show recent events"
303
+ arg :obj, nil, :required => false, :default => '.', :lookup => Object
304
+ opt :lines, "Output the last N events", :short => 'n', :type => :int, :default => 10
305
+ end
306
+
307
+ rvc_alias :events
308
+
309
+ def events obj, opts
310
+ err "'events' not supported at this level" unless obj.respond_to?(:_connection)
311
+ manager = obj._connection.serviceContent.eventManager
312
+ @event_details ||= Hash[manager.collect("description.eventInfo").first.collect { |d| [d.key, d] }]
313
+
314
+ spec = VIM::EventFilterSpec(:entity => VIM::EventFilterSpecByEntity(:entity => obj, :recursion => "all"))
315
+
316
+ collector = manager.CreateCollectorForEvents(:filter => spec)
317
+ collector.SetCollectorPageSize(:maxCount => opts[:lines])
318
+ collector.latestPage.reverse.each do |event|
319
+ time = event.createdTime.localtime.strftime("%m/%d/%Y %I:%M %p")
320
+ category = @event_details[event.class.to_s].category
321
+ puts "[#{time}] [#{category}] #{event.fullFormattedMessage.strip}"
322
+ end
323
+ ensure
324
+ collector.DestroyCollector if collector
325
+ end
@@ -20,30 +20,101 @@
20
20
 
21
21
  opts :download do
22
22
  summary "Download a file from a datastore"
23
- arg 'datastore-path', "Filename on the datastore"
23
+ arg 'datastore-path', "Filename on the datastore", :lookup => VIM::Datastore::FakeDatastoreFile
24
24
  arg 'local-path', "Filename on the local machine"
25
25
  end
26
26
 
27
- def download datastore_path, local_path
28
- file = lookup_single(datastore_path)
29
- err "not a datastore file" unless file.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFile
30
- file.datastore.download file.path, local_path
27
+ def download file, local_path
28
+ main_http = file.datastore._connection.http
29
+ http = Net::HTTP.new(main_http.address, main_http.port)
30
+ http.use_ssl = true
31
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
32
+ #http.set_debug_output $stderr
33
+ http.start
34
+ err "certificate mismatch" unless main_http.peer_cert.to_der == http.peer_cert.to_der
35
+
36
+ headers = { 'cookie' => file.datastore._connection.cookie }
37
+ path = http_path file.datastore.send(:datacenter).name, file.datastore.name, file.path
38
+ http.request_get(path, headers) do |res|
39
+ case res
40
+ when Net::HTTPOK
41
+ len = res.content_length
42
+ count = 0
43
+ File.open(local_path, 'wb') do |io|
44
+ res.read_body do |segment|
45
+ count += segment.length
46
+ io.write segment
47
+ $stdout.write "\e[0G\e[Kdownloading #{count}/#{len} bytes (#{(count*100)/len}%)"
48
+ $stdout.flush
49
+ end
50
+ end
51
+ $stdout.puts
52
+ else
53
+ err "download failed: #{res.message}"
54
+ end
55
+ end
31
56
  end
32
57
 
33
58
 
34
59
  opts :upload do
35
60
  summary "Upload a file to a datastore"
36
61
  arg 'local-path', "Filename on the local machine"
37
- arg 'datastore-path', "Filename on the datastore"
62
+ arg 'datastore-path', "Filename on the datastore", :lookup_parent => VIM::Datastore::FakeDatastoreFolder
38
63
  end
39
64
 
40
- def upload local_path, datastore_path
41
- datastore_dir_path = File.dirname datastore_path
42
- dir = lookup_single(datastore_dir_path)
43
- err "datastore directory does not exist" unless dir.is_a? RbVmomi::VIM::Datastore::FakeDatastoreFolder
65
+ def upload local_path, dest
66
+ dir, datastore_filename = *dest
44
67
  err "local file does not exist" unless File.exists? local_path
45
- real_datastore_path = "#{dir.path}/#{File.basename(datastore_path)}"
46
- dir.datastore.upload real_datastore_path, local_path
68
+ real_datastore_path = "#{dir.path}/#{datastore_filename}"
69
+
70
+ main_http = dir.datastore._connection.http
71
+ http = Net::HTTP.new(main_http.address, main_http.port)
72
+ http.use_ssl = true
73
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
74
+ #http.set_debug_output $stderr
75
+ http.start
76
+ err "certificate mismatch" unless main_http.peer_cert.to_der == http.peer_cert.to_der
77
+
78
+ File.open(local_path, 'rb') do |io|
79
+ stream = ProgressStream.new(io, io.stat.size) do |s|
80
+ $stdout.write "\e[0G\e[Kuploading #{s.count}/#{s.len} bytes (#{(s.count*100)/s.len}%)"
81
+ $stdout.flush
82
+ end
83
+
84
+ headers = {
85
+ 'cookie' => dir.datastore._connection.cookie,
86
+ 'content-length' => io.stat.size.to_s,
87
+ 'Content-Type' => 'application/octet-stream',
88
+ }
89
+ path = http_path dir.datastore.send(:datacenter).name, dir.datastore.name, real_datastore_path
90
+ request = Net::HTTP::Put.new path, headers
91
+ request.body_stream = stream
92
+ res = http.request(request)
93
+ $stdout.puts
94
+ case res
95
+ when Net::HTTPOK
96
+ else
97
+ err "upload failed: #{res.message}"
98
+ end
99
+ end
100
+ end
101
+
102
+ class ProgressStream
103
+ attr_reader :io, :len, :count
104
+
105
+ def initialize io, len, &b
106
+ @io = io
107
+ @len = len
108
+ @count = 0
109
+ @cb = b
110
+ end
111
+
112
+ def read n
113
+ io.read(n).tap do |c|
114
+ @count += c.length if c
115
+ @cb[self]
116
+ end
117
+ end
47
118
  end
48
119
 
49
120
 
@@ -75,15 +146,20 @@ rvc_alias :edit, :vi
75
146
  def edit file
76
147
  editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
77
148
  filename = File.join(Dir.tmpdir, "rvc.#{Time.now.to_i}.#{rand(65536)}")
78
- file.datastore.download(file.path, filename) rescue err("download failed")
149
+ download file, filename
79
150
  begin
80
151
  pre_stat = File.stat filename
81
152
  system("#{editor} #{filename}")
82
153
  post_stat = File.stat filename
83
154
  if pre_stat != post_stat
84
- file.datastore.upload(file.path, filename) rescue err("upload failed")
155
+ upload filename, [file.parent, File.basename(file.path)]
85
156
  end
86
157
  ensure
87
158
  File.unlink filename
88
159
  end
89
160
  end
161
+
162
+
163
+ def http_path dc_name, ds_name, path
164
+ "/folder/#{URI.escape path}?dcPath=#{URI.escape dc_name}&dsName=#{URI.escape ds_name}"
165
+ end
@@ -119,3 +119,24 @@ def reconnect hosts, opts
119
119
  }
120
120
  tasks hosts, :ReconnectHost
121
121
  end
122
+
123
+
124
+ opts :add_iscsi_target do
125
+ arg :host, nil, :lookup => VIM::HostSystem, :multi => true
126
+ opt :address, "Address of iSCSI server", :short => 'a', :type => :string, :required => true
127
+ opt :iqn, "IQN of iSCSI target", :short => 'i', :type => :string, :required => true
128
+ end
129
+
130
+ def add_iscsi_target hosts, opts
131
+ hosts.each do |host|
132
+ puts "configuring host #{host.name}"
133
+ storage = host.configManager.storageSystem
134
+ storage.UpdateSoftwareInternetScsiEnabled(enabled: true)
135
+ adapter = storage.storageDeviceInfo.hostBusAdapter.grep(VIM::HostInternetScsiHba)[0]
136
+ storage.AddInternetScsiStaticTargets(
137
+ iScsiHbaDevice: adapter.device,
138
+ targets: [ VIM::HostInternetScsiHbaStaticTarget(address: opts[:address], iScsiName: opts[:iqn]) ]
139
+ )
140
+ storage.RescanAllHba
141
+ end
142
+ end
@@ -29,7 +29,7 @@ rvc_alias :mark, :m
29
29
 
30
30
  def mark key, objs
31
31
  err "invalid mark name" unless key =~ /^\w+$/
32
- $shell.fs.mark key, objs
32
+ $shell.session.set_mark key, objs
33
33
  end
34
34
 
35
35
 
@@ -42,7 +42,7 @@ rvc_alias :edit, :me
42
42
 
43
43
  def edit key
44
44
  editor = ENV['VISUAL'] || ENV['EDITOR'] || 'vi'
45
- objs = $shell.fs.marks[key] or err "no such mark #{key.inspect}"
45
+ objs = $shell.session.get_mark(key) or err "no such mark #{key.inspect}"
46
46
  filename = File.join(Dir.tmpdir, "rvc.#{Time.now.to_i}.#{rand(65536)}")
47
47
  File.open(filename, 'w') { |io| objs.each { |obj| io.puts(obj.rvc_path_str) } }
48
48
  begin
@@ -54,3 +54,12 @@ def edit key
54
54
  File.unlink filename
55
55
  end
56
56
  end
57
+
58
+
59
+ opts :list do
60
+ summary "List marks"
61
+ end
62
+
63
+ def list
64
+ $shell.session.marks.each { |x| puts x }
65
+ end