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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +24 -0
- data/README.md +1 -4
- data/lib/vagrant-parallels/action/forward_ports.rb +19 -17
- data/lib/vagrant-parallels/action/network.rb +72 -75
- data/lib/vagrant-parallels/cap/mount_options.rb +50 -0
- data/lib/vagrant-parallels/driver/base.rb +76 -96
- data/lib/vagrant-parallels/errors.rb +4 -4
- data/lib/vagrant-parallels/guest_cap/linux/mount_parallels_shared_folder.rb +31 -64
- data/lib/vagrant-parallels/model/forwarded_port.rb +14 -2
- data/lib/vagrant-parallels/plugin.rb +15 -0
- data/lib/vagrant-parallels/synced_folder.rb +13 -21
- data/lib/vagrant-parallels/util/compile_forwarded_ports.rb +19 -17
- data/lib/vagrant-parallels/util/unix_mount_helpers.rb +121 -0
- data/lib/vagrant-parallels/version.rb +1 -1
- data/locales/en.yml +13 -8
- metadata +4 -2
@@ -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
|
-
|
19
|
-
|
30
|
+
guest_path = Shellwords.escape(guestpath)
|
31
|
+
mount_type = options[:plugin].capability(:mount_type)
|
20
32
|
|
21
|
-
|
33
|
+
@@logger.debug("Mounting #{name} (#{options[:hostpath]} to #{guestpath})")
|
22
34
|
|
23
|
-
|
24
|
-
|
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 #{
|
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
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
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
|
-
|
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
|
-
|
92
|
-
|
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 #{
|
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
|
-
|
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.
|
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
|
13
|
-
#
|
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:
|
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 |
|
53
|
-
|
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
|
-
|
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,
|
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
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|