vagrant-parallels 1.7.8 → 2.2.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,49 @@
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
+ ## We have to support 2 different expected interfaces of `mount_name` call:
34
+ ## Vagrant < 2.2.15: `def self.mount_name(machine, data)`
35
+ ## Vagrant >= 2.2.15: `def self.mount_name(machine, id, data)`
36
+ ## https://github.com/Parallels/vagrant-parallels/issues/384
37
+ def self.mount_name(*args)
38
+ if args.length >= 3
39
+ id = args[1]
40
+ else
41
+ id = args[-1][:guestpath]
42
+ end
43
+
44
+ id.gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
45
+ end
46
+ end
47
+ end
48
+ end
49
+ 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)
@@ -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, id, 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, id, 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, id, 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