vagrant-parallels 2.1.0 → 2.2.3

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