vagrant-parallels 1.3.13 → 1.4.0.rc1
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.
- 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
|