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 +8 -0
- data/Rakefile +12 -0
- data/TODO +5 -1
- data/VERSION +1 -1
- data/bin/rvc +34 -14
- data/lib/rvc/completion.rb +1 -1
- data/lib/rvc/extensions/VirtualMachine.rb +26 -1
- data/lib/rvc/filesystem_session.rb +81 -0
- data/lib/rvc/fs.rb +4 -10
- data/lib/rvc/memory_session.rb +39 -0
- data/lib/rvc/modules/basic.rb +35 -6
- data/lib/rvc/modules/datastore.rb +90 -14
- data/lib/rvc/modules/host.rb +21 -0
- data/lib/rvc/modules/mark.rb +11 -2
- data/lib/rvc/modules/permissions.rb +62 -0
- data/lib/rvc/modules/role.rb +116 -0
- data/lib/rvc/modules/vim.rb +149 -32
- data/lib/rvc/modules/vm.rb +12 -6
- data/lib/rvc/modules/vmrc.rb +16 -2
- data/lib/rvc/modules.rb +4 -0
- data/lib/rvc/option_parser.rb +13 -11
- data/lib/rvc/shell.rb +20 -11
- data/lib/rvc/util.rb +3 -0
- data/lib/rvc.rb +2 -0
- metadata +6 -2
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.
|
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
|
-
|
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,
|
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
|
data/lib/rvc/completion.rb
CHANGED
@@ -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.
|
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
|
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
|
-
|
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 =
|
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 =
|
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
|
data/lib/rvc/modules/basic.rb
CHANGED
@@ -132,9 +132,9 @@ rvc_alias :cd
|
|
132
132
|
|
133
133
|
def cd obj
|
134
134
|
$shell.fs.cd(obj)
|
135
|
-
$shell.
|
136
|
-
$shell.
|
137
|
-
$shell.
|
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
|
-
|
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
|
-
|
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.
|
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
|
28
|
-
|
29
|
-
|
30
|
-
|
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,
|
41
|
-
|
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}/#{
|
46
|
-
|
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
|
-
|
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.
|
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
|
data/lib/rvc/modules/host.rb
CHANGED
@@ -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
|
data/lib/rvc/modules/mark.rb
CHANGED
@@ -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.
|
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.
|
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
|