vagrant-parallels 1.7.8 → 2.2.1
Sign up to get free protection for your applications and to get access to all the features.
- checksums.yaml +5 -5
- data/CHANGELOG.md +73 -35
- data/README.md +29 -31
- data/lib/vagrant-parallels/action.rb +2 -0
- data/lib/vagrant-parallels/action/box_unregister.rb +2 -2
- data/lib/vagrant-parallels/action/import.rb +2 -12
- data/lib/vagrant-parallels/action/network.rb +6 -1
- data/lib/vagrant-parallels/action/package_vagrantfile.rb +33 -0
- data/lib/vagrant-parallels/action/prepare_clone_snapshot.rb +1 -2
- data/lib/vagrant-parallels/action/sane_defaults.rb +3 -18
- data/lib/vagrant-parallels/cap/mount_options.rb +49 -0
- data/lib/vagrant-parallels/config.rb +1 -1
- data/lib/vagrant-parallels/driver/base.rb +11 -33
- data/lib/vagrant-parallels/driver/meta.rb +6 -8
- data/lib/vagrant-parallels/driver/pd_11.rb +2 -2
- data/lib/vagrant-parallels/driver/pd_12.rb +1 -1
- data/lib/vagrant-parallels/errors.rb +9 -5
- data/lib/vagrant-parallels/guest_cap/linux/mount_parallels_shared_folder.rb +31 -64
- data/lib/vagrant-parallels/plugin.rb +15 -1
- data/lib/vagrant-parallels/synced_folder.rb +10 -20
- data/lib/vagrant-parallels/util/unix_mount_helpers.rb +121 -0
- data/lib/vagrant-parallels/version.rb +1 -1
- data/locales/en.yml +27 -14
- metadata +14 -13
- data/lib/vagrant-parallels/driver/pd_10.rb +0 -20
@@ -0,0 +1,49 @@
|
|
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
|
+
## We have to support 2 different expected interfaces of `mount_name` call:
|
34
|
+
## Vagrant < 2.2.15: `def self.mount_name(machine, data)`
|
35
|
+
## Vagrant >= 2.2.15: `def self.mount_name(machine, id, data)`
|
36
|
+
## https://github.com/Parallels/vagrant-parallels/issues/384
|
37
|
+
def self.mount_name(*args)
|
38
|
+
if args.length >= 3
|
39
|
+
id = args[1]
|
40
|
+
else
|
41
|
+
id = args[-1][:guestpath]
|
42
|
+
end
|
43
|
+
|
44
|
+
id.gsub(/[*":<>?|\/\\]/,'_').sub(/^_/, '')
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
end
|
@@ -71,7 +71,7 @@ module VagrantPlugins
|
|
71
71
|
@functional_psf = true
|
72
72
|
end
|
73
73
|
|
74
|
-
@linked_clone =
|
74
|
+
@linked_clone = true if @linked_clone == UNSET_VALUE
|
75
75
|
@linked_clone_snapshot = nil if @linked_clone_snapshot == UNSET_VALUE
|
76
76
|
|
77
77
|
@name = nil if @name == UNSET_VALUE
|
@@ -193,14 +193,13 @@ module VagrantPlugins
|
|
193
193
|
end
|
194
194
|
end
|
195
195
|
|
196
|
-
# Deletes
|
196
|
+
# Deletes host-only networks that aren't being used by any virtual machine.
|
197
197
|
def delete_unused_host_only_networks
|
198
198
|
networks = read_virtual_networks
|
199
|
-
|
200
|
-
#
|
199
|
+
|
200
|
+
# Exclude all host-only network interfaces which were not created by vagrant provider.
|
201
201
|
networks.keep_if do |net|
|
202
|
-
net['Type'] == 'host-only' && net['
|
203
|
-
net['Bound To'].match(/^(?>vnic|Parallels Host-Only #)(\d+)$/)[1].to_i >= 2
|
202
|
+
net['Type'] == 'host-only' && net['Network ID'] =~ /^vagrant-vnet(\d+)$/
|
204
203
|
end
|
205
204
|
|
206
205
|
read_vms_info.each do |vm|
|
@@ -210,8 +209,8 @@ module VagrantPlugins
|
|
210
209
|
end
|
211
210
|
end
|
212
211
|
|
212
|
+
# Delete all unused network interfaces.
|
213
213
|
networks.each do |net|
|
214
|
-
# Delete the actual host only network interface.
|
215
214
|
execute_prlsrvctl('net', 'del', net['Network ID'])
|
216
215
|
end
|
217
216
|
end
|
@@ -496,7 +495,6 @@ module VagrantPlugins
|
|
496
495
|
#
|
497
496
|
# {
|
498
497
|
# name: 'Host-Only', # Parallels Network ID
|
499
|
-
# bound_to: 'vnic1', # interface name
|
500
498
|
# ip: '10.37.129.2', # IP address of the interface
|
501
499
|
# netmask: '255.255.255.0', # netmask associated with the interface
|
502
500
|
# status: 'Up' # status of the interface
|
@@ -519,11 +517,9 @@ module VagrantPlugins
|
|
519
517
|
}
|
520
518
|
|
521
519
|
adapter = net_info['Parallels adapter']
|
522
|
-
if adapter
|
523
|
-
|
524
|
-
iface[:
|
525
|
-
iface[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask']
|
526
|
-
iface[:bound_to] = net_info['Bound To']
|
520
|
+
if adapter
|
521
|
+
iface[:ip] = adapter['IPv4 address']
|
522
|
+
iface[:netmask] = adapter['IPv4 subnet mask']
|
527
523
|
iface[:status] = 'Up'
|
528
524
|
|
529
525
|
if adapter['IPv6 address'] && adapter['IPv6 subnet mask']
|
@@ -618,11 +614,9 @@ module VagrantPlugins
|
|
618
614
|
}
|
619
615
|
adapter = net_info['Parallels adapter']
|
620
616
|
|
621
|
-
if adapter
|
622
|
-
|
623
|
-
iface[:
|
624
|
-
iface[:netmask] = adapter['Subnet mask'] || adapter['IPv4 subnet mask']
|
625
|
-
iface[:bound_to] = net_info['Bound To']
|
617
|
+
if adapter
|
618
|
+
iface[:ip] = adapter['IPv4 address']
|
619
|
+
iface[:netmask] = adapter['IPv4 subnet mask']
|
626
620
|
iface[:status] = 'Up'
|
627
621
|
end
|
628
622
|
|
@@ -724,22 +718,6 @@ module VagrantPlugins
|
|
724
718
|
vms_arr | templates_arr
|
725
719
|
end
|
726
720
|
|
727
|
-
# Regenerates 'SourceVmUuid' to avoid SMBIOS UUID collision [GH-113]
|
728
|
-
#
|
729
|
-
def regenerate_src_uuid
|
730
|
-
settings = read_settings
|
731
|
-
vm_config = File.join(settings.fetch('Home'), 'config.pvs')
|
732
|
-
|
733
|
-
# Generate and put new SourceVmUuid
|
734
|
-
xml = Nokogiri::XML(File.open(vm_config))
|
735
|
-
p = '//ParallelsVirtualMachine/Identification/SourceVmUuid'
|
736
|
-
xml.xpath(p).first.content = "{#{SecureRandom.uuid}}"
|
737
|
-
|
738
|
-
File.open(vm_config, 'w') do |f|
|
739
|
-
f.write xml.to_xml
|
740
|
-
end
|
741
|
-
end
|
742
|
-
|
743
721
|
# Registers the virtual machine
|
744
722
|
#
|
745
723
|
# @param [String] pvm_file Path to the machine image (*.pvm)
|
@@ -37,26 +37,23 @@ module VagrantPlugins
|
|
37
37
|
@@version_lock.synchronize do
|
38
38
|
@@version = read_version
|
39
39
|
end
|
40
|
-
|
40
|
+
|
41
41
|
# Instantiate the proper version driver for Parallels Desktop
|
42
42
|
@logger.debug("Finding driver for Parallels Desktop version: #{@@version}")
|
43
43
|
|
44
44
|
major_ver = @@version.split('.').first.to_i
|
45
45
|
driver_klass =
|
46
46
|
case major_ver
|
47
|
-
when 1..
|
48
|
-
when 10 then PD_10
|
47
|
+
when 1..10 then raise Errors::ParallelsUnsupportedVersion
|
49
48
|
when 11 then PD_11
|
50
49
|
else PD_12
|
51
50
|
end
|
52
51
|
|
53
52
|
# Starting since PD 11 only Pro and Business editions have CLI
|
54
53
|
# functionality and can be used with Vagrant.
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
raise Errors::ParallelsUnsupportedEdition
|
59
|
-
end
|
54
|
+
edition = read_edition
|
55
|
+
if !edition || !%w(any pro business).include?(edition)
|
56
|
+
raise Errors::ParallelsUnsupportedEdition
|
60
57
|
end
|
61
58
|
|
62
59
|
@logger.info("Using Parallels driver: #{driver_klass}")
|
@@ -143,6 +140,7 @@ module VagrantPlugins
|
|
143
140
|
# * prlctl version 8.0.12345.123456
|
144
141
|
# * prlctl version 9.0.12345.123456
|
145
142
|
# * prlctl version 10.0.0 (12345) rev 123456
|
143
|
+
# * prlctl version 14.0.1 (45154)
|
146
144
|
#
|
147
145
|
# But we need exactly the first 3 numbers: "x.x.x"
|
148
146
|
output = execute(@prlctl_path, '--version')
|
@@ -2,13 +2,13 @@ require 'log4r'
|
|
2
2
|
|
3
3
|
require 'vagrant/util/platform'
|
4
4
|
|
5
|
-
require_relative '
|
5
|
+
require_relative 'base'
|
6
6
|
|
7
7
|
module VagrantPlugins
|
8
8
|
module Parallels
|
9
9
|
module Driver
|
10
10
|
# Driver for Parallels Desktop 11.
|
11
|
-
class PD_11 <
|
11
|
+
class PD_11 < Base
|
12
12
|
def initialize(uuid)
|
13
13
|
super(uuid)
|
14
14
|
|
@@ -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
|
@@ -39,6 +35,10 @@ module VagrantPlugins
|
|
39
35
|
error_key(:mac_os_x_required)
|
40
36
|
end
|
41
37
|
|
38
|
+
class NetworkCollision < VagrantParallelsError
|
39
|
+
error_key(:network_collision)
|
40
|
+
end
|
41
|
+
|
42
42
|
class NetworkInvalidAddress < VagrantParallelsError
|
43
43
|
error_key(:network_invalid_address)
|
44
44
|
end
|
@@ -55,6 +55,10 @@ module VagrantPlugins
|
|
55
55
|
error_key(:parallels_invalid_version)
|
56
56
|
end
|
57
57
|
|
58
|
+
class ParallelsMountFailed < VagrantParallelsError
|
59
|
+
error_key(:parallels_mount_failed)
|
60
|
+
end
|
61
|
+
|
58
62
|
class ParallelsNotDetected < VagrantParallelsError
|
59
63
|
error_key(:parallels_not_detected)
|
60
64
|
end
|
@@ -104,4 +108,4 @@ module VagrantPlugins
|
|
104
108
|
end
|
105
109
|
end
|
106
110
|
end
|
107
|
-
end
|
111
|
+
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
|
|
@@ -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)
|
@@ -143,7 +158,6 @@ module VagrantPlugins
|
|
143
158
|
# our drivers only when they are needed.
|
144
159
|
module Driver
|
145
160
|
autoload :Meta, File.expand_path('../driver/meta', __FILE__)
|
146
|
-
autoload :PD_10, File.expand_path('../driver/pd_10', __FILE__)
|
147
161
|
autoload :PD_11, File.expand_path('../driver/pd_11', __FILE__)
|
148
162
|
autoload :PD_12, File.expand_path('../driver/pd_12', __FILE__)
|
149
163
|
end
|
@@ -19,14 +19,11 @@ module VagrantPlugins
|
|
19
19
|
end
|
20
20
|
|
21
21
|
defs << {
|
22
|
-
|
23
|
-
|
22
|
+
name: data[:plugin].capability(:mount_name, id, data),
|
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 |
|
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
|
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
|
-
|
64
|
+
:mount_parallels_shared_folder,
|
65
|
+
data[:plugin].capability(:mount_name, id, 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,
|
87
|
+
names = folders.map { |id, data| data[:plugin].capability(:mount_name, id, 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
|