vagrant-parallels 0.2.1 → 0.2.2.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.
Files changed (43) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +21 -13
  3. data/.travis.yml +1 -0
  4. data/README.md +43 -54
  5. data/config/i18n-tasks.yml.erb +1 -1
  6. data/debug.log +941 -0
  7. data/lib/vagrant-parallels/action.rb +0 -7
  8. data/lib/vagrant-parallels/action/check_accessible.rb +1 -1
  9. data/lib/vagrant-parallels/action/check_guest_tools.rb +10 -2
  10. data/lib/vagrant-parallels/action/clear_network_interfaces.rb +1 -1
  11. data/lib/vagrant-parallels/action/customize.rb +6 -4
  12. data/lib/vagrant-parallels/action/export.rb +56 -12
  13. data/lib/vagrant-parallels/action/import.rb +49 -30
  14. data/lib/vagrant-parallels/action/network.rb +137 -48
  15. data/lib/vagrant-parallels/action/package_config_files.rb +0 -12
  16. data/lib/vagrant-parallels/action/prepare_nfs_valid_ids.rb +1 -1
  17. data/lib/vagrant-parallels/action/set_name.rb +2 -2
  18. data/lib/vagrant-parallels/config.rb +11 -2
  19. data/lib/vagrant-parallels/driver/base.rb +281 -0
  20. data/lib/vagrant-parallels/driver/meta.rb +138 -0
  21. data/lib/vagrant-parallels/driver/{prl_ctl.rb → pd_8.rb} +116 -256
  22. data/lib/vagrant-parallels/driver/pd_9.rb +417 -0
  23. data/lib/vagrant-parallels/errors.rb +15 -7
  24. data/lib/vagrant-parallels/plugin.rb +7 -7
  25. data/lib/vagrant-parallels/provider.rb +33 -3
  26. data/lib/vagrant-parallels/version.rb +1 -1
  27. data/locales/en.yml +30 -16
  28. data/test/unit/base.rb +1 -5
  29. data/test/unit/config_test.rb +13 -2
  30. data/test/unit/driver/pd_8_test.rb +196 -0
  31. data/test/unit/driver/pd_9_test.rb +196 -0
  32. data/test/unit/locales/locales_test.rb +1 -1
  33. data/test/unit/support/shared/parallels_context.rb +2 -2
  34. data/test/unit/support/shared/pd_driver_examples.rb +243 -0
  35. data/test/unit/synced_folder_test.rb +37 -0
  36. data/vagrant-parallels.gemspec +5 -5
  37. metadata +39 -32
  38. data/lib/vagrant-parallels/action/match_mac_address.rb +0 -28
  39. data/lib/vagrant-parallels/action/register_template.rb +0 -24
  40. data/lib/vagrant-parallels/action/unregister_template.rb +0 -26
  41. data/test/support/isolated_environment.rb +0 -46
  42. data/test/support/tempdir.rb +0 -43
  43. data/test/unit/driver/prl_ctl_test.rb +0 -148
@@ -1,54 +1,41 @@
1
1
  require 'log4r'
2
- require 'json'
3
2
 
4
- require 'vagrant/util/busy'
5
- require "vagrant/util/network_ip"
6
3
  require 'vagrant/util/platform'
7
- require 'vagrant/util/retryable'
8
- require 'vagrant/util/subprocess'
4
+
5
+ require File.expand_path("../base", __FILE__)
9
6
 
10
7
  module VagrantPlugins
11
8
  module Parallels
12
9
  module Driver
13
- # Base class for all Parallels drivers.
14
- #
15
- # This class provides useful tools for things such as executing
16
- # PrlCtl and handling SIGINTs and so on.
17
- class PrlCtl
18
- # Include this so we can use `Subprocess` more easily.
19
- include Vagrant::Util::Retryable
20
- include Vagrant::Util::NetworkIP
21
-
22
- attr_reader :uuid
23
-
10
+ # Driver for Parallels Desktop 8.
11
+ class PD_8 < Base
24
12
  def initialize(uuid)
