vagrant-share 0.0.1 → 2.0.0
Sign up to get free protection for your applications and to get access to all the features.
- 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,153 @@
|
|
1
|
+
require "log4r"
|
2
|
+
|
3
|
+
require "vagrant/util/subprocess"
|
4
|
+
|
5
|
+
module VagrantPlugins
|
6
|
+
module Share
|
7
|
+
module Command
|
8
|
+
class Share < Vagrant.plugin("2", "command")
|
9
|
+
|
10
|
+
include Ngrok::Share
|
11
|
+
|
12
|
+
def self.synopsis
|
13
|
+
"share your Vagrant environment with anyone in the world"
|
14
|
+
end
|
15
|
+
|
16
|
+
attr_reader :options
|
17
|
+
|
18
|
+
def execute
|
19
|
+
@logger = Log4r::Logger.new("vagrant::share::command::share")
|
20
|
+
@options = {}
|
21
|
+
|
22
|
+
options[:use_key_once] = false
|
23
|
+
options[:disable_http] = false
|
24
|
+
|
25
|
+
opts = OptionParser.new do |o|
|
26
|
+
o.banner = "Usage: vagrant share"
|
27
|
+
o.separator ""
|
28
|
+
o.separator "Allows anyone in the world with an internet connection to access"
|
29
|
+
o.separator "your Vagrant environment by giving you a globally accessible"
|
30
|
+
o.separator "URL. "
|
31
|
+
o.separator ""
|
32
|
+
o.separator "If the person wanting to connect to your environment also has"
|
33
|
+
o.separator "Vagrant installed, they can use `vagrant connect` to connect to"
|
34
|
+
o.separator "open any TCP stream to your Vagrant machine."
|
35
|
+
o.separator ""
|
36
|
+
o.separator "Vagrant will attempt to auto-discover your HTTP port and enable"
|
37
|
+
o.separator "that. If you want to disable HTTP, specify the --disable-http flag"
|
38
|
+
o.separator "directly. If you want HTTPS, specify the --https flag directly."
|
39
|
+
o.separator ""
|
40
|
+
o.separator "If the --ssh flag is specified, others can easily SSH into your"
|
41
|
+
o.separator "Vagrant environment by using `vagrant connect --ssh`. We generate"
|
42
|
+
o.separator "a new SSH key for this that you can encrypt with a password."
|
43
|
+
o.separator "When --ssh is specified, all other SSH flags are optional."
|
44
|
+
o.separator ""
|
45
|
+
|
46
|
+
o.on("--disable-http", "Disable publicly visible HTTP(S) endpoint") do |d|
|
47
|
+
options[:disable_http] = d
|
48
|
+
end
|
49
|
+
|
50
|
+
o.on("--disable-https", "Disable publicly visible HTTPS endpoint only") do |d|
|
51
|
+
options[:disable_https] = d
|
52
|
+
end
|
53
|
+
|
54
|
+
o.on("--domain VALUE", String, "Domain the share name will be a subdomain of") do |n|
|
55
|
+
options[:domain] = n
|
56
|
+
end
|
57
|
+
|
58
|
+
o.on("--http VALUE", String, "Local HTTP port to forward to") do |p|
|
59
|
+
options[:http_port] = p
|
60
|
+
end
|
61
|
+
|
62
|
+
o.on("--https VALUE", String, "Local HTTPS port to forward to") do |p|
|
63
|
+
options[:https_port] = p
|
64
|
+
end
|
65
|
+
|
66
|
+
o.on("--name VALUE", String, "Specific name for the share") do |n|
|
67
|
+
options[:name] = n
|
68
|
+
end
|
69
|
+
|
70
|
+
o.on("--ssh", "Allow 'vagrant connect --ssh' access") do |ssh|
|
71
|
+
options[:ssh] = ssh
|
72
|
+
end
|
73
|
+
|
74
|
+
o.on("--ssh-no-password", "Key won't be encrypted with --ssh") do |p|
|
75
|
+
options[:ssh_no_password] = p
|
76
|
+
options[:ssh_flag] = true
|
77
|
+
end
|
78
|
+
|
79
|
+
o.on("--ssh-port PORT", Integer, "Specific port for SSH when using --ssh") do |p|
|
80
|
+
options[:ssh_port] = p
|
81
|
+
options[:ssh_flag] = true
|
82
|
+
end
|
83
|
+
|
84
|
+
o.on("--ssh-once", "Allow 'vagrant connect --ssh' only one time") do |r|
|
85
|
+
options[:use_key_once] = true
|
86
|
+
options[:ssh_flag] = true
|
87
|
+
end
|
88
|
+
|
89
|
+
o.on("--driver DRIVER", "Deprecated option for compatibility") do |d|
|
90
|
+
options[:driver] = d
|
91
|
+
end
|
92
|
+
|
93
|
+
o.on("--full", "Share entire machine") do |full|
|
94
|
+
options[:full_share] = true
|
95
|
+
end
|
96
|
+
|
97
|
+
o.on("--share-password", "Custom share password") do |p|
|
98
|
+
options[:share_password] = p
|
99
|
+
end
|
100
|
+
end
|
101
|
+
|
102
|
+
# Parse the options
|
103
|
+
argv = parse_options(opts)
|
104
|
+
return if !argv
|
105
|
+
if argv.length > 1
|
106
|
+
raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp
|
107
|
+
end
|
108
|
+
|
109
|
+
# If an SSH option was specified without the --ssh flag, let
|
110
|
+
# the user know.
|
111
|
+
if !options[:ssh] && options[:ssh_flag]
|
112
|
+
@env.ui.error(I18n.t("vagrant_share.ssh_flag_missing") + "\n ")
|
113
|
+
raise Vagrant::Errors::CLIInvalidUsage, help: opts.help.chomp
|
114
|
+
end
|
115
|
+
|
116
|
+
# If we're not using HTTP, get rid of the ports
|
117
|
+
if options[:disable_http]
|
118
|
+
options[:http_port] = nil
|
119
|
+
options[:https_port] = nil
|
120
|
+
end
|
121
|
+
|
122
|
+
start_share(argv, options)
|
123
|
+
end
|
124
|
+
|
125
|
+
protected
|
126
|
+
|
127
|
+
def detect_ports!(options, target, machine)
|
128
|
+
detected = nil
|
129
|
+
if target == "127.0.0.1"
|
130
|
+
# Use forwarded ports to detect because we're NATting
|
131
|
+
detected = Helper.detect_forwarded_ports(machine) || []
|
132
|
+
if !detected[0]
|
133
|
+
raise Errors::DetectHTTPForwardedPortFailed
|
134
|
+
end
|
135
|
+
else
|
136
|
+
# Try forwarded ports first because its not such a
|
137
|
+
# shot in the dark.
|
138
|
+
detected = Helper.detect_hybrid(machine, target)
|
139
|
+
if !detected[0]
|
140
|
+
raise Errors::DetectHTTPCommonPortFailed,
|
141
|
+
target: target
|
142
|
+
end
|
143
|
+
end
|
144
|
+
|
145
|
+
options[:http_port] = detected[0]
|
146
|
+
if !options[:https_port] && detected[1]
|
147
|
+
options[:https_port] = detected[1]
|
148
|
+
end
|
149
|
+
end
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,96 @@
|
|
1
|
+
require "vagrant"
|
2
|
+
|
3
|
+
module VagrantPlugins
|
4
|
+
module Share
|
5
|
+
module Errors
|
6
|
+
# A convenient superclass for all our errors.
|
7
|
+
class ShareError < Vagrant::Errors::VagrantError
|
8
|
+
error_namespace("vagrant_share.errors")
|
9
|
+
end
|
10
|
+
|
11
|
+
class APIError < ShareError
|
12
|
+
error_key(:api_error)
|
13
|
+
end
|
14
|
+
|
15
|
+
class AuthExpired < ShareError
|
16
|
+
error_key(:auth_expired)
|
17
|
+
end
|
18
|
+
|
19
|
+
class AuthRequired < ShareError
|
20
|
+
error_key(:auth_required)
|
21
|
+
end
|
22
|
+
|
23
|
+
class ConnectNameIsURL < ShareError
|
24
|
+
error_key(:connect_name_is_url)
|
25
|
+
end
|
26
|
+
|
27
|
+
class DetectHTTPCommonPortFailed < ShareError
|
28
|
+
error_key(:detect_http_common_port_failed)
|
29
|
+
end
|
30
|
+
|
31
|
+
class DetectHTTPForwardedPortFailed < ShareError
|
32
|
+
error_key(:detect_http_forwarded_port_failed)
|
33
|
+
end
|
34
|
+
|
35
|
+
class IPCouldNotAutoAcquire < ShareError
|
36
|
+
error_key(:ip_could_not_auto_acquire)
|
37
|
+
end
|
38
|
+
|
39
|
+
class IPInUse < ShareError
|
40
|
+
error_key(:ip_in_use)
|
41
|
+
end
|
42
|
+
|
43
|
+
class IPInvalid < ShareError
|
44
|
+
error_key(:ip_invalid)
|
45
|
+
end
|
46
|
+
|
47
|
+
class PortCouldNotAcquire < ShareError
|
48
|
+
error_key(:port_could_not_acquire)
|
49
|
+
end
|
50
|
+
|
51
|
+
class MachineNotReady < ShareError
|
52
|
+
error_key(:machine_not_ready)
|
53
|
+
end
|
54
|
+
|
55
|
+
class NgrokUnavailable < ShareError
|
56
|
+
error_key(:ngrok_unavailable)
|
57
|
+
end
|
58
|
+
|
59
|
+
class ProxyExit < ShareError
|
60
|
+
error_key(:proxy_exit)
|
61
|
+
end
|
62
|
+
|
63
|
+
class ProxyMachineBadProvider < ShareError
|
64
|
+
error_key(:proxy_machine_bad_provider)
|
65
|
+
end
|
66
|
+
|
67
|
+
class ServerNotSet < ShareError
|
68
|
+
error_key(:server_not_set)
|
69
|
+
end
|
70
|
+
|
71
|
+
class ShareNotFound < ShareError
|
72
|
+
error_key(:share_not_found)
|
73
|
+
end
|
74
|
+
|
75
|
+
class SSHCantInsertKey < ShareError
|
76
|
+
error_key(:ssh_cant_insert_key)
|
77
|
+
end
|
78
|
+
|
79
|
+
class SSHNotReady < ShareError
|
80
|
+
error_key(:ssh_not_ready)
|
81
|
+
end
|
82
|
+
|
83
|
+
class SSHNotShared < ShareError
|
84
|
+
error_key(:ssh_not_shared)
|
85
|
+
end
|
86
|
+
|
87
|
+
class SSHPortNotDetected < ShareError
|
88
|
+
error_key(:ssh_port_not_detected)
|
89
|
+
end
|
90
|
+
|
91
|
+
class SSHHostPortNotDetected < ShareError
|
92
|
+
error_key(:ssh_host_port_not_detected)
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
end
|
@@ -0,0 +1,488 @@
|
|
1
|
+
require "base64"
|
2
|
+
require "ipaddr"
|
3
|
+
require "openssl"
|
4
|
+
require "pathname"
|
5
|
+
require "timeout"
|
6
|
+
require "thread"
|
7
|
+
require "tmpdir"
|
8
|
+
require "securerandom"
|
9
|
+
require "log4r"
|
10
|
+
require "rest_client"
|
11
|
+
|
12
|
+
require "vagrant/util/retryable"
|
13
|
+
require "vagrant/util/subprocess"
|
14
|
+
require "vagrant/vagrantfile"
|
15
|
+
|
16
|
+
module VagrantPlugins
|
17
|
+
module Share
|
18
|
+
# Contains helper methods that the command uses.
|
19
|
+
class Helper
|
20
|
+
autoload :Api, "vagrant-share/helper/api"
|
21
|
+
autoload :WordList, "vagrant-share/helper/word_list"
|
22
|
+
|
23
|
+
extend Vagrant::Util::Retryable
|
24
|
+
|
25
|
+
# List of ephemeral ports
|
26
|
+
SHARE_PORT_RANGE = (49152..65535).freeze
|
27
|
+
|
28
|
+
# Acquires the given IP for the static IP for the machine. This
|
29
|
+
# also cleverly uses file locks to try to make sure that IPs
|
30
|
+
# aren't reused if multiple `connect` calls are called.
|
31
|
+
#
|
32
|
+
# @param [Vagrant::Environment] env
|
33
|
+
# @param [String] requested A specific requested IP, or nil if
|
34
|
+
# it doesn't matter.
|
35
|
+
# @return [Array<String, File>] The first element is the IP, and
|
36
|
+
# the second is a File that holds the file lock. Make sure to
|
37
|
+
# close this when you're done. Nil is returned if the requested
|
38
|
+
# IP can't be acquired.
|
39
|
+
def self.acquire_ip(env, requested)
|
40
|
+
if requested
|
41
|
+
path = env.tmp_path.join("vagrant_connect_#{requested}")
|
42
|
+
f = path.open("w+")
|
43
|
+
if f.flock(File::LOCK_EX | File::LOCK_NB) === false
|
44
|
+
# Someone already has this IP.
|
45
|
+
f.close
|
46
|
+
return nil
|
47
|
+
end
|
48
|
+
|
49
|
+
return requested.to_s, f
|
50
|
+
end
|
51
|
+
|
52
|
+
# We just skip ".1" and ".2" because they cause problems often.
|
53
|
+
range = IPAddr.new("172.16.0.0/12").succ.succ
|
54
|
+
while true
|
55
|
+
results = acquire_ip(env, range.to_s)
|
56
|
+
return results if results
|
57
|
+
range = range.succ
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
# Acquires the given port if free.
|
62
|
+
#
|
63
|
+
# @param [Vagrant::Environment] env
|
64
|
+
# @param [Integer] requested
|
65
|
+
# @return [Array<Integer, File>]
|
66
|
+
def self.acquire_port(env, requested=nil)
|
67
|
+
if requested
|
68
|
+
path = env.tmp_path.join("vagrant_share_#{requested}")
|
69
|
+
handle = path.open("w+")
|
70
|
+
if !handle.flock(File::LOCK_EX | File::LOCK_NB)
|
71
|
+
handle.close
|
72
|
+
nil
|
73
|
+
else
|
74
|
+
begin
|
75
|
+
Socket.tcp('127.0.0.1', requested, connect_timeout: 1)
|
76
|
+
nil
|
77
|
+
rescue Errno::ECONNREFUSED
|
78
|
+
[requested, handle]
|
79
|
+
end
|
80
|
+
end
|
81
|
+
else
|
82
|
+
port_list = SHARE_PORT_RANGE.to_a
|
83
|
+
port = acquire_port(env, port_list.pop) until port || port_list.empty?
|
84
|
+
if port.nil?
|
85
|
+
raise Errors::PortCouldNotAcquire.new
|
86
|
+
else
|
87
|
+
port
|
88
|
+
end
|
89
|
+
end
|
90
|
+
end
|
91
|
+
|
92
|
+
# Detects the HTTP/HTTPs ports of the machine automatically by
|
93
|
+
# trying a bunch of commonly used ports.
|
94
|
+
#
|
95
|
+
# @param [String] target IP or FQDN
|
96
|
+
# @return [Array] Same as {#detect_forwarded_ports}
|
97
|
+
def self.detect_ports(target)
|
98
|
+
common = [80, 3000, 4567, 8000, 8080]
|
99
|
+
|
100
|
+
logger = Log4r::Logger.new("vagrant::share::helper")
|
101
|
+
queue = Queue.new
|
102
|
+
workers = []
|
103
|
+
common.each do |port|
|
104
|
+
workers << Thread.new(port) do |p|
|
105
|
+
Thread.current.abort_on_exception = true
|
106
|
+
|
107
|
+
url = "http://#{target}:#{p}/"
|
108
|
+
logger.debug("Trying: #{url}")
|
109
|
+
queue << p if http_url?(url)
|
110
|
+
end
|
111
|
+
end
|
112
|
+
|
113
|
+
# Make a thread that puts a tombstone if all the workers are dead
|
114
|
+
workers << Thread.new(workers.dup) do |waiters|
|
115
|
+
Thread.current.abort_on_exception = true
|
116
|
+
|
117
|
+
begin
|
118
|
+
waiters.map(&:join)
|
119
|
+
ensure
|
120
|
+
queue << nil
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
[queue.pop, nil]
|
125
|
+
ensure
|
126
|
+
workers.map(&:kill)
|
127
|
+
end
|
128
|
+
|
129
|
+
# Detects the HTTP/HTTPs ports of the machine automatically using
|
130
|
+
# forwarded ports.
|
131
|
+
#
|
132
|
+
# @param [Vagrant::Machine]
|
133
|
+
# @return [Array] Results where first element is HTTP and second
|
134
|
+
# is HTTPs port. Nil elements if not detected.
|
135
|
+
def self.detect_forwarded_ports(machine, **opts)
|
136
|
+
logger = Log4r::Logger.new("vagrant::share::helper")
|
137
|
+
queue = Queue.new
|
138
|
+
https_queue = Queue.new
|
139
|
+
found_https = false
|
140
|
+
workers = []
|
141
|
+
|
142
|
+
machine.config.vm.networks.each do |type, netopts|
|
143
|
+
next if type != :forwarded_port
|
144
|
+
next if !netopts[:host]
|
145
|
+
|
146
|
+
target = opts[:target] || "127.0.0.1"
|
147
|
+
port = opts[:guest_port] ? netopts[:guest] : netopts[:host]
|
148
|
+
|
149
|
+
# Try the forwarded port
|
150
|
+
workers << Thread.new(target, port) do |t, p|
|
151
|
+
Thread.current.abort_on_exception = true
|
152
|
+
|
153
|
+
url = "http://#{t}:#{p}/"
|
154
|
+
logger.debug("Trying: #{url}")
|
155
|
+
if http_url?(url)
|
156
|
+
logger.info("HTTP url found: #{url}")
|
157
|
+
queue << port
|
158
|
+
else
|
159
|
+
logger.debug("Not an HTTP URL: #{url}")
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
163
|
+
# If it is an SSL port, attempt an SSL connection...
|
164
|
+
if netopts[:guest].to_i == 443 && !found_https
|
165
|
+
found_https = true
|
166
|
+
|
167
|
+
workers << Thread.new(target, port) do |t, p|
|
168
|
+
Thread.current.abort_on_exception = true
|
169
|
+
|
170
|
+
url = "https://#{t}:#{p}/"
|
171
|
+
logger.debug("Trying HTTPS: #{url}")
|
172
|
+
if http_url?(url, secure: true)
|
173
|
+
https_queue << port
|
174
|
+
else
|
175
|
+
https_queue << nil
|
176
|
+
end
|
177
|
+
end
|
178
|
+
end
|
179
|
+
end
|
180
|
+
|
181
|
+
# Make a thread that puts a tombstone if all the workers are dead
|
182
|
+
workers << Thread.new(workers.dup) do |waiters|
|
183
|
+
Thread.current.abort_on_exception = true
|
184
|
+
|
185
|
+
begin
|
186
|
+
waiters.map(&:join)
|
187
|
+
ensure
|
188
|
+
queue << nil
|
189
|
+
end
|
190
|
+
end
|
191
|
+
|
192
|
+
if !found_https
|
193
|
+
Thread.new do
|
194
|
+
# We never had an https candidate, so just put a nil on it
|
195
|
+
https_queue << nil
|
196
|
+
end
|
197
|
+
end
|
198
|
+
|
199
|
+
[queue.pop, https_queue.pop]
|
200
|
+
ensure
|
201
|
+
workers.map(&:kill)
|
202
|
+
end
|
203
|
+
|
204
|
+
# Get information from vagrant internal share API
|
205
|
+
#
|
206
|
+
# @param [String] path
|
207
|
+
# @param [String] ip
|
208
|
+
# @param [Integer] port
|
209
|
+
# @return [Hash]
|
210
|
+
def self.share_info(path, ip, port)
|
211
|
+
uri = URI.parse("https://#{ip}:#{port}/#{path}")
|
212
|
+
conn = Net::HTTP.new(uri.host, uri.port)
|
213
|
+
conn.use_ssl = true
|
214
|
+
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
215
|
+
response = conn.get(uri.path)
|
216
|
+
JSON.parse(response.body.to_s)
|
217
|
+
end
|
218
|
+
|
219
|
+
# Ping share to validate connection
|
220
|
+
#
|
221
|
+
# @param [String] ip
|
222
|
+
# @param [Integer] port
|
223
|
+
# @return [TrueClass, FalseClass] connection is valid
|
224
|
+
def self.ping_share(ip, port, **opts)
|
225
|
+
uri = URI.parse("https://#{ip}:#{port}/#{opts.fetch(:path, "ping")}")
|
226
|
+
begin
|
227
|
+
conn = Net::HTTP.new(uri.host, uri.port)
|
228
|
+
conn.use_ssl = true
|
229
|
+
conn.verify_mode = OpenSSL::SSL::VERIFY_NONE
|
230
|
+
response = conn.get(uri.path)
|
231
|
+
response.code.to_s == "200"
|
232
|
+
rescue
|
233
|
+
false
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
# Convert number into words
|
238
|
+
#
|
239
|
+
# @param [Integer] int
|
240
|
+
# @return [Array<String>]
|
241
|
+
def self.wordify(int)
|
242
|
+
WordList.encode(int)
|
243
|
+
end
|
244
|
+
|
245
|
+
# Convert words into number
|
246
|
+
#
|
247
|
+
# @param [Array<String>] words
|
248
|
+
# @return [Integer]
|
249
|
+
def self.dewordify(words)
|
250
|
+
WordList.decode(words)
|
251
|
+
end
|
252
|
+
|
253
|
+
# Trap given signal and execute given block. Reapply replaced
|
254
|
+
# signal trap block when complete.
|
255
|
+
#
|
256
|
+
# @param [String] type
|
257
|
+
# @return [Proc]
|
258
|
+
def self.signal_retrap(type)
|
259
|
+
initial_trap = Signal.trap(type) do
|
260
|
+
yield
|
261
|
+
Signal.trap(type, initial_trap)
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# Detects the HTTP/HTTPS ports using a hybrid approach by
|
266
|
+
# trying both the forwarded port and common port methods.
|
267
|
+
#
|
268
|
+
# @return [Array]
|
269
|
+
def self.detect_hybrid(machine, target)
|
270
|
+
http_queue = Queue.new
|
271
|
+
https_queue = Queue.new
|
272
|
+
|
273
|
+
workers = []
|
274
|
+
workers << Thread.new do
|
275
|
+
Thread.current.abort_on_exception = true
|
276
|
+
|
277
|
+
results = detect_forwarded_ports(
|
278
|
+
machine, target: target, guest_port: true)
|
279
|
+
http_queue << results[0] if results && results[0]
|
280
|
+
https_queue << results[1] if results && results[1]
|
281
|
+
end
|
282
|
+
|
283
|
+
workers << Thread.new do
|
284
|
+
Thread.current.abort_on_exception = true
|
285
|
+
|
286
|
+
results = detect_ports(target)
|
287
|
+
http_queue << results[0] if results && results[0]
|
288
|
+
https_queue << results[1] if results && results[1]
|
289
|
+
end
|
290
|
+
|
291
|
+
workers << Thread.new(workers.dup) do |waiters|
|
292
|
+
Thread.current.abort_on_exception = true
|
293
|
+
|
294
|
+
begin
|
295
|
+
waiters.map(&:join)
|
296
|
+
ensure
|
297
|
+
http_queue << nil
|
298
|
+
https_queue << nil
|
299
|
+
end
|
300
|
+
end
|
301
|
+
|
302
|
+
[http_queue.pop, https_queue.pop]
|
303
|
+
ensure
|
304
|
+
workers.map(&:kill)
|
305
|
+
end
|
306
|
+
|
307
|
+
# Returns the forwarded ports (on the host) for the machine.
|
308
|
+
#
|
309
|
+
# @return [Hash<Integer, Integer>] Mapping of forwarded ports. The
|
310
|
+
# key is the host port and the value is the guest port.
|
311
|
+
def self.forwarded_ports(machine)
|
312
|
+
if machine.provider.capability?(:forwarded_ports)
|
313
|
+
# This is a much more correct way, if it is available
|
314
|
+
return machine.provider.capability(:forwarded_ports)
|
315
|
+
end
|
316
|
+
|
317
|
+
# This is so we can implement it for other providers while still
|
318
|
+
# allowing them to implement it eventually. For example, we can
|
319
|
+
# support people running older VMware plugins.
|
320
|
+
if machine.provider.capability?(:vagrant_share__forwarded_ports)
|
321
|
+
return machine.provider.capability(:vagrant_share__forwarded_ports)
|
322
|
+
end
|
323
|
+
|
324
|
+
{}.tap do |result|
|
325
|
+
machine.config.vm.networks.each do |type, netopts|
|
326
|
+
next if type != :forwarded_port
|
327
|
+
next if !netopts[:host]
|
328
|
+
result[netopts[:host]] = netopts[:guest]
|
329
|
+
end
|
330
|
+
end
|
331
|
+
end
|
332
|
+
|
333
|
+
# Looks up the guest port of a forwarded port.
|
334
|
+
#
|
335
|
+
# @param [Integer] port Port on the host.
|
336
|
+
# @return [Integer]
|
337
|
+
def self.guest_forwarded_port(machine, port)
|
338
|
+
forwarded_ports(machine)[port]
|
339
|
+
end
|
340
|
+
|
341
|
+
# Looks up the host port of a forwarded port.
|
342
|
+
#
|
343
|
+
# @param [Integer] port Port on the guest.
|
344
|
+
# @return [Integer]
|
345
|
+
def self.host_forwarded_port(machine, port)
|
346
|
+
forwarded_ports(machine).invert[port]
|
347
|
+
end
|
348
|
+
|
349
|
+
# Creates an SSH keypair and returns it.
|
350
|
+
#
|
351
|
+
# @param [String] password Password for the key, or nil for no password.
|
352
|
+
# @return [Array<String, String, String>] PEM-encoded public and private key,
|
353
|
+
# respectively. The final element is the OpenSSH encoded public
|
354
|
+
# key.
|
355
|
+
def self.generate_keypair(password)
|
356
|
+
rsa_key = OpenSSL::PKey::RSA.new(2048)
|
357
|
+
public_key = rsa_key.public_key
|
358
|
+
private_key = rsa_key.to_pem
|
359
|
+
|
360
|
+
if password
|
361
|
+
cipher = OpenSSL::Cipher.new('des3')
|
362
|
+
private_key = rsa_key.to_pem(cipher, password)
|
363
|
+
end
|
364
|
+
|
365
|
+
# Generate the binary necessary for the OpenSSH public key.
|
366
|
+
binary = [7].pack("N")
|
367
|
+
binary += "ssh-rsa"
|
368
|
+
["e", "n"].each do |m|
|
369
|
+
val = public_key.send(m)
|
370
|
+
data = val.to_s(2)
|
371
|
+
|
372
|
+
first_byte = data[0,1].unpack("c").first
|
373
|
+
if val < 0
|
374
|
+
data[0] = [0x80 & first_byte].pack("c")
|
375
|
+
elsif first_byte < 0
|
376
|
+
data = 0.chr + data
|
377
|
+
end
|
378
|
+
|
379
|
+
binary += [data.length].pack("N") + data
|
380
|
+
end
|
381
|
+
|
382
|
+
openssh_key = "ssh-rsa #{Base64.encode64(binary).gsub("\n", "")} vagrant-share"
|
383
|
+
public_key = public_key.to_pem
|
384
|
+
return [public_key, private_key, openssh_key]
|
385
|
+
end
|
386
|
+
|
387
|
+
# Tests if the URL given is a valid URL for HTTP access.
|
388
|
+
#
|
389
|
+
# @return [Boolean]
|
390
|
+
def self.http_url?(url, **opts)
|
391
|
+
exceptions = [
|
392
|
+
Errno::EACCES,
|
393
|
+
Errno::ECONNREFUSED,
|
394
|
+
Net::HTTPBadResponse,
|
395
|
+
]
|
396
|
+
|
397
|
+
begin
|
398
|
+
args = {
|
399
|
+
method: :get,
|
400
|
+
url: url,
|
401
|
+
}
|
402
|
+
|
403
|
+
if opts[:secure]
|
404
|
+
args[:verify_ssl] = OpenSSL::SSL::VERIFY_NONE
|
405
|
+
end
|
406
|
+
|
407
|
+
retryable(on: exceptions, tries: 2) do
|
408
|
+
Timeout.timeout(2) do
|
409
|
+
RestClient::Request.execute(args)
|
410
|
+
end
|
411
|
+
end
|
412
|
+
return true
|
413
|
+
rescue Errno::EACCES
|
414
|
+
rescue Errno::ECONNREFUSED
|
415
|
+
rescue Errno::ECONNRESET
|
416
|
+
rescue Net::HTTPBadResponse
|
417
|
+
rescue RestClient::RequestTimeout
|
418
|
+
rescue RestClient::ServerBrokeConnection
|
419
|
+
rescue Timeout::Error
|
420
|
+
# All the above are bad and should return false.
|
421
|
+
# Below this are good exceptions.
|
422
|
+
rescue RestClient::ExceptionWithResponse
|
423
|
+
# This will catch any HTTP status code errors, which means
|
424
|
+
# there is an HTTP sever on the other end, so we should accept that.
|
425
|
+
return true
|
426
|
+
end
|
427
|
+
|
428
|
+
false
|
429
|
+
end
|
430
|
+
|
431
|
+
# Returns a {Vagrant::Machine} that is properly configured
|
432
|
+
# and can be started/stopped for connections.
|
433
|
+
#
|
434
|
+
# @return [Vagrant::Machine]
|
435
|
+
# @note This is used for Ngrok implementation
|
436
|
+
def self.share_machine(env, **opts)
|
437
|
+
provider = env.default_provider.to_sym
|
438
|
+
if ![:dummy, :virtualbox, :vmware_fusion, :vmware_workstation, :vmware_desktop].include?(provider)
|
439
|
+
raise Errors::ProxyMachineBadProvider, provider: provider.to_s
|
440
|
+
end
|
441
|
+
|
442
|
+
machine_name = opts.fetch(:name, "connect").to_sym
|
443
|
+
|
444
|
+
# Our "Vagrantfile" for the proxy machine
|
445
|
+
config = lambda do |c|
|
446
|
+
c.vm.box = "hashicorp/vagrant-share"
|
447
|
+
c.vm.box_version = ">= #{VERSION.segments[0,1].join(".")}, < #{VERSION.bump.version}"
|
448
|
+
c.vm.box_check_update = false
|
449
|
+
|
450
|
+
# Configure a DHCP network so that we can get an IP
|
451
|
+
if opts[:ip]
|
452
|
+
c.vm.network "private_network", ip: opts[:ip]
|
453
|
+
end
|
454
|
+
|
455
|
+
if opts[:port]
|
456
|
+
c.vm.network "forwarded_port", guest: opts[:port][:guest], host: opts[:port][:host]
|
457
|
+
end
|
458
|
+
|
459
|
+
c.vm.provider "virtualbox" do |v|
|
460
|
+
v.memory = opts[:memory] || 128
|
461
|
+
v.auto_nat_dns_proxy = false
|
462
|
+
v.check_guest_additions = false
|
463
|
+
v.name = "#{machine_name}-#{SecureRandom.uuid}"
|
464
|
+
end
|
465
|
+
|
466
|
+
# NOTE: Default the vmware instances to 256MB for memory. We
|
467
|
+
# can get away with much less on virtualbox but will get panics
|
468
|
+
# when using less on vmware
|
469
|
+
["vmware_fusion", "vmware_workstation", "vmware_desktop"].each do |vmware|
|
470
|
+
c.vm.provider vmware do |v|
|
471
|
+
v.vmx["memsize"] = opts[:memory] || 256
|
472
|
+
v.whitelist_verified = true
|
473
|
+
end
|
474
|
+
end
|
475
|
+
|
476
|
+
# Make our only VM the connect-proxy
|
477
|
+
c.vm.define machine_name.to_s
|
478
|
+
end
|
479
|
+
env.config_loader.set(machine_name, [["2", config]])
|
480
|
+
|
481
|
+
data_path = Pathname.new(Dir.mktmpdir)
|
482
|
+
vagrantfile = Vagrant::Vagrantfile.new(env.config_loader, [machine_name])
|
483
|
+
vagrantfile.machine(
|
484
|
+
machine_name, provider, env.boxes, data_path, env)
|
485
|
+
end
|
486
|
+
end
|
487
|
+
end
|
488
|
+
end
|