vagrant-parallels 0.0.4 → 0.0.5
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.
- data/.gitignore +1 -0
- data/README.md +63 -8
- data/lib/vagrant-parallels/action.rb +8 -1
- data/lib/vagrant-parallels/action/check_guest_tools.rb +1 -3
- data/lib/vagrant-parallels/action/clear_network_interfaces.rb +1 -1
- data/lib/vagrant-parallels/action/customize.rb +43 -0
- data/lib/vagrant-parallels/action/destroy_unused_network_interfaces.rb +22 -0
- data/lib/vagrant-parallels/action/network.rb +272 -0
- data/lib/vagrant-parallels/action/share_folders.rb +2 -2
- data/lib/vagrant-parallels/config.rb +59 -0
- data/lib/vagrant-parallels/driver/prl_ctl.rb +231 -58
- data/lib/vagrant-parallels/errors.rb +6 -2
- data/lib/vagrant-parallels/plugin.rb +5 -5
- data/lib/vagrant-parallels/version.rb +1 -1
- data/locales/en.yml +8 -4
- data/sandi_meter/assets/chart.js +12 -0
- data/sandi_meter/assets/forkme.png +0 -0
- data/sandi_meter/assets/jquery.js +6 -0
- data/sandi_meter/assets/morris.css +2 -0
- data/sandi_meter/assets/normalize.css +406 -0
- data/sandi_meter/assets/script.js +83 -0
- data/sandi_meter/assets/style.css +128 -0
- data/sandi_meter/index.html +392 -0
- data/sandi_meter/sandi_meter.log +1 -0
- data/vagrant-parallels.gemspec +2 -2
- metadata +30 -8
- checksums.yaml +0 -15
@@ -27,8 +27,8 @@ module VagrantPlugins
|
|
27
27
|
mount_shared_folders
|
28
28
|
end
|
29
29
|
|
30
|
-
# This method returns an actual list of
|
31
|
-
# folders to create and their proper path.
|
30
|
+
# This method returns an actual list of Parallels Desktop
|
31
|
+
# shared folders to create and their proper path.
|
32
32
|
def shared_folders
|
33
33
|
{}.tap do |result|
|
34
34
|
@env[:machine].config.vm.synced_folders.each do |id, data|
|
@@ -0,0 +1,59 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
class Config < Vagrant.plugin("2", :config)
|
4
|
+
attr_reader :customizations
|
5
|
+
attr_accessor :destroy_unused_network_interfaces
|
6
|
+
attr_reader :network_adapters
|
7
|
+
attr_accessor :name
|
8
|
+
|
9
|
+
def initialize
|
10
|
+
@customizations = []
|
11
|
+
@destroy_unused_network_interfaces = UNSET_VALUE
|
12
|
+
@network_adapters = {}
|
13
|
+
@name = UNSET_VALUE
|
14
|
+
|
15
|
+
network_adapter(0, :shared)
|
16
|
+
end
|
17
|
+
|
18
|
+
def customize(*command)
|
19
|
+
event = command.first.is_a?(String) ? command.shift : "pre-boot"
|
20
|
+
command = command[0]
|
21
|
+
@customizations << [event, command]
|
22
|
+
end
|
23
|
+
|
24
|
+
def network_adapter(slot, type, *args)
|
25
|
+
@network_adapters[slot] = [type, args]
|
26
|
+
end
|
27
|
+
|
28
|
+
def finalize!
|
29
|
+
if @destroy_unused_network_interfaces == UNSET_VALUE
|
30
|
+
@destroy_unused_network_interfaces = true
|
31
|
+
end
|
32
|
+
|
33
|
+
@name = nil if @name == UNSET_VALUE
|
34
|
+
end
|
35
|
+
|
36
|
+
def validate(machine)
|
37
|
+
errors = []
|
38
|
+
valid_events = ["pre-import", "pre-boot", "post-boot"]
|
39
|
+
@customizations.each do |event, _|
|
40
|
+
if !valid_events.include?(event)
|
41
|
+
errors << I18n.t(
|
42
|
+
"vagrant_parallels.config.invalid_event",
|
43
|
+
event: event.to_s,
|
44
|
+
valid_events: valid_events.join(", "))
|
45
|
+
end
|
46
|
+
end
|
47
|
+
@customizations.each do |event, command|
|
48
|
+
if event == "pre-import" && command.index(:id)
|
49
|
+
errors << I18n.t("vagrant_parallels.config.id_in_pre_import")
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
{ "Parallels Provider" => errors }
|
54
|
+
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
end
|
59
|
+
end
|
@@ -2,6 +2,7 @@ require 'log4r'
|
|
2
2
|
require 'json'
|
3
3
|
|
4
4
|
require 'vagrant/util/busy'
|
5
|
+
require "vagrant/util/network_ip"
|
5
6
|
require 'vagrant/util/platform'
|
6
7
|
require 'vagrant/util/retryable'
|
7
8
|
require 'vagrant/util/subprocess'
|
@@ -16,6 +17,7 @@ module VagrantPlugins
|
|
16
17
|
class PrlCtl
|
17
18
|
# Include this so we can use `Subprocess` more easily.
|
18
19
|
include Vagrant::Util::Retryable
|
20
|
+
include Vagrant::Util::NetworkIP
|
19
21
|
|
20
22
|
attr_reader :uuid
|
21
23
|
|
@@ -29,9 +31,112 @@ module VagrantPlugins
|
|
29
31
|
@uuid = uuid
|
30
32
|
|
31
33
|
# Set the path to prlctl
|
32
|
-
@
|
34
|
+
@prlctl_path = "prlctl"
|
35
|
+
@prlsrvctl_path = "prlsrvctl"
|
33
36
|
|
34
|
-
@logger.info("
|
37
|
+
@logger.info("CLI prlctl path: #{@prlctl_path}")
|
38
|
+
@logger.info("CLI prlsrvctl path: #{@prlsrvctl_path}")
|
39
|
+
end
|
40
|
+
|
41
|
+
def create_host_only_network(options)
|
42
|
+
# Create the interface
|
43
|
+
execute(:prlsrvctl, "net", "add", options[:name], "--type", "host-only")
|
44
|
+
|
45
|
+
# Configure it
|
46
|
+
args = ["--ip", "#{options[:adapter_ip]}/#{options[:netmask]}"]
|
47
|
+
if options[:dhcp]
|
48
|
+
args.concat(["--dhcp-ip", options[:dhcp][:ip],
|
49
|
+
"--ip-scope-start", options[:dhcp][:lower],
|
50
|
+
"--ip-scope-end", options[:dhcp][:upper]])
|
51
|
+
end
|
52
|
+
|
53
|
+
execute(:prlsrvctl, "net", "set", options[:name], *args)
|
54
|
+
|
55
|
+
# Determine interface to which it has been bound
|
56
|
+
net_info = json { execute(:prlsrvctl, 'net', 'info', options[:name], '--json', retryable: true) }
|
57
|
+
bound_to = net_info['Bound To']
|
58
|
+
|
59
|
+
# Return the details
|
60
|
+
return {
|
61
|
+
:name => options[:name],
|
62
|
+
:bound_to => bound_to,
|
63
|
+
:ip => options[:adapter_ip],
|
64
|
+
:netmask => options[:netmask],
|
65
|
+
:dhcp => options[:dhcp]
|
66
|
+
}
|
67
|
+
end
|
68
|
+
|
69
|
+
def delete_unused_host_only_networks
|
70
|
+
networks = read_virtual_networks()
|
71
|
+
|
72
|
+
# 'Shared'(vnic0) and 'Host-Only'(vnic1) are default in Parallels Desktop
|
73
|
+
# They should not be deleted anyway.
|
74
|
+
networks.keep_if do |net|
|
75
|
+
net['Type'] == "host-only" && /^vnic(\d+)$/.match(net['Bound To'])[1].to_i >= 2
|
76
|
+
end
|
77
|
+
|
78
|
+
read_all_info.each do |vm|
|
79
|
+
used_nets = vm.fetch('Hardware', {}).select { |name, _| name.start_with? 'net' }
|
80
|
+
used_nets.each_value do |net_params|
|
81
|
+
networks.delete_if { |net| net['Bound To'] == net_params.fetch('iface', nil)}
|
82
|
+
end
|
83
|
+
|
84
|
+
end
|
85
|
+
|
86
|
+
networks.each do |net|
|
87
|
+
# Delete the actual host only network interface.
|
88
|
+
execute(:prlsrvctl, "net", "del", net["Network ID"])
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
def enable_adapters(adapters)
|
93
|
+
# Get adapters which have already configured for this VM
|
94
|
+
# Such adapters will be just overridden
|
95
|
+
existing_adapters = read_settings.fetch('Hardware', {}).keys.select { |name| name.start_with? 'net' }
|
96
|
+
|
97
|
+
# Disable all previously existing adapters (except shared 'vnet0')
|
98
|
+
existing_adapters.each do |adapter|
|
99
|
+
if adapter != 'vnet0'
|
100
|
+
execute('set', @uuid, '--device-set', adapter, '--disable')
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
adapters.each do |adapter|
|
105
|
+
args = []
|
106
|
+
if existing_adapters.include? "net#{adapter[:adapter]}"
|
107
|
+
args.concat(["--device-set","net#{adapter[:adapter]}", "--enable"])
|
108
|
+
else
|
109
|
+
args.concat(["--device-add", "net"])
|
110
|
+
end
|
111
|
+
|
112
|
+
if adapter[:hostonly] or adapter[:bridge]
|
113
|
+
# Oddly enough, but there is a 'bridge' anyway.
|
114
|
+
# The only difference is the destination interface:
|
115
|
+
# - in host-only (private) network it will be bridged to the 'vnicX' device
|
116
|
+
# - in real bridge (public) network it will be bridged to the assigned device
|
117
|
+
args.concat(["--type", "bridged", "--iface", adapter[:bound_to]])
|
118
|
+
end
|
119
|
+
|
120
|
+
if adapter[:shared]
|
121
|
+
args.concat(["--type", "shared"])
|
122
|
+
end
|
123
|
+
|
124
|
+
if adapter[:dhcp]
|
125
|
+
args.concat(["--dhcp", "yes"])
|
126
|
+
elsif adapter[:ip]
|
127
|
+
args.concat(["--ipdel", "all", "--ipadd", "#{adapter[:ip]}/#{adapter[:netmask]}"])
|
128
|
+
end
|
129
|
+
|
130
|
+
if adapter[:mac_address]
|
131
|
+
args.concat(["--mac", adapter[:mac_address]])
|
132
|
+
end
|
133
|
+
|
134
|
+
if adapter[:nic_type]
|
135
|
+
args.concat(["--adapter-type", adapter[:nic_type].to_s])
|
136
|
+
end
|
137
|
+
|
138
|
+
execute("set", @uuid, *args)
|
139
|
+
end
|
35
140
|
end
|
36
141
|
|
37
142
|
# Returns the current state of this VM.
|
@@ -69,10 +174,80 @@ module VagrantPlugins
|
|
69
174
|
list
|
70
175
|
end
|
71
176
|
|
177
|
+
def read_bridged_interfaces
|
178
|
+
net_list = read_virtual_networks()
|
179
|
+
|
180
|
+
# Skip 'vnicXXX' and 'Default' interfaces
|
181
|
+
net_list.delete_if do |net|
|
182
|
+
net['Type'] != "bridged" or net['Bound To'] =~ /^(vnic(.+?)|Default)$/
|
183
|
+
end
|
184
|
+
|
185
|
+
bridged_ifaces = []
|
186
|
+
net_list.collect do |iface|
|
187
|
+
info = {}
|
188
|
+
ifconfig = raw('ifconfig', iface['Bound To']).stdout
|
189
|
+
# Assign default values
|
190
|
+
info[:name] = iface['Network ID']
|
191
|
+
info[:ip] = "0.0.0.0"
|
192
|
+
info[:netmask] = "0.0.0.0"
|
193
|
+
info[:status] = "Down"
|
194
|
+
|
195
|
+
ifconfig.split("\n").each do |line|
|
196
|
+
if line =~ /(?<=inet\s)(\S*)/
|
197
|
+
info[:ip] = $1.to_s
|
198
|
+
end
|
199
|
+
if line =~ /(?<=netmask\s)(\S*)/
|
200
|
+
# Netmask will be converted from hex to dec:
|
201
|
+
# '0xffffff00' -> '255.255.255.0'
|
202
|
+
info[:netmask] = $1.hex.to_s(16).scan(/../).each.map{|octet| octet.hex}.join(".")
|
203
|
+
elsif line =~ /\W(UP)\W/
|
204
|
+
info[:status] = "Up"
|
205
|
+
end
|
206
|
+
end
|
207
|
+
bridged_ifaces << info
|
208
|
+
end
|
209
|
+
bridged_ifaces
|
210
|
+
end
|
211
|
+
|
212
|
+
def read_host_only_interfaces
|
213
|
+
net_list = read_virtual_networks()
|
214
|
+
net_list.keep_if { |net| net['Type'] == "host-only" }
|
215
|
+
|
216
|
+
hostonly_ifaces = []
|
217
|
+
net_list.collect do |iface|
|
218
|
+
info = {}
|
219
|
+
net_info = json { execute(:prlsrvctl, 'net', 'info', iface['Network ID'], '--json') }
|
220
|
+
# Really we need to work with bounded virtual interface
|
221
|
+
info[:name] = net_info['Network ID']
|
222
|
+
info[:bound_to] = net_info['Bound To']
|
223
|
+
info[:ip] = net_info['Parallels adapter']['IP address']
|
224
|
+
info[:netmask] = net_info['Parallels adapter']['Subnet mask']
|
225
|
+
# Such interfaces are always in 'Up'
|
226
|
+
info[:status] = "Up"
|
227
|
+
|
228
|
+
# There may be a fake DHCPv4 parameters
|
229
|
+
# We can trust them only if adapter IP and DHCP IP are in the same subnet
|
230
|
+
dhcp_ip = net_info['DHCPv4 server']['Server address']
|
231
|
+
if network_address(info[:ip], info[:netmask]) == network_address(dhcp_ip, info[:netmask])
|
232
|
+
info[:dhcp] = {
|
233
|
+
:ip => dhcp_ip,
|
234
|
+
:lower => net_info['DHCPv4 server']['IP scope start address'],
|
235
|
+
:upper => net_info['DHCPv4 server']['IP scope end address']
|
236
|
+
}
|
237
|
+
end
|
238
|
+
hostonly_ifaces << info
|
239
|
+
end
|
240
|
+
hostonly_ifaces
|
241
|
+
end
|
242
|
+
|
72
243
|
def read_mac_address
|
73
244
|
read_settings.fetch('Hardware', {}).fetch('net0', {}).fetch('mac', nil)
|
74
245
|
end
|
75
246
|
|
247
|
+
def read_virtual_networks
|
248
|
+
json { execute(:prlsrvctl, 'net', 'list', '--json', retryable: true) }
|
249
|
+
end
|
250
|
+
|
76
251
|
# Verifies that the driver is ready to accept work.
|
77
252
|
#
|
78
253
|
# This should raise a VagrantError if things are not ready.
|
@@ -93,26 +268,21 @@ module VagrantPlugins
|
|
93
268
|
end
|
94
269
|
|
95
270
|
def import(template_uuid, vm_name)
|
96
|
-
last = 0
|
97
271
|
execute("clone", template_uuid, '--name', vm_name) do |type, data|
|
98
272
|
lines = data.split("\r")
|
99
273
|
# The progress of the import will be in the last line. Do a greedy
|
100
274
|
# regular expression to find what we're looking for.
|
101
|
-
if lines.last =~ /.+?(\d{,3})
|
102
|
-
|
103
|
-
if current > last
|
104
|
-
last = current
|
105
|
-
yield current if block_given?
|
106
|
-
end
|
275
|
+
if lines.last =~ /.+?(\d{,3}) ?%/
|
276
|
+
yield $1.to_i if block_given?
|
107
277
|
end
|
108
278
|
end
|
109
279
|
@uuid = read_settings(vm_name).fetch('ID', vm_name)
|
110
280
|
end
|
111
281
|
|
112
282
|
def delete_adapters
|
113
|
-
read_settings.fetch('Hardware').each do |
|
114
|
-
if
|
115
|
-
execute('set', @uuid, '--device-del',
|
283
|
+
read_settings.fetch('Hardware', {}).each do |adapter, params|
|
284
|
+
if adapter.start_with?('net') and !params.fetch("enabled", true)
|
285
|
+
execute('set', @uuid, '--device-del', adapter)
|
116
286
|
end
|
117
287
|
end
|
118
288
|
end
|
@@ -140,17 +310,12 @@ module VagrantPlugins
|
|
140
310
|
end
|
141
311
|
|
142
312
|
def export(path, vm_name)
|
143
|
-
last = 0
|
144
313
|
execute("clone", @uuid, "--name", vm_name, "--template", "--dst", path.to_s) do |type, data|
|
145
314
|
lines = data.split("\r")
|
146
315
|
# The progress of the import will be in the last line. Do a greedy
|
147
316
|
# regular expression to find what we're looking for.
|
148
|
-
if lines.last =~ /.+?(\d{,3})
|
149
|
-
|
150
|
-
if current > last
|
151
|
-
last = current
|
152
|
-
yield current if block_given?
|
153
|
-
end
|
317
|
+
if lines.last =~ /.+?(\d{,3}) ?%/
|
318
|
+
yield $1.to_i if block_given?
|
154
319
|
end
|
155
320
|
end
|
156
321
|
|
@@ -160,17 +325,12 @@ module VagrantPlugins
|
|
160
325
|
def compact(uuid=nil)
|
161
326
|
uuid ||= @uuid
|
162
327
|
path_to_hdd = read_settings(uuid).fetch("Hardware", {}).fetch("hdd0", {}).fetch("image", nil)
|
163
|
-
last = 0
|
164
328
|
raw('prl_disk_tool', 'compact', '--hdd', path_to_hdd) do |type, data|
|
165
329
|
lines = data.split("\r")
|
166
330
|
# The progress of the import will be in the last line. Do a greedy
|
167
331
|
# regular expression to find what we're looking for.
|
168
332
|
if lines.last =~ /.+?(\d{,3}) ?%/
|
169
|
-
|
170
|
-
if current > last
|
171
|
-
last = current
|
172
|
-
yield current if block_given?
|
173
|
-
end
|
333
|
+
yield $1.to_i if block_given?
|
174
334
|
end
|
175
335
|
end
|
176
336
|
end
|
@@ -222,6 +382,11 @@ module VagrantPlugins
|
|
222
382
|
end
|
223
383
|
end
|
224
384
|
|
385
|
+
# apply custom vm setting via set parameter
|
386
|
+
def set_vm_settings(command)
|
387
|
+
raw(@manager_path, *command)
|
388
|
+
end
|
389
|
+
|
225
390
|
private
|
226
391
|
|
227
392
|
# Parse the JSON from *all* VMs and templates. Then return an array of objects (without duplicates)
|
@@ -249,14 +414,51 @@ module VagrantPlugins
|
|
249
414
|
execute('exec', @uuid, *command)
|
250
415
|
end
|
251
416
|
|
417
|
+
def error_detection(command_response)
|
418
|
+
errored = false
|
419
|
+
# If the command was a failure, then raise an exception that is
|
420
|
+
# nicely handled by Vagrant.
|
421
|
+
if command_response.exit_code != 0
|
422
|
+
if @interrupted
|
423
|
+
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
424
|
+
elsif command_response.exit_code == 126
|
425
|
+
# This exit code happens if PrlCtl is on the PATH,
|
426
|
+
# but another executable it tries to execute is missing.
|
427
|
+
# This is usually indicative of a corrupted Parallels install.
|
428
|
+
raise VagrantPlugins::Parallels::Errors::ParallelsErrorNotFoundError
|
429
|
+
else
|
430
|
+
errored = true
|
431
|
+
end
|
432
|
+
elsif command_response.stderr =~ /failed to open \/dev\/prlctl/i
|
433
|
+
# This catches an error message that only shows when kernel
|
434
|
+
# drivers aren't properly installed.
|
435
|
+
@logger.error("Error message about unable to open prlctl")
|
436
|
+
raise VagrantPlugins::Parallels::Errors::ParallelsErrorKernelModuleNotLoaded
|
437
|
+
elsif command_response.stderr =~ /Unable to perform/i
|
438
|
+
@logger.info("VM not running for command to work.")
|
439
|
+
errored = true
|
440
|
+
elsif command_response.stderr =~ /Invalid usage/i
|
441
|
+
@logger.info("PrlCtl error text found, assuming error.")
|
442
|
+
errored = true
|
443
|
+
end
|
444
|
+
errored
|
445
|
+
end
|
446
|
+
|
252
447
|
# Execute the given subcommand for PrlCtl and return the output.
|
253
448
|
def execute(*command, &block)
|
449
|
+
# Get the utility to execute: 'prlctl' by default and 'prlsrvctl' if it set as a first argument in command
|
450
|
+
if command.first == :prlsrvctl
|
451
|
+
cli = @prlsrvctl_path
|
452
|
+
command.delete_at(0)
|
453
|
+
else
|
454
|
+
cli = @prlctl_path
|
455
|
+
end
|
456
|
+
|
254
457
|
# Get the options hash if it exists
|
255
458
|
opts = {}
|
256
459
|
opts = command.pop if command.last.is_a?(Hash)
|
257
460
|
|
258
|
-
tries = 0
|
259
|
-
tries = 3 if opts[:retryable]
|
461
|
+
tries = opts[:retryable] ? 3 : 0
|
260
462
|
|
261
463
|
# Variable to store our execution result
|
262
464
|
r = nil
|
@@ -266,37 +468,8 @@ module VagrantPlugins
|
|
266
468
|
|
267
469
|
retryable(on: VagrantPlugins::Parallels::Errors::ParallelsError, tries: tries, sleep: 1) do
|
268
470
|
# Execute the command
|
269
|
-
r = raw(
|
270
|
-
|
271
|
-
# If the command was a failure, then raise an exception that is
|
272
|
-
# nicely handled by Vagrant.
|
273
|
-
if r.exit_code != 0
|
274
|
-
if @interrupted
|
275
|
-
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
276
|
-
elsif r.exit_code == 126
|
277
|
-
# This exit code happens if PrlCtl is on the PATH,
|
278
|
-
# but another executable it tries to execute is missing.
|
279
|
-
# This is usually indicative of a corrupted Parallels install.
|
280
|
-
raise VagrantPlugins::Parallels::Errors::ParallelsErrorNotFoundError
|
281
|
-
else
|
282
|
-
errored = true
|
283
|
-
end
|
284
|
-
else
|
285
|
-
if r.stderr =~ /failed to open \/dev\/prlctl/i
|
286
|
-
# This catches an error message that only shows when kernel
|
287
|
-
# drivers aren't properly installed.
|
288
|
-
@logger.error("Error message about unable to open prlctl")
|
289
|
-
raise VagrantPlugins::Parallels::Errors::ParallelsErrorKernelModuleNotLoaded
|
290
|
-
end
|
291
|
-
|
292
|
-
if r.stderr =~ /Unable to perform/i
|
293
|
-
@logger.info("VM not running for command to work.")
|
294
|
-
errored = true
|
295
|
-
elsif r.stderr =~ /Invalid usage/i
|
296
|
-
@logger.info("PrlCtl error text found, assuming error.")
|
297
|
-
errored = true
|
298
|
-
end
|
299
|
-
end
|
471
|
+
r = raw(cli, *command, &block)
|
472
|
+
errored = error_detection(r)
|
300
473
|
end
|
301
474
|
|
302
475
|
# If there was an error running PrlCtl, show the error and the
|