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,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
|