forward 0.2.1 → 0.3.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.
- 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
|