vagrant-parallels 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
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