forward 0.2.1 → 0.3.0
Sign up to get free protection for your applications and to get access to all the features.
- data/lib/forward.rb +11 -9
- data/lib/forward/api/resource.rb +2 -2
- data/lib/forward/api/tunnel.rb +6 -14
- data/lib/forward/cli.rb +117 -80
- data/lib/forward/client.rb +9 -15
- data/lib/forward/config.rb +8 -6
- data/lib/forward/error.rb +2 -0
- data/lib/forward/tunnel.rb +3 -7
- data/lib/forward/version.rb +1 -1
- data/test/api/tunnel_test.rb +0 -20
- data/test/cli_test.rb +45 -13
- metadata +2 -2
data/lib/forward.rb
CHANGED
@@ -56,8 +56,9 @@ module Forward
|
|
56
56
|
end
|
57
57
|
|
58
58
|
def self.debug!
|
59
|
-
@
|
60
|
-
|
59
|
+
@logdev = STDOUT
|
60
|
+
@debug = true
|
61
|
+
log.level = Logger::DEBUG
|
61
62
|
end
|
62
63
|
|
63
64
|
def self.debug?
|
@@ -69,21 +70,22 @@ module Forward
|
|
69
70
|
end
|
70
71
|
|
71
72
|
def self.debug_remotely!
|
72
|
-
|
73
|
-
debug
|
73
|
+
@logdev = stringio_log
|
74
|
+
@debug = true
|
74
75
|
@debug_remotely = true
|
76
|
+
log.level = Logger::DEBUG
|
75
77
|
end
|
76
78
|
|
77
79
|
def self.debug_remotely?
|
78
|
-
@debug_remotely
|
80
|
+
@debug_remotely ||= false
|
79
81
|
end
|
80
82
|
|
81
|
-
def self.
|
82
|
-
|
83
|
+
def self.logdev
|
84
|
+
@logdev ||= '/dev/null'
|
83
85
|
end
|
84
86
|
|
85
|
-
def self.
|
86
|
-
@
|
87
|
+
def self.log
|
88
|
+
@log ||= Logger.new(logdev)
|
87
89
|
end
|
88
90
|
|
89
91
|
# Returns a string representing a detailed client version.
|
data/lib/forward/api/resource.rb
CHANGED
@@ -96,7 +96,7 @@ module Forward
|
|
96
96
|
end
|
97
97
|
|
98
98
|
# def self.dispatch_error(error)
|
99
|
-
# Forward.log(
|
99
|
+
# Forward.log.debug("Dispatching ResourceError: action: #{error.action} errors: #{error.errors.inspect}")
|
100
100
|
# method = :"#{error.action}_error"
|
101
101
|
#
|
102
102
|
# if respond_to? method
|
@@ -110,7 +110,7 @@ module Forward
|
|
110
110
|
|
111
111
|
def log(level, message)
|
112
112
|
unless self.class.to_s == 'Forward::Api::ClientLog'
|
113
|
-
Forward.log(level, message)
|
113
|
+
Forward.log.send(level.to_sym, message)
|
114
114
|
end
|
115
115
|
end
|
116
116
|
|
data/lib/forward/api/tunnel.rb
CHANGED
@@ -10,8 +10,9 @@ module Forward
|
|
10
10
|
:vhost => options[:host],
|
11
11
|
:client => Forward.client_string,
|
12
12
|
}
|
13
|
-
|
14
|
-
[
|
13
|
+
|
14
|
+
params[:subdomain] = options[:subdomain_prefix] if options.has_key?(:subdomain_prefix)
|
15
|
+
[ :cname, :username, :password, :no_auth ].each do |param|
|
15
16
|
params[param] = options[param] if options.has_key?(param)
|
16
17
|
end
|
17
18
|
|
@@ -27,15 +28,6 @@ module Forward
|
|
27
28
|
resource.get[:tunnels]
|
28
29
|
end
|
29
30
|
|
30
|
-
def self.destroy(id)
|
31
|
-
resource = Tunnel.new(:destroy)
|
32
|
-
resource.uri = "/api/v2/tunnels/#{id}"
|
33
|
-
|
34
|
-
resource.delete
|
35
|
-
rescue ResourceError => e
|
36
|
-
nil
|
37
|
-
end
|
38
|
-
|
39
31
|
def self.show(id)
|
40
32
|
resource = Tunnel.new(:show)
|
41
33
|
resource.uri = "/api/v2/tunnels/#{id}"
|
@@ -64,17 +56,17 @@ module Forward
|
|
64
56
|
end
|
65
57
|
|
66
58
|
def self.destroy_and_create(id, options)
|
67
|
-
Forward.log(
|
59
|
+
Forward.log.debug("Destroying tunnel: #{id}")
|
68
60
|
destroy(id)
|
69
61
|
puts "tunnel removed, now we're creating a new one"
|
70
62
|
create(options)
|
71
63
|
end
|
72
64
|
|
73
65
|
def self.error_on_create(error, options)
|
74
|
-
Forward.log(
|
66
|
+
Forward.log.debug("An error occured creating tunnel:\n#{error.inspect}")
|
75
67
|
|
76
68
|
if error.type == 'tunnel_limit_reached'
|
77
|
-
Forward.log(
|
69
|
+
Forward.log.debug('Tunnel limit reached')
|
78
70
|
ask_to_destroy(error.api_message, options)
|
79
71
|
elsif error.type =~ /(?:account_suspended|trial_expired)/i
|
80
72
|
Forward::Client.cleanup_and_exit!(error.api_message)
|
data/lib/forward/cli.rb
CHANGED
@@ -1,7 +1,10 @@
|
|
1
1
|
module Forward
|
2
2
|
class CLI
|
3
|
-
|
4
|
-
|
3
|
+
BASIC_AUTH_REGEX = /\A[^\s:]+:[^\s:]+\z/i
|
4
|
+
CNAME_REGEX = /\A[a-z0-9]+(?:[\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}\z/i
|
5
|
+
SUBDOMAIN_PREFIX_REGEX = /\A[a-z0-9]{1}[a-z0-9\-]+\z/i
|
6
|
+
USERNAME_REGEX = PASSWORD_REGEX = /\A[^\s]+\z/i
|
7
|
+
|
5
8
|
BANNER = <<-BANNER
|
6
9
|
Usage: forward <port> [options]
|
7
10
|
forward <host> [options]
|
@@ -20,7 +23,7 @@ module Forward
|
|
20
23
|
> forward 3000
|
21
24
|
Forward created at https://4ad3f-mycompany.fwd.wf
|
22
25
|
|
23
|
-
Assigning a subdomain:
|
26
|
+
Assigning a subdomain prefix:
|
24
27
|
|
25
28
|
> rails server &
|
26
29
|
> forward 3000 myapp
|
@@ -37,22 +40,11 @@ module Forward
|
|
37
40
|
BANNER
|
38
41
|
|
39
42
|
# Parse non-published options and remove them from ARGV, then
|
40
|
-
# parse published options and update the
|
43
|
+
# parse published options and update the options Hash with provided
|
41
44
|
# options and removes switches from ARGV.
|
42
|
-
def self.
|
43
|
-
Forward.log(
|
44
|
-
|
45
|
-
:host => '127.0.0.1',
|
46
|
-
:port => 80
|
47
|
-
}
|
48
|
-
|
49
|
-
if ARGV.include?('--debug')
|
50
|
-
Forward.debug!
|
51
|
-
ARGV.delete('--debug')
|
52
|
-
elsif ARGV.include?('--debug-remotely')
|
53
|
-
Forward.debug_remotely!
|
54
|
-
ARGV.delete('--debug-remotely')
|
55
|
-
end
|
45
|
+
def self.parse_cli_options
|
46
|
+
Forward.log.debug("Parsing options")
|
47
|
+
options = {}
|
56
48
|
|
57
49
|
@opts = OptionParser.new do |opts|
|
58
50
|
opts.banner = BANNER.gsub(/^ {6}/, '')
|
@@ -61,18 +53,18 @@ module Forward
|
|
61
53
|
opts.separator 'Options:'
|
62
54
|
|
63
55
|
opts.on('-a', '--auth [USER:PASS]', 'Protect this tunnel with HTTP Basic Auth.') do |credentials|
|
64
|
-
|
65
|
-
|
66
|
-
|
56
|
+
exit_with_error("Basic Auth: bad format, expecting USER:PASS") if credentials !~ BASIC_AUTH_REGEX
|
57
|
+
username, password = credentials.split(':')
|
58
|
+
options[:username] = username
|
59
|
+
options[:password] = password
|
67
60
|
end
|
68
61
|
|
69
62
|
opts.on('-A', '--no-auth', 'Disable authentication on this tunnel (if a default is set in your preferences)') do |credentials|
|
70
|
-
|
63
|
+
options[:no_auth] = true
|
71
64
|
end
|
72
65
|
|
73
|
-
opts.on('-c', '--cname [CNAME]', 'Allow access to this tunnel as
|
74
|
-
|
75
|
-
@options[:cname] = cname
|
66
|
+
opts.on('-c', '--cname [CNAME]', 'Allow access to this tunnel as CNAME (you will need to setup a CNAME entry on your DNS server).') do |cname|
|
67
|
+
options[:cname] = cname.downcase
|
76
68
|
end
|
77
69
|
|
78
70
|
opts.on( '-h', '--help', 'Display this help.' ) do
|
@@ -87,20 +79,54 @@ module Forward
|
|
87
79
|
end
|
88
80
|
|
89
81
|
@opts.parse!
|
82
|
+
|
83
|
+
options
|
84
|
+
end
|
85
|
+
|
86
|
+
# Returns a String file path for PWD/Forwardfile
|
87
|
+
def self.forwardfile_path
|
88
|
+
File.join(Dir.pwd, 'Forwardfile')
|
90
89
|
end
|
91
90
|
|
92
|
-
#
|
93
|
-
# the @options Hash with given credentials.
|
91
|
+
# Parse arguments from CLI and options from Forwardfile
|
94
92
|
#
|
95
|
-
#
|
96
|
-
# by a colon.
|
93
|
+
# args - An Array of command line arguments
|
97
94
|
#
|
98
|
-
# Returns
|
99
|
-
def self.
|
100
|
-
|
101
|
-
|
95
|
+
# Returns a Hash of options.
|
96
|
+
def self.parse_args_and_options(args)
|
97
|
+
options = {
|
98
|
+
:host => '127.0.0.1',
|
99
|
+
:port => 80
|
100
|
+
}
|
101
|
+
|
102
|
+
Forward.log.debug("Default options: `#{options.inspect}'")
|
103
|
+
|
104
|
+
if File.exist? forwardfile_path
|
105
|
+
options.merge!(parse_forwardfile)
|
106
|
+
Forward.log.debug("Forwardfile options: `#{options.inspect}'")
|
107
|
+
end
|
108
|
+
|
109
|
+
options.merge!(parse_cli_options)
|
102
110
|
|
103
|
-
|
111
|
+
forwarded, prefix = args[0..1]
|
112
|
+
options[:subdomain_prefix] = prefix unless prefix.nil?
|
113
|
+
options.merge!(parse_forwarded(forwarded))
|
114
|
+
Forward.log.debug("CLI options: `#{options.inspect}'")
|
115
|
+
|
116
|
+
options
|
117
|
+
end
|
118
|
+
|
119
|
+
# Parse a local Forwardfile (in the PWD) and return it as a Hash.
|
120
|
+
# Raise an error and exit if unable to parse or result isn't a Hash.
|
121
|
+
#
|
122
|
+
# Returns a Hash of the options found in the Forwardfile
|
123
|
+
def self.parse_forwardfile
|
124
|
+
options = YAML.load_file(forwardfile_path)
|
125
|
+
raise CLIError unless options.kind_of?(Hash)
|
126
|
+
|
127
|
+
options.symbolize_keys
|
128
|
+
rescue ArgumentError, SyntaxError, CLIError
|
129
|
+
exit_with_error("Unable to parse #{forwardfile_path}")
|
104
130
|
end
|
105
131
|
|
106
132
|
# Parses the arguments to determine if we're forwarding a port or host
|
@@ -110,12 +136,11 @@ module Forward
|
|
110
136
|
#
|
111
137
|
# Returns a Hash containing the forwarded host or port
|
112
138
|
def self.parse_forwarded(arg)
|
113
|
-
Forward.log(
|
139
|
+
Forward.log.debug("Forwarded: `#{arg}'")
|
114
140
|
forwarded = {}
|
115
141
|
|
116
142
|
if arg =~ /\A\d{1,5}\z/
|
117
143
|
port = arg.to_i
|
118
|
-
validate_port(port)
|
119
144
|
|
120
145
|
forwarded[:port] = port
|
121
146
|
elsif arg =~ /\A[-a-z0-9\.\-]+\z/i
|
@@ -123,7 +148,6 @@ module Forward
|
|
123
148
|
elsif arg =~ /\A[-a-z0-9\.\-]+:\d{1,5}\z/i
|
124
149
|
host, port = arg.split(':')
|
125
150
|
port = port.to_i
|
126
|
-
validate_port(port)
|
127
151
|
|
128
152
|
forwarded[:host] = host
|
129
153
|
forwarded[:port] = port
|
@@ -135,89 +159,102 @@ module Forward
|
|
135
159
|
# Checks to make sure the port being set is a number between 1 and 65535
|
136
160
|
# and exits with an error message if it's not.
|
137
161
|
#
|
138
|
-
# port -
|
162
|
+
# port - port number Integer
|
139
163
|
def self.validate_port(port)
|
140
|
-
Forward.log(
|
164
|
+
Forward.log.debug("Validating Port: `#{port}'")
|
141
165
|
unless port.between?(1, 65535)
|
142
166
|
exit_with_error "Invalid Port: #{port} is an invalid port number"
|
143
167
|
end
|
144
168
|
end
|
145
169
|
|
146
|
-
# Checks to make sure the
|
147
|
-
# and exits with an error message if
|
170
|
+
# Checks to make sure the username is a valid format
|
171
|
+
# and exits with an error message if not.
|
148
172
|
#
|
149
|
-
#
|
150
|
-
def self.
|
151
|
-
Forward.log(
|
152
|
-
|
153
|
-
|
154
|
-
|
173
|
+
# username - username String
|
174
|
+
def self.validate_username(username)
|
175
|
+
Forward.log.debug("Validating Username: `#{username}'")
|
176
|
+
exit_with_error("`#{username}' is an invalid username format") unless username =~ USERNAME_REGEX
|
177
|
+
end
|
178
|
+
|
179
|
+
# Checks to make sure the password is a valid format
|
180
|
+
# and exits with an error message if not.
|
181
|
+
#
|
182
|
+
# password - password String
|
183
|
+
def self.validate_password(password)
|
184
|
+
Forward.log.debug("Validating Password: `#{password}'")
|
185
|
+
exit_with_error("`#{password}' is an invalid password format") unless password =~ PASSWORD_REGEX
|
155
186
|
end
|
156
187
|
|
157
188
|
# Checks to make sure the cname is in the correct format and exits with an
|
158
189
|
# error message if it isn't.
|
159
190
|
#
|
160
|
-
# cname -
|
191
|
+
# cname - cname String
|
161
192
|
def self.validate_cname(cname)
|
162
|
-
Forward.log(
|
193
|
+
Forward.log.debug("Validating CNAME: `#{cname}'")
|
163
194
|
exit_with_error("`#{cname}' is an invalid domain format") unless cname =~ CNAME_REGEX
|
164
195
|
end
|
165
196
|
|
166
|
-
#
|
197
|
+
# Checks to make sure the subdomain prefix is in the correct format
|
198
|
+
# and exits with an error message if it isn't.
|
167
199
|
#
|
168
|
-
#
|
169
|
-
|
170
|
-
|
171
|
-
|
172
|
-
validate_subdomain(subdomain)
|
173
|
-
{ :subdomain => subdomain }
|
200
|
+
# prefix - subdomain prefix String
|
201
|
+
def self.validate_subdomain_prefix(prefix)
|
202
|
+
Forward.log.debug("Validating Subdomain Prefix: `#{prefix}'")
|
203
|
+
exit_with_error("`#{prefix}' is an invalid subdomain prefix format") unless prefix =~ SUBDOMAIN_PREFIX_REGEX
|
174
204
|
end
|
175
205
|
|
176
|
-
#
|
177
|
-
# error message if it isn't.
|
206
|
+
# Validate all options in options Hash.
|
178
207
|
#
|
179
|
-
#
|
180
|
-
def self.
|
181
|
-
Forward.log(
|
182
|
-
|
183
|
-
|
184
|
-
|
185
|
-
|
186
|
-
|
208
|
+
# options - the options Hash
|
209
|
+
def self.validate_options(options)
|
210
|
+
Forward.log.debug("Validating options: `#{options.inspect}'")
|
211
|
+
options.each do |key, value|
|
212
|
+
next if value.nil?
|
213
|
+
validate_method = :"validate_#{key}"
|
214
|
+
send(validate_method, value) if respond_to?(validate_method)
|
215
|
+
end
|
187
216
|
end
|
188
217
|
|
189
218
|
# Asks for the user's email and password and puts them in a Hash.
|
190
219
|
#
|
191
|
-
# Returns a Hash with the email and password
|
220
|
+
# Returns a Hash with the email and password
|
192
221
|
def self.authenticate
|
193
222
|
puts 'Enter your email and password'
|
194
223
|
email = ask('email: ').chomp
|
195
224
|
password = ask('password: ') { |q| q.echo = false }.chomp
|
196
|
-
Forward.log(
|
225
|
+
Forward.log.debug("Authenticating User: `#{email}:#{password.gsub(/./, 'x')}'")
|
197
226
|
|
198
227
|
{ :email => email, :password => password }
|
199
228
|
end
|
200
229
|
|
201
|
-
# Parses various options and arguments, validates everything to ensure
|
202
|
-
# we're safe to proceed, and finally passes
|
230
|
+
# Parses various options and arguments, validates everything to ensure
|
231
|
+
# we're safe to proceed, and finally passes options to the Client.
|
203
232
|
def self.run(args)
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
208
|
-
|
233
|
+
::HighLine.use_color = false if Forward::Config.windows?
|
234
|
+
if ARGV.include?('--debug')
|
235
|
+
Forward.debug!
|
236
|
+
ARGV.delete('--debug')
|
237
|
+
elsif ARGV.include?('--rdebug')
|
238
|
+
Forward.debug_remotely!
|
239
|
+
ARGV.delete('--rdebug')
|
240
|
+
end
|
241
|
+
|
242
|
+
Forward.log.debug("Starting forward v#{Forward::VERSION}")
|
243
|
+
|
244
|
+
options = parse_args_and_options(args)
|
209
245
|
|
210
|
-
|
246
|
+
validate_options(options)
|
247
|
+
print_usage_and_exit if args.empty? && !File.exist?(forwardfile_path)
|
211
248
|
|
212
|
-
Client.start(
|
249
|
+
Client.start(options)
|
213
250
|
end
|
214
251
|
|
215
252
|
# Colors an error message red and displays it.
|
216
253
|
#
|
217
|
-
# message -
|
254
|
+
# message - error message String
|
218
255
|
def self.exit_with_error(message)
|
219
|
-
Forward.log(
|
220
|
-
puts
|
256
|
+
Forward.log.fatal(message)
|
257
|
+
puts HighLine.color(message, :red)
|
221
258
|
exit 1
|
222
259
|
end
|
223
260
|
|
data/lib/forward/client.rb
CHANGED
@@ -15,14 +15,14 @@ module Forward
|
|
15
15
|
|
16
16
|
# Sets up a Tunnel instance and adds it to the Client.
|
17
17
|
def setup_tunnel
|
18
|
-
Forward.log(
|
18
|
+
Forward.log.debug('Setting up tunnel')
|
19
19
|
@tunnel = Forward::Tunnel.new(self.options)
|
20
20
|
if @tunnel.id
|
21
21
|
@tunnel.poll_status
|
22
22
|
else
|
23
23
|
Forward::Client.cleanup_and_exit!('Unable to create a tunnel. If this continues contact support@forwardhq.com')
|
24
24
|
end
|
25
|
-
Forward.log(
|
25
|
+
Forward.log.debug("Tunnel setup: #{@tunnel.inspect}")
|
26
26
|
|
27
27
|
@tunnel
|
28
28
|
end
|
@@ -50,10 +50,10 @@ module Forward
|
|
50
50
|
end
|
51
51
|
|
52
52
|
def self.forwarding_message(tunnel)
|
53
|
-
remote = "
|
53
|
+
remote = HighLine.color("https://#{@tunnel.subdomain}.fwd.wf", :underline)
|
54
54
|
|
55
55
|
unless tunnel.cname.nil? || tunnel.cname.empty?
|
56
|
-
remote <<
|
56
|
+
remote << ' and '<< HighLine.color("http://#{@tunnel.cname}", :underline)
|
57
57
|
end
|
58
58
|
|
59
59
|
if !tunnel.vhost.nil? && tunnel.vhost !~ /\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3}/
|
@@ -67,25 +67,24 @@ module Forward
|
|
67
67
|
end
|
68
68
|
|
69
69
|
def self.start(options = {})
|
70
|
-
Forward.log(
|
70
|
+
Forward.log.debug('Starting client')
|
71
71
|
trap(:INT) { cleanup_and_exit!('closing tunnel and exiting...') }
|
72
72
|
|
73
73
|
Forward.client = @client = Client.new(options)
|
74
74
|
@tunnel = @client.setup_tunnel
|
75
75
|
@session = Net::SSH.start(@tunnel.tunneler, Forward.ssh_user, @client.ssh_options)
|
76
76
|
|
77
|
-
Forward.log(
|
78
|
-
# puts "Forwarding port #{@tunnel.hostport} at \033[04mhttps://#{@tunnel.subdomain}.fwd.wf\033[0m\nCtrl-C to stop forwarding"
|
77
|
+
Forward.log.debug("Starting remote forward at #{@tunnel.subdomain}.fwd.wf")
|
79
78
|
puts forwarding_message(@tunnel)
|
80
79
|
|
81
80
|
@session.forward.remote(@tunnel.hostport, @tunnel.host, @tunnel.port)
|
82
81
|
@session.loop { watch_session(@session) }
|
83
82
|
|
84
83
|
rescue Net::SSH::AuthenticationFailed => e
|
85
|
-
Forward.log(
|
84
|
+
Forward.log.fatal("SSH Auth failed `#{e}'")
|
86
85
|
cleanup_and_exit!("Authentication failed, try deleting `#{Forward::Config.config_path}' and giving it another go. If the problem continues, contact support@forwardhq.com")
|
87
86
|
rescue => e
|
88
|
-
Forward.log(
|
87
|
+
Forward.log.fatal("#{e.message}\n#{e.backtrace.join("\n")}")
|
89
88
|
cleanup_and_exit!("You've been disconnected...")
|
90
89
|
end
|
91
90
|
|
@@ -93,13 +92,8 @@ module Forward
|
|
93
92
|
puts message.chomp
|
94
93
|
|
95
94
|
@session.close if @session && !@session.closed?
|
96
|
-
if @client && @client.tunnel && @client.tunnel.id
|
97
|
-
Forward.log(:debug, "Cleaning up tunnel: `#{@client.tunnel.id}'")
|
98
|
-
@client.tunnel.cleanup
|
99
|
-
@client.tunnel = nil
|
100
|
-
end
|
101
95
|
|
102
|
-
Forward.log(
|
96
|
+
Forward.log.debug('Exiting')
|
103
97
|
send_debug_log if Forward.debug_remotely?
|
104
98
|
ensure
|
105
99
|
Thread.main.exit
|
data/lib/forward/config.rb
CHANGED
@@ -25,7 +25,7 @@ module Forward
|
|
25
25
|
#
|
26
26
|
# Returns the updated Config object.
|
27
27
|
def update(attributes)
|
28
|
-
Forward.log(
|
28
|
+
Forward.log.debug('Updating Config')
|
29
29
|
attributes.each do |key, value|
|
30
30
|
self.send(:"#{key}=", value)
|
31
31
|
end
|
@@ -43,7 +43,7 @@ module Forward
|
|
43
43
|
# Validate that the required values are in the Config.
|
44
44
|
# Raises a config error if values are missing.
|
45
45
|
def validate
|
46
|
-
Forward.log(
|
46
|
+
Forward.log.debug('Validating Config')
|
47
47
|
attributes = [:api_token, :private_key]
|
48
48
|
errors = []
|
49
49
|
|
@@ -65,7 +65,7 @@ module Forward
|
|
65
65
|
#
|
66
66
|
# Returns the Config object.
|
67
67
|
def write
|
68
|
-
Forward.log(
|
68
|
+
Forward.log.debug('Writing Config')
|
69
69
|
key_folder = File.dirname(Config.key_path)
|
70
70
|
config_data = to_hash.delete_if { |k,v| !CONFIG_FILE_VALUES.include?(k) }
|
71
71
|
|
@@ -141,7 +141,7 @@ module Forward
|
|
141
141
|
#
|
142
142
|
# Returns the new Config object.
|
143
143
|
def self.create
|
144
|
-
Forward.log(
|
144
|
+
Forward.log.debug('Creating Config')
|
145
145
|
if @updating_config || ask('Already have an account with Forward? ').chomp =~ /\Ay/i
|
146
146
|
config = Config.new
|
147
147
|
email, password = CLI.authenticate.values_at(:email, :password)
|
@@ -151,7 +151,9 @@ module Forward
|
|
151
151
|
|
152
152
|
config.write
|
153
153
|
else
|
154
|
-
|
154
|
+
message = "You'll need a Forward account first. You can create one at "
|
155
|
+
message << HighLine.color('https://forwardhq.com', :underline)
|
156
|
+
Client.cleanup_and_exit!(message)
|
155
157
|
end
|
156
158
|
end
|
157
159
|
|
@@ -161,7 +163,7 @@ module Forward
|
|
161
163
|
#
|
162
164
|
# Returns the Config object.
|
163
165
|
def self.load
|
164
|
-
Forward.log(
|
166
|
+
Forward.log.debug('Loading Config')
|
165
167
|
config = Config.new
|
166
168
|
|
167
169
|
raise ConfigError, "Unable to find a forward config file at `#{config_path}'" unless Config.present?
|
data/lib/forward/error.rb
CHANGED
@@ -1,6 +1,8 @@
|
|
1
1
|
module Forward
|
2
2
|
# An error occurred with the API
|
3
3
|
class ApiError < StandardError; end
|
4
|
+
# An error occurred with the CLI
|
5
|
+
class CLIError < StandardError; end
|
4
6
|
# An error occurred with the Client
|
5
7
|
class ClientError < StandardError; end
|
6
8
|
# An error occurred with the Config
|
data/lib/forward/tunnel.rb
CHANGED
@@ -45,11 +45,11 @@ module Forward
|
|
45
45
|
Thread.new {
|
46
46
|
loop do
|
47
47
|
if @timeout && !@timeout.zero? && @inactive_for > @timeout
|
48
|
-
Forward.log(
|
48
|
+
Forward.log.debug("Session closing due to inactivity `#{@inactive_for}' seconds")
|
49
49
|
Client.cleanup_and_exit!("Tunnel has been inactive for #{@inactive_for} seconds, exiting...")
|
50
50
|
elsif Forward::Api::Tunnel.show(@id).nil?
|
51
51
|
Client.current.tunnel = nil
|
52
|
-
Forward.log(
|
52
|
+
Forward.log.debug("Tunnel destroyed, closing session")
|
53
53
|
Client.cleanup_and_exit!
|
54
54
|
else
|
55
55
|
sleep CHECK_INTERVAL
|
@@ -59,11 +59,7 @@ module Forward
|
|
59
59
|
end
|
60
60
|
}
|
61
61
|
end
|
62
|
-
|
63
|
-
def cleanup
|
64
|
-
Forward::Api::Tunnel.destroy(@id) if @id
|
65
|
-
end
|
66
|
-
|
62
|
+
|
67
63
|
def active?
|
68
64
|
@active
|
69
65
|
end
|
data/lib/forward/version.rb
CHANGED
data/test/api/tunnel_test.rb
CHANGED
@@ -87,24 +87,4 @@ describe Forward::Api::Tunnel do
|
|
87
87
|
dev_null { Forward::Api::Tunnel.create(:port => 3000) }
|
88
88
|
end
|
89
89
|
|
90
|
-
it 'destroys a tunnel and returns the attributes' do
|
91
|
-
fake_body = { :_id => '1', :subdomain => 'foo', :port => 56789 }
|
92
|
-
|
93
|
-
stub_api_request(:delete, '/api/v2/tunnels/1', :body => fake_body.to_json)
|
94
|
-
|
95
|
-
response = Forward::Api::Tunnel.destroy(1)
|
96
|
-
|
97
|
-
fake_body.each do |key, value|
|
98
|
-
response[key].must_equal fake_body[key]
|
99
|
-
end
|
100
|
-
end
|
101
|
-
|
102
|
-
it 'gracefully handles the error if destroy has errors' do
|
103
|
-
fake_body = { :type => 'api_error' }
|
104
|
-
|
105
|
-
stub_api_request(:delete, '/api/v2/tunnels/1', :body => fake_body.to_json, :status => [ 422, 'Unprocessable Entity' ])
|
106
|
-
|
107
|
-
Forward::Api::Tunnel.destroy(1).must_be_nil
|
108
|
-
end
|
109
|
-
|
110
90
|
end
|
data/test/cli_test.rb
CHANGED
@@ -22,23 +22,55 @@ describe Forward::CLI do
|
|
22
22
|
forwarded[:port].must_equal 88
|
23
23
|
end
|
24
24
|
|
25
|
-
it '
|
26
|
-
|
27
|
-
|
25
|
+
it 'exits if username is invalid' do
|
26
|
+
[ 'foo ', ' asdfasdf ', 'fooo bar' ].each do |username|
|
27
|
+
lambda {
|
28
|
+
dev_null { Forward::CLI.validate_username(username) }
|
29
|
+
}.must_raise SystemExit
|
30
|
+
end
|
31
|
+
end
|
28
32
|
|
29
|
-
|
30
|
-
|
31
|
-
|
33
|
+
it 'validates a good username' do
|
34
|
+
[ 'foo', 'asdflkj3r&)(#@#)', 'DF#R::#SFSDF' ].each do |username|
|
35
|
+
Forward::CLI.validate_username(username).must_be_nil
|
36
|
+
end
|
32
37
|
end
|
33
38
|
|
34
|
-
it '
|
35
|
-
[ '
|
39
|
+
it 'exits if password is invalid' do
|
40
|
+
[ 'foo ', ' asdfasdf ', 'fooo bar' ].each do |password|
|
36
41
|
lambda {
|
37
|
-
dev_null { Forward::CLI.
|
42
|
+
dev_null { Forward::CLI.validate_password(password) }
|
38
43
|
}.must_raise SystemExit
|
39
44
|
end
|
40
45
|
end
|
41
46
|
|
47
|
+
it 'validates a good password' do
|
48
|
+
[ 'foo', 'asdflkj3r&)(#@#)', 'DF#R::#SFSDF' ].each do |password|
|
49
|
+
Forward::CLI.validate_password(password).must_be_nil
|
50
|
+
end
|
51
|
+
end
|
52
|
+
|
53
|
+
it 'parses a Forwardfile' do
|
54
|
+
yaml = YAML.dump(:auth => 'username:password')
|
55
|
+
File.open('Forwardfile', 'w') { |f| f.write(yaml) }
|
56
|
+
|
57
|
+
options = Forward::CLI.parse_forwardfile
|
58
|
+
options.has_key?(:auth).must_equal true
|
59
|
+
end
|
60
|
+
|
61
|
+
it 'exits if a Forwardfile does not parse to a Hash' do
|
62
|
+
yaml = YAML.dump('username:password')
|
63
|
+
File.open('Forwardfile', 'w') { |f| f.write(yaml) }
|
64
|
+
|
65
|
+
lambda {
|
66
|
+
dev_null { Forward::CLI.parse_forwardfile }
|
67
|
+
}.must_raise SystemExit
|
68
|
+
end
|
69
|
+
|
70
|
+
it 'overloads Forwardfile options via commandline' do
|
71
|
+
flunk
|
72
|
+
end
|
73
|
+
|
42
74
|
it 'doesnt exit on valid ports' do
|
43
75
|
Forward::CLI.validate_port(69).must_be_nil
|
44
76
|
Forward::CLI.validate_port(3000).must_be_nil
|
@@ -67,16 +99,16 @@ describe Forward::CLI do
|
|
67
99
|
end
|
68
100
|
end
|
69
101
|
|
70
|
-
it 'doesnt exit on valid subdomains' do
|
102
|
+
it 'doesnt exit on valid subdomains prefix' do
|
71
103
|
[ 'foo', 'whatever-foo', 'asdf40' ].each do |subdomain|
|
72
|
-
Forward::CLI.
|
104
|
+
Forward::CLI.validate_subdomain_prefix(subdomain).must_be_nil
|
73
105
|
end
|
74
106
|
end
|
75
107
|
|
76
|
-
it 'validates subdomain and exits if invalid' do
|
108
|
+
it 'validates subdomain prefix and exits if invalid' do
|
77
109
|
[ '-asdf', 'adsf#$)' ].each do |subdomain|
|
78
110
|
lambda {
|
79
|
-
dev_null { Forward::CLI.
|
111
|
+
dev_null { Forward::CLI.validate_subdomain_prefix(subdomain) }
|
80
112
|
}.must_raise SystemExit
|
81
113
|
end
|
82
114
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: forward
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.
|
4
|
+
version: 0.3.0
|
5
5
|
prerelease:
|
6
6
|
platform: ruby
|
7
7
|
authors:
|
@@ -9,7 +9,7 @@ authors:
|
|
9
9
|
autorequire:
|
10
10
|
bindir: bin
|
11
11
|
cert_chain: []
|
12
|
-
date: 2013-01-
|
12
|
+
date: 2013-01-22 00:00:00.000000000 Z
|
13
13
|
dependencies:
|
14
14
|
- !ruby/object:Gem::Dependency
|
15
15
|
name: json
|