vagrant-parallels 1.7.7 → 2.2.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.
@@ -0,0 +1,39 @@
1
+ require_relative "../util/unix_mount_helpers"
2
+
3
+ module VagrantPlugins
4
+ module Parallels
5
+ module SyncedFolderCap
6
+ module MountOptions
7
+ extend VagrantPlugins::Parallels::Util::UnixMountHelpers
8
+
9
+ PRL_MOUNT_TYPE = "prl_fs".freeze
10
+
11
+ # Returns mount options for a parallels synced folder
12
+ #
13
+ # @param [Machine] machine
14
+ # @param [String] name of mount
15
+ # @param [String] path of mount on guest
16
+ # @param [Hash] hash of mount options
17
+ def self.mount_options(machine, name, guest_path, options)
18
+ mount_options = options.fetch(:mount_options, [])
19
+ detected_ids = detect_owner_group_ids(machine, guest_path, mount_options, options)
20
+ mount_uid = detected_ids[:uid]
21
+ mount_gid = detected_ids[:gid]
22
+
23
+ mount_options << "uid=#{mount_uid}"
24
+ mount_options << "gid=#{mount_gid}"
25
+ mount_options = mount_options.join(',')
26
+ return mount_options, mount_uid, mount_gid
27
+ end
28
+
29
+ def self.mount_type(machine)
30
+ return PRL_MOUNT_TYPE
31
+ end
32
+
33
+ def self.mount_name(machine, data)
34
+ data[:guestpath].gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
35
+ end
36
+ end
37
+ end
38
+ end
39
+ end
@@ -71,7 +71,7 @@ module VagrantPlugins
71
71
  @functional_psf = true
72
72
  end
73
73
 
74
- @linked_clone = false if @linked_clone == UNSET_VALUE
74
+ @linked_clone = true if @linked_clone == UNSET_VALUE
75
75
  @linked_clone_snapshot = nil if @linked_clone_snapshot == UNSET_VALUE
76
76
 
77
77
  @name = nil if @name == UNSET_VALUE
@@ -193,14 +193,13 @@ module VagrantPlugins
193
193
  end
194
194
  end
195
195
 
196
- # Deletes any host only networks that aren't being used for anything.
196
+ # Deletes host-only networks that aren't being used by any virtual machine.
197
197
  def delete_unused_host_only_networks
198
198
  networks = read_virtual_networks
199
- # 'Shared'(vnic0) and 'Host-Only'(vnic1) are default in Parallels Desktop
200
- # They should not be deleted anyway.
199
+
200
+ # Exclude all host-only network interfaces which were not created by vagrant provider.
201
201
  networks.keep_if do |net|
