vagrant-parallels 0.0.1.dev
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.
- checksums.yaml +15 -0
- data/.gitignore +23 -0
- data/.ruby-gemset +1 -0
- data/Gemfile +13 -0
- data/LICENSE.txt +22 -0
- data/README.md +59 -0
- data/Rakefile +23 -0
- data/lib/vagrant-parallels.rb +18 -0
- data/lib/vagrant-parallels/action.rb +282 -0
- data/lib/vagrant-parallels/action/boot.rb +21 -0
- data/lib/vagrant-parallels/action/check_accessible.rb +23 -0
- data/lib/vagrant-parallels/action/check_created.rb +21 -0
- data/lib/vagrant-parallels/action/check_guest_tools.rb +32 -0
- data/lib/vagrant-parallels/action/check_parallels.rb +22 -0
- data/lib/vagrant-parallels/action/check_running.rb +21 -0
- data/lib/vagrant-parallels/action/clear_network_interfaces.rb +19 -0
- data/lib/vagrant-parallels/action/clear_shared_folders.rb +17 -0
- data/lib/vagrant-parallels/action/created.rb +20 -0
- data/lib/vagrant-parallels/action/destroy.rb +19 -0
- data/lib/vagrant-parallels/action/forced_halt.rb +20 -0
- data/lib/vagrant-parallels/action/import.rb +63 -0
- data/lib/vagrant-parallels/action/is_paused.rb +21 -0
- data/lib/vagrant-parallels/action/is_running.rb +20 -0
- data/lib/vagrant-parallels/action/is_saved.rb +21 -0
- data/lib/vagrant-parallels/action/match_mac_address.rb +21 -0
- data/lib/vagrant-parallels/action/message_already_running.rb +16 -0
- data/lib/vagrant-parallels/action/message_not_created.rb +16 -0
- data/lib/vagrant-parallels/action/message_not_running.rb +16 -0
- data/lib/vagrant-parallels/action/message_will_not_destroy.rb +17 -0
- data/lib/vagrant-parallels/action/prepare_nfs_settings.rb +64 -0
- data/lib/vagrant-parallels/action/prune_nfs_exports.rb +20 -0
- data/lib/vagrant-parallels/action/register_template.rb +22 -0
- data/lib/vagrant-parallels/action/resume.rb +25 -0
- data/lib/vagrant-parallels/action/share_folders.rb +121 -0
- data/lib/vagrant-parallels/action/suspend.rb +20 -0
- data/lib/vagrant-parallels/action/unregister_template.rb +24 -0
- data/lib/vagrant-parallels/driver/prl_ctl.rb +281 -0
- data/lib/vagrant-parallels/errors.rb +23 -0
- data/lib/vagrant-parallels/plugin.rb +79 -0
- data/lib/vagrant-parallels/provider.rb +68 -0
- data/lib/vagrant-parallels/version.rb +5 -0
- data/locales/en.yml +1137 -0
- data/spec/vagrant-parallels/setup_spec.rb +7 -0
- data/vagrant-parallels.gemspec +59 -0
- metadata +158 -0
@@ -0,0 +1,16 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class MessageNotRunning
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
env[:ui].info I18n.t("vagrant.commands.common.vm_not_running")
|
11
|
+
@app.call(env)
|
12
|
+
end
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class MessageWillNotDestroy
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
env[:ui].info I18n.t("vagrant.commands.destroy.will_not_destroy",
|
11
|
+
:name => env[:machine].name)
|
12
|
+
@app.call(env)
|
13
|
+
end
|
14
|
+
end
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,64 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class PrepareNFSSettings
|
5
|
+
def initialize(app,env)
|
6
|
+
@app = app
|
7
|
+
@logger = Log4r::Logger.new("vagrant::action::vm::nfs")
|
8
|
+
end
|
9
|
+
|
10
|
+
def call(env)
|
11
|
+
@app.call(env)
|
12
|
+
|
13
|
+
using_nfs = false
|
14
|
+
env[:machine].config.vm.synced_folders.each do |id, opts|
|
15
|
+
if opts[:nfs]
|
16
|
+
using_nfs = true
|
17
|
+
break
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
if using_nfs
|
22
|
+
@logger.info("Using NFS, preparing NFS settings by reading host IP and machine IP")
|
23
|
+
env[:nfs_host_ip] = read_host_ip(env[:machine])
|
24
|
+
env[:nfs_machine_ip] = read_machine_ip(env[:machine])
|
25
|
+
|
26
|
+
raise Vagrant::Errors::NFSNoHostonlyNetwork if !env[:nfs_machine_ip]
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
# Returns the IP address of the first host only network adapter
|
31
|
+
#
|
32
|
+
# @param [Machine] machine
|
33
|
+
# @return [String]
|
34
|
+
def read_host_ip(machine)
|
35
|
+
machine.provider.driver.read_network_interfaces.each do |adapter, opts|
|
36
|
+
if opts[:type] == :hostonly
|
37
|
+
machine.provider.driver.read_host_only_interfaces.each do |interface|
|
38
|
+
if interface[:name] == opts[:hostonly]
|
39
|
+
return interface[:ip]
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
45
|
+
nil
|
46
|
+
end
|
47
|
+
|
48
|
+
# Returns the IP address of the guest by looking at the first
|
49
|
+
# enabled host only network.
|
50
|
+
#
|
51
|
+
# @return [String]
|
52
|
+
def read_machine_ip(machine)
|
53
|
+
machine.config.vm.networks.each do |type, options|
|
54
|
+
if type == :private_network && options[:ip].is_a?(String)
|
55
|
+
return options[:ip]
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
nil
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class PruneNFSExports
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env[:host]
|
11
|
+
vms = env[:machine].provider.driver.read_vms
|
12
|
+
env[:host].nfs_prune(vms.values)
|
13
|
+
end
|
14
|
+
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,22 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class RegisterTemplate
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
pvm_file = Pathname.glob(env[:machine].box.directory.join('*.pvm')).first
|
11
|
+
|
12
|
+
unless env[:machine].provider.driver.registered?(pvm_file.basename.to_s[0...-4])
|
13
|
+
env[:machine].provider.driver.register(pvm_file.to_s)
|
14
|
+
end
|
15
|
+
# Call the next if we have one (but we shouldn't, since this
|
16
|
+
# middleware is built to run with the Call-type middlewares)
|
17
|
+
@app.call(env)
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class Resume
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
current_state = env[:machine].provider.state.id
|
11
|
+
|
12
|
+
if current_state == :suspended
|
13
|
+
env[:ui].info I18n.t("vagrant.actions.vm.resume.unpausing")
|
14
|
+
env[:machine].provider.driver.resume
|
15
|
+
elsif current_state == :stopped
|
16
|
+
env[:ui].info I18n.t("vagrant.actions.vm.resume.resuming")
|
17
|
+
env[:action_runner].run(Boot, env)
|
18
|
+
end
|
19
|
+
|
20
|
+
@app.call(env)
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
25
|
+
end
|
@@ -0,0 +1,121 @@
|
|
1
|
+
require "pathname"
|
2
|
+
|
3
|
+
require "log4r"
|
4
|
+
|
5
|
+
require "vagrant/util/platform"
|
6
|
+
require "vagrant/util/scoped_hash_override"
|
7
|
+
|
8
|
+
module VagrantPlugins
|
9
|
+
module Parallels
|
10
|
+
module Action
|
11
|
+
class ShareFolders
|
12
|
+
include Vagrant::Util::ScopedHashOverride
|
13
|
+
|
14
|
+
def initialize(app, env)
|
15
|
+
@logger = Log4r::Logger.new("vagrant::action::vm::share_folders")
|
16
|
+
@app = app
|
17
|
+
end
|
18
|
+
|
19
|
+
def call(env)
|
20
|
+
@env = env
|
21
|
+
|
22
|
+
prepare_folders
|
23
|
+
create_metadata
|
24
|
+
|
25
|
+
@app.call(env)
|
26
|
+
|
27
|
+
mount_shared_folders
|
28
|
+
end
|
29
|
+
|
30
|
+
# This method returns an actual list of VirtualBox shared
|
31
|
+
# folders to create and their proper path.
|
32
|
+
def shared_folders
|
33
|
+
{}.tap do |result|
|
34
|
+
@env[:machine].config.vm.synced_folders.each do |id, data|
|
35
|
+
data = scoped_hash_override(data, :parallels)
|
36
|
+
|
37
|
+
# Ignore NFS shared folders
|
38
|
+
next if data[:nfs]
|
39
|
+
|
40
|
+
# Ignore disabled shared folders
|
41
|
+
next if data[:disabled]
|
42
|
+
|
43
|
+
# This to prevent overwriting the actual shared folders data
|
44
|
+
id = Pathname.new(id).to_s.split('/').drop_while{|i| i.empty?}.join('_')
|
45
|
+
result[id] = data.dup
|
46
|
+
end
|
47
|
+
end
|
48
|
+
end
|
49
|
+
|
50
|
+
# Prepares the shared folders by verifying they exist and creating them
|
51
|
+
# if they don't.
|
52
|
+
def prepare_folders
|
53
|
+
shared_folders.each do |id, options|
|
54
|
+
hostpath = Pathname.new(options[:hostpath]).expand_path(@env[:root_path])
|
55
|
+
|
56
|
+
if !hostpath.directory? && options[:create]
|
57
|
+
# Host path doesn't exist, so let's create it.
|
58
|
+
@logger.debug("Host path doesn't exist, creating: #{hostpath}")
|
59
|
+
|
60
|
+
begin
|
61
|
+
hostpath.mkpath
|
62
|
+
rescue Errno::EACCES
|
63
|
+
raise Vagrant::Errors::SharedFolderCreateFailed,
|
64
|
+
:path => hostpath.to_s
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
def create_metadata
|
71
|
+
@env[:ui].info I18n.t("vagrant.actions.vm.share_folders.creating")
|
72
|
+
|
73
|
+
folders = []
|
74
|
+
shared_folders.each do |id, data|
|
75
|
+
hostpath = File.expand_path(data[:hostpath], @env[:root_path])
|
76
|
+
hostpath = Vagrant::Util::Platform.cygwin_windows_path(hostpath)
|
77
|
+
|
78
|
+
folders << {
|
79
|
+
:name => id,
|
80
|
+
:hostpath => hostpath,
|
81
|
+
:transient => data[:transient]
|
82
|
+
}
|
83
|
+
end
|
84
|
+
|
85
|
+
@env[:machine].provider.driver.share_folders(folders)
|
86
|
+
end
|
87
|
+
|
88
|
+
# TODO: Fix this, doesn't execute at the correct time
|
89
|
+
def mount_shared_folders
|
90
|
+
@env[:ui].info I18n.t("vagrant.actions.vm.share_folders.mounting")
|
91
|
+
|
92
|
+
# short guestpaths first, so we don't step on ourselves
|
93
|
+
folders = shared_folders.sort_by do |id, data|
|
94
|
+
if data[:guestpath]
|
95
|
+
data[:guestpath].length
|
96
|
+
else
|
97
|
+
# A long enough path to just do this at the end.
|
98
|
+
10000
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Go through each folder and mount
|
103
|
+
folders.each do |id, data|
|
104
|
+
if data[:guestpath]
|
105
|
+
# Guest path specified, so mount the folder to specified point
|
106
|
+
@env[:ui].info(I18n.t("vagrant.actions.vm.share_folders.mounting_entry",
|
107
|
+
:guest_path => data[:guestpath]))
|
108
|
+
|
109
|
+
# Symlink to mounted folder to guest path
|
110
|
+
@env[:machine].provider.driver.symlink(id, data[:guestpath])
|
111
|
+
else
|
112
|
+
# If no guest path is specified, then automounting is disabled
|
113
|
+
@env[:ui].info(I18n.t("vagrant.actions.vm.share_folders.nomount_entry",
|
114
|
+
:host_path => data[:hostpath]))
|
115
|
+
end
|
116
|
+
end
|
117
|
+
end
|
118
|
+
end
|
119
|
+
end
|
120
|
+
end
|
121
|
+
end
|
@@ -0,0 +1,20 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class Suspend
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
if env[:machine].provider.state.id == :running
|
11
|
+
env[:ui].info I18n.t("vagrant.actions.vm.suspend.suspending")
|
12
|
+
env[:machine].provider.driver.suspend
|
13
|
+
end
|
14
|
+
|
15
|
+
@app.call(env)
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
19
|
+
end
|
20
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Parallels
|
3
|
+
module Action
|
4
|
+
class UnregisterTemplate
|
5
|
+
def initialize(app, env)
|
6
|
+
@app = app
|
7
|
+
end
|
8
|
+
|
9
|
+
def call(env)
|
10
|
+
vm_name = Pathname.glob(
|
11
|
+
env[:machine].box.directory.join('*.pvm')
|
12
|
+
).first.basename.to_s[0...-4]
|
13
|
+
|
14
|
+
if env[:machine].provider.driver.registered?(vm_name)
|
15
|
+
env[:machine].provider.driver.unregister(vm_name)
|
16
|
+
end
|
17
|
+
# Call the next if we have one (but we shouldn't, since this
|
18
|
+
# middleware is built to run with the Call-type middlewares)
|
19
|
+
@app.call(env)
|
20
|
+
end
|
21
|
+
end
|
22
|
+
end
|
23
|
+
end
|
24
|
+
end
|
@@ -0,0 +1,281 @@
|
|
1
|
+
require 'log4r'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
require 'vagrant/util/busy'
|
5
|
+
require 'vagrant/util/platform'
|
6
|
+
require 'vagrant/util/retryable'
|
7
|
+
require 'vagrant/util/subprocess'
|
8
|
+
|
9
|
+
module VagrantPlugins
|
10
|
+
module Parallels
|
11
|
+
module Driver
|
12
|
+
# Base class for all Parallels drivers.
|
13
|
+
#
|
14
|
+
# This class provides useful tools for things such as executing
|
15
|
+
# PrlCtl and handling SIGINTs and so on.
|
16
|
+
class PrlCtl
|
17
|
+
# Include this so we can use `Subprocess` more easily.
|
18
|
+
include Vagrant::Util::Retryable
|
19
|
+
|
20
|
+
attr_reader :uuid
|
21
|
+
|
22
|
+
def initialize(uuid)
|
23
|
+
@logger = Log4r::Logger.new("vagrant::provider::parallels::prlctl")
|
24
|
+
|
25
|
+
# This flag is used to keep track of interrupted state (SIGINT)
|
26
|
+
@interrupted = false
|
27
|
+
|
28
|
+
# Store machine id
|
29
|
+
@uuid = uuid
|
30
|
+
|
31
|
+
# Set the path to prlctl
|
32
|
+
@manager_path = "prlctl"
|
33
|
+
|
34
|
+
@logger.info("Parallels path: #{@manager_path}")
|
35
|
+
end
|
36
|
+
|
37
|
+
# Returns the current state of this VM.
|
38
|
+
#
|
39
|
+
# @return [Symbol]
|
40
|
+
def read_state
|
41
|
+
read_settings(@uuid).fetch('State', 'inaccessible').to_sym
|
42
|
+
end
|
43
|
+
|
44
|
+
# Returns a hash of all UUIDs of virtual machines currently
|
45
|
+
# known by Parallels. Hash keys is VM names
|
46
|
+
#
|
47
|
+
# @return [Hash]
|
48
|
+
def read_vms
|
49
|
+
list = {}
|
50
|
+
json({}) { execute('list', '--all', '--json', retryable: true) }.each do |item|
|
51
|
+
list[item.fetch('name')] = item.fetch('uuid')
|
52
|
+
end
|
53
|
+
|
54
|
+
list
|
55
|
+
end
|
56
|
+
|
57
|
+
# Returns a hash of all UUIDs of VM templates currently
|
58
|
+
# known by Parallels. Hash keys is template names
|
59
|
+
#
|
60
|
+
# @return [Hash]
|
61
|
+
def read_templates
|
62
|
+
list = {}
|
63
|
+
json({}) { execute('list', '--template', '--json', retryable: true) }.each do |item|
|
64
|
+
list[item.fetch('name')] = item.fetch('uuid')
|
65
|
+
end
|
66
|
+
|
67
|
+
list
|
68
|
+
end
|
69
|
+
|
70
|
+
def read_mac_address
|
71
|
+
read_settings.fetch('Hardware', {}).fetch('net0', {}).fetch('mac', nil)
|
72
|
+
end
|
73
|
+
|
74
|
+
# Verifies that the driver is ready to accept work.
|
75
|
+
#
|
76
|
+
# This should raise a VagrantError if things are not ready.
|
77
|
+
def verify!
|
78
|
+
execute('--version')
|
79
|
+
end
|
80
|
+
|
81
|
+
def clear_shared_folders
|
82
|
+
read_settings.fetch("Host Shared Folders", {}).keys.drop(1).each do |folder|
|
83
|
+
execute("set", @uuid, "--shf-host-del", folder)
|
84
|
+
end
|
85
|
+
end
|
86
|
+
|
87
|
+
def import(template_name, vm_name)
|
88
|
+
last = 0
|
89
|
+
execute("clone", template_name, '--name', vm_name) do |type, data|
|
90
|
+
lines = data.split("\r")
|
91
|
+
# The progress of the import will be in the last line. Do a greedy
|
92
|
+
# regular expression to find what we're looking for.
|
93
|
+
if lines.last =~ /.+?(\d{,3})%/
|
94
|
+
current = $1.to_i
|
95
|
+
if current > last
|
96
|
+
last = current
|
97
|
+
yield current if block_given?
|
98
|
+
end
|
99
|
+
end
|
100
|
+
end
|
101
|
+
@uuid = read_settings(vm_name).fetch('ID', vm_name)
|
102
|
+
end
|
103
|
+
|
104
|
+
def delete_adapters
|
105
|
+
read_settings.fetch('Hardware').each do |k, _|
|
106
|
+
if k != 'net0' and k.start_with? 'net'
|
107
|
+
execute('set', @uuid, '--device-del', k)
|
108
|
+
end
|
109
|
+
end
|
110
|
+
end
|
111
|
+
|
112
|
+
def resume
|
113
|
+
execute('resume', @uuid)
|
114
|
+
end
|
115
|
+
|
116
|
+
def suspend
|
117
|
+
execute('suspend', @uuid)
|
118
|
+
end
|
119
|
+
|
120
|
+
def start
|
121
|
+
execute('start', @uuid)
|
122
|
+
end
|
123
|
+
|
124
|
+
def halt(force=false)
|
125
|
+
args = ['stop', @uuid]
|
126
|
+
args << '--kill' if force
|
127
|
+
execute(*args)
|
128
|
+
end
|
129
|
+
|
130
|
+
def delete
|
131
|
+
execute('delete', @uuid)
|
132
|
+
end
|
133
|
+
|
134
|
+
def register(pvm_file)
|
135
|
+
execute("register", pvm_file)
|
136
|
+
end
|
137
|
+
|
138
|
+
def unregister(uuid)
|
139
|
+
execute("unregister", uuid)
|
140
|
+
end
|
141
|
+
|
142
|
+
def registered?(name)
|
143
|
+
read_templates.has_key?(name) || read_vms.has_key?(name)
|
144
|
+
end
|
145
|
+
|
146
|
+
def set_mac_address(mac)
|
147
|
+
execute('set', @uuid, '--device-set', 'net0', '--type', 'shared', '--mac', mac)
|
148
|
+
end
|
149
|
+
|
150
|
+
def ssh_port(expected_port)
|
151
|
+
22
|
152
|
+
end
|
153
|
+
|
154
|
+
def read_guest_tools_version
|
155
|
+
read_settings.fetch('GuestTools', {}).fetch('version', nil)
|
156
|
+
end
|
157
|
+
|
158
|
+
def share_folders(folders)
|
159
|
+
folders.each do |folder|
|
160
|
+
# Add the shared folder
|
161
|
+
execute('set', @uuid, '--shf-host-add', folder[:name], '--path', folder[:hostpath])
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|
165
|
+
def symlink(id, path)
|
166
|
+
guest_execute('ln', '-sf', "/media/psf/#{id}", path)
|
167
|
+
end
|
168
|
+
|
169
|
+
def execute_command(command)
|
170
|
+
raw(*command)
|
171
|
+
end
|
172
|
+
|
173
|
+
def ready?
|
174
|
+
!!guest_execute('uname') rescue false
|
175
|
+
end
|
176
|
+
|
177
|
+
def ip
|
178
|
+
mac_addr = read_mac_address.downcase
|
179
|
+
File.foreach("/Library/Preferences/Parallels/parallels_dhcp_leases") do |line|
|
180
|
+
if line.include? mac_addr
|
181
|
+
ip = line[/^(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})/]
|
182
|
+
return ip
|
183
|
+
end
|
184
|
+
end
|
185
|
+
end
|
186
|
+
|
187
|
+
private
|
188
|
+
|
189
|
+
def read_settings(uuid=nil)
|
190
|
+
uuid ||= @uuid
|
191
|
+
json({}) { execute('list', uuid, '--info', '--json', retryable: true).gsub(/^(INFO)?\[/, '').gsub(/\]$/, '') }
|
192
|
+
end
|
193
|
+
|
194
|
+
def json(default=nil)
|
195
|
+
data = yield
|
196
|
+
JSON.parse(data) rescue default
|
197
|
+
end
|
198
|
+
|
199
|
+
def guest_execute(*command)
|
200
|
+
execute('exec', @uuid, *command)
|
201
|
+
end
|
202
|
+
|
203
|
+
# Execute the given subcommand for PrlCtl and return the output.
|
204
|
+
def execute(*command, &block)
|
205
|
+
# Get the options hash if it exists
|
206
|
+
opts = {}
|
207
|
+
opts = command.pop if command.last.is_a?(Hash)
|
208
|
+
|
209
|
+
tries = 0
|
210
|
+
tries = 3 if opts[:retryable]
|
211
|
+
|
212
|
+
# Variable to store our execution result
|
213
|
+
r = nil
|
214
|
+
|
215
|
+
# If there is an error with PrlCtl, this gets set to true
|
216
|
+
errored = false
|
217
|
+
|
218
|
+
retryable(on: VagrantPlugins::Parallels::Errors::ParallelsError, tries: tries, sleep: 1) do
|
219
|
+
# Execute the command
|
220
|
+
r = raw(@manager_path, *command, &block)
|
221
|
+
|
222
|
+
# If the command was a failure, then raise an exception that is
|
223
|
+
# nicely handled by Vagrant.
|
224
|
+
if r.exit_code != 0
|
225
|
+
if @interrupted
|
226
|
+
@logger.info("Exit code != 0, but interrupted. Ignoring.")
|
227
|
+
elsif r.exit_code == 126
|
228
|
+
# This exit code happens if PrlCtl is on the PATH,
|
229
|
+
# but another executable it tries to execute is missing.
|
230
|
+
# This is usually indicative of a corrupted Parallels install.
|
231
|
+
raise VagrantPlugins::Parallels::Errors::ParallelsErrorNotFoundError
|
232
|
+
else
|
233
|
+
errored = true
|
234
|
+
end
|
235
|
+
else
|
236
|
+
if r.stderr =~ /failed to open \/dev\/prlctl/i
|
237
|
+
# This catches an error message that only shows when kernel
|
238
|
+
# drivers aren't properly installed.
|
239
|
+
@logger.error("Error message about unable to open prlctl")
|
240
|
+
raise VagrantPlugins::Parallels::Errors::ParallelsErrorKernelModuleNotLoaded
|
241
|
+
end
|
242
|
+
|
243
|
+
if r.stderr =~ /Unable to perform/i
|
244
|
+
@logger.info("VM not running for command to work.")
|
245
|
+
errored = true
|
246
|
+
elsif r.stderr =~ /Invalid usage/i
|
247
|
+
@logger.info("PrlCtl error text found, assuming error.")
|
248
|
+
errored = true
|
249
|
+
end
|
250
|
+
end
|
251
|
+
end
|
252
|
+
|
253
|
+
# If there was an error running PrlCtl, show the error and the
|
254
|
+
# output.
|
255
|
+
if errored
|
256
|
+
raise VagrantPlugins::Parallels::Errors::ParallelsError,
|
257
|
+
command: command.inspect,
|
258
|
+
stderr: r.stderr
|
259
|
+
end
|
260
|
+
|
261
|
+
r.stdout
|
262
|
+
end
|
263
|
+
|
264
|
+
# Executes a command and returns the raw result object.
|
265
|
+
def raw(cli, *command, &block)
|
266
|
+
int_callback = lambda do
|
267
|
+
@interrupted = true
|
268
|
+
@logger.info("Interrupted.")
|
269
|
+
end
|
270
|
+
|
271
|
+
# Append in the options for subprocess
|
272
|
+
command << { notify: [:stdout, :stderr] }
|
273
|
+
|
274
|
+
Vagrant::Util::Busy.busy(int_callback) do
|
275
|
+
Vagrant::Util::Subprocess.execute(cli, *command, &block)
|
276
|
+
end
|
277
|
+
end
|
278
|
+
end
|
279
|
+
end
|
280
|
+
end
|
281
|
+
end
|