Class: Forward::CLI

Inherits:
Object
  • Object
show all
Defined in:
lib/forward/cli.rb

Constant Summary collapse

BASIC_AUTH_REGEX =
/\A[^\s:]+:[^\s:]+\z/i
CNAME_REGEX =
/\A[a-z0-9]+(?:[\-\.]{1}[a-z0-9]+)*\.[a-z]{2,5}\z/i
SUBDOMAIN_PREFIX_REGEX =
/\A[a-z0-9]{1}[a-z0-9\-]+\z/i
USERNAME_REGEX =
PASSWORD_REGEX = /\A[^\s]+\z/i
"Usage: forward <port> [options]\n       forward <host> [options]\n       forward <host:port> [options]\n\nDescription:\n\n   Share a server running on localhost:port over the web by tunneling\n   through Forward. A URL is created for each tunnel.\n\nSimple example:\n\n  # You are developing a Rails site.\n\n  > rails server &\n  > forward 3000\n    Forward created at https://mycompany.fwd.wf\n\nAssigning a static subdomain prefix:\n\n  > rails server &\n  > forward 3000 myapp\n    Forward created at https://myapp-mycompany.fwd.wf\n\nVirtual Host example:\n\n  # You are already running something on port 80 that uses\n  # virtual host names.\n\n  > forward mysite.dev\n    Forward created at https://mycompany.fwd.wf\n\n"

Class Method Summary collapse

Class Method Details

.authenticateObject

Asks for the user’s email and password and puts them in a Hash.

Returns a Hash with the email and password



225
226
227
228
229
230
231
232
# File 'lib/forward/cli.rb', line 225

def self.authenticate
  puts 'Enter your email and password'
  email    = ask('email: ').chomp
  password = ask('password: ') { |q| q.echo = false }.chomp
  Forward.log.debug("Authenticating User: `#{email}:#{password.gsub(/./, 'x')}'")

  { :email => email, :password => password }
end

.exit_with_error(message) ⇒ Object

Colors an error message red and displays it.

message - error message String



268
269
270
271
272
# File 'lib/forward/cli.rb', line 268

def self.exit_with_error(message)
  Forward.log.fatal(message)
  puts HighLine.color(message, :red)
  exit 1
end

.forwardfile_pathObject

Returns a String file path for PWD/Forwardfile



91
92
93
# File 'lib/forward/cli.rb', line 91

def self.forwardfile_path
  File.join(Dir.pwd, 'Forwardfile')
end

.logoutObject

Remove .forward file and SSH key (log a user out)



235
236
237
238
239
240
241
# File 'lib/forward/cli.rb', line 235

def self.logout
  FileUtils.rm_f(Config.config_path)
  FileUtils.rm_f(Config.key_path)

  puts "You've been logged out. You'll be asked to log back in when you create a new tunnel."
  exit
end

.parse_args_and_options(args) ⇒ Object

Parse arguments from CLI and options from Forwardfile

args - An Array of command line arguments

Returns a Hash of options.



100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
# File 'lib/forward/cli.rb', line 100

def self.parse_args_and_options(args)
  options = {
    :host   => '127.0.0.1',
    :port   => 80
  }

  Forward.log.debug("Default options: `#{options.inspect}'")

  if File.exist? forwardfile_path
    options.merge!(parse_forwardfile)
    Forward.log.debug("Forwardfile options: `#{options.inspect}'")
  end

  options.merge!(parse_cli_options(args))

  forwarded, prefix = args[0..1]
  options[:subdomain_prefix] = prefix unless prefix.nil?
  options.merge!(parse_forwarded(forwarded))
  Forward.log.debug("CLI options: `#{options.inspect}'")

  options
end

.parse_cli_options(args) ⇒ Object

Parse non-published options and remove them from ARGV, then parse published options and update the options Hash with provided options and removes switches from ARGV.



45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
# File 'lib/forward/cli.rb', line 45

def self.parse_cli_options(args)
  Forward.log.debug("Parsing options")
  options = {}

  @opts = OptionParser.new do |opts|
    opts.banner = BANNER.gsub(/^ {6}/, '')

    opts.separator ''
    opts.separator 'Options:'

    opts.on('-a', '--auth [USER:PASS]', 'Protect this tunnel with HTTP Basic Auth.') do |credentials|
      exit_with_error("Basic Auth: bad format, expecting USER:PASS") if credentials !~ BASIC_AUTH_REGEX
      username, password  = credentials.split(':')
      options[:username] = username
      options[:password] = password
    end

    opts.on('-A', '--no-auth', 'Disable authentication on this tunnel (if a default is set in your preferences)') do |credentials|
      options[:no_auth] = true
    end

    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|
      options[:cname] = cname.downcase
    end

    opts.on('--logout', 'Remove credentials to allow logging in as another user.' ) do
      logout
    end

    opts.on( '-h', '--help', 'Display this help.' ) do
      puts opts
      exit
    end

    opts.on('-v', '--version', 'Display version number.') do
      puts "forward #{VERSION}"
      exit
    end
  end

  @opts.parse!(args)

  options