25
- @logger = Log4r::Logger.new("vagrant::provider::parallels::prlctl")
13
+ super()
26
14
 
27
- # This flag is used to keep track of interrupted state (SIGINT)
28
- @interrupted = false
29
-
30
- # Store machine id
15
+ @logger = Log4r::Logger.new("vagrant::provider::parallels::pd_8")
31
16
  @uuid = uuid
17
+ end
32
18
 
33
- # Set the path to prlctl
34
- @prlctl_path = "prlctl"
35
- @prlsrvctl_path = "prlsrvctl"
36
19
 
37
- @logger.info("CLI prlctl path: #{@prlctl_path}")
38
- @logger.info("CLI prlsrvctl path: #{@prlsrvctl_path}")
20
+ def compact(uuid)
21
+ used_drives = read_settings.fetch('Hardware', {}).select { |name, _| name.start_with? 'hdd' }
22
+ used_drives.each_value do |drive_params|
23
+ execute(:prl_disk_tool, 'compact', '--hdd', drive_params["image"]) do |type, data|
24
+ lines = data.split("\r")
25
+ # The progress of the compact will be in the last line. Do a greedy
26
+ # regular expression to find what we're looking for.
27
+ if lines.last =~ /.+?(\d{,3}) ?%/
28
+ yield $1.to_i if block_given?
29
+ end
30
+ end
31
+ end
39
32
  end
40
33
 
41
- def compact(uuid=nil)
42
- uuid ||= @uuid
43
- # TODO: VM can have more than one hdd!
44
- path_to_hdd = read_settings(uuid).fetch("Hardware", {}).fetch("hdd0", {}).fetch("image", nil)
45
- raw('prl_disk_tool', 'compact', '--hdd', path_to_hdd) do |type, data|
46
- lines = data.split("\r")
47
- # The progress of the import will be in the last line. Do a greedy
48
- # regular expression to find what we're looking for.
49
- if lines.last =~ /.+?(\d{,3}) ?%/
50
- yield $1.to_i if block_given?
51
- end
34
+ def clear_shared_folders
35
+ shf = read_settings.fetch("Host Shared Folders", {}).keys
36
+ shf.delete("enabled")
37
+ shf.each do |folder|
38
+ execute("set", @uuid, "--shf-host-del", folder)
52
39
  end
53
40
  end
54
41
 
@@ -72,27 +59,19 @@ module VagrantPlugins
72
59
 
73
60
  # Return the details
74
61
  return {
75
- :name => options[:name],
76
- :bound_to => bound_to,
77
- :ip => options[:adapter_ip],
78
- :netmask => options[:netmask],
79
- :dhcp => options[:dhcp]
62
+ :name => options[:name],
63
+ :bound_to => bound_to,
64
+ :ip => options[:adapter_ip],
65
+ :netmask => options[:netmask],
66
+ :dhcp => options[:dhcp]
80
67
  }
81
68
  end
82
69
 
83
- def clear_shared_folders
84
- shf = read_settings.fetch("Host Shared Folders", {}).keys
85
- shf.delete("enabled")
86
- shf.each do |folder|
87
- execute("set", @uuid, "--shf-host-del", folder)
88
- end
89
- end
90
-
91
70
  def delete
92
71
  execute('delete', @uuid)
93
72
  end
94
73
 
95
- def delete_adapters
74
+ def delete_disabled_adapters
96
75
  read_settings.fetch('Hardware', {}).each do |adapter, params|
97
76
  if adapter.start_with?('net') and !params.fetch("enabled", true)
98
77
  execute('set', @uuid, '--device-del', adapter)
@@ -101,19 +80,19 @@ module VagrantPlugins
101
80
  end
102
81
 
103
82
  def delete_unused_host_only_networks
104
- networks = read_virtual_networks()
83
+ networks = read_virtual_networks
105
84
 
106
85
  # 'Shared'(vnic0) and 'Host-Only'(vnic1) are default in Parallels Desktop
