Skip to content

Commit 3ecb744

Browse files
committed
driver: import execute function of virtualbox
1 parent 4b0cd8f commit 3ecb744

File tree

3 files changed

+147
-2
lines changed

3 files changed

+147
-2
lines changed

lib/vagrant_utm/driver/base.rb

Lines changed: 135 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,10 @@
11
# frozen_string_literal: true
22

3+
require "log4r"
34
require "pathname"
45
require "vagrant/util/busy"
56
require "vagrant/util/subprocess"
7+
require "vagrant/util/which"
68
require_relative "../model/list_result"
79

810
module VagrantPlugins
@@ -15,10 +17,19 @@ class Base
1517
include Vagrant::Util::Retryable
1618

1719
def initialize
20+
@logger = Log4r::Logger.new("vagrant::provider::utm::base")
21+
1822
# This flag is used to keep track of interrupted state (SIGINT)
1923
@interrupted = false
2024
# The path to the scripts directory.
2125
@script_path = Pathname.new(File.expand_path("../scripts", __dir__))
26+
27+
# Set 'utmctl' path
28+
@utmctl_path = Vagrant::Util::Which.which("utmctl")
29+
30+
# if not found, fall back to /usr/local/bin/utmctl
31+
@utmctl_path ||= "/Applications/UTM.app/Contents/MacOS/utmctl"
32+
@logger.info("utmctl path: #{@utmctl_path}")
2233
end
2334

2435
# Check if the VM with the given UUID (Name) exists.
@@ -94,6 +105,130 @@ def execute(*cmd, &block)
94105
# Return the outputs of the command
95106
"#{result.stdout} #{result.stderr}"
96107
end
108+
109+
# Execute the given subcommand for utmctl and return the output.
110+
# Heavily inspired from https://github.com/hashicorp/vagrant/blob/main/plugins/providers/virtualbox/driver/base.rb.
111+
# @param [String] subcommand The subcommand to execute.
112+
# @return [String] The output of the command.
113+
def utmctl_execute(*command, &block)
114+
# Get the options hash if it exists
115+
opts = {}
116+
opts = command.pop if command.last.is_a?(Hash)
117+
118+
tries = 0
119+
tries = 3 if opts[:retryable]
120+
121+
# Variable to store our execution result
122+
r = nil
123+
124+
retryable(on: Errors::UtmctlError, tries: tries, sleep: 1) do
125+
# if there is an error with utmctl, this gets set to true
126+
errored = false
127+
128+
# Execute the command
129+
r = raw(*command, &block)
130+
131+
# If the command was a failure, then raise an exception that is
132+
# nicely handled by Vagrant.
133+
if r.exit_code != 0
134+
if @interrupted
135+
@logger.info("Exit code != 0, but interrupted. Ignoring.")
136+
elsif r.exit_code == 126
137+
# To be consistent with VBoxManage
138+
raise Errors::UtmctlNotFoundError
139+
else
140+
errored = true
141+
end
142+
elsif r.stdout.include?("Error:")
143+
# if utmctl fails but doesn't return a non-zero exit code
144+
# Handle that here
145+
errored = true
146+
end
147+
148+
# If there was an error running utmctl, show the error and the output
149+
return unless errored
150+
151+
raise Errors::UtmctlError,
152+
command: command.inspect,
153+
stderr: r.stderr,
154+
stdout: r.stdout
155+
end
156+
157+
# Return the output, making sure to replace any Windows-style
158+
# newlines with Unix-style.
159+
r.stdout.gsub("\r\n", "\n")
160+
end
161+
162+
# Executes a command and returns the raw result object.
163+
def raw(*command, &block)
164+
int_callback = lambda do
165+
@interrupted = true
166+
167+
# We have to execute this in a thread due to trap contexts
168+
# and locks.
169+
Thread.new { @logger.info("Interrupted.") }.join
170+
end
171+
172+
# Append in the options for subprocess
173+
# NOTE: We include the LANG env var set to C to prevent command output
174+
# from being localized
175+
command << { notify: %i[stdout stderr], env: env_lang }
176+
177+
Vagrant::Util::Busy.busy(int_callback) do
178+
Vagrant::Util::Subprocess.execute(@utmctl_path, *command, &block)
179+
end
180+
rescue Vagrant::Util::Subprocess::LaunchError => e
181+
raise Vagrant::Errors::UtmctlLaunchError,
182+
message: e.to_s
183+
end
184+
185+
private
186+
187+
# List of LANG values to attempt to use
188+
LANG_VARIATIONS = %w[C.UTF-8 C.utf8 en_US.UTF-8 en_US.utf8 C POSIX].map(&:freeze).freeze
189+
190+
# By default set the LANG to C. If the host has the locale command
191+
# available, check installed locales and verify C is included (or
192+
# use C variant if available).
193+
def env_lang
194+
# If already set, just return immediately
195+
return @env_lang if @env_lang
196+
197+
# Default the LANG to C
198+
@env_lang = { LANG: "C" }
199+
200+
# If the locale command is not available, return default
201+
return @env_lang unless Vagrant::Util::Which.which("locale")
202+
203+
return @env_lang = @@env_lang if defined?(@@env_lang)
204+
205+
@logger.debug("validating LANG value for virtualbox cli commands")
206+
# Get list of available locales on the system
207+
result = Vagrant::Util::Subprocess.execute("locale", "-a")
208+
209+
# If the command results in an error, just log the error
210+
# and return the default value
211+
if result.exit_code != 0
212+
@logger.warn("locale command failed (exit code: #{result.exit_code}): #{result.stderr}")
213+
return @env_lang
214+
end
215+
available = result.stdout.lines.map(&:chomp).find_all do |l|
216+
l == "C" || l == "POSIX" || l.start_with?("C.") || l.start_with?("en_US.")
217+
end
218+
@logger.debug("list of available C locales: #{available.inspect}")
219+
220+
# Attempt to find a valid LANG from locale list
221+
lang = LANG_VARIATIONS.detect { |l| available.include?(l) }
222+
223+
if lang
224+
@logger.debug("valid variation found for LANG value: #{lang}")
225+
@env_lang[:LANG] = lang
226+
@@env_lang = @env_lang
227+
end
228+
229+
@logger.debug("LANG value set: #{@env_lang[:LANG].inspect}")
230+
@env_lang
231+
end
97232
end
98233
end
99234
end

lib/vagrant_utm/errors.rb

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,11 +13,21 @@ class MacOSRequired < UtmError
1313
error_key(:macos_required)
1414
end
1515

16-
# This error is raised if the utmctl binary is not found.
16+
# This error is raised if the UTM is not found.
1717
class UtmRequired < UtmError
1818
error_key(:utm_required)
1919
end
2020

21+
# This error is raised if the utmctl is not found.
22+
class UtmctlNotFoundError < UtmError
23+
error_key(:utmctl_not_found)
24+
end
25+
26+
# This error is raised if the utmctl command fails.
27+
class UtmctlError < UtmError
28+
error_key(:utmctl_error)
29+
end
30+
2131
# This error is raised if a UTM command fails.
2232
class CommandError < UtmError
2333
error_key(:command_error)

locales/en.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -29,7 +29,7 @@ en:
2929
pausing: |-
3030
The VM is pausing.
3131
paused: |-
32-
The VM is paused. To resume this VM, simply run `vagrant up`.
32+
The VM is paused. To resume this VM, simply run `vagrant resume`.
3333
resuming: |-
3434
The VM is resuming.
3535
stopping: |-

0 commit comments

Comments
 (0)