202
- net['Type'] == 'host-only' && net['Bound To'] &&
203
- net['Bound To'].match(/^(?>vnic|Parallels Host-Only #)(\d+)$/)[1].to_i >= 2
202
+ net['Type'] == 'host-only' && net['Network ID'] =~ /^vagrant-vnet(\d+)$/
204
203
  end
205
204
 
206
205
  read_vms_info.each do |vm|
@@ -210,8 +209,8 @@ module VagrantPlugins
210
209
  end
211
210
  end
212
211
 
212
+ # Delete all unused network interfaces.
213
213
  networks.each do |net|
214
- # Delete the actual host only network interface.
215
214
  execute_prlsrvctl('net', 'del', net['Network ID'])
216
215
  end
217
216
  end
@@ -496,7 +495,6 @@ module VagrantPlugins
496
495
  #
497
496
  # {
498
497
  # name: 'Host-Only', # Parallels Network ID
499
- # bound_to: 'vnic1', # interface name
500
498
  # ip: '10.37.129.2', # IP address of the interface
501
499
  # netmask: '255.255.255.0', # netmask associated with the interface
502
500
  # status: 'Up' # status of the interface
@@ -519,11 +517,9 @@ module VagrantPlugins
519
517
  }
520
518
 
521
519
  adapter = net_info['Parallels adapter']
522
- if adapter && net_info['Bound To']
523
- # In PD >= 10.1.2 there are new field names for an IP/Subnet
524
- iface[:ip] = adapter['IP address'] || adapter['IPv4 address']
525
- iface[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask']
526
- iface[:bound_to] = net_info['Bound To']
520
+ if adapter
521
+ iface[:ip] = adapter['IPv4 address']
522
+ iface[:netmask] = adapter['IPv4 subnet mask']
527
523
  iface[:status] = 'Up'
528
524
 
529
525
  if adapter['IPv6 address'] && adapter['IPv6 subnet mask']
@@ -618,11 +614,9 @@ module VagrantPlugins
618
614
  }
619
615
  adapter = net_info['Parallels adapter']
620
616
 
621
- if adapter && net_info['Bound To']
622
- # In PD >= 10.1.2 there are new field names for an IP/Subnet
623
- iface[:ip] = adapter['IP address'] || adapter['IPv4 address']
624
- iface[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask']
625
- iface[:bound_to] = net_info['Bound To']
617
+ if adapter
618
+ iface[:ip] = adapter['IPv4 address']
619
+ iface[:netmask] = adapter['IPv4 subnet mask']
626
620
  iface[:status] = 'Up'
627
621
  end
628
622
 
@@ -724,22 +718,6 @@ module VagrantPlugins
724
718
  vms_arr | templates_arr
725
719
  end
726
720
 
727
- # Regenerates 'SourceVmUuid' to avoid SMBIOS UUID collision [GH-113]
728
- #
729
- def regenerate_src_uuid
730
- settings = read_settings
731
- vm_config = File.join(settings.fetch('Home'), 'config.pvs')
732
-
733
- # Generate and put new SourceVmUuid
734
- xml = Nokogiri::XML(File.open(vm_config))
735
- p = '//ParallelsVirtualMachine/Identification/SourceVmUuid'
736
- xml.xpath(p).first.content = "{#{SecureRandom.uuid}}"
737
-
738
- File.open(vm_config, 'w') do |f|
739
- f.write xml.to_xml
740
- end
741
- end
742
-
743
721
  # Registers the virtual machine
744
722
  #
745
723
  # @param [String] pvm_file Path to the machine image (*.pvm)
@@ -858,14 +836,14 @@ module VagrantPlugins
858
836
  true
859
837
  end
860
838
 
861
- private
862
-
863
839
  # Wraps 'execute' and returns the output of given 'prlctl' subcommand.
864
840
  def execute_prlctl(*command, &block)
865
841
  execute(@prlctl_path, *command, &block)
866
842
  end
867
843
 
868
- #Wraps 'execute' and returns the output of given 'prlsrvctl' subcommand.
844
+ private
845
+
846
+ # Wraps 'execute' and returns the output of given 'prlsrvctl' subcommand.
869
847
  def execute_prlsrvctl(*command, &block)
870
848
  execute(@prlsrvctl_path, *command, &block)
871
849
  end
@@ -37,26 +37,23 @@ module VagrantPlugins
37
37
  @@version_lock.synchronize do
38
38
  @@version = read_version
39
39
  end
40
-
40
+
41
41
  # Instantiate the proper version driver for Parallels Desktop
42
42
  @logger.debug("Finding driver for Parallels Desktop version: #{@@version}")
43
43
 
44
44
  major_ver = @@version.split('.').first.to_i
45
45
  driver_klass =
46
46
  case major_ver
47
- when 1..9 then raise Errors::ParallelsUnsupportedVersion
48
- when 10 then PD_10
47
+ when 1..10 then raise Errors::ParallelsUnsupportedVersion
49
48
  when 11 then PD_11
50
49
  else PD_12
51
50
  end
52
51
 
53
52
  # Starting since PD 11 only Pro and Business editions have CLI
54
53
  # functionality and can be used with Vagrant.
55
- if major_ver >= 11
56
- edition = read_edition
57
- if !edition || !%w(any pro business).include?(edition)
58
- raise Errors::ParallelsUnsupportedEdition
59
- end
54
+ edition = read_edition
55
+ if !edition || !%w(any pro business).include?(edition)
56
+ raise Errors::ParallelsUnsupportedEdition
60
57
  end
61
58
 
62
59
  @logger.info("Using Parallels driver: #{driver_klass}")
@@ -143,6 +140,7 @@ module VagrantPlugins
143
140
  # * prlctl version 8.0.12345.123456
144
141
  # * prlctl version 9.0.12345.123456
145
142
  # * prlctl version 10.0.0 (12345) rev 123456
143
+ # * prlctl version 14.0.1 (45154)
146
144
  #
147
145
  # But we need exactly the first 3 numbers: "x.x.x"
148
146
  output = execute(@prlctl_path, '--version')
@@ -2,13 +2,13 @@ require 'log4r'
2
2
 
3
3
  require 'vagrant/util/platform'
4
4
 
5
- require_relative 'pd_10'
5
+ require_relative 'base'
6
6
 
7
7
  module VagrantPlugins
8
8
  module Parallels
9
9
  module Driver
10
10
  # Driver for Parallels Desktop 11.
11
- class PD_11 < PD_10
11
+ class PD_11 < Base
12
12
  def initialize(uuid)
13
13
  super(uuid)
14
14
 
@@ -7,7 +7,7 @@ require_relative 'pd_11'
7
7
  module VagrantPlugins
8
8
  module Parallels
9
9
  module Driver
10
- # Driver for Parallels Desktop 12.
10
+ # Driver for Parallels Desktop 12 and later.
11
11
  class PD_12 < PD_11
12
12
  def initialize(uuid)
13
13
  super(uuid)
@@ -27,10 +27,6 @@ module VagrantPlugins
27
27
  error_key(:json_parse_error)
28
28
  end
29
29
 
30
- class LinuxMountFailed < VagrantParallelsError
31
- error_key(:linux_mount_failed)
32
- end
33
-
34
30
  class LinuxPrlFsInvalidOptions < VagrantParallelsError
35
31
  error_key(:linux_prl_fs_invalid_options)
36
32
  end
@@ -39,6 +35,10 @@ module VagrantPlugins
39
35
  error_key(:mac_os_x_required)
40
36
  end
41
37
 
38
+ class NetworkCollision < VagrantParallelsError
39
+ error_key(:network_collision)
40
+ end
41
+
42
42
  class NetworkInvalidAddress < VagrantParallelsError
43
43
  error_key(:network_invalid_address)
44
44
  end
@@ -55,6 +55,10 @@ module VagrantPlugins
55
55
  error_key(:parallels_invalid_version)
56
56
  end
57
57
 
58
+ class ParallelsMountFailed < VagrantParallelsError
59
+ error_key(:parallels_mount_failed)
60
+ end
61
+
58
62
  class ParallelsNotDetected < VagrantParallelsError
59
63
  error_key(:parallels_not_detected)
60
64
  end
@@ -104,4 +108,4 @@ module VagrantPlugins
104
108
  end
105
109
  end
106
110
  end
107
- end
111
+ end
@@ -1,7 +1,19 @@
1
+ require 'shellwords'
2
+
3
+ require_relative "../../util/unix_mount_helpers"
4
+
1
5
  module VagrantPlugins
2
6
  module Parallels
3
7
  module GuestLinuxCap
4
8
  class MountParallelsSharedFolder
9
+ extend VagrantPlugins::Parallels::Util::UnixMountHelpers
10
+
11
+ # Mounts Parallels Desktop shared folder on linux guest
12
+ #
13
+ # @param [Machine] machine
14
+ # @param [String] name of mount
15
+ # @param [String] path of mount on guest
16
+ # @param [Hash] hash of mount options
5
17
  def self.mount_parallels_shared_folder(machine, name, guestpath, options)
6
18
  # Sanity check for mount options: we are not supporting
7
19
  # VirtualBox-specific 'fmode' and 'dmode' options
@@ -15,83 +27,38 @@ module VagrantPlugins
15
27
  end
16
28
  end
17
29
 
18
- expanded_guest_path = machine.guest.capability(
19
- :shell_expand_guest_path, guestpath)
30
+ guest_path = Shellwords.escape(guestpath)
31
+ mount_type = options[:plugin].capability(:mount_type)
20
32
 
21
- mount_commands = []
33
+ @@logger.debug("Mounting #{name} (#{options[:hostpath]} to #{guestpath})")
22
34
 
23
- if options[:owner].is_a? Integer
24
- mount_uid = options[:owner]
25
- else
26
- mount_uid = "`id -u #{options[:owner]}`"
27
- end
28
-
29
- if options[:group].is_a? Integer
30
- mount_gid = options[:group]
31
- mount_gid_old = options[:group]
32
- else
33
- mount_gid = "`getent group #{options[:group]} | cut -d: -f3`"
34
- mount_gid_old = "`id -g #{options[:group]}`"
35
- end
36
-
37
- # First mount command uses getent to get the group
38
- mount_options = "-o uid=#{mount_uid},gid=#{mount_gid}"
39
- mount_options += ",#{options[:mount_options].join(',')}" if options[:mount_options]
40
- mount_commands << "mount -t prl_fs #{mount_options} #{name} #{expanded_guest_path}"
41
-
42
- # Second mount command uses the old style `id -g`
43
- mount_options = "-o uid=#{mount_uid},gid=#{mount_gid_old}"
44
- mount_options += ",#{options[:mount_options].join(',')}" if options[:mount_options]
45
- mount_commands << "mount -t prl_fs #{mount_options} #{name} #{expanded_guest_path}"
46
-
47
- # Clear prior symlink if exists
48
- if machine.communicate.test("test -L #{expanded_guest_path}")
49
- machine.communicate.sudo("rm #{expanded_guest_path}")
50
- end
35
+ mount_options, mount_uid, mount_gid = options[:plugin].capability(:mount_options, name, guest_path, options)
36
+ mount_command = "mount -t #{mount_type} -o #{mount_options} #{name} #{guest_path}"
51
37
 
52
38
  # Create the guest path if it doesn't exist
53
- machine.communicate.sudo("mkdir -p #{expanded_guest_path}")
39
+ machine.communicate.sudo("mkdir -p #{guest_path}")
54
40
 
55
41
  # Attempt to mount the folder. We retry here a few times because
56
42
  # it can fail early on.
57
- attempts = 0
58
- while true
59
- success = true
60
-
61
- mount_commands.each do |command|
62
- no_such_device = false
63
- status = machine.communicate.sudo(command, error_check: false) do |type, data|
64
- no_such_device = true if type == :stderr && data =~ /No such device/i
65
- end
66
-
67
- success = status == 0 && !no_such_device
68
- break if success
69
- end
70
-
71
- break if success
72
-
73
- attempts += 1
74
- if attempts > 10
75
- raise VagrantPlugins::Parallels::Errors::LinuxMountFailed,
76
- command: mount_commands.join("\n")
77
- end
78
-
79
- sleep 2
43
+ stderr = ""
44
+ retryable(on: Errors::ParallelsMountFailed, tries: 3, sleep: 5) do
45
+ machine.communicate.sudo(mount_command,
46
+ error_class: Errors::ParallelsMountFailed,
47
+ error_key: :parallels_mount_failed,
48
+ command: mount_command,
49
+ output: stderr,
50
+ ) { |type, data| stderr = data if type == :stderr }
80
51
  end
81
52
 
82
- # Emit an upstart event if we can
83
- machine.communicate.sudo <<-EOH.gsub(/^ {10}/, "")
84
- if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
85
- /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{expanded_guest_path}
86
- fi
87
- EOH
53
+ emit_upstart_notification(machine, guest_path)
88
54
  end
89
55
 
90
56
  def self.unmount_parallels_shared_folder(machine, guestpath, options)
91
- result = machine.communicate.sudo(
92
- "umount #{guestpath}", error_check: false)
57
+ guest_path = Shellwords.escape(guestpath)
58
+
59
+ result = machine.communicate.sudo("umount #{guest_path}", error_check: false)
93
60
  if result == 0
94
- machine.communicate.sudo("rmdir #{guestpath}", error_check: false)
61
+ machine.communicate.sudo("rmdir #{guest_path}", error_check: false)
95
62
  end
96
63
  end
97
64
 
@@ -103,6 +103,21 @@ module VagrantPlugins
103
103
  SyncedFolder
104
104
  end
105
105
 
106
+ synced_folder_capability(:parallels, "mount_name") do
107
+ require_relative "cap/mount_options"
108
+ SyncedFolderCap::MountOptions
109
+ end
110
+
111
+ synced_folder_capability(:parallels, "mount_options") do
112
+ require_relative "cap/mount_options"
113
+ SyncedFolderCap::MountOptions
114
+ end
115
+
116
+ synced_folder_capability(:parallels, "mount_type") do
117
+ require_relative "cap/mount_options"
118
+ SyncedFolderCap::MountOptions
119
+ end
120
+
106
121
  # This initializes the internationalization strings.
107
122
  def self.setup_i18n
108
123
  I18n.load_path << File.expand_path('locales/en.yml', Parallels.source_root)
@@ -143,7 +158,6 @@ module VagrantPlugins
143
158
  # our drivers only when they are needed.
144
159
  module Driver
145
160
  autoload :Meta, File.expand_path('../driver/meta', __FILE__)
146
- autoload :PD_10, File.expand_path('../driver/pd_10', __FILE__)
147
161
  autoload :PD_11, File.expand_path('../driver/pd_11', __FILE__)
148
162
  autoload :PD_12, File.expand_path('../driver/pd_12', __FILE__)
149
163
  end
@@ -19,14 +19,11 @@ module VagrantPlugins
19
19
  end
20
20
 
21
21
  defs << {
22
- name: os_friendly_id(id),
23
- hostpath: hostpath.to_s,
22
+ name: data[:plugin].capability(:mount_name, data),
23
+ hostpath: hostpath.to_s,
24
24
  }
25
25
  end
26
26
 
27
- # We should prepare only folders with unique hostpath values.
28
- # Anyway, duplicates will be mounted later.
29
- defs.uniq! { |d| d[:hostpath] }
30
27
  driver(machine).share_folders(defs)
31
28
 
32
29
  # short guestpaths first, so we don't step on ourselves
@@ -39,8 +36,6 @@ module VagrantPlugins
39
36
  end
40
37
  end
41
38
 
42
- shf_config = driver(machine).read_shared_folders
43
-
44
39
  # Parallels Shared Folder services can override Vagrant synced folder
45
40
  # configuration. These services should be pre-configured.
46
41
  if machine.guest.capability?(:prepare_psf_services)
@@ -49,12 +44,8 @@ module VagrantPlugins
49
44
 
50
45
  # Go through each folder and mount
51
46
  machine.ui.output(I18n.t('vagrant.actions.vm.share_folders.mounting'))
52
- folders.each do |_ , data|
53
- # Parallels specific: get id from the VM setting
54
- # It allows to mount one host folder more then one time [GH-105]
55
- id = shf_config.key(data[:hostpath])
56
-
57
- if data[:guestpath] and id
47
+ folders.each do |id , data|
48
+ if data[:guestpath]
58
49
  # Guest path specified, so mount the folder to specified point
59
50
  machine.ui.detail(I18n.t('vagrant.actions.vm.share_folders.mounting_entry',
60
51
  guestpath: data[:guestpath],
@@ -70,7 +61,11 @@ module VagrantPlugins
70
61
 
71
62
  # Mount the actual folder
72
63
  machine.guest.capability(
73
- :mount_parallels_shared_folder, id, data[:guestpath], data)
64
+ :mount_parallels_shared_folder,
65
+ data[:plugin].capability(:mount_name, data),
66
+ data[:guestpath],
67
+ data
68
+ )
74
69
  else
75
70
  # If no guest path is specified, then automounting is disabled
76
71
  machine.ui.detail(I18n.t('vagrant.actions.vm.share_folders.nomount_entry',
@@ -89,7 +84,7 @@ module VagrantPlugins
89
84
  end
90
85
 
91
86
  # Remove the shared folders from the VM metadata
92
- names = folders.map { |id, _data| os_friendly_id(id) }
87
+ names = folders.map { |_id, data| data[:plugin].capability(:mount_name, data) }
93
88
  driver(machine).unshare_folders(names)
94
89
  end
95
90
 
@@ -103,11 +98,6 @@ module VagrantPlugins
103
98
  def driver(machine)
104
99
  machine.provider.driver
105
100
  end
106
-
107
- def os_friendly_id(id)
108
- # Replace chars *, ", :, <, >, ?, |, /, \
109
- id.gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
110
- end
111
101
  end
112
102
  end
113
103
  end