vagrant-parallels 1.3.13 → 1.4.0.rc1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +4 -4
- data/Vagrantfile +5 -6
- data/lib/vagrant-parallels/action/export.rb +7 -1
- data/lib/vagrant-parallels/action/import.rb +35 -3
- data/lib/vagrant-parallels/action/network.rb +8 -8
- data/lib/vagrant-parallels/config.rb +4 -0
- data/lib/vagrant-parallels/driver/base.rb +424 -53
- data/lib/vagrant-parallels/driver/meta.rb +8 -10
- data/lib/vagrant-parallels/driver/pd_10.rb +15 -21
- data/lib/vagrant-parallels/driver/pd_11.rb +41 -0
- data/lib/vagrant-parallels/driver/pd_8.rb +1 -397
- data/lib/vagrant-parallels/driver/pd_9.rb +1 -1
- data/lib/vagrant-parallels/errors.rb +4 -0
- data/lib/vagrant-parallels/plugin.rb +1 -0
- data/lib/vagrant-parallels/version.rb +1 -1
- data/locales/en.yml +9 -2
- data/tasks/acceptance.rake +2 -2
- data/tasks/test.rake +0 -4
- data/test/acceptance/provider/linked_clone_spec.rb +26 -0
- data/test/acceptance/skeletons/linked_clone/Vagrantfile +7 -0
- data/test/unit/support/shared/pd_driver_examples.rb +13 -18
- metadata +5 -2
@@ -18,7 +18,8 @@ module VagrantPlugins
|
|
18
18
|
include Vagrant::Util::Retryable
|
19
19
|
include Vagrant::Util::NetworkIP
|
20
20
|
|
21
|
-
def initialize
|
21
|
+
def initialize(uuid)
|
22
|
+
@uuid = uuid
|
22
23
|
@logger = Log4r::Logger.new('vagrant_parallels::driver::base')
|
23
24
|
|
24
25
|
# This flag is used to keep track of interrupted state (SIGINT)
|
@@ -38,38 +39,125 @@ module VagrantPlugins
|
|
38
39
|
@logger.info("prlsrvctl path: #{@prlsrvctl_path}")
|
39
40
|
end
|
40
41
|
|
42
|
+
# Removes all port forwarding rules for the virtual machine.
|
43
|
+
def clear_forwarded_ports
|
44
|
+
raise NotImplementedError
|
45
|
+
end
|
46
|
+
|
41
47
|
# Clears the shared folders that have been set on the virtual machine.
|
42
48
|
def clear_shared_folders
|
49
|
+
share_ids = read_shared_folders.keys
|
50
|
+
share_ids.each do |id|
|
51
|
+
execute_prlctl('set', @uuid, '--shf-host-del', id)
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
# Makes a clone of the virtual machine.
|
56
|
+
#
|
57
|
+
# @param [String] src_name Name or UUID of the source VM or template.
|
58
|
+
# @param [String] dst_name Name of the destination VM.
|
59
|
+
# @param [<String => String>] options Options to clone virtual machine.
|
60
|
+
# @return [String] UUID of the new VM.
|
61
|
+
def clone_vm(src_name, dst_name, options={})
|
62
|
+
args = ['clone', src_name, '--name', dst_name]
|
63
|
+
args << '--template' if options[:template]
|
64
|
+
args.concat(['--dst', options[:dst]]) if options[:dst]
|
65
|
+
|
66
|
+
# Linked clone options
|
67
|
+
args << '--linked' if options[:linked]
|
68
|
+
args.concat(['--id', options[:snapshot_id]]) if options[:snapshot_id]
|
69
|
+
|
70
|
+
execute_prlctl(*args) do |_, data|
|
71
|
+
lines = data.split('\r')
|
72
|
+
# The progress of the clone will be in the last line. Do a greedy
|
73
|
+
# regular expression to find what we're looking for.
|
74
|
+
if lines.last =~ /Copying hard disk.+?(\d{,3}) ?%/
|
75
|
+
yield $1.to_i if block_given?
|
76
|
+
end
|
77
|
+
end
|
78
|
+
read_vms[dst_name]
|
43
79
|
end
|
44
80
|
|
45
81
|
# Compacts all disk drives of virtual machine
|
46
|
-
def compact
|
82
|
+
def compact(uuid)
|
83
|
+
hw_info = read_settings(uuid).fetch('Hardware', {})
|
84
|
+
used_drives = hw_info.select do |name, _|
|
85
|
+
name.start_with? 'hdd'
|
86
|
+
end
|
87
|
+
used_drives.each_value do |drive_params|
|
88
|
+
execute(@prldisktool_path, 'compact', '--hdd', drive_params['image']) do |_, data|
|
89
|
+
lines = data.split('\r')
|
90
|
+
# The progress of the compact will be in the last line. Do a greedy
|
91
|
+
# regular expression to find what we're looking for.
|
92
|
+
if lines.last =~ /.+?(\d{,3}) ?%/
|
93
|
+
yield $1.to_i if block_given?
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
47
97
|
end
|
48
98
|
|
49
99
|
# Creates a host only network with the given options.
|
50
100
|
#
|
51
|
-
# @param [
|
52
|
-
# @return [
|
53
|
-
#
|
101
|
+
# @param [<Symbol => String>] options Hostonly network options.
|
102
|
+
# @return [<Symbol => String>] The details of the host only network,
|
103
|
+
# including keys `:name`, `:ip`, `:netmask` and `:dhcp`
|
104
|
+
# @param [<Symbol => String>] options
|
54
105
|
def create_host_only_network(options)
|
106
|
+
# Create the interface
|
107
|
+
execute_prlsrvctl('net', 'add', options[:network_id], '--type', 'host-only')
|
108
|
+
|
109
|
+
# Configure it
|
110
|
+
args = ['--ip', "#{options[:adapter_ip]}/#{options[:netmask]}"]
|
111
|
+
if options[:dhcp]
|
112
|
+
args.concat(['--dhcp-ip', options[:dhcp][:ip],
|
113
|
+
'--ip-scope-start', options[:dhcp][:lower],
|
114
|
+
'--ip-scope-end', options[:dhcp][:upper]])
|
115
|
+
end
|
116
|
+
|
117
|
+
execute_prlsrvctl('net', 'set', options[:network_id], *args)
|
118
|
+
|
119
|
+
# Return the details
|
120
|
+
{
|
121
|
+
name: options[:network_id],
|
122
|
+
ip: options[:adapter_ip],
|
123
|
+
netmask: options[:netmask],
|
124
|
+
dhcp: options[:dhcp]
|
125
|
+
}
|
126
|
+
end
|
127
|
+
|
128
|
+
# Creates a snapshot for the specified virtual machine.
|
129
|
+
#
|
130
|
+
# @param [String] uuid Name or UUID of the target VM.
|
131
|
+
# @param [<Symbol => String, Boolean>] options Snapshot options.
|
132
|
+
# @return [String] ID of the created snapshot.
|
133
|
+
def create_snapshot(uuid, options)
|
134
|
+
raise NotImplementedError
|
55
135
|
end
|
56
136
|
|
57
137
|
# Deletes the virtual machine references by this driver.
|
58
138
|
def delete
|
139
|
+
execute_prlctl('delete', @uuid)
|
59
140
|
end
|
60
141
|
|
61
142
|
# Deletes all disabled network adapters from the VM configuration
|
62
143
|
def delete_disabled_adapters
|
144
|
+
read_settings.fetch('Hardware', {}).each do |adapter, params|
|
145
|
+
if adapter.start_with?('net') and !params.fetch('enabled', true)
|
146
|
+
execute_prlctl('set', @uuid, '--device-del', adapter)
|
147
|
+
end
|
148
|
+
end
|
63
149
|
end
|
64
150
|
|
65
151
|
# Deletes any host only networks that aren't being used for anything.
|
66
152
|
def delete_unused_host_only_networks
|
153
|
+
raise NotImplementedError
|
67
154
|
end
|
68
155
|
|
69
156
|
# Disables requiring password on such operations as creating, adding,
|
70
157
|
# removing or cloning the virtual machine.
|
71
158
|
#
|
72
159
|
def disable_password_restrictions
|
160
|
+
raise NotImplementedError
|
73
161
|
end
|
74
162
|
|
75
163
|
# Enables network adapters on the VM.
|
@@ -85,104 +173,259 @@ module VagrantPlugins
|
|
85
173
|
#
|
86
174
|
# This must support setting up both host only and bridged networks.
|
87
175
|
#
|
88
|
-
# @param [Array<
|
176
|
+
# @param [Array<Symbol => Symbol, String>] adapters
|
177
|
+
# Array of adapters to be enabled.
|
89
178
|
def enable_adapters(adapters)
|
179
|
+
raise NotImplementedError
|
90
180
|
end
|
91
181
|
|
92
|
-
#
|
182
|
+
# Create a set of port forwarding rules for a virtual machine.
|
183
|
+
#
|
184
|
+
# This will not affect any previously set forwarded ports,
|
185
|
+
# so be sure to delete those if you need to.
|
93
186
|
#
|
94
|
-
#
|
95
|
-
#
|
96
|
-
|
187
|
+
# The format of each port hash should be the following:
|
188
|
+
#
|
189
|
+
# {
|
190
|
+
# guestport: 80,
|
191
|
+
# hostport: 8500,
|
192
|
+
# name: "foo",
|
193
|
+
# protocol: "tcp"
|
194
|
+
# }
|
195
|
+
#
|
196
|
+
# Note that "protocol" is optional and will default to "tcp".
|
197
|
+
#
|
198
|
+
# @param [Array<Hash>] ports An array of ports to set. See documentation
|
199
|
+
# for more information on the format.
|
200
|
+
def forward_ports(ports)
|
201
|
+
raise NotImplementedError
|
97
202
|
end
|
98
203
|
|
99
204
|
# Halts the virtual machine (pulls the plug).
|
100
|
-
def halt(force)
|
205
|
+
def halt(force=false)
|
206
|
+
args = ['stop', @uuid]
|
207
|
+
args << '--kill' if force
|
208
|
+
execute_prlctl(*args)
|
101
209
|
end
|
102
210
|
|
103
|
-
#
|
211
|
+
# Returns a list of bridged interfaces.
|
104
212
|
#
|
105
|
-
# @
|
106
|
-
|
107
|
-
|
213
|
+
# @return [Array<Symbol => String>]
|
214
|
+
def read_bridged_interfaces
|
215
|
+
host_hw_info = read_host_info.fetch('Hardware info', {})
|
216
|
+
net_list = host_hw_info.select do |name, attrs|
|
217
|
+
# Get all network interfaces except 'vnicXXX'
|
218
|
+
attrs.fetch('type') == 'net' and name !~ /^(vnic(.+?))$/
|
219
|
+
end
|
220
|
+
|
221
|
+
bridged_ifaces = []
|
222
|
+
net_list.keys.each do |iface|
|
223
|
+
info = {}
|
224
|
+
ifconfig = execute('ifconfig', iface)
|
225
|
+
# Assign default values
|
226
|
+
info[:name] = iface
|
227
|
+
info[:ip] = '0.0.0.0'
|
228
|
+
info[:netmask] = '0.0.0.0'
|
229
|
+
info[:status] = 'Down'
|
230
|
+
|
231
|
+
if ifconfig =~ /(?<=inet\s)(\S*)/
|
232
|
+
info[:ip] = $1.to_s
|
233
|
+
end
|
234
|
+
if ifconfig =~ /(?<=netmask\s)(\S*)/
|
235
|
+
# Netmask will be converted from hex to dec:
|
236
|
+
# '0xffffff00' -> '255.255.255.0'
|
237
|
+
info[:netmask] = $1.hex.to_s(16).scan(/../).each.map{|octet| octet.hex}.join('.')
|
238
|
+
end
|
239
|
+
if ifconfig =~ /\W(UP)\W/ and ifconfig !~ /(?<=status:\s)inactive$/
|
240
|
+
info[:status] = 'Up'
|
241
|
+
end
|
242
|
+
|
243
|
+
bridged_ifaces << info
|
244
|
+
end
|
245
|
+
bridged_ifaces
|
108
246
|
end
|
109
247
|
|
110
|
-
#
|
111
|
-
|
112
|
-
|
113
|
-
|
248
|
+
# Returns current snapshot ID for the specified VM. Returns nil if
|
249
|
+
# the VM doesn't have any snapshot.
|
250
|
+
#
|
251
|
+
# @param [String] uuid Name or UUID of the target VM.
|
252
|
+
# @return [String]
|
253
|
+
def read_current_snapshot(uuid)
|
254
|
+
raise NotImplementedError
|
114
255
|
end
|
115
256
|
|
116
|
-
|
117
|
-
|
118
|
-
16
|
257
|
+
def read_forwarded_ports(global=false)
|
258
|
+
raise NotImplementedError
|
119
259
|
end
|
120
260
|
|
121
|
-
# Returns
|
261
|
+
# Returns an IP of the virtual machine. It requires that Shared network
|
262
|
+
# adapter is configured for this VM and it obtains an IP via DHCP.
|
122
263
|
#
|
123
|
-
#
|
124
|
-
|
264
|
+
#
|
265
|
+
# @return [String] IP address leased by DHCP server in "Shared" network
|
266
|
+
def read_guest_ip
|
267
|
+
mac_addr = read_mac_address.downcase
|
268
|
+
leases_file = '/Library/Preferences/Parallels/parallels_dhcp_leases'
|
269
|
+
begin
|
270
|
+
File.open(leases_file).grep(/#{mac_addr}/) do |line|
|
271
|
+
return line[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/]
|
272
|
+
end
|
273
|
+
rescue Errno::EACCES
|
274
|
+
raise Errors::DhcpLeasesNotAccessible, :leases_file => leases_file.to_s
|
275
|
+
rescue Errno::ENOENT
|
276
|
+
# File does not exist
|
277
|
+
# Perhaps, it is the fist start of Parallels Desktop
|
278
|
+
return nil
|
279
|
+
end
|
280
|
+
|
281
|
+
nil
|
282
|
+
end
|
283
|
+
|
284
|
+
# Returns path to the Parallels Tools ISO file.
|
285
|
+
#
|
286
|
+
# @param [String] guest_os Guest os type: "linux", "darwin" or "windows"
|
287
|
+
# @return [String] Path to the ISO.
|
288
|
+
def read_guest_tools_iso_path(guest_os)
|
289
|
+
guest_os = guest_os.to_sym
|
290
|
+
iso_name ={
|
291
|
+
linux: 'prl-tools-lin.iso',
|
292
|
+
darwin: 'prl-tools-mac.iso',
|
293
|
+
windows: 'prl-tools-win.iso'
|
294
|
+
}
|
295
|
+
return nil if !iso_name[guest_os]
|
296
|
+
|
297
|
+
bundle_id = 'com.parallels.desktop.console'
|
298
|
+
bundle_path = execute('mdfind', "kMDItemCFBundleIdentifier == #{bundle_id}")
|
299
|
+
iso_path = File.expand_path("./Contents/Resources/Tools/#{iso_name[guest_os]}",
|
300
|
+
bundle_path.split("\n")[0])
|
301
|
+
|
302
|
+
if !File.exist?(iso_path)
|
303
|
+
raise Errors::ParallelsToolsIsoNotFound, :iso_path => iso_path
|
304
|
+
end
|
305
|
+
|
306
|
+
iso_path
|
125
307
|
end
|
126
308
|
|
127
309
|
# Returns the state of guest tools that is installed on this VM.
|
128
310
|
# Can be any of:
|
129
|
-
# *
|
130
|
-
# *
|
131
|
-
# *
|
132
|
-
# *
|
311
|
+
# * :installed
|
312
|
+
# * :not_installed
|
313
|
+
# * :possibly_installed
|
314
|
+
# * :outdated
|
133
315
|
#
|
134
|
-
# @return [
|
316
|
+
# @return [Symbol]
|
135
317
|
def read_guest_tools_state
|
318
|
+
state = read_settings.fetch('GuestTools', {}).fetch('state', nil)
|
319
|
+
state = 'not_installed' if !state
|
320
|
+
state.to_sym
|
136
321
|
end
|
137
322
|
|
138
|
-
# Returns
|
323
|
+
# Returns Parallels Desktop properties and common information about
|
324
|
+
# the host machine.
|
139
325
|
#
|
140
|
-
# @
|
141
|
-
|
142
|
-
|
326
|
+
# @return [<Symbol => String>]
|
327
|
+
def read_host_info
|
328
|
+
json { execute_prlctl('server', 'info', '--json') }
|
143
329
|
end
|
144
330
|
|
145
331
|
# Returns a list of available host only interfaces.
|
146
332
|
#
|
147
|
-
# @return [
|
333
|
+
# @return [Array<Symbol => String>]
|
148
334
|
def read_host_only_interfaces
|
335
|
+
raise NotImplementedError
|
149
336
|
end
|
150
337
|
|
151
338
|
# Returns the MAC address of the first Shared network interface.
|
152
339
|
#
|
153
340
|
# @return [String]
|
154
341
|
def read_mac_address
|
342
|
+
hw_info = read_settings.fetch('Hardware', {})
|
343
|
+
shared_ifaces = hw_info.select do |name, params|
|
344
|
+
name.start_with?('net') && params['type'] == 'shared'
|
345
|
+
end
|
346
|
+
|
347
|
+
if shared_ifaces.empty?
|
348
|
+
raise Errors::SharedAdapterNotFound
|
349
|
+
end
|
350
|
+
|
351
|
+
shared_ifaces.values.first.fetch('mac', nil)
|
155
352
|
end
|
156
353
|
|
157
354
|
# Returns the array of network interface card MAC addresses
|
158
355
|
#
|
159
356
|
# @return [Array<String>]
|
160
357
|
def read_mac_addresses
|
358
|
+
read_vm_option('mac').strip.gsub(':', '').split(' ')
|
161
359
|
end
|
162
360
|
|
163
361
|
# Returns a list of network interfaces of the VM.
|
164
362
|
#
|
165
|
-
# @return [Hash]
|
363
|
+
# @return [<Integer => Hash>]
|
166
364
|
def read_network_interfaces
|
365
|
+
raise NotImplementedError
|
366
|
+
end
|
367
|
+
|
368
|
+
# Returns virtual machine settings
|
369
|
+
#
|
370
|
+
# @return [<String => String, Hash>]
|
371
|
+
def read_settings(uuid=@uuid)
|
372
|
+
vm = json { execute_prlctl('list', uuid, '--info', '--no-header', '--json') }
|
373
|
+
vm.last
|
374
|
+
end
|
375
|
+
|
376
|
+
# Returns the unique name (e.q. "ID") on the first Shared network in
|
377
|
+
# Parallels Desktop configuration.
|
378
|
+
# By default there is only one called "Shared".
|
379
|
+
#
|
380
|
+
# @return [String] Shared network ID
|
381
|
+
def read_shared_network_id
|
382
|
+
# There should be only one Shared interface
|
383
|
+
shared_net = read_virtual_networks.detect do |net|
|
384
|
+
net['Type'] == 'shared'
|
385
|
+
end
|
386
|
+
shared_net.fetch('Network ID')
|
167
387
|
end
|
168
388
|
|
169
389
|
# Returns info about shared network interface.
|
170
390
|
#
|
171
|
-
# @return [Hash]
|
391
|
+
# @return [<Symbol => String, Hash>]
|
172
392
|
def read_shared_interface
|
393
|
+
raise NotImplementedError
|
173
394
|
end
|
174
395
|
|
175
396
|
# Returns a list of shared folders in format:
|
176
397
|
# { id => hostpath, ... }
|
177
398
|
#
|
178
|
-
# @return [
|
399
|
+
# @return [<String => String>]
|
179
400
|
def read_shared_folders
|
401
|
+
shf_info = read_settings.fetch('Host Shared Folders', {})
|
402
|
+
list = {}
|
403
|
+
shf_info.delete_if { |k,v| k == 'enabled' }.each do |id, data|
|
404
|
+
list[id] = data.fetch('path')
|
405
|
+
end
|
406
|
+
|
407
|
+
list
|
180
408
|
end
|
181
409
|
|
182
410
|
# Returns the current state of this VM.
|
183
411
|
#
|
184
|
-
# @return [Symbol]
|
412
|
+
# @return [Symbol] Virtual machine state
|
185
413
|
def read_state
|
414
|
+
read_vm_option('status').strip.to_sym
|
415
|
+
end
|
416
|
+
|
417
|
+
# Returns a list of all forwarded ports in use by active
|
418
|
+
# virtual machines.
|
419
|
+
#
|
420
|
+
# @return [Array]
|
421
|
+
def read_used_ports
|
422
|
+
end
|
423
|
+
|
424
|
+
# Returns the configuration of all virtual networks in Parallels Desktop.
|
425
|
+
#
|
426
|
+
# @return [Array<String => String>]
|
427
|
+
def read_virtual_networks
|
428
|
+
json { execute_prlsrvctl('net', 'list', '--json') }
|
186
429
|
end
|
187
430
|
|
188
431
|
# Returns a value of specified VM option. Raises an exception if value
|
@@ -192,41 +435,99 @@ module VagrantPlugins
|
|
192
435
|
# @param [String] uuid Virtual machine UUID
|
193
436
|
# @return [String]
|
194
437
|
def read_vm_option(option, uuid=@uuid)
|
438
|
+
out = execute_prlctl('list', uuid,'--no-header', '-o', option).strip
|
439
|
+
if out.empty?
|
440
|
+
raise Errors::ParallelsVMOptionNotFound, vm_option: option
|
441
|
+
end
|
442
|
+
|
443
|
+
out
|
195
444
|
end
|
196
445
|
|
197
|
-
# Returns
|
198
|
-
# virtual machines and templates.
|
446
|
+
# Returns names and ids of all virtual machines and templates.
|
199
447
|
#
|
200
|
-
# @return [
|
448
|
+
# @return [<String => String>]
|
201
449
|
def read_vms
|
450
|
+
args = %w(list --all --no-header --json -o name,uuid)
|
451
|
+
vms_arr = json([]) { execute_prlctl(*args) }
|
452
|
+
templates_arr = json([]) { execute_prlctl(*args, '--template') }
|
453
|
+
|
454
|
+
vms = vms_arr | templates_arr
|
455
|
+
Hash[vms.map { |i| [i.fetch('name'), i.fetch('uuid')] }]
|
456
|
+
end
|
457
|
+
|
458
|
+
# Returns the configuration of all VMs and templates.
|
459
|
+
#
|
460
|
+
# @return [Array <String => String>]
|
461
|
+
def read_vms_info
|
462
|
+
args = %w(list --all --info --no-header --json)
|
463
|
+
vms_arr = json([]) { execute_prlctl(*args) }
|
464
|
+
templates_arr = json([]) { execute_prlctl(*args, '--template') }
|
465
|
+
|
466
|
+
vms_arr | templates_arr
|
202
467
|
end
|
203
468
|
|
204
469
|
# Regenerates 'SourceVmUuid' to avoid SMBIOS UUID collision [GH-113]
|
205
470
|
#
|
206
471
|
def regenerate_src_uuid
|
472
|
+
settings = read_settings
|
473
|
+
vm_config = File.join(settings.fetch('Home'), 'config.pvs')
|
474
|
+
|
475
|
+
# Generate and put new SourceVmUuid
|
476
|
+
xml = Nokogiri::XML(File.open(vm_config))
|
477
|
+
p = '//ParallelsVirtualMachine/Identification/SourceVmUuid'
|
478
|
+
xml.xpath(p).first.content = "{#{SecureRandom.uuid}}"
|
479
|
+
|
480
|
+
File.open(vm_config, 'w') do |f|
|
481
|
+
f.write xml.to_xml
|
482
|
+
end
|
207
483
|
end
|
208
484
|
|
209
485
|
# Registers the virtual machine
|
210
486
|
#
|
211
487
|
# @param [String] pvm_file Path to the machine image (*.pvm)
|
212
488
|
def register(pvm_file)
|
489
|
+
args = [@prlctl_path, 'register', pvm_file]
|
490
|
+
|
491
|
+
3.times do
|
492
|
+
result = raw(*args)
|
493
|
+
# Exit if everything is OK
|
494
|
+
return if result.exit_code == 0
|
495
|
+
|
496
|
+
# It may occur in the race condition with other Vagrant processes.
|
497
|
+
# It is OK, just exit.
|
498
|
+
return if result.stderr.include?('is already registered.')
|
499
|
+
|
500
|
+
# Sleep a bit though to give Parallels Desktop time to fix itself
|
501
|
+
sleep 2
|
502
|
+
end
|
503
|
+
|
504
|
+
# If we reach this point, it means that we consistently got the
|
505
|
+
# failure, do a standard execute now. This will raise an
|
506
|
+
# exception if it fails again.
|
507
|
+
execute(*args)
|
213
508
|
end
|
214
509
|
|
215
|
-
#
|
510
|
+
# Checks that specified virtual machine is registered
|
216
511
|
#
|
217
|
-
|
512
|
+
# @return [Boolean]
|
513
|
+
def registered?(uuid)
|
514
|
+
args = %w(list --all --info --no-header -o uuid)
|
515
|
+
|
516
|
+
execute_prlctl(*args).include?(uuid) ||
|
517
|
+
execute_prlctl(*args, '--template').include?(uuid)
|
218
518
|
end
|
219
519
|
|
220
|
-
#
|
520
|
+
# Resumes the virtual machine.
|
221
521
|
#
|
222
|
-
|
223
|
-
|
522
|
+
def resume
|
523
|
+
execute_prlctl('resume', @uuid)
|
224
524
|
end
|
225
525
|
|
226
526
|
# Sets the name of the virtual machine.
|
227
527
|
#
|
228
528
|
# @param [String] name New VM name.
|
229
529
|
def set_name(name)
|
530
|
+
execute_prlctl('set', @uuid, '--name', name)
|
230
531
|
end
|
231
532
|
|
232
533
|
# Sets Power Consumption method.
|
@@ -234,39 +535,104 @@ module VagrantPlugins
|
|
234
535
|
# @param [Boolean] optimized Use "Longer Battery Life"
|
235
536
|
# instead "Better Performance"
|
236
537
|
def set_power_consumption_mode(optimized)
|
538
|
+
raise NotImplementedError
|
237
539
|
end
|
238
540
|
|
239
541
|
# Share a set of folders on this VM.
|
240
542
|
#
|
241
|
-
# @param [Array<
|
543
|
+
# @param [Array<Symbol => String>] folders
|
242
544
|
def share_folders(folders)
|
545
|
+
folders.each do |folder|
|
546
|
+
# Add the shared folder
|
547
|
+
execute_prlctl('set', @uuid,
|
548
|
+
'--shf-host-add', folder[:name],
|
549
|
+
'--path', folder[:hostpath])
|
550
|
+
end
|
551
|
+
end
|
552
|
+
|
553
|
+
# Reads the SSH IP of this VM.
|
554
|
+
#
|
555
|
+
# @return [String] IP address to use for SSH connection to the VM.
|
556
|
+
def ssh_ip
|
557
|
+
read_guest_ip
|
243
558
|
end
|
244
559
|
|
245
560
|
# Reads the SSH port of this VM.
|
246
561
|
#
|
247
562
|
# @param [Integer] expected Expected guest port of SSH.
|
563
|
+
# @return [Integer] Port number to use for SSH connection to the VM.
|
248
564
|
def ssh_port(expected)
|
565
|
+
expected
|
249
566
|
end
|
250
567
|
|
251
568
|
# Starts the virtual machine.
|
252
569
|
#
|
253
570
|
def start
|
571
|
+
execute_prlctl('start', @uuid)
|
254
572
|
end
|
255
573
|
|
256
|
-
#
|
574
|
+
# Suspends the virtual machine.
|
257
575
|
def suspend
|
576
|
+
execute_prlctl('suspend', @uuid)
|
577
|
+
end
|
578
|
+
|
579
|
+
# Performs un-registeration of the specified VM in Parallels Desktop.
|
580
|
+
# Virtual machine will be removed from the VM list, but its image will
|
581
|
+
# not be deleted from the disk. So, it can be registered again.
|
582
|
+
def unregister(uuid)
|
583
|
+
args = [@prlctl_path, 'unregister', uuid]
|
584
|
+
3.times do
|
585
|
+
result = raw(*args)
|
586
|
+
# Exit if everything is OK
|
587
|
+
return if result.exit_code == 0
|
588
|
+
|
589
|
+
# It may occur in the race condition with other Vagrant processes.
|
590
|
+
# Both are OK, just exit.
|
591
|
+
return if result.stderr.include?('is not registered')
|
592
|
+
return if result.stderr.include?('is being cloned')
|
593
|
+
|
594
|
+
# Sleep a bit though to give Parallels Desktop time to fix itself
|
595
|
+
sleep 2
|
596
|
+
end
|
597
|
+
|
598
|
+
# If we reach this point, it means that we consistently got the
|
599
|
+
# failure, do a standard execute now. This will raise an
|
600
|
+
# exception if it fails again.
|
601
|
+
execute(*args)
|
258
602
|
end
|
259
603
|
|
260
604
|
# Unshare folders.
|
261
605
|
def unshare_folders(names)
|
606
|
+
names.each do |name|
|
607
|
+
execute_prlctl('set', @uuid, '--shf-host-del', name)
|
608
|
+
end
|
262
609
|
end
|
263
610
|
|
264
611
|
# Checks if a VM with the given UUID exists.
|
265
612
|
#
|
266
613
|
# @return [Boolean]
|
267
614
|
def vm_exists?(uuid)
|
615
|
+
5.times do
|
616
|
+
result = raw(@prlctl_path, 'list', uuid)
|
617
|
+
return true if result.exit_code == 0
|
618
|
+
|
619
|
+
# Sometimes this happens. In this case, retry.
|
620
|
+
# If we don't see this text, the VM really doesn't exist.
|
621
|
+
return false if !result.stderr.include?('Login failed:')
|
622
|
+
|
623
|
+
# Sleep a bit though to give Parallels Desktop time to fix itself
|
624
|
+
sleep 2
|
625
|
+
end
|
626
|
+
|
627
|
+
# If we reach this point, it means that we consistently got the
|
628
|
+
# failure, do a standard prlctl now. This will raise an
|
629
|
+
# exception if it fails again.
|
630
|
+
execute_prlctl('list', uuid)
|
631
|
+
true
|
268
632
|
end
|
269
633
|
|
634
|
+
private
|
635
|
+
|
270
636
|
# Wraps 'execute' and returns the output of given 'prlctl' subcommand.
|
271
637
|
def execute_prlctl(*command, &block)
|
272
638
|
execute(@prlctl_path, *command, &block)
|
@@ -285,7 +651,7 @@ module VagrantPlugins
|
|
285
651
|
# nicely handled by Vagrant.
|
286
652
|
if r.exit_code != 0
|
287
653
|
if @interrupted
|
288
|
-
@logger.info(
|
654
|
+
@logger.info('Exit code != 0, but interrupted. Ignoring.')
|
289
655
|
else
|
290
656
|
# If there was an error running command, show the error and the
|
291
657
|
# output.
|
@@ -297,6 +663,12 @@ module VagrantPlugins
|
|
297
663
|
r.stdout
|
298
664
|
end
|
299
665
|
|
666
|
+
# Parses given block (JSON string) to object
|
667
|
+
def json(default=nil)
|
668
|
+
data = yield
|
669
|
+
JSON.parse(data) rescue default
|
670
|
+
end
|
671
|
+
|
300
672
|
# Executes a command and returns the raw result object.
|
301
673
|
def raw(*command, &block)
|
302
674
|
int_callback = lambda do
|
@@ -304,7 +676,7 @@ module VagrantPlugins
|
|
304
676
|
|
305
677
|
# We have to execute this in a thread due to trap contexts
|
306
678
|
# and locks.
|
307
|
-
Thread.new { @logger.info(
|
679
|
+
Thread.new { @logger.info('Interrupted.') }
|
308
680
|
end
|
309
681
|
|
310
682
|
# Append in the options for subprocess
|
@@ -315,8 +687,6 @@ module VagrantPlugins
|
|
315
687
|
end
|
316
688
|
end
|
317
689
|
|
318
|
-
private
|
319
|
-
|
320
690
|
def util_path(bin)
|
321
691
|
path = Vagrant::Util::Which.which(bin)
|
322
692
|
return path if path
|
@@ -325,6 +695,7 @@ module VagrantPlugins
|
|
325
695
|
path = File.join(folder, bin)
|
326
696
|
return path if File.file?(path)
|
327
697
|
end
|
698
|
+
nil
|
328
699
|
end
|
329
700
|
|
330
701
|
end
|