vagrant-parallels 2.1.0 → 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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 5fdb9ca9fa5d338982ac6541f4f5678e0ef2dee8c59702db2e306db6b78e680b
4
- data.tar.gz: aa9b5f1f0e36c1738f110f398625a06966c1000372709f7f1c0b66a3ff8464cc
3
+ metadata.gz: 86f3ba721613e8310adfe4cb4adb335703bc398eb6f28e0d8525af257b3be729
4
+ data.tar.gz: 389a8f69e3125f0ecd3e66d3b418f16cbb8a424a17097300cde59b9f91ace6df
5
5
  SHA512:
6
- metadata.gz: fcad3b44951d96a81e85a70de8eced5a79cc345927233de61cd0dae7cfc04793a6f5259d92f420accb1663563d285454435848c569b3de67e3df01bec90934ce
7
- data.tar.gz: e3d94acb423edbe4222ad979717a7379780b4be870fc33c793f0c09f64fa48721086d3ef03d14b08e560ead90d44ed183a8a37035b7a043379d9148548bb13eb
6
+ metadata.gz: d36309ae8b86da70114723881277f840d76220fcedb570b047cdc345b0cde34bc7e363614698d3c959429a86e34470c9a2463b9c613b708b1a9046dcf9e9fe6a
7
+ data.tar.gz: 97e8598b3ab0ea621264efcae32877f3afb914a73f0d36d9653cce093a01207de46db0a2af619fd6350fa8a078e740e74199294d1f0b05cdaae836898d68a23d
data/CHANGELOG.md CHANGED
@@ -1,3 +1,12 @@
1
+ ## 2.2.0 (March 3, 2021)
2
+ IMPROVEMENTS:
3
+ - Mount shared folders after manual VM reboot
4
+ [[GH-377](https://github.com/Parallels/vagrant-parallels/pull/377)]
5
+
6
+ BUG FIXES:
7
+ - Fixed mount of shared folders with non-ASCII symbols in the name
8
+ [[GH-290](https://github.com/Parallels/vagrant-parallels/issues/290)]
9
+
1
10
  ## 2.1.0 (November 25, 2020)
2
11
  BUG FIXES:
3
12
  - Fixed the private network adapter workflow on macOS 11.0 Big Sur
@@ -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
@@ -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
@@ -59,6 +55,10 @@ module VagrantPlugins
59
55
  error_key(:parallels_invalid_version)
60
56
  end
61
57
 
58
+ class ParallelsMountFailed < VagrantParallelsError
59
+ error_key(:parallels_mount_failed)
60
+ end
61
+
62
62
  class ParallelsNotDetected < VagrantParallelsError
63
63
  error_key(:parallels_not_detected)
64
64
  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)
@@ -19,14 +19,11 @@ module VagrantPlugins
19
19
  end
20
20
 
21
21
  defs << {
22
- name: os_friendly_id(id),
22
+ name: data[:plugin].capability(:mount_name, data),
23
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
@@ -0,0 +1,121 @@
1
+ require "shellwords"
2
+ require "vagrant/util/retryable"
3
+
4
+ module VagrantPlugins
5
+ module Parallels
6
+ module Util
7
+ module UnixMountHelpers
8
+
9
+ def self.extended(klass)
10
+ if !klass.class_variable_defined?(:@@logger)
11
+ klass.class_variable_set(:@@logger, Log4r::Logger.new(klass.name.downcase))
12
+ end
13
+ klass.extend Vagrant::Util::Retryable
14
+ end
15
+
16
+ def detect_owner_group_ids(machine, guest_path, mount_options, options)
17
+ mount_uid = find_mount_options_id("uid", mount_options)
18
+ mount_gid = find_mount_options_id("gid", mount_options)
19
+
20
+ if mount_uid.nil?
21
+ if options[:owner].to_i.to_s == options[:owner].to_s
22
+ mount_uid = options[:owner]
23
+ self.class_variable_get(:@@logger).debug("Owner user ID (provided): #{mount_uid}")
24
+ else
25
+ output = {stdout: '', stderr: ''}
26
+ uid_command = "id -u #{options[:owner]}"
27
+ machine.communicate.execute(uid_command,
28
+ error_class: Errors::ParallelsMountFailed,
29
+ error_key: :parallels_mount_failed,
30
+ command: uid_command,
31
+ output: output[:stderr]
32
+ ) { |type, data| output[type] << data if output[type] }
33
+ mount_uid = output[:stdout].chomp
34
+ self.class_variable_get(:@@logger).debug("Owner user ID (lookup): #{options[:owner]} -> #{mount_uid}")
35
+ end
36
+ else
37
+ machine.ui.warn "Detected mount owner ID within mount options. (uid: #{mount_uid} guestpath: #{guest_path})"
38
+ end
39
+
40
+ if mount_gid.nil?
41
+ if options[:group].to_i.to_s == options[:group].to_s
42
+ mount_gid = options[:group]
43
+ self.class_variable_get(:@@logger).debug("Owner group ID (provided): #{mount_gid}")
44
+ else
45
+ begin
46
+ output = {stdout: '', stderr: ''}
47
+ gid_command = "getent group #{options[:group]}"
48
+ machine.communicate.execute(gid_command,
49
+ error_class: Errors::ParallelsMountFailed,
50
+ error_key: :parallels_mount_failed,
51
+ command: gid_command,
52
+ output: output[:stderr]
53
+ ) { |type, data| output[type] << data if output[type] }
54
+ mount_gid = output[:stdout].split(':').at(2).to_s.chomp
55
+ self.class_variable_get(:@@logger).debug("Owner group ID (lookup): #{options[:group]} -> #{mount_gid}")
56
+ rescue Vagrant::Errors::ParallelsMountFailed
57
+ if options[:owner] == options[:group]
58
+ self.class_variable_get(:@@logger).debug("Failed to locate group `#{options[:group]}`. Group name matches owner. Fetching effective group ID.")
59
+ output = {stdout: ''}
60
+ result = machine.communicate.execute("id -g #{options[:owner]}",
61
+ error_check: false
62
+ ) { |type, data| output[type] << data if output[type] }
63
+ mount_gid = output[:stdout].chomp if result == 0
64
+ self.class_variable_get(:@@logger).debug("Owner group ID (effective): #{mount_gid}")
65
+ end
66
+ raise unless mount_gid
67
+ end
68
+ end
69
+ else
70
+ machine.ui.warn "Detected mount group ID within mount options. (gid: #{mount_gid} guestpath: #{guest_path})"
71
+ end
72
+ {:gid => mount_gid, :uid => mount_uid}
73
+ end
74
+
75
+ def find_mount_options_id(id_name, mount_options)
76
+ id_line = mount_options.detect{|line| line.include?("#{id_name}=")}
77
+ if id_line
78
+ match = id_line.match(/,?#{Regexp.escape(id_name)}=(?<option_id>\d+),?/)
79
+ found_id = match["option_id"]
80
+ updated_id_line = [
81
+ match.pre_match,
82
+ match.post_match
83
+ ].find_all{|string| !string.empty?}.join(',')
84
+ if updated_id_line.empty?
85
+ mount_options.delete(id_line)
86
+ else
87
+ idx = mount_options.index(id_line)
88
+ mount_options.delete(idx)
89
+ mount_options.insert(idx, updated_id_line)
90
+ end
91
+ end
92
+ found_id
93
+ end
94
+
95
+ def emit_upstart_notification(machine, guest_path)
96
+ # Emit an upstart event if we can
97
+ machine.communicate.sudo <<-EOH.gsub(/^ {12}/, "")
98
+ if command -v /sbin/init && /sbin/init 2>/dev/null --version | grep upstart; then
99
+ /sbin/initctl emit --no-wait vagrant-mounted MOUNTPOINT=#{guest_path}
100
+ fi
101
+ EOH
102
+ end
103
+
104
+ def merge_mount_options(base, overrides)
105
+ base = base.join(",").split(",")
106
+ overrides = overrides.join(",").split(",")
107
+ b_kv = Hash[base.map{|item| item.split("=", 2) }]
108
+ o_kv = Hash[overrides.map{|item| item.split("=", 2) }]
109
+ merged = {}.tap do |opts|
110
+ (b_kv.keys + o_kv.keys).uniq.each do |key|
111
+ opts[key] = o_kv.fetch(key, b_kv[key])
112
+ end
113
+ end
114
+ merged.map do |key, value|
115
+ [key, value].compact.join("=")
116
+ end
117
+ end
118
+ end
119
+ end
120
+ end
121
+ end
@@ -1,5 +1,5 @@
1
1
  module VagrantPlugins
2
2
  module Parallels
3
- VERSION = '2.1.0'
3
+ VERSION = '2.2.0'
4
4
  end
5
5
  end
data/locales/en.yml CHANGED
@@ -45,14 +45,6 @@ en:
45
45
  disk is inconsistent, please remove it from the VM configuration.
46
46
 
47
47
  Disk image path: %{path}
48
- linux_mount_failed: |-
49
- Failed to mount folders in Linux guest. This is usually because
50
- the "prl_fs" file system is not available. Please verify that
51
- Parallels Tools are properly installed in the guest and
52
- can work properly. If so, the VM reboot can solve a problem.
53
- The command attempted was:
54
-
55
- %{command}
56
48
  linux_prl_fs_invalid_options: |-
57
49
  Failed to mount folders in Linux guest. You've specified mount options
58
50
  which are not supported by "prl_fs" file system.
@@ -81,6 +73,19 @@ en:
81
73
  %{output}
82
74
 
83
75
  This is an internal error that should be reported as a bug.
76
+ parallels_mount_failed: |-
77
+ Vagrant was unable to mount Parallels Desktop shared folders. This is usually
78
+ because the filesystem "prl_fs" is not available. This filesystem is
79
+ made available via the Parallels Tools and kernel module.
80
+ Please verify that these guest tools are properly installed in the
81
+ guest. This is not a bug in Vagrant and is usually caused by a faulty
82
+ Vagrant box. For context, the command attempted was:
83
+
84
+ %{command}
85
+
86
+ The error output from the command was:
87
+
88
+ %{output}
84
89
  parallels_no_room_for_high_level_network: |-
85
90
  There is no available slots on the Parallels Desktop VM for the configured
86
91
  high-level network interfaces. "private_network" and "public_network"
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: vagrant-parallels
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.1.0
4
+ version: 2.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikhail Zholobov
@@ -9,7 +9,7 @@ authors:
9
9
  autorequire:
10
10
  bindir: bin
11
11
  cert_chain: []
12
- date: 2020-11-25 00:00:00.000000000 Z
12
+ date: 2021-03-03 00:00:00.000000000 Z
13
13
  dependencies:
14
14
  - !ruby/object:Gem::Dependency
15
15
  name: nokogiri
@@ -112,6 +112,7 @@ files:
112
112
  - lib/vagrant-parallels/action/snapshot_save.rb
113
113
  - lib/vagrant-parallels/action/suspend.rb
114
114
  - lib/vagrant-parallels/cap.rb
115
+ - lib/vagrant-parallels/cap/mount_options.rb
115
116
  - lib/vagrant-parallels/config.rb
116
117
  - lib/vagrant-parallels/driver/base.rb
117
118
  - lib/vagrant-parallels/driver/meta.rb
@@ -128,6 +129,7 @@ files:
128
129
  - lib/vagrant-parallels/provider.rb
129
130
  - lib/vagrant-parallels/synced_folder.rb
130
131
  - lib/vagrant-parallels/util/compile_forwarded_ports.rb
132
+ - lib/vagrant-parallels/util/unix_mount_helpers.rb
131
133
  - lib/vagrant-parallels/version.rb
132
134
  - locales/en.yml
133
135
  homepage: https://github.com/Parallels/vagrant-parallels