rvc 1.3.6 → 1.4.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 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