vagrant-share 0.0.1 → 2.0.0
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 +4 -4
- data/lib/vagrant-share.rb +18 -0
- data/lib/vagrant-share/cap/tinycore.rb +89 -0
- data/lib/vagrant-share/command.rb +9 -0
- data/lib/vagrant-share/command/connect.rb +82 -0
- data/lib/vagrant-share/command/ngrok.rb +14 -0
- data/lib/vagrant-share/command/ngrok/connect.rb +236 -0
- data/lib/vagrant-share/command/ngrok/share.rb +500 -0
- data/lib/vagrant-share/command/share.rb +153 -0
- data/lib/vagrant-share/errors.rb +96 -0
- data/lib/vagrant-share/helper.rb +488 -0
- data/lib/vagrant-share/helper/api.rb +46 -0
- data/lib/vagrant-share/helper/word_list.rb +550 -0
- data/lib/vagrant-share/plugin.rb +49 -0
- data/lib/vagrant-share/version.rb +5 -0
- data/locales/en.yml +297 -0
- data/version.txt +1 -0
- metadata +58 -13
- data/vagrant-share.gemspec +0 -9
@@ -0,0 +1,500 @@
|
|
1
|
+
module VagrantPlugins
|
2
|
+
module Share
|
3
|
+
module Command
|
4
|
+
# Ngrok specific implementation
|
5
|
+
module Ngrok
|
6
|
+
# Ngrok share implementation
|
7
|
+
module Share
|
8
|
+
# Guest port within proxy
|
9
|
+
GUEST_PROXY_PORT = 31338
|
10
|
+
|
11
|
+
# Start the ngrok based share
|
12
|
+
#
|
13
|
+
# @param [Array<String>] argv CLI arguments
|
14
|
+
# @param [Hash] options CLI options
|
15
|
+
def start_share(argv, options)
|
16
|
+
validate_ngrok_installation!
|
17
|
+
|
18
|
+
# Define variables to ensure availability within ensure block
|
19
|
+
# Set this here so they're available in our ensure block
|
20
|
+
vagrant_port = nil
|
21
|
+
share_machine = nil
|
22
|
+
share_api = nil
|
23
|
+
share_api_runner = nil
|
24
|
+
proxy_runner = nil
|
25
|
+
output_runner = nil
|
26
|
+
port_file = nil
|
27
|
+
ui = nil
|
28
|
+
share_info_output = Queue.new
|
29
|
+
configuration = {
|
30
|
+
"tunnels" => {}
|
31
|
+
}
|
32
|
+
|
33
|
+
begin
|
34
|
+
with_target_vms(argv, single_target: true) do |machine|
|
35
|
+
ui = machine.ui
|
36
|
+
machine.ui.output(I18n.t("vagrant_share.detecting"))
|
37
|
+
|
38
|
+
target = validate_target_machine(machine, options) || "127.0.0.1"
|
39
|
+
|
40
|
+
restrict = false
|
41
|
+
ports = []
|
42
|
+
|
43
|
+
machine.ui.detail("Local machine address: #{target}")
|
44
|
+
if target == "127.0.0.1"
|
45
|
+
machine.ui.detail(" \n" + I18n.t(
|
46
|
+
"vagrant_share.local_address_only",
|
47
|
+
provider: machine.provider_name.to_s,
|
48
|
+
) + "\n ")
|
49
|
+
|
50
|
+
# Restrict the ports that can be accessed since we're
|
51
|
+
# on localhost.
|
52
|
+
restrict = true
|
53
|
+
ports = Helper.forwarded_ports(machine).keys
|
54
|
+
end
|
55
|
+
|
56
|
+
if !options[:http_port] && !options[:disable_http]
|
57
|
+
begin
|
58
|
+
@logger.debug("No HTTP port set. Auto-detection will be attempted.")
|
59
|
+
# Always target localhost when using ngrok
|
60
|
+
detect_ports!(options, "127.0.0.1", machine)
|
61
|
+
options[:https_port] = nil if options[:disable_https]
|
62
|
+
rescue Errors::DetectHTTPForwardedPortFailed,
|
63
|
+
Errors::DetectHTTPCommonPortFailed
|
64
|
+
# If SSH isn't enabled, raise the errors. If SSH is enabled,
|
65
|
+
# then we can ignore that HTTP is unavailable.
|
66
|
+
raise if !options[:ssh]
|
67
|
+
|
68
|
+
machine.ui.detail(I18n.t("vagrant_share.ssh_no_http") + "\n ")
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
if options[:ssh]
|
73
|
+
options[:ssh_username], options[:ssh_port] = configure_ssh_share(machine, "127.0.0.1", options)
|
74
|
+
options[:ssh_password], options[:ssh_privkey] = configure_ssh_connect(machine, configuration, options)
|
75
|
+
end
|
76
|
+
|
77
|
+
machine.ui.detail(
|
78
|
+
"Local HTTP port: #{options[:http_port] || "disabled"}")
|
79
|
+
machine.ui.detail(
|
80
|
+
"Local HTTPS port: #{options[:https_port] || "disabled"}")
|
81
|
+
if options[:ssh]
|
82
|
+
machine.ui.detail("SSH Port: #{options[:ssh_port]}")
|
83
|
+
end
|
84
|
+
if restrict
|
85
|
+
ports.each do |port|
|
86
|
+
machine.ui.detail("Port: #{port}")
|
87
|
+
end
|
88
|
+
end
|
89
|
+
|
90
|
+
machine.ui.output(I18n.t("vagrant_share.creating"))
|
91
|
+
|
92
|
+
if options[:http_port]
|
93
|
+
configuration["tunnels"]["http"] = {
|
94
|
+
"proto" => "http",
|
95
|
+
"bind_tls" => false,
|
96
|
+
"addr" => options[:http_port]
|
97
|
+
}
|
98
|
+
end
|
99
|
+
|
100
|
+
if options[:https_port]
|
101
|
+
configuration["tunnels"]["https"] = {
|
102
|
+
"proto" => "tls",
|
103
|
+
"addr" => options[:https_port]
|
104
|
+
}
|
105
|
+
end
|
106
|
+
|
107
|
+
if options[:full_share] || (options[:ssh] && !options[:ssh_no_password])
|
108
|
+
configuration["tunnels"].delete("ssh")
|
109
|
+
|
110
|
+
if options[:full_share]
|
111
|
+
options[:shared_ports] = ports
|
112
|
+
else
|
113
|
+
options[:shared_ports] = [options[:ssh_port]]
|
114
|
+
end
|
115
|
+
|
116
|
+
@logger.debug("Starting local Vagrant API")
|
117
|
+
|
118
|
+
share_api = setup_share_api(machine)
|
119
|
+
|
120
|
+
options[:vagrant_api_port] = share_api.listeners.first.addr[1]
|
121
|
+
proxy_port, port_file = Helper.acquire_port(@env)
|
122
|
+
|
123
|
+
@logger.debug("Local Vagrant API is listening on port `#{vagrant_port}`")
|
124
|
+
@logger.debug("Local port for proxy forwarding: `#{proxy_port}`")
|
125
|
+
configuration["tunnels"]["proxy"] = {
|
126
|
+
"proto" => "tcp",
|
127
|
+
"addr" => proxy_port
|
128
|
+
}
|
129
|
+
share_machine = Helper.share_machine(@env, port: {guest: GUEST_PROXY_PORT, host: proxy_port}, name: "share")
|
130
|
+
|
131
|
+
ui = share_machine.ui
|
132
|
+
proxy_ui = share_machine.ui.dup
|
133
|
+
proxy_ui.opts[:bold] = false
|
134
|
+
proxy_ui.opts[:prefix_spaces] = true
|
135
|
+
port_forwards = target ? options[:shared_ports] : []
|
136
|
+
port_forwards << options[:vagrant_api_port]
|
137
|
+
|
138
|
+
@logger.debug("Starting share proxy VM")
|
139
|
+
share_machine.with_ui(proxy_ui) do
|
140
|
+
share_machine.action(:up)
|
141
|
+
share_machine.guest.capability(:share_proxy,
|
142
|
+
proxy: GUEST_PROXY_PORT,
|
143
|
+
forwards: port_forwards,
|
144
|
+
target: target
|
145
|
+
)
|
146
|
+
end
|
147
|
+
end
|
148
|
+
end
|
149
|
+
|
150
|
+
if share_api
|
151
|
+
@logger.debug("Starting internal Vagrant API")
|
152
|
+
share_api_runner = Thread.new{ share_api.start }
|
153
|
+
end
|
154
|
+
output_runner = start_connect_info_watcher(share_info_output, ui, options)
|
155
|
+
ngrok_process = start_ngrok_proxy(ui, configuration, share_info_output, options)
|
156
|
+
|
157
|
+
# Allow user to halt the share process and
|
158
|
+
# proxy VM via ctrl-c
|
159
|
+
Helper.signal_retrap("INT") do
|
160
|
+
ui.warn("Halting Vagrant share!")
|
161
|
+
ngrok_process.stop
|
162
|
+
share_api.stop if share_api
|
163
|
+
share_info_output.push(nil)
|
164
|
+
end
|
165
|
+
ensure
|
166
|
+
if port_file
|
167
|
+
port_file.close
|
168
|
+
File.delete(port_file) rescue nil
|
169
|
+
end
|
170
|
+
output_runner.join if output_runner
|
171
|
+
share_api_runner.join if share_api_runner
|
172
|
+
if share_machine
|
173
|
+
share_machine.action(:destroy, force_confirm_destroy: true)
|
174
|
+
end
|
175
|
+
end
|
176
|
+
end
|
177
|
+
|
178
|
+
# Start the ngrok proxy process
|
179
|
+
#
|
180
|
+
# @param [Vagrant::UI] ui UI instance for output
|
181
|
+
# @param [Hash] configurations ngrok process configuration
|
182
|
+
# @param [Queue] share_info_output location to push share information
|
183
|
+
# @param [Hash] options CLI options
|
184
|
+
def start_ngrok_proxy(ui, configuration, share_info_output, options)
|
185
|
+
ngrok_process = nil
|
186
|
+
base_config = File.expand_path("~/.ngrok2/ngrok.yml")
|
187
|
+
share_config = Tempfile.new("vagrant-share")
|
188
|
+
share_config.write(configuration.to_yaml)
|
189
|
+
share_config.close
|
190
|
+
if !File.exists?(base_config)
|
191
|
+
base_config = share_config.path
|
192
|
+
end
|
193
|
+
@logger.debug("Generated configuration for ngrok:\n#{configuration.to_yaml}")
|
194
|
+
@logger.debug("Starting ngrok proxy process.")
|
195
|
+
|
196
|
+
ngrok_process = Vagrant::Util::Subprocess.new(
|
197
|
+
*["ngrok", "start", "--config", base_config, "--config", share_config.path,
|
198
|
+
"--all", "--log", "stdout", "--log-format", "json", "--log-level", "debug"],
|
199
|
+
notify: [:stdout]
|
200
|
+
)
|
201
|
+
|
202
|
+
Thread.new do
|
203
|
+
begin
|
204
|
+
share_info = {}
|
205
|
+
share_info_keys = []
|
206
|
+
share_info_keys.push(:http) if options[:http_port]
|
207
|
+
share_info_keys.push(:https) if options[:https_port]
|
208
|
+
share_info_keys.push(:name) if options[:ssh] || options[:full_share]
|
209
|
+
|
210
|
+
ngrok_process.execute do |type, data|
|
211
|
+
if type == :stdout
|
212
|
+
data.split("\n").each do |line|
|
213
|
+
begin
|
214
|
+
info = JSON.parse(line)
|
215
|
+
if info["msg"].to_s == "decoded response"
|
216
|
+
begin
|
217
|
+
r_info = info["resp"]
|
218
|
+
if !r_info["Error"].to_s.empty?
|
219
|
+
@logger.error("Error encountered with ngrok connection: #{r_info["Error"]}")
|
220
|
+
share_info_output.push(r_info["Error"])
|
221
|
+
Process.kill("INT", Process.pid)
|
222
|
+
end
|
223
|
+
|
224
|
+
if r_info["URL"] && r_info["Proto"]
|
225
|
+
share_info[:uri] = URI.parse(r_info["URL"])
|
226
|
+
case share_info[:uri].scheme
|
227
|
+
when "http"
|
228
|
+
share_info[:http] = share_info[:uri].to_s
|
229
|
+
when "https"
|
230
|
+
share_info[:https] = share_info[:uri].to_s
|
231
|
+
when "tcp"
|
232
|
+
connect_name = [share_info[:uri].port, options[:vagrant_api_port]].map do |item|
|
233
|
+
Helper.wordify(item).join('_')
|
234
|
+
end
|
235
|
+
share_info[:tcp] = share_info[:uri].to_s
|
236
|
+
share_info[:name] = connect_name.join(":")
|
237
|
+
if share_info[:uri].host != DEFAULT_NGROK_TCP_ENDPOINT
|
238
|
+
host_num = share_info[:uri].host.split(".").first
|
239
|
+
host_num_word = Helper.wordify(host_num.to_i).join("_")
|
240
|
+
share_info[:name] += "@#{host_num_word}"
|
241
|
+
end
|
242
|
+
else
|
243
|
+
@logger.warn("Unhandled URI scheme detected: #{share_info[:uri].scheme} - `#{share_info[:uri]}`")
|
244
|
+
share_info.delete(:uri)
|
245
|
+
end
|
246
|
+
end
|
247
|
+
rescue => err
|
248
|
+
@logger.warn("Failed to parse line: #{err}")
|
249
|
+
end
|
250
|
+
end
|
251
|
+
if info["err"] && info["msg"] == "start tunnel listen" && info["err"] != "<nil>"
|
252
|
+
@logger.error("Error encountered with ngrok connection: #{info["err"]}")
|
253
|
+
share_info_output.push(info["err"])
|
254
|
+
# Force shutdown
|
255
|
+
Process.kill("INT", Process.pid)
|
256
|
+
end
|
257
|
+
if share_info_keys.all?{|key| share_info.keys.include?(key)}
|
258
|
+
share_info_output.push(share_info.dup)
|
259
|
+
share_info = {}
|
260
|
+
end
|
261
|
+
rescue => e
|
262
|
+
@logger.warn("Failure handling ngrok process output line: #{e.class} - #{e} (`#{line}`)")
|
263
|
+
end
|
264
|
+
end
|
265
|
+
end
|
266
|
+
end
|
267
|
+
ensure
|
268
|
+
share_config.unlink
|
269
|
+
end
|
270
|
+
end
|
271
|
+
ngrok_process
|
272
|
+
end
|
273
|
+
|
274
|
+
# Start the share information watcher to print connect instructions
|
275
|
+
#
|
276
|
+
# @param [Queue] share_info_output Queue to receive share information
|
277
|
+
# @param [Vagrant::UI] ui UI for output
|
278
|
+
# @param [Hash] options CLI options
|
279
|
+
def start_connect_info_watcher(share_info_output, ui, options)
|
280
|
+
Thread.new do
|
281
|
+
until((info = share_info_output.pop).nil?)
|
282
|
+
begin
|
283
|
+
case info
|
284
|
+
when String
|
285
|
+
ui.error(info)
|
286
|
+
when Hash
|
287
|
+
if info[:name]
|
288
|
+
i_uri = URI.parse(info[:tcp])
|
289
|
+
if i_uri.host != DEFAULT_NGROK_TCP_ENDPOINT
|
290
|
+
driver_name = i_uri.host
|
291
|
+
else
|
292
|
+
driver_name = "ngrok"
|
293
|
+
end
|
294
|
+
ui.success("")
|
295
|
+
ui.success(I18n.t("vagrant_share.started", name: info[:name]))
|
296
|
+
if options[:full_share]
|
297
|
+
ui.success("")
|
298
|
+
ui.success(I18n.t("vagrant_share.ngrok.started_full", name: info[:name], driver: driver_name))
|
299
|
+
end
|
300
|
+
if options[:ssh]
|
301
|
+
ui.success("")
|
302
|
+
ui.success(I18n.t("vagrant_share.ngrok.started_ssh", name: info[:name], driver: driver_name))
|
303
|
+
end
|
304
|
+
ui.success("")
|
305
|
+
end
|
306
|
+
if info[:http]
|
307
|
+
ui.success("HTTP URL: #{info[:http]}")
|
308
|
+
ui.success("")
|
309
|
+
end
|
310
|
+
if info[:https]
|
311
|
+
ui.success("HTTPS URL: #{info[:https]}")
|
312
|
+
ui.success("")
|
313
|
+
end
|
314
|
+
else
|
315
|
+
@logger.warn("Unknown data type receied for output: #{e.class} - #{e}")
|
316
|
+
end
|
317
|
+
rescue => e
|
318
|
+
@logger.error("Unexpected error processing connect information: #{e.class} - #{e}")
|
319
|
+
end
|
320
|
+
end
|
321
|
+
end
|
322
|
+
end
|
323
|
+
|
324
|
+
# Validate the target machine for the share
|
325
|
+
#
|
326
|
+
# @param [Vagrant::Machine] machine
|
327
|
+
# @param [Hash] options
|
328
|
+
# @return [String, NilClass] public address
|
329
|
+
def validate_target_machine(machine, options)
|
330
|
+
if !machine.ssh_info
|
331
|
+
# We use this as a flag of whether or not the machine is
|
332
|
+
# running. We can't share a machine that is not running.
|
333
|
+
raise Errors::MachineNotReady
|
334
|
+
end
|
335
|
+
|
336
|
+
if options[:ssh]
|
337
|
+
# Do some quick checks to make sure we can setup this
|
338
|
+
# machine for SSH access from other users.
|
339
|
+
begin
|
340
|
+
if !machine.guest.capability?(:insert_public_key)
|
341
|
+
raise Errors::SSHCantInsertKey,
|
342
|
+
guest: machine.guest.name.to_s
|
343
|
+
end
|
344
|
+
rescue Vagrant::Errors::MachineGuestNotReady
|
345
|
+
raise Errors::SSHNotReady
|
346
|
+
end
|
347
|
+
end
|
348
|
+
|
349
|
+
target = nil
|
350
|
+
if !machine.provider.capability?(:public_address)
|
351
|
+
machine.ui.warn(I18n.t(
|
352
|
+
"vagrant_share.provider_unsupported",
|
353
|
+
provider: machine.provider_name.to_s,
|
354
|
+
))
|
355
|
+
else
|
356
|
+
target = machine.provider.capability(:public_address)
|
357
|
+
end
|
358
|
+
target
|
359
|
+
end
|
360
|
+
|
361
|
+
# Configure settings for SSH sharing
|
362
|
+
#
|
363
|
+
# @param [Vagrant::Machine] machine
|
364
|
+
# @param [Hash] options
|
365
|
+
# @return [Array<String>] ssh username, ssh port
|
366
|
+
def configure_ssh_share(machine, target, options)
|
367
|
+
ssh_username = nil
|
368
|
+
ssh_port = options[:ssh_port] if options[:ssh] && options[:ssh_port]
|
369
|
+
if options[:ssh]
|
370
|
+
ssh_info = machine.ssh_info
|
371
|
+
raise Errors::SSHNotReady if !ssh_info
|
372
|
+
|
373
|
+
ssh_username = ssh_info[:username]
|
374
|
+
if !ssh_port
|
375
|
+
ssh_port = ssh_info[:port]
|
376
|
+
if ssh_info[:host] == "127.0.0.1" && target != "127.0.0.1"
|
377
|
+
# Since we're targetting ourselves, the port probably
|
378
|
+
# points to a forwarded port. Look it up.
|
379
|
+
ssh_port = Helper.guest_forwarded_port(machine, ssh_port)
|
380
|
+
|
381
|
+
if !ssh_port
|
382
|
+
raise Errors::SSHPortNotDetected
|
383
|
+
end
|
384
|
+
end
|
385
|
+
|
386
|
+
if target == "127.0.0.1" && ssh_info[:host] != "127.0.0.1"
|
387
|
+
# The opposite case now. We're proxying to localhost, but
|
388
|
+
# the SSH port is NOT on localhost. We need to look for
|
389
|
+
# a host forwarded port.
|
390
|
+
guest_port = ssh_port
|
391
|
+
ssh_port = Helper.host_forwarded_port(machine, guest_port)
|
392
|
+
|
393
|
+
if !ssh_port
|
394
|
+
raise Errors::SSHHostPortNotDetected,
|
395
|
+
guest_port: guest_port.to_s
|
396
|
+
end
|
397
|
+
end
|
398
|
+
end
|
399
|
+
end
|
400
|
+
[ssh_username, ssh_port]
|
401
|
+
end
|
402
|
+
|
403
|
+
# Configure settings for SSH connect
|
404
|
+
#
|
405
|
+
# @param [Vagrant::Machine] machine Machine to share
|
406
|
+
# @param [Hash] configuration ngrok configuration hash
|
407
|
+
# @param [Hash] options CLI options
|
408
|
+
# @param [Array<String>] ssh password, ssh privkey
|
409
|
+
def configure_ssh_connect(machine, configuration, options)
|
410
|
+
ssh_password = nil
|
411
|
+
ssh_privkey = nil
|
412
|
+
machine.ui.output(I18n.t("vagrant_share.generating_ssh_key"))
|
413
|
+
|
414
|
+
if !options[:ssh_no_password]
|
415
|
+
while !ssh_password
|
416
|
+
ssh_password = machine.ui.ask(
|
417
|
+
"#{I18n.t("vagrant_share.ssh_password_prompt")} ",
|
418
|
+
echo: false)
|
419
|
+
end
|
420
|
+
|
421
|
+
while ssh_password.length < 4
|
422
|
+
machine.ui.warn(
|
423
|
+
"#{I18n.t("vagrant_share.password_not_long_enough")}")
|
424
|
+
ssh_password = machine.ui.ask(
|
425
|
+
"#{I18n.t("vagrant_share.ssh_password_prompt")} ",
|
426
|
+
echo: false)
|
427
|
+
end
|
428
|
+
|
429
|
+
confirm_password = nil
|
430
|
+
while confirm_password != ssh_password
|
431
|
+
confirm_password = machine.ui.ask(
|
432
|
+
"#{I18n.t("vagrant_share.ssh_password_confirm_prompt")} ",
|
433
|
+
echo: false)
|
434
|
+
end
|
435
|
+
else
|
436
|
+
configuration["tunnels"]["ssh"] = {
|
437
|
+
"proto" => "tcp",
|
438
|
+
"addr" => ssh_port
|
439
|
+
}
|
440
|
+
end
|
441
|
+
|
442
|
+
_, ssh_privkey, openssh_key = Helper.generate_keypair(ssh_password)
|
443
|
+
|
444
|
+
machine.ui.detail(I18n.t("vagrant_share.inserting_ssh_key"))
|
445
|
+
machine.guest.capability(:insert_public_key, openssh_key)
|
446
|
+
[ssh_password, ssh_privkey]
|
447
|
+
end
|
448
|
+
|
449
|
+
# Setup the local share API
|
450
|
+
#
|
451
|
+
# @param [Vagrant::Machine] machine
|
452
|
+
# @return [WEBrick::HTTPServer]
|
453
|
+
def setup_share_api(machine)
|
454
|
+
share_api = Helper::Api.start_api(machine) do |api|
|
455
|
+
api.mount_proc("/ping") do |req, res|
|
456
|
+
res.status = 200
|
457
|
+
res.body = {message: "pong"}.to_json
|
458
|
+
end
|
459
|
+
api.mount_proc("/share-info") do |req, res|
|
460
|
+
res.status = 200
|
461
|
+
res.body = {
|
462
|
+
ports: options[:shared_ports],
|
463
|
+
has_private_key: !!options[:ssh_privkey],
|
464
|
+
private_key_password: !options[:ssh_no_password],
|
465
|
+
ssh_username: options[:ssh_username],
|
466
|
+
ssh_port: options[:ssh_port]
|
467
|
+
}.to_json
|
468
|
+
end
|
469
|
+
api.mount_proc("/shared-ports") do |req, res|
|
470
|
+
res.body = {ports: options[:shared_ports]}.to_json
|
471
|
+
res.status = 200
|
472
|
+
end
|
473
|
+
api.mount_proc("/connect-ssh") do |req, res|
|
474
|
+
res.body = {
|
475
|
+
ssh_username: options[:ssh_username],
|
476
|
+
ssh_port: options[:ssh_port],
|
477
|
+
ssh_key: options[:ssh_privkey],
|
478
|
+
has_private_key: !!options[:ssh_privkey],
|
479
|
+
private_key_password: !options[:ssh_no_password]
|
480
|
+
}.to_json
|
481
|
+
res.status = 200
|
482
|
+
end
|
483
|
+
end
|
484
|
+
end
|
485
|
+
|
486
|
+
# Check that ngrok is available on user's PATH
|
487
|
+
def validate_ngrok_installation!
|
488
|
+
begin
|
489
|
+
Vagrant::Util::Subprocess.new("ngrok")
|
490
|
+
rescue Vagrant::Errors::CommandUnavailable
|
491
|
+
raise Errors::NgrokUnavailable
|
492
|
+
end
|
493
|
+
end
|
494
|
+
end
|
495
|
+
end
|
496
|
+
end
|
497
|
+
end
|
498
|
+
end
|
499
|
+
|
500
|
+
Thread.abort_on_exception = true
|