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 +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
|