vagrant-parallels 2.1.0 → 2.2.3

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.
@@ -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
 
@@ -29,14 +29,26 @@ module VagrantPlugins
29
29
  # @return [Integer]
30
30
  attr_reader :host_port
31
31
 
32
- def initialize(id, host_port, guest_port, options)
32
+ # The ip of the guest to be used for the port.
33
+ #
34
+ # @return [String]
35
+ attr_reader :guest_ip
36
+
37
+ # The ip of the host used to access the port.
38
+ #
39
+ # @return [String]
40
+ attr_reader :host_ip
41
+
42
+ def initialize(id, host_port, guest_port, host_ip, guest_ip, **options)
33
43
  @id = id
34
44
  @guest_port = guest_port
45
+ @guest_ip = guest_ip
35
46
  @host_port = host_port
47
+ @host_ip = host_ip
36
48
 
37
49
  options ||= {}
38
50
  @auto_correct = false
39
- @auto_correct = options[:auto_correct] if options.has_key?(:auto_correct)
51
+ @auto_correct = options[:auto_correct] if options.key?(:auto_correct)
40
52
  @protocol = options[:protocol] || 'tcp'
41
53
  end
42
54
 
@@ -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)
@@ -9,8 +9,8 @@ module VagrantPlugins
9
9
  machine.provider_config.functional_psf
10
10
  end
11
11
 
12
- def enable(machine, folders, _opts)
13
- # Export the shared folders to the VM
12
+ def prepare(machine, folders, _opts)
13
+ # Setup shared folder definitions in the VM config.
14
14
  defs = []
15
15
  folders.each do |id, data|
16
16
  hostpath = data[:hostpath]
@@ -19,16 +19,15 @@ module VagrantPlugins
19
19
  end
20
20
 
21
21
  defs << {
22
- name: os_friendly_id(id),
22
+ name: data[:plugin].capability(:mount_name, id, 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)
28
+ end
31
29
 
30
+ def enable(machine, folders, _opts)
32
31
  # short guestpaths first, so we don't step on ourselves
33
32
  folders = folders.sort_by do |id, data|
34
33
  if data[:guestpath]
@@ -39,8 +38,6 @@ module VagrantPlugins
39
38
  end
40
39
  end
41
40
 
42
- shf_config = driver(machine).read_shared_folders
43
-
44
41
  # Parallels Shared Folder services can override Vagrant synced folder
45
42
  # configuration. These services should be pre-configured.
46
43
  if machine.guest.capability?(:prepare_psf_services)
@@ -49,12 +46,8 @@ module VagrantPlugins
49
46
 
50
47
  # Go through each folder and mount
51
48
  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
49
+ folders.each do |id , data|
50
+ if data[:guestpath]
58
51
  # Guest path specified, so mount the folder to specified point
59
52
  machine.ui.detail(I18n.t('vagrant.actions.vm.share_folders.mounting_entry',
60
53
  guestpath: data[:guestpath],
@@ -70,7 +63,11 @@ module VagrantPlugins
70
63
 
71
64
  # Mount the actual folder
72
65
  machine.guest.capability(
73
- :mount_parallels_shared_folder, id, data[:guestpath], data)
66
+ :mount_parallels_shared_folder,
67
+ data[:plugin].capability(:mount_name, id, data),
68
+ data[:guestpath],
69
+ data
70
+ )
74
71
  else
75
72
  # If no guest path is specified, then automounting is disabled
76
73
  machine.ui.detail(I18n.t('vagrant.actions.vm.share_folders.nomount_entry',
@@ -89,7 +86,7 @@ module VagrantPlugins
89
86
  end
90
87
 
91
88
  # Remove the shared folders from the VM metadata
92
- names = folders.map { |id, _data| os_friendly_id(id) }
89
+ names = folders.map { |id, data| data[:plugin].capability(:mount_name, id, data) }
93
90
  driver(machine).unshare_folders(names)
94
91
  end
95
92
 
@@ -103,11 +100,6 @@ module VagrantPlugins
103
100
  def driver(machine)
104
101
  machine.provider.driver
105
102
  end
106
-
107
- def os_friendly_id(id)
108
- # Replace chars *, ", :, <, >, ?, |, /, \
109
- id.gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
110
- end
111
103
  end
112
104
  end
113
105
  end
@@ -12,23 +12,25 @@ module VagrantPlugins
12
12
  mappings = {}
13
13
 
14
14
  config.vm.networks.each do |type, options|
15
- if type == :forwarded_port
16
- guest_port = options[:guest]
17
- host_port = options[:host]
18
- protocol = options[:protocol] || 'tcp'
19
- options = scoped_hash_override(options, :parallels)
20
- id = options[:id]
21
-
22
- # If the forwarded port was marked as disabled, ignore.
23
- next if options[:disabled]
24
-
25
- # Temporary disable automatically pre-configured forwarded ports
26
- # for SSH, since it is working not so well [GH-146]
27
- next if id == 'ssh'
28
-
29
- mappings[host_port.to_s + protocol.to_s] =
30
- Model::ForwardedPort.new(id, host_port, guest_port, options)
31
- end
15
+ next unless type == :forwarded_port
16
+
17
+ guest_port = options[:guest]
18
+ guest_ip = options[:guest_ip]
19
+ host_port = options[:host]
20
+ host_ip = options[:host_ip]
21
+ protocol = options[:protocol] || 'tcp'
22
+ options = scoped_hash_override(options, :parallels)
23
+ id = options[:id]
24
+
25
+ # If the forwarded port was marked as disabled, ignore.
26
+ next if options[:disabled]
27
+
28
+ # Temporary disable automatically pre-configured forwarded ports
29
+ # for SSH, since it is working not so well [GH-146]
30
+ next if id == 'ssh'
31
+
32
+ mappings[host_port.to_s + protocol.to_s] =
33
+ Model::ForwardedPort.new(id, host_port, guest_port, host_ip, guest_ip, **options)
32
34
  end
33
35
 
34
36
  mappings.values
@@ -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