end

.parse_forwarded(arg) ⇒ Object

Parses the arguments to determine if we’re forwarding a port or host and validates the port or host and updates @options if valid.

arg - A String representing the port or host.

Returns a Hash containing the forwarded host or port



142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
# File 'lib/forward/cli.rb', line 142

def self.parse_forwarded(arg)
  Forward.log.debug("Forwarded: `#{arg}'")
  forwarded = {}

  if arg =~ /\A\d{1,5}\z/
    port = arg.to_i

    forwarded[:port] = port
  elsif arg =~ /\A[-a-z0-9\.\-]+\z/i
    forwarded[:host] = arg
  elsif arg =~ /\A[-a-z0-9\.\-]+:\d{1,5}\z/i
    host, port = arg.split(':')
    port       = port.to_i

    forwarded[:host] = host
    forwarded[:port] = port
  end

  forwarded
end

.parse_forwardfileObject

Parse a local Forwardfile (in the PWD) and return it as a Hash. Raise an error and exit if unable to parse or result isn’t a Hash.

Returns a Hash of the options found in the Forwardfile



127
128
129
130
131
132
133
134
# File 'lib/forward/cli.rb', line 127

def self.parse_forwardfile
  options = YAML.load_file(forwardfile_path)
  raise CLIError unless options.kind_of?(Hash)

  options.symbolize_keys
rescue ArgumentError, SyntaxError, CLIError
  exit_with_error("Unable to parse #{forwardfile_path}")
end

Print the usage banner and Exit Code 0.



275
276
277
278
# File 'lib/forward/cli.rb', line 275

def self.print_usage_and_exit
  puts @opts
  exit
end

.run(args) ⇒ Object

Parses various options and arguments, validates everything to ensure we’re safe to proceed, and finally passes options to the Client.



245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
# File 'lib/forward/cli.rb', line 245

def self.run(args)
  ::HighLine.use_color = false if Forward.windows?
  if ARGV.include?('--debug')
    Forward.debug!
    ARGV.delete('--debug')
  elsif ARGV.include?('--rdebug')
    Forward.debug_remotely!
    ARGV.delete('--rdebug')
  end

  Forward.log.debug("Starting forward v#{Forward::VERSION}")

  options = parse_args_and_options(args)

  validate_options(options)
  print_usage_and_exit if args.empty? && !File.exist?(forwardfile_path)

  Client.start(options)
end

.validate_cname(cname) ⇒ Object

Checks to make sure the cname is in the correct format and exits with an error message if it isn’t.

cname - cname String



196
197
198
199
# File 'lib/forward/cli.rb', line 196

def self.validate_cname(cname)
  Forward.log.debug("Validating CNAME: `#{cname}'")
  exit_with_error("`#{cname}' is an invalid domain format") unless cname =~ CNAME_REGEX
end

.validate_options(options) ⇒ Object

Validate all options in options Hash.

options - the options Hash



213
214
215
216
217
218
219
220
# File 'lib/forward/cli.rb', line 213

def self.validate_options(options)
  Forward.log.debug("Validating options: `#{options.inspect}'")
  options.each do |key, value|
    next if value.nil?
    validate_method = :"validate_#{key}"
    send(validate_method, value) if respond_to?(validate_method)
  end
end

.validate_password(password) ⇒ Object

Checks to make sure the password is a valid format and exits with an error message if not.

password - password String



187
188
189
190
# File 'lib/forward/cli.rb', line 187

def self.validate_password(password)
  Forward.log.debug("Validating Password: `#{password}'")
  exit_with_error("`#{password}' is an invalid password format") unless password =~ PASSWORD_REGEX
end

.validate_port(port) ⇒ Object

Checks to make sure the port being set is a number between 1 and 65535 and exits with an error message if it’s not.

port - port number Integer



167
168
169
170
171
172
# File 'lib/forward/cli.rb', line 167

def self.validate_port(port)
  Forward.log.debug("Validating Port: `#{port}'")
  unless port.between?(1, 65535)
    exit_with_error "Invalid Port: #{port} is an invalid port number"
  end
end

.validate_subdomain_prefix(prefix) ⇒ Object

Checks to make sure the subdomain prefix is in the correct format and exits with an error message if it isn’t.

prefix - subdomain prefix String



205
206
207
208
# File 'lib/forward/cli.rb', line 205

def self.validate_subdomain_prefix(prefix)
  Forward.log.debug("Validating Subdomain Prefix: `#{prefix}'")
  exit_with_error("`#{prefix}' is an invalid subdomain prefix format") unless prefix =~ SUBDOMAIN_PREFIX_REGEX
end

.validate_username(username) ⇒ Object

Checks to make sure the username is a valid format and exits with an error message if not.

username - username String



178
179
180
181
# File 'lib/forward/cli.rb', line 178

def self.validate_username(username)
  Forward.log.debug("Validating Username: `#{username}'")
  exit_with_error("`#{username}' is an invalid username format") unless username =~ USERNAME_REGEX
end