rvc 1.0.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/LICENSE +19 -0
- data/README.rdoc +120 -0
- data/Rakefile +39 -0
- data/TODO +4 -0
- data/VERSION +1 -0
- data/bin/rvc +104 -0
- data/lib/rvc.rb +31 -0
- data/lib/rvc/completion.rb +110 -0
- data/lib/rvc/extensions/ClusterComputeResource.rb +42 -0
- data/lib/rvc/extensions/ComputeResource.rb +47 -0
- data/lib/rvc/extensions/Datacenter.rb +36 -0
- data/lib/rvc/extensions/Datastore.rb +188 -0
- data/lib/rvc/extensions/DistributedVirtualPortgroup.rb +38 -0
- data/lib/rvc/extensions/DistributedVirtualSwitch.rb +40 -0
- data/lib/rvc/extensions/Folder.rb +33 -0
- data/lib/rvc/extensions/HostSystem.rb +48 -0
- data/lib/rvc/extensions/ManagedEntity.rb +28 -0
- data/lib/rvc/extensions/Network.rb +28 -0
- data/lib/rvc/extensions/ResourcePool.rb +52 -0
- data/lib/rvc/extensions/VirtualMachine.rb +72 -0
- data/lib/rvc/fs.rb +123 -0
- data/lib/rvc/inventory.rb +125 -0
- data/lib/rvc/modules.rb +74 -0
- data/lib/rvc/modules/basic.rb +276 -0
- data/lib/rvc/modules/datastore.rb +63 -0
- data/lib/rvc/modules/host.rb +29 -0
- data/lib/rvc/modules/resource_pool.rb +95 -0
- data/lib/rvc/modules/vim.rb +128 -0
- data/lib/rvc/modules/vm.rb +607 -0
- data/lib/rvc/modules/vmrc.rb +72 -0
- data/lib/rvc/modules/vnc.rb +111 -0
- data/lib/rvc/option_parser.rb +114 -0
- data/lib/rvc/path.rb +37 -0
- data/lib/rvc/readline-ffi.rb +41 -0
- data/lib/rvc/shell.rb +220 -0
- data/lib/rvc/ttl_cache.rb +44 -0
- data/lib/rvc/util.rb +168 -0
- data/test/_test_completion.rb +27 -0
- data/test/_test_option_parser.rb +6 -0
- data/test/inventory_fixtures.rb +15 -0
- data/test/test_fs.rb +113 -0
- data/test/test_parse_path.rb +41 -0
- metadata +146 -0
@@ -0,0 +1,128 @@
|
|
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
|
+
URI_REGEX = %r{
|
22
|
+
^
|
23
|
+
(?:
|
24
|
+
([^@:]+)
|
25
|
+
(?::
|
26
|
+
([^@]*)
|
27
|
+
)?
|
28
|
+
@
|
29
|
+
)?
|
30
|
+
([^@:]+)
|
31
|
+
(?::(.*))?
|
32
|
+
$
|
33
|
+
}x
|
34
|
+
|
35
|
+
opts :connect do
|
36
|
+
summary 'Open a connection to ESX/VC'
|
37
|
+
arg :uri, "Host to connect to"
|
38
|
+
opt :insecure, "don't verify ssl certificate", :short => 'k', :default => (ENV['RBVMOMI_INSECURE'] == '1')
|
39
|
+
end
|
40
|
+
|
41
|
+
rvc_alias :connect
|
42
|
+
|
43
|
+
def connect uri, opts
|
44
|
+
match = URI_REGEX.match uri
|
45
|
+
Trollop.die "invalid hostname" unless match
|
46
|
+
|
47
|
+
username = match[1] || ENV['RBVMOMI_USER']
|
48
|
+
password = match[2] || ENV['RBVMOMI_PASSWORD']
|
49
|
+
host = match[3]
|
50
|
+
path = match[4]
|
51
|
+
insecure = opts[:insecure]
|
52
|
+
|
53
|
+
vim = nil
|
54
|
+
loop do
|
55
|
+
begin
|
56
|
+
vim = RbVmomi::VIM.new :host => host,
|
57
|
+
:port => 443,
|
58
|
+
:path => '/sdk',
|
59
|
+
:ns => 'urn:vim25',
|
60
|
+
:rev => '4.0',
|
61
|
+
:ssl => true,
|
62
|
+
:insecure => insecure
|
63
|
+
break
|
64
|
+
rescue OpenSSL::SSL::SSLError
|
65
|
+
err "Connection failed" unless prompt_insecure
|
66
|
+
insecure = true
|
67
|
+
rescue Errno::EHOSTUNREACH, SocketError
|
68
|
+
err $!.message
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# negotiate API version
|
73
|
+
rev = vim.serviceContent.about.apiVersion
|
74
|
+
vim.rev = [rev, '4.1'].min
|
75
|
+
isVC = vim.serviceContent.about.apiType == "VirtualCenter"
|
76
|
+
|
77
|
+
# authenticate
|
78
|
+
if username == nil
|
79
|
+
username = isVC ? 'Administrator' : 'root'
|
80
|
+
puts "Using default username #{username.inspect}."
|
81
|
+
end
|
82
|
+
|
83
|
+
password_given = password != nil
|
84
|
+
loop do
|
85
|
+
begin
|
86
|
+
password = prompt_password unless password_given
|
87
|
+
vim.serviceContent.sessionManager.Login :userName => username,
|
88
|
+
:password => password
|
89
|
+
break
|
90
|
+
rescue RbVmomi::VIM::InvalidLogin
|
91
|
+
err $!.message if password_given
|
92
|
+
end
|
93
|
+
end
|
94
|
+
|
95
|
+
Thread.new do
|
96
|
+
while true
|
97
|
+
sleep 600
|
98
|
+
vim.serviceInstance.RetrieveServiceContent
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Stash the address we used to connect so VMRC can use it.
|
103
|
+
vim.define_singleton_method(:_host) { host }
|
104
|
+
|
105
|
+
conn_name = host.dup
|
106
|
+
conn_name = "#{conn_name}:1" if $shell.connections.member? conn_name
|
107
|
+
conn_name.succ! while $shell.connections.member? conn_name
|
108
|
+
|
109
|
+
$shell.connections[conn_name] = vim
|
110
|
+
end
|
111
|
+
|
112
|
+
def prompt_password
|
113
|
+
system "stty -echo"
|
114
|
+
$stdout.write "password: "
|
115
|
+
$stdout.flush
|
116
|
+
begin
|
117
|
+
($stdin.gets||exit(1)).chomp
|
118
|
+
ensure
|
119
|
+
system "stty echo"
|
120
|
+
puts
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
def prompt_insecure
|
125
|
+
answer = Readline.readline "SSL certificate verification failed. Connect anyway? (y/n) "
|
126
|
+
answer == 'yes' or answer == 'y'
|
127
|
+
end
|
128
|
+
|
@@ -0,0 +1,607 @@
|
|
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 :on do
|
22
|
+
summary "Power on VMs"
|
23
|
+
arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
|
24
|
+
end
|
25
|
+
|
26
|
+
rvc_alias :on
|
27
|
+
|
28
|
+
def on vms
|
29
|
+
tasks vms, :PowerOnVM
|
30
|
+
end
|
31
|
+
|
32
|
+
|
33
|
+
opts :off do
|
34
|
+
summary "Power off VMs"
|
35
|
+
arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
|
36
|
+
end
|
37
|
+
|
38
|
+
rvc_alias :off
|
39
|
+
|
40
|
+
def off vms
|
41
|
+
tasks vms, :PowerOffVM
|
42
|
+
end
|
43
|
+
|
44
|
+
|
45
|
+
opts :reset do
|
46
|
+
summary "Reset VMs"
|
47
|
+
arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
|
48
|
+
end
|
49
|
+
|
50
|
+
rvc_alias :reset
|
51
|
+
rvc_alias :reset, :r
|
52
|
+
|
53
|
+
def reset vms
|
54
|
+
tasks vms, :ResetVM
|
55
|
+
end
|
56
|
+
|
57
|
+
|
58
|
+
opts :suspend do
|
59
|
+
summary "Suspend VMs"
|
60
|
+
arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
|
61
|
+
end
|
62
|
+
|
63
|
+
rvc_alias :suspend
|
64
|
+
rvc_alias :suspend, :s
|
65
|
+
|
66
|
+
def suspend vms
|
67
|
+
tasks vms, :SuspendVM
|
68
|
+
end
|
69
|
+
|
70
|
+
|
71
|
+
opts :create do
|
72
|
+
summary "Create a new VM"
|
73
|
+
arg :name, "Name"
|
74
|
+
opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
|
75
|
+
opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
|
76
|
+
opt :datastore, "Datastore", :short => 'd', :type => :string, :lookup => VIM::Datastore
|
77
|
+
end
|
78
|
+
|
79
|
+
def create name, opts
|
80
|
+
err "must specify resource pool (--pool)" unless opts[:pool]
|
81
|
+
err "must specify datastore (--datastore)" unless opts[:datastore]
|
82
|
+
vmFolder = lookup!(File.dirname(name), VIM::Folder)
|
83
|
+
datastore_path = "[#{opts[:datastore].name}]"
|
84
|
+
config = {
|
85
|
+
:name => File.basename(name),
|
86
|
+
:guestId => 'otherGuest',
|
87
|
+
:files => { :vmPathName => datastore_path },
|
88
|
+
:numCPUs => 1,
|
89
|
+
:memoryMB => 128,
|
90
|
+
:deviceChange => [
|
91
|
+
{
|
92
|
+
:operation => :add,
|
93
|
+
:device => VIM.VirtualLsiLogicController(
|
94
|
+
:key => 1000,
|
95
|
+
:busNumber => 0,
|
96
|
+
:sharedBus => :noSharing
|
97
|
+
)
|
98
|
+
}, {
|
99
|
+
:operation => :add,
|
100
|
+
:fileOperation => :create,
|
101
|
+
:device => VIM.VirtualDisk(
|
102
|
+
:key => 0,
|
103
|
+
:backing => VIM.VirtualDiskFlatVer2BackingInfo(
|
104
|
+
:fileName => datastore_path,
|
105
|
+
:diskMode => :persistent,
|
106
|
+
:thinProvisioned => true
|
107
|
+
),
|
108
|
+
:controllerKey => 1000,
|
109
|
+
:unitNumber => 0,
|
110
|
+
:capacityInKB => 4000000
|
111
|
+
)
|
112
|
+
}, {
|
113
|
+
:operation => :add,
|
114
|
+
:device => VIM.VirtualCdrom(
|
115
|
+
:key => 0,
|
116
|
+
:connectable => {
|
117
|
+
:allowGuestControl => true,
|
118
|
+
:connected => true,
|
119
|
+
:startConnected => true,
|
120
|
+
},
|
121
|
+
:backing => VIM.VirtualCdromIsoBackingInfo(
|
122
|
+
:fileName => datastore_path
|
123
|
+
),
|
124
|
+
:controllerKey => 200,
|
125
|
+
:unitNumber => 0
|
126
|
+
)
|
127
|
+
}, {
|
128
|
+
:operation => :add,
|
129
|
+
:device => VIM.VirtualE1000(
|
130
|
+
:key => 0,
|
131
|
+
:deviceInfo => {
|
132
|
+
:label => 'Network Adapter 1',
|
133
|
+
:summary => 'VM Network'
|
134
|
+
},
|
135
|
+
:backing => VIM.VirtualEthernetCardNetworkBackingInfo(
|
136
|
+
:deviceName => 'VM Network'
|
137
|
+
),
|
138
|
+
:addressType => 'generated'
|
139
|
+
)
|
140
|
+
}
|
141
|
+
],
|
142
|
+
}
|
143
|
+
vmFolder.CreateVM_Task(:config => config,
|
144
|
+
:pool => opts[:pool],
|
145
|
+
:host => opts[:host]).wait_for_completion
|
146
|
+
end
|
147
|
+
|
148
|
+
|
149
|
+
opts :insert_cdrom do
|
150
|
+
summary "Put a disc in a virtual CDROM drive"
|
151
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
152
|
+
arg :iso, "Path to the ISO image on a datastore", :lookup => VIM::Datastore::FakeDatastoreFile
|
153
|
+
end
|
154
|
+
|
155
|
+
def insert_cdrom vm, iso
|
156
|
+
device = vm.config.hardware.device.grep(VIM::VirtualCdrom)[0]
|
157
|
+
err "No virtual CDROM drive found" unless device
|
158
|
+
|
159
|
+
device.backing = VIM.VirtualCdromIsoBackingInfo(:fileName => iso.datastore_path)
|
160
|
+
|
161
|
+
spec = {
|
162
|
+
:deviceChange => [
|
163
|
+
{
|
164
|
+
:operation => :edit,
|
165
|
+
:device => device
|
166
|
+
}
|
167
|
+
]
|
168
|
+
}
|
169
|
+
|
170
|
+
vm.ReconfigVM_Task(:spec => spec)
|
171
|
+
end
|
172
|
+
|
173
|
+
opts :register do
|
174
|
+
summary "Register a VM already in a datastore"
|
175
|
+
arg :file, "RVC path to the VMX file", :lookup => VIM::Datastore::FakeDatastoreFile
|
176
|
+
opt :resource_pool, 'Resource pool', :short => 'R', :type => :string, :lookup => VIM::ResourcePool
|
177
|
+
opt :folder, 'VM Folder', :short => 'F', :default => ".", :lookup => VIM::Folder
|
178
|
+
end
|
179
|
+
|
180
|
+
def register vmx_file, opts
|
181
|
+
rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool
|
182
|
+
vm = opts[:folder].RegisterVM_Task(:path => vmx_file.datastore_path,
|
183
|
+
:asTemplate => false,
|
184
|
+
:pool => rp).wait_for_completion
|
185
|
+
end
|
186
|
+
|
187
|
+
|
188
|
+
opts :unregister do
|
189
|
+
summary "Unregister a VM"
|
190
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
191
|
+
end
|
192
|
+
|
193
|
+
def unregister vm
|
194
|
+
vm.UnregisterVM
|
195
|
+
end
|
196
|
+
|
197
|
+
|
198
|
+
opts :kill do
|
199
|
+
summary "Power off and destroy VMs"
|
200
|
+
arg :vm, nil, :multi => true, :lookup => VIM::VirtualMachine
|
201
|
+
end
|
202
|
+
|
203
|
+
rvc_alias :kill
|
204
|
+
rvc_alias :kill, :k
|
205
|
+
|
206
|
+
def kill vms
|
207
|
+
on_vms = vms.select { |x| x.summary.runtime.powerState == 'poweredOn' }
|
208
|
+
off on_vms unless on_vms.empty?
|
209
|
+
CMD.basic.destroy vms unless vms.empty?
|
210
|
+
end
|
211
|
+
|
212
|
+
|
213
|
+
opts :answer do
|
214
|
+
summary "Answer a VM question"
|
215
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
216
|
+
arg :choice, "Answer ID"
|
217
|
+
end
|
218
|
+
|
219
|
+
def answer vm, str
|
220
|
+
choice = q.choice.choiceInfo.find { |x| x.label == str }
|
221
|
+
err("invalid answer") unless choice
|
222
|
+
vm.AnswerVM :questionid => q.path, :answerChoice => choice.key
|
223
|
+
end
|
224
|
+
|
225
|
+
|
226
|
+
opts :layout do
|
227
|
+
summary "Display info about VM files"
|
228
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
229
|
+
end
|
230
|
+
|
231
|
+
def layout vm
|
232
|
+
vm.layoutEx.file.each do |f|
|
233
|
+
puts "#{f.type}: #{f.name}"
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
|
238
|
+
opts :devices do
|
239
|
+
summary "Display info about VM devices"
|
240
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
241
|
+
end
|
242
|
+
|
243
|
+
def devices vm
|
244
|
+
devs = vm.config.hardware.device
|
245
|
+
devs.each do |dev|
|
246
|
+
tags = []
|
247
|
+
tags << (dev.connectable.connected ? :connected : :disconnected) if dev.props.member? :connectable
|
248
|
+
puts "#{dev.deviceInfo.label} (#{dev.class}): #{dev.deviceInfo.summary}; #{tags * ' '}"
|
249
|
+
end
|
250
|
+
end
|
251
|
+
|
252
|
+
|
253
|
+
opts :connect do
|
254
|
+
summary "Connect a virtual device"
|
255
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
256
|
+
arg :label, "Device label"
|
257
|
+
end
|
258
|
+
|
259
|
+
def connect vm, label
|
260
|
+
change_device_connectivity vm, label, true
|
261
|
+
end
|
262
|
+
|
263
|
+
|
264
|
+
opts :disconnect do
|
265
|
+
summary "Disconnect a virtual device"
|
266
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
267
|
+
arg :label, "Device label"
|
268
|
+
end
|
269
|
+
|
270
|
+
def disconnect vm, label
|
271
|
+
change_device_connectivity vm, label, false
|
272
|
+
end
|
273
|
+
|
274
|
+
|
275
|
+
opts :find do
|
276
|
+
summary "Display a menu of VMX files to register"
|
277
|
+
arg :datastore, nil, :lookup => VIM::Datastore
|
278
|
+
opt :resource_pool, "Resource pool", :short => 'R', :type => :string, :lookup => VIM::ResourcePool
|
279
|
+
opt :folder, "Folder to register in", :short => 'F', :type => :string, :default => ".", :lookup => VIM::Folder
|
280
|
+
end
|
281
|
+
|
282
|
+
def find ds, opts
|
283
|
+
folder = opts[:folder]
|
284
|
+
rp = opts[:resource_pool] || opts[:folder]._connection.rootFolder.childEntity[0].hostFolder.childEntity[0].resourcePool
|
285
|
+
|
286
|
+
paths = find_vmx_files(ds)
|
287
|
+
if paths.empty?
|
288
|
+
puts "no VMX files found"
|
289
|
+
return
|
290
|
+
end
|
291
|
+
|
292
|
+
puts "Select a VMX file"
|
293
|
+
path = menu(paths) or return
|
294
|
+
|
295
|
+
folder.RegisterVM_Task(:path => path,
|
296
|
+
:asTemplate => false,
|
297
|
+
:pool => rp).wait_for_completion
|
298
|
+
end
|
299
|
+
|
300
|
+
|
301
|
+
opts :extraConfig do
|
302
|
+
summary "Display extraConfig options"
|
303
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
304
|
+
arg :regex, "Regexes to filter keys", :multi => true, :required => false
|
305
|
+
end
|
306
|
+
|
307
|
+
def extraConfig vm, regexes
|
308
|
+
_extraConfig(vm, *regexes.map { |x| /#{x}/ })
|
309
|
+
end
|
310
|
+
|
311
|
+
|
312
|
+
opts :setExtraConfig do
|
313
|
+
summary "Set extraConfig options"
|
314
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
315
|
+
arg 'key=value', "extraConfig key/value pairs", :multi => true
|
316
|
+
end
|
317
|
+
|
318
|
+
def setExtraConfig vm, pairs
|
319
|
+
h = Hash[pairs.map { |x| x.split('=', 2).tap { |a| a << '' if a.size == 1 } }]
|
320
|
+
_setExtraConfig vm, h
|
321
|
+
end
|
322
|
+
|
323
|
+
|
324
|
+
def _setExtraConfig vm, hash
|
325
|
+
cfg = {
|
326
|
+
:extraConfig => hash.map { |k,v| { :key => k, :value => v } },
|
327
|
+
}
|
328
|
+
vm.ReconfigVM_Task(:spec => cfg).wait_for_completion
|
329
|
+
end
|
330
|
+
|
331
|
+
def _extraConfig vm, *regexes
|
332
|
+
vm.config.extraConfig.each do |h|
|
333
|
+
if regexes.empty? or regexes.any? { |r| h[:key] =~ r }
|
334
|
+
puts "#{h[:key]}: #{h[:value]}"
|
335
|
+
end
|
336
|
+
end
|
337
|
+
nil
|
338
|
+
end
|
339
|
+
|
340
|
+
|
341
|
+
opts :ssh do
|
342
|
+
summary "SSH to a VM"
|
343
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
344
|
+
end
|
345
|
+
|
346
|
+
rvc_alias :ssh
|
347
|
+
|
348
|
+
def ssh vm
|
349
|
+
ip = vm_ip vm
|
350
|
+
ssh_cmd = "ssh -o UserKnownHostsFile=/dev/null -o StrictHostKeyChecking=no root@#{ip}"
|
351
|
+
system_fg(ssh_cmd)
|
352
|
+
end
|
353
|
+
|
354
|
+
|
355
|
+
opts :rvc do
|
356
|
+
summary "RVC to a VM"
|
357
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
358
|
+
end
|
359
|
+
|
360
|
+
rvc_alias :rvc
|
361
|
+
|
362
|
+
def rvc vm
|
363
|
+
ip = vm_ip vm
|
364
|
+
|
365
|
+
env = Hash[%w(RBVMOMI_PASSWORD RBVMOMI_HOST RBVMOMI_USER RBVMOMI_SSL RBVMOMI_PORT
|
366
|
+
RBVMOMI_FOLDER RBVMOMI_DATASTORE RBVMOMI_PATH RBVMOMI_DATACENTER
|
367
|
+
RBVMOMI_COMPUTER).map { |k| [k,nil] }]
|
368
|
+
cmd = "rvc #{ip}"
|
369
|
+
system_fg(cmd, env)
|
370
|
+
end
|
371
|
+
|
372
|
+
|
373
|
+
opts :ping do
|
374
|
+
summary "Ping a VM"
|
375
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
376
|
+
end
|
377
|
+
|
378
|
+
rvc_alias :ping
|
379
|
+
|
380
|
+
def ping vm
|
381
|
+
ip = vm_ip vm
|
382
|
+
system_fg "ping #{ip}"
|
383
|
+
end
|
384
|
+
|
385
|
+
|
386
|
+
opts :ip do
|
387
|
+
summary "Wait for and display VM IP addresses"
|
388
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
|
389
|
+
end
|
390
|
+
|
391
|
+
def ip vms
|
392
|
+
props = %w(summary.runtime.powerState summary.guest.ipAddress summary.config.annotation)
|
393
|
+
connection = single_connection vms
|
394
|
+
|
395
|
+
filters = vms.map do |vm|
|
396
|
+
connection.propertyCollector.CreateFilter :spec => {
|
397
|
+
:propSet => [{ :type => 'VirtualMachine', :all => false, :pathSet => props }],
|
398
|
+
:objectSet => [{ :obj => vm }],
|
399
|
+
}, :partialUpdates => false
|
400
|
+
end
|
401
|
+
|
402
|
+
ver = ''
|
403
|
+
while not vms.empty?
|
404
|
+
result = connection.propertyCollector.WaitForUpdates(:version => ver)
|
405
|
+
ver = result.version
|
406
|
+
|
407
|
+
vms.reject! do |vm|
|
408
|
+
begin
|
409
|
+
ip = vm_ip(vm)
|
410
|
+
puts "#{vm.name}: #{ip}"
|
411
|
+
true
|
412
|
+
rescue UserError
|
413
|
+
false
|
414
|
+
end
|
415
|
+
end
|
416
|
+
end
|
417
|
+
ensure
|
418
|
+
filters.each(&:DestroyPropertyFilter) if filters
|
419
|
+
end
|
420
|
+
|
421
|
+
|
422
|
+
opts :add_net_device do
|
423
|
+
summary "Add a network adapter to a virtual machine"
|
424
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
425
|
+
opt :type, "Adapter type", :default => 'e1000'
|
426
|
+
opt :network, "Network to connect to", :default => 'VM Network'
|
427
|
+
end
|
428
|
+
|
429
|
+
def add_net_device vm, opts
|
430
|
+
case opts[:type]
|
431
|
+
when 'e1000'
|
432
|
+
_add_net_device vm, VIM::VirtualE1000, opts[:network]
|
433
|
+
when 'vmxnet3'
|
434
|
+
_add_net_device vm, VIM::VirtualVmxnet3, opts[:network]
|
435
|
+
else err "unknown device"
|
436
|
+
end
|
437
|
+
end
|
438
|
+
|
439
|
+
|
440
|
+
def _add_device vm, dev
|
441
|
+
spec = {
|
442
|
+
:deviceChange => [
|
443
|
+
{ :operation => :add, :device => dev },
|
444
|
+
]
|
445
|
+
}
|
446
|
+
vm.ReconfigVM_Task(:spec => spec).wait_for_completion
|
447
|
+
end
|
448
|
+
|
449
|
+
def _add_net_device vm, klass, network
|
450
|
+
_add_device vm, klass.new(
|
451
|
+
:key => -1,
|
452
|
+
:deviceInfo => {
|
453
|
+
:summary => network,
|
454
|
+
:label => `uuidgen`.chomp
|
455
|
+
},
|
456
|
+
:backing => VIM.VirtualEthernetCardNetworkBackingInfo(
|
457
|
+
:deviceName => network
|
458
|
+
),
|
459
|
+
:addressType => 'generated'
|
460
|
+
)
|
461
|
+
end
|
462
|
+
|
463
|
+
|
464
|
+
opts :remove_device do
|
465
|
+
summary "Remove a virtual device"
|
466
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
467
|
+
arg :label, "Device label"
|
468
|
+
end
|
469
|
+
|
470
|
+
def remove_device vm, label
|
471
|
+
dev = vm.config.hardware.device.find { |x| x.deviceInfo.label == label }
|
472
|
+
err "no such device" unless dev
|
473
|
+
spec = {
|
474
|
+
:deviceChange => [
|
475
|
+
{ :operation => :remove, :device => dev },
|
476
|
+
]
|
477
|
+
}
|
478
|
+
vm.ReconfigVM_Task(:spec => spec).wait_for_completion
|
479
|
+
end
|
480
|
+
|
481
|
+
|
482
|
+
opts :snapshot do
|
483
|
+
summary "Snapshot a VM"
|
484
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
485
|
+
arg :name, "Name of new snapshot"
|
486
|
+
end
|
487
|
+
|
488
|
+
def snapshot vm, name
|
489
|
+
tasks [vm], :CreateSnapshot, :memory => true, :name => name, :quiesce => false
|
490
|
+
end
|
491
|
+
|
492
|
+
|
493
|
+
# TODO make fake folder
|
494
|
+
opts :snapshots do
|
495
|
+
summary "Display VM snapshot tree"
|
496
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
497
|
+
end
|
498
|
+
|
499
|
+
def snapshots vm
|
500
|
+
_display_snapshot_tree vm.snapshot.rootSnapshotList, 0
|
501
|
+
end
|
502
|
+
|
503
|
+
def _display_snapshot_tree nodes, indent
|
504
|
+
nodes.each do |node|
|
505
|
+
puts "#{' '*indent}#{node.name} #{node.createTime}"
|
506
|
+
_display_snapshot_tree node.childSnapshotList, (indent+1)
|
507
|
+
end
|
508
|
+
end
|
509
|
+
|
510
|
+
|
511
|
+
opts :revert do
|
512
|
+
summary "Revert a VM to its current snapshot"
|
513
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine
|
514
|
+
end
|
515
|
+
|
516
|
+
def revert vm
|
517
|
+
tasks [vm], :RevertToCurrentSnapshot
|
518
|
+
end
|
519
|
+
|
520
|
+
|
521
|
+
opts :migrate do
|
522
|
+
summary "Migrate a VM"
|
523
|
+
arg :vm, nil, :lookup => VIM::VirtualMachine, :multi => true
|
524
|
+
opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
|
525
|
+
opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
|
526
|
+
end
|
527
|
+
|
528
|
+
def migrate vms, opts
|
529
|
+
tasks vms, :MigrateVM, :pool => opts[:pool],
|
530
|
+
:host => opts[:host],
|
531
|
+
:priority => :defaultPriority
|
532
|
+
end
|
533
|
+
|
534
|
+
|
535
|
+
opts :clone do
|
536
|
+
summary "Clone a VM"
|
537
|
+
arg :src, nil, :lookup => VIM::VirtualMachine
|
538
|
+
arg :dst, "Path to new VM"
|
539
|
+
opt :pool, "Resource pool", :short => 'p', :type => :string, :lookup => VIM::ResourcePool
|
540
|
+
opt :host, "Host", :short => 'h', :type => :string, :lookup => VIM::HostSystem
|
541
|
+
opt :template, "Create a template"
|
542
|
+
opt :powerOn, "Power on VM after clone"
|
543
|
+
end
|
544
|
+
|
545
|
+
def clone src, dst, opts
|
546
|
+
folder = lookup! File.dirname(dst), VIM::Folder
|
547
|
+
task = src.CloneVM_Task(:folder => folder,
|
548
|
+
:name => File.basename(dst),
|
549
|
+
:spec => {
|
550
|
+
:location => {
|
551
|
+
:host => opts[:host],
|
552
|
+
:pool => opts[:pool],
|
553
|
+
},
|
554
|
+
:template => opts[:template],
|
555
|
+
:powerOn => opts[:powerOn],
|
556
|
+
})
|
557
|
+
progress [task]
|
558
|
+
end
|
559
|
+
|
560
|
+
|
561
|
+
def find_vmx_files ds
|
562
|
+
datastorePath = "[#{ds.name}] /"
|
563
|
+
searchSpec = {
|
564
|
+
:details => { :fileOwner => false, :fileSize => false, :fileType => true, :modification => false },
|
565
|
+
:query => [
|
566
|
+
VIM::VmConfigFileQuery()
|
567
|
+
]
|
568
|
+
}
|
569
|
+
task = ds.browser.SearchDatastoreSubFolders_Task(:datastorePath => datastorePath, :searchSpec => searchSpec)
|
570
|
+
|
571
|
+
results = task.wait_for_completion
|
572
|
+
|
573
|
+
files = []
|
574
|
+
results.each do |result|
|
575
|
+
result.file.each do |file|
|
576
|
+
files << result.folderPath + file.path
|
577
|
+
end
|
578
|
+
end
|
579
|
+
|
580
|
+
files
|
581
|
+
end
|
582
|
+
|
583
|
+
def change_device_connectivity id, label, connected
|
584
|
+
dev = vm(id).config.hardware.device.find { |x| x.deviceInfo.label == label }
|
585
|
+
err "no such device" unless dev
|
586
|
+
dev.connectable.connected = connected
|
587
|
+
spec = {
|
588
|
+
:deviceChange => [
|
589
|
+
{ :operation => :edit, :device => dev },
|
590
|
+
]
|
591
|
+
}
|
592
|
+
vm(id).ReconfigVM_Task(:spec => spec).wait_for_completion
|
593
|
+
end
|
594
|
+
|
595
|
+
def vm_ip vm
|
596
|
+
summary = vm.summary
|
597
|
+
|
598
|
+
err "VM is not powered on" unless summary.runtime.powerState == 'poweredOn'
|
599
|
+
|
600
|
+
ip = if summary.guest.ipAddress and summary.guest.ipAddress != '127.0.0.1'
|
601
|
+
summary.guest.ipAddress
|
602
|
+
elsif note = YAML.load(summary.config.annotation) and note.is_a? Hash and note.member? 'ip'
|
603
|
+
note['ip']
|
604
|
+
else
|
605
|
+
err "no IP known for this VM"
|
606
|
+
end
|
607
|
+
end
|