107
86
  # They should not be deleted anyway.
108
87
  networks.keep_if do |net|
109
88
  net['Type'] == "host-only" &&
110
- net['Bound To'].match(/^(?>vnic|Parallels Host-Only #)(\d+)$/)[1].to_i >= 2
89
+ net['Bound To'].match(/^(?>vnic|Parallels Host-Only #)(\d+)$/)[1].to_i >= 2
111
90
  end
112
91
 
113
- read_all_info.each do |vm|
92
+ read_vms_info.each do |vm|
114
93
  used_nets = vm.fetch('Hardware', {}).select { |name, _| name.start_with? 'net' }
115
94
  used_nets.each_value do |net_params|
116
- networks.delete_if { |net| net['Bound To'] == net_params.fetch('iface', nil)}
95
+ networks.delete_if { |net| net['Bound To'] == net_params.fetch('iface', nil) }
117
96
  end
118
97
 
119
98
  end
@@ -152,18 +131,10 @@ module VagrantPlugins
152
131
  args.concat(["--type", "bridged", "--iface", adapter[:bound_to]])
153
132
  end
154
133
 
155
- if adapter[:shared]
134
+ if adapter[:type] == :shared
156
135
  args.concat(["--type", "shared"])
157
136
  end
158
137
 
159
- if adapter[:dhcp]
160
- args.concat(["--dhcp", "yes"])
161
- elsif adapter[:ip]
162
- args.concat(["--ipdel", "all", "--ipadd", "#{adapter[:ip]}/#{adapter[:netmask]}"])
163
- else
164
- args.concat(["--dhcp", "no"])
165
- end
166
-
167
138
  if adapter[:mac_address]
168
139
  args.concat(["--mac", adapter[:mac_address]])
169
140
  end
@@ -176,17 +147,20 @@ module VagrantPlugins
176
147
  end
177
148
  end
178
149
 
179
- def export(path, vm_name)
180
- execute("clone", @uuid, "--name", vm_name, "--template", "--dst", path.to_s) do |type, data|
150
+ def execute_command(command)
151
+ execute(*command)
152
+ end
153
+
154
+ def export(path, tpl_name)
155
+ execute("clone", @uuid, "--name", tpl_name, "--template", "--dst", path.to_s) do |type, data|
181
156
  lines = data.split("\r")
182
- # The progress of the import will be in the last line. Do a greedy
157
+ # The progress of the export will be in the last line. Do a greedy
183
158
  # regular expression to find what we're looking for.
184
159
  if lines.last =~ /.+?(\d{,3}) ?%/
185
160
  yield $1.to_i if block_given?
186
161
  end
187
162
  end
188
-
189
- read_settings(vm_name).fetch('ID', vm_name)
163
+ read_vms[tpl_name]
190
164
  end
191
165
 
192
166
  def halt(force=false)
@@ -195,7 +169,10 @@ module VagrantPlugins
195
169
  execute(*args)
196
170
  end
197
171
 
198
- def import(template_uuid, vm_name)
172
+ def import(template_uuid)
173
+ template_name = read_vms.key(template_uuid)
174
+ vm_name = "#{template_name}_#{(Time.now.to_f * 1000.0).to_i}_#{rand(100000)}"
175
+
199
176
  execute("clone", template_uuid, '--name', vm_name) do |type, data|
200
177
  lines = data.split("\r")
201
178
  # The progress of the import will be in the last line. Do a greedy
@@ -204,71 +181,23 @@ module VagrantPlugins
204
181
  yield $1.to_i if block_given?
205
182
  end
206
183
  end
207
- @uuid = read_settings(vm_name).fetch('ID', vm_name)
208
- end
209
-
210
- def ip
211
- mac_addr = read_mac_address.downcase
212
- File.foreach("/Library/Preferences/Parallels/parallels_dhcp_leases") do |line|
213
- if line.include? mac_addr
214
- ip = line[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/]
215
- return ip
216
- end
217
- end
218
- end
219
-
220
-
221
- def mac_in_use?(mac)
222
- all_macs_in_use = []
223
- read_all_info.each do |vm|
224
- all_macs_in_use << vm.fetch('Hardware', {}).fetch('net0',{}).fetch('mac', '')
225
- end
226
-
227
- valid_mac = mac.upcase.tr('^A-F0-9', '')
228
-
229
- all_macs_in_use.include?(valid_mac)
230
- end
231
-
232
- # Returns a hash of all UUIDs assigned to VMs and templates currently
233
- # known by Parallels. Keys are 'name' values
234
- #
235
- # @return [Hash]
236
- def read_all_names
237
- list = {}
238
- read_all_info.each do |item|
239
- list[item.fetch('Name')] = item.fetch('ID')
240
- end
241
-
242
- list
243
- end
244
-
245
- # Returns a hash of all UUIDs assigned to VMs and templates currently
246
- # known by Parallels. Keys are 'Home' directories
247
- #
248
- # @return [Hash]
249
- def read_all_paths
250
- list = {}
251
- read_all_info.each do |item|
252
- if Dir.exists? item.fetch('Home')
253
- list[File.realpath item.fetch('Home')] = item.fetch('ID')
254
- end
255
- end
256
-
257
- list
184
+ read_vms[vm_name]
258
185
  end
259
186
 
260
187
  def read_bridged_interfaces
261
- net_list = read_virtual_networks()
188
+ net_list = read_virtual_networks
262
189
 
263
190
  # Skip 'vnicXXX' and 'Default' interfaces
264
191
  net_list.delete_if do |net|
265
- net['Type'] != "bridged" or net['Bound To'] =~ /^(vnic(.+?)|Default)$/
192
+ net['Type'] != "bridged" or
193
+ net['Bound To'] =~ /^(vnic(.+?))$/ or
194
+ net['Network ID'] == "Default"
266
195
  end
267
196
 
268
197
  bridged_ifaces = []
269
198
  net_list.collect do |iface|
270
199
  info = {}
271
- ifconfig = raw('ifconfig', iface['Bound To']).stdout
200
+ ifconfig = execute(:ifconfig, iface['Bound To'])
272
201
  # Assign default values
273
202
  info[:name] = iface['Network ID'].gsub(/\s\(.*?\)$/, '')
274
203
  info[:bound_to] = iface['Bound To']
@@ -298,7 +227,7 @@ module VagrantPlugins
298
227
  end
299
228
 
300
229
  def read_host_only_interfaces
301
- net_list = read_virtual_networks()
230
+ net_list = read_virtual_networks
302
231
  net_list.keep_if { |net| net['Type'] == "host-only" }
303
232
 
304
233
  hostonly_ifaces = []
@@ -325,7 +254,17 @@ module VagrantPlugins
325
254
  end
326
255
  hostonly_ifaces << info
327
256
  end
328
- hostonly_ifaces
257
+ hostonly_ifaces
258
+ end
259
+
260
+ def read_ip_dhcp
261
+ mac_addr = read_mac_address.downcase
262
+ File.foreach("/Library/Preferences/Parallels/parallels_dhcp_leases") do |line|
263
+ if line.include? mac_addr
264
+ ip = line[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/]
265
+ return ip
266
+ end
267
+ end
329
268
  end
330
269
 
331
270
  def read_mac_address
@@ -361,46 +300,77 @@ module VagrantPlugins
361
300
  nics
362
301
  end
363
302
 
364
- # Returns the current state of this VM.
365
- #
366
- # @return [Symbol]
303
+ def read_settings
304
+ vm = json { execute('list', @uuid, '--info', '--json', retryable: true).gsub(/^INFO/, '') }
305
+ vm.last
306
+ end
307
+
367
308
  def read_state
368
- read_settings(@uuid).fetch('State', 'inaccessible').to_sym
309
+ vm = json { execute('list', @uuid, '--json', retryable: true).gsub(/^INFO/, '') }
310
+ return nil if !vm.last
311
+ vm.last.fetch('status').to_sym
369
312
  end
370
313
 
371
314
  def read_virtual_networks
372
315
  json { execute(:prlsrvctl, 'net', 'list', '--json', retryable: true) }
373
316
  end
374
317
 
375
- def ready?
376
- !!guest_execute('uname') rescue false
318
+ def read_vms
319
+ results = {}
320
+ vms_arr = json([]) do
321
+ execute('list', '--all', '--json', retryable: true).gsub(/^INFO/, '')
322
+ end
323
+ templates_arr = json([]) do
324
+ execute('list', '--all', '--json', '--template', retryable: true).gsub(/^INFO/, '')
325
+ end
326
+ vms = vms_arr | templates_arr
327
+ vms.each do |item|
328
+ results[item.fetch('name')] = item.fetch('uuid')
329
+ end
330
+
331
+ results
332
+ end
333
+
334
+ # Parse the JSON from *all* VMs and templates. Then return an array of objects (without duplicates)
335
+ def read_vms_info
336
+ vms_arr = json([]) do
337
+ execute('list', '--all','--info', '--json', retryable: true).gsub(/^INFO/, '')
338
+ end
339
+ templates_arr = json([]) do
340
+ execute('list', '--all','--info', '--json', '--template', retryable: true).gsub(/^INFO/, '')
341
+ end
342
+ vms_arr | templates_arr
343
+ end
344
+
345
+ def read_vms_paths
346
+ list = {}
347
+ read_vms_info.each do |item|
348
+ if Dir.exists? item.fetch('Home')
349
+ list[File.realpath item.fetch('Home')] = item.fetch('ID')
350
+ end
351
+ end
352
+
353
+ list
377
354
  end
378
355
 
379
356
  def register(pvm_file)
380
357
  execute("register", pvm_file)
381
358
  end
382
359
 
383
- def registered?(path)
384
- # TODO: Make this take UUID and have callers pass that instead
385
- # Need a way to get the UUID from unregistered templates though (config.pvs XML parsing/regex?)
386
- read_all_paths.has_key?(path)
360
+ def registered?(uuid)
361
+ read_vms.has_value?(uuid)
387
362
  end
388
363
 
389
364
  def resume
390
365
  execute('resume', @uuid)
391
366
  end
392
367
 
393
- def set_name(name)
394
- execute('set', @uuid, '--name', name, :retryable => true)
395
- end
396
-
397
368
  def set_mac_address(mac)
398
369
  execute('set', @uuid, '--device-set', 'net0', '--type', 'shared', '--mac', mac)
399
370
  end
400
371
 
401
- # apply custom vm setting via set parameter
402
- def set_vm_settings(command)
403
- raw(@prlctl_path, *command)
372
+ def set_name(name)
373
+ execute('set', @uuid, '--name', name, :retryable => true)
404
374
  end
405
375
 
406
376
  def share_folders(folders)
@@ -411,7 +381,7 @@ module VagrantPlugins
411
381
  end
412
382
 
413
383
  def ssh_port(expected_port)
414
- 22
384
+ expected_port
415
385
  end
416
386
 
417
387
  def start
@@ -426,9 +396,6 @@ module VagrantPlugins
426
396
  execute("unregister", uuid)
427
397
  end
428
398
 
429
- # Verifies that the driver is ready to accept work.
430
- #
431
- # This should raise a VagrantError if things are not ready.
432
399
  def verify!
433
400
  version
434
401
  end
@@ -441,115 +408,8 @@ module VagrantPlugins
441
408
  end
442
409
  end
443
410
 
444
- private
445
-
446
- def guest_execute(*command)
447
- execute('exec', @uuid, *command)
448
- end
449
-
450
- def json(default=nil)
451
- data = yield
452
- JSON.parse(data) rescue default
453
- end
454
-
455
- # Parse the JSON from *all* VMs and templates. Then return an array of objects (without duplicates)
456
- def read_all_info
457
- vms_arr = json({}) do
458
- execute('list', '--info', '--json', retryable: true).gsub(/^(INFO)?/, '')
459
- end
460
- templates_arr = json({}) do
461
- execute('list', '--info', '--json', '--template', retryable: true).gsub(/^(INFO)?/, '')
462
- end
463
- vms_arr | templates_arr
464
- end
465
-
466
- def read_settings(uuid=nil)
467
- uuid ||= @uuid
468
- json({}) { execute('list', uuid, '--info', '--json', retryable: true).gsub(/^(INFO)?\[/, '').gsub(/\]$/, '') }
469
- end
470
-
471
- def error_detection(command_response)
472
- errored = false
473
- # If the command was a failure, then raise an exception that is
474
- # nicely handled by Vagrant.
475
- if command_response.exit_code != 0
476
- if @interrupted
477
- @logger.info("Exit code != 0, but interrupted. Ignoring.")
478
- elsif command_response.exit_code == 126
479
- # This exit code happens if PrlCtl is on the PATH,
480
- # but another executable it tries to execute is missing.
481
- # This is usually indicative of a corrupted Parallels install.
482
- raise VagrantPlugins::Parallels::Errors::ParallelsErrorNotFoundError
483
- else
484
- errored = true
485
- end
486
- elsif command_response.stderr =~ /failed to open \/dev\/prlctl/i
487
- # This catches an error message that only shows when kernel
488
- # drivers aren't properly installed.
489
- @logger.error("Error message about unable to open prlctl")
490
- raise VagrantPlugins::Parallels::Errors::ParallelsErrorKernelModuleNotLoaded
491
- elsif command_response.stderr =~ /Unable to perform/i
492
- @logger.info("VM not running for command to work.")
493
- errored = true
494
- elsif command_response.stderr =~ /Invalid usage/i
495
- @logger.info("PrlCtl error text found, assuming error.")
496
- errored = true
497
- end
498
- errored
499
- end
500
-
501
- # Execute the given subcommand for PrlCtl and return the output.
502
- def execute(*command, &block)
503
- # Get the utility to execute: 'prlctl' by default and 'prlsrvctl' if it set as a first argument in command
504
- if command.first == :prlsrvctl
505
- cli = @prlsrvctl_path
506
- command.delete_at(0)
507
- else
508
- cli = @prlctl_path
509
- end
510
-
511
- # Get the options hash if it exists
512
- opts = {}
513
- opts = command.pop if command.last.is_a?(Hash)
514
-
515
- tries = opts[:retryable] ? 3 : 0
516
-
517
- # Variable to store our execution result
518
- r = nil
519
-
520
- # If there is an error with PrlCtl, this gets set to true
521
- errored = false
522
-
523
- retryable(on: VagrantPlugins::Parallels::Errors::ParallelsError, tries: tries, sleep: 1) do
524
- # Execute the command
525
- r = raw(cli, *command, &block)
526
- errored = error_detection(r)
527
- end
528
-
529
- # If there was an error running PrlCtl, show the error and the
530
- # output.
531
- if errored
532
- raise VagrantPlugins::Parallels::Errors::ParallelsError,
533
- command: command.inspect,
534
- stderr: r.stderr
535
- end
536
-
537
- r.stdout
538
- end
539
-
540
- # Executes a command and returns the raw result object.
541
- def raw(cli, *command, &block)
542
- int_callback = lambda do
543
- @interrupted = true
544
- @logger.info("Interrupted.")
545
- end
546
-
547
- # Append in the options for subprocess
548
- command << { notify: [:stdout, :stderr] }
549
-
550
- Vagrant::Util::Busy.busy(int_callback) do
551
- Vagrant::Util::Subprocess.execute(cli, *command, &block)
552
- end
411
+ def vm_exists?(uuid)
412
+ raw("list", uuid).exit_code == 0
553
413
  end
554
414
  end
555
415
  end