devtunnel 0.0.1 → 1.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: 74585f8cbe97dd0296d319a555829ac09287cfa4
4
- data.tar.gz: 20c128695ef4246c22a52e28d3c659070efe0160
3
+ metadata.gz: 8310b1def1d120f7914349a6b78a38b2f4eac9ad
4
+ data.tar.gz: 08e4be19e03a9eb02153934db0b1c420a852ff95
5
5
  SHA512:
6
- metadata.gz: c7d24793ab6470191918c0c4921f39568775d215023f75b0966ec74f01237231cc5f9abe3af66b5c1f33f9d43d8f8cbeaf498a98bbe476dec6b240c495f47c03
7
- data.tar.gz: 47885134987032dd82307aea3ac1a2eb207c28cc3b0759fb6eef5d2260096659022d93f4150690ebb3760351938023eea5da3396c38c87d7f3e939ea5683690b
6
+ metadata.gz: 80ce31bc5c9e1c84ebfeac48736531cc17230191e8ad57ef3032983337421efdcb7ea6a1e44e233e01837c8e26928e6bd326efd6cfd9e299f14f568d6cdc6458
7
+ data.tar.gz: 9ac6cec42c64010789253ff734efc346dade4adfe47017b6d6fd5ceae53210df37e2c23166877f15a6d9658cd3d0a6ee61f1e90c311a884ae74352c49562b220
@@ -0,0 +1,79 @@
1
+ #!/usr/bin/env ruby
2
+ #
3
+ require 'devtunnel'
4
+ require 'commander/import'
5
+ require 'io/console'
6
+
7
+ program :version, '0.0.1'
8
+ program :description, 'Command-line interface for https://devtunnel.madebymarket.com.'
9
+
10
+ default_command :upload
11
+
12
+ command :signup do |c|
13
+ c.syntax = 'devtunnel signup, [options]'
14
+ c.summary = ''
15
+ c.description = ''
16
+ c.example 'description', 'command example'
17
+ c.action do |args, options|
18
+ puts "Register for a free devtunnel account using your email address and a password."
19
+ email = ask "email address: "
20
+ account = Devtunnel::Account.signup email
21
+ if account.success?
22
+ account.write_creds
23
+ puts "Account created, saving your credentials to #{File.expand_path("~/.devtunnel_auth")}."
24
+ puts "Once you have confirmed your account, you can run 'devtunnel forward --port 8080' to forward port 8080 through your devtunnel."
25
+ else
26
+ puts account.errors
27
+ end
28
+ end
29
+ end
30
+
31
+ command :login do |c|
32
+ c.syntax = 'devtunnel login, [options]'
33
+ c.summary = ''
34
+ c.description = ''
35
+ c.example 'description', 'command example'
36
+ c.action do |args, options|
37
+ email = ask "Enter your email address: "
38
+ pw = password "Enter your devtunnel password (will not be shown):"
39
+ account = Devtunnel::Account.login email, pw
40
+ if account.success?
41
+ account.write_creds
42
+ puts "Logged in, saving your credentials to #{File.expand_path("~/.devtunnel_auth")}"
43
+ else
44
+ puts account.errors
45
+ end
46
+ end
47
+ end
48
+
49
+ command :forward do |c|
50
+ c.syntax = 'devtunnel upload, [options]'
51
+ c.summary = ''
52
+ c.description = ''
53
+ c.example 'description', 'command example'
54
+ c.option '--file STRING', String, 'File to upload'
55
+ c.action do |args, options|
56
+ account = Devtunnel::Account.from_creds
57
+ unless options.file
58
+ puts "Syntax: devtunnel --file /your/file.pdf"
59
+ exit 0
60
+ end
61
+ Devtunnel.api_key = account.api_key
62
+
63
+ puts "forwarding port. Other people can connect at: xyz"
64
+
65
+ end
66
+ end
67
+
68
+ command :logout do |c|
69
+ c.syntax = 'devtunnel logout [options]'
70
+ c.summary = ''
71
+ c.description = ''
72
+ c.example 'description', 'command example'
73
+ c.option '--some-switch', 'Some switch that does something'
74
+ c.action do |args, options|
75
+ Devtunnel::Account.logout
76
+ puts "You've been logged out successfully."
77
+ end
78
+ end
79
+
@@ -14,7 +14,7 @@ Gem::Specification.new do |spec|
14
14
  spec.license = "MIT"
15
15
 
16
16
  spec.files = `git ls-files`.split($/)
17
- spec.executables = spec.files.grep(%r{^bin/}) { |f| File.basename(f) }
17
+ spec.executables = spec.files.grep(%r{^bin/}) { |f| puts f; File.basename(f) }
18
18
  spec.test_files = spec.files.grep(%r{^(test|spec|features)/})
19
19
  spec.require_paths = ["lib"]
20
20
 
@@ -1,5 +1,25 @@
1
- require "devtunnel/version"
1
+ require_relative "devtunnel/version"
2
+ require_relative "devtunnel/api"
3
+ require_relative "devtunnel/api_response"
4
+ require_relative "devtunnel/entity"
5
+ require_relative "devtunnel/api_error"
6
+ require "rest_client"
7
+ require "json"
2
8
 
3
9
  module Devtunnel
4
- # Your code goes here...
10
+ @api_key = nil
11
+ @api_url = "https://devtunnel.madebymarket.com/api/v1"
12
+
13
+ def self.api_key=(api_key)
14
+ @api_key = api_key
15
+ end
16
+ def self.api_key
17
+ @api_key
18
+ end
19
+ def self.api_url=(api_url)
20
+ @api_url = api_url
21
+ end
22
+ def self.api_url
23
+ @api_url
24
+ end
5
25
  end
@@ -0,0 +1,38 @@
1
+ require_relative "api_model"
2
+
3
+ module Devtunnel
4
+ class Account
5
+ include ApiModel
6
+ attr_accessor :api_key, :email
7
+
8
+ def self.login(email, password)
9
+ params = {:basic_username => email, :basic_password => password}
10
+ Devtunnel::Api.request :post, "/login", params
11
+ end
12
+
13
+ def self.signup(email)
14
+ params = {:email => email}
15
+ Devtunnel::Api.request :post, "/signup", params
16
+ end
17
+
18
+ def write_creds
19
+ File.open(File.expand_path("~/.devtunnel_auth"), "w") do |f|
20
+ f.write({api_key: api_key, email: email}.to_json)
21
+ end
22
+ end
23
+
24
+ def self.from_creds
25
+ fn = File.expand_path("~/.devtunnel_auth")
26
+ raise Exception.new("Please log in first") unless File.exist? fn
27
+ File.open(fn, "r") do |f|
28
+ hash = JSON.parse f.read
29
+ self.new hash
30
+ end
31
+ end
32
+
33
+ def self.logout
34
+ fn = File.expand_path("~/.devtunnel_auth")
35
+ File.unlink(fn) if File.exist? fn
36
+ end
37
+ end
38
+ end
@@ -0,0 +1,72 @@
1
+ require "rest_client"
2
+
3
+ module Devtunnel
4
+ class Api
5
+ @@api_jack = nil
6
+
7
+ def self.jack=(double)
8
+ @@api_jack = double
9
+ end
10
+ def self.jack
11
+ @@api_jack
12
+ end
13
+
14
+ def self.request method, url, params = {}, api_key = :account_api_key
15
+ begin
16
+ basic_username = Devtunnel.api_key
17
+
18
+ if params[:basic_username]
19
+ basic_username = params[:basic_username]
20
+ params.delete(:basic_username)
21
+ end
22
+ if params[:basic_password]
23
+ basic_password = params[:basic_password]
24
+ params.delete(:basic_password)
25
+ end
26
+
27
+ req_params = { :user => basic_username, :password => basic_password,
28
+ :method => method, :url => "#{Devtunnel.api_url}#{url}", :payload => params}
29
+
30
+ self.execute_request(req_params)
31
+
32
+ rescue Errno::ECONNREFUSED => e
33
+ raise ApiError.new(500, {}, {"errors" => [{"code" => 993, "message" => "Unable to connect to API server"}]})
34
+ rescue ExpiredApiKey => e
35
+ raise e
36
+ rescue InvalidApiKey => e
37
+ raise e
38
+ rescue Exception => e
39
+ # what kind of generic exceptions might we be loking for?
40
+ raise ApiError.new(500, {}, {"errors" => [{"code" => 996, "message" => "Error getting response from API server "+e.inspect}]})
41
+ end
42
+ end
43
+
44
+ def self.execute_request(params)
45
+ if Devtunnel::Api.jack
46
+ ApiResponse.new Devtunnel::Api.jack.execute(params)
47
+ else
48
+ RestClient::Request.new(params).execute do |response, request, result, &block|
49
+ ApiResponse.new(response)
50
+ end
51
+ end
52
+ end
53
+
54
+ def self.xml_request method, url, params = {}
55
+ basic_username = Devtunnel.api_key
56
+ req_params = { :method => method, :url => url, :payload => params}
57
+
58
+ self.execute_xml_request(req_params)
59
+
60
+ end
61
+
62
+ def self.execute_xml_request(params)
63
+ if Devtunnel::Api.jack
64
+ ApiResponse.new Devtunnel::Api.jack.execute(params)
65
+ else
66
+ RestClient::Request.new(params).execute do |response, request, result, &block|
67
+ response
68
+ end
69
+ end
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,15 @@
1
+ module Devtunnel
2
+ class ApiError < StandardError
3
+ attr_accessor :status, :headers, :errors
4
+ def initialize(status, headers, body)
5
+ self.status = status
6
+ self.headers = headers
7
+ self.errors = body["errors"]
8
+ end
9
+ end
10
+
11
+ class ExpiredApiKey < ApiError; end
12
+ class InvalidApiKey < ApiError; end
13
+ class UnknownResponse < ApiError; end
14
+ class NilApiResponse < ApiError; end
15
+ end
@@ -0,0 +1,38 @@
1
+ module Devtunnel
2
+ module ApiModel
3
+
4
+ def self.included(base)
5
+ base.extend ClassMethods
6
+ end
7
+
8
+ def initialize(params = {})
9
+ populate(params)
10
+ end
11
+
12
+ def populate(params = {})
13
+ params.each do |k,v|
14
+ instance_variable_set "@#{k}", v
15
+ end
16
+ end
17
+
18
+ module ClassMethods
19
+ attr_accessor :new_record, :errors
20
+ end
21
+
22
+ def errors=(arr); @errors = arr; end
23
+ def errors; @errors; end
24
+
25
+ def new_record?
26
+ @new_record.nil? ? true : @new_record
27
+ end
28
+
29
+ def success?
30
+ @errors.nil?
31
+ end
32
+
33
+ def attrs
34
+ {}.tap {|h| instance_variables.each { |var| h[var[1..-1]] = instance_variable_get(var) } }
35
+ end
36
+
37
+ end
38
+ end
@@ -0,0 +1,92 @@
1
+ require_relative "entity"
2
+ require "json"
3
+
4
+ module Devtunnel
5
+ class ApiResponse
6
+ attr_accessor :_body, :_status, :_headers, :_entity, :errors
7
+
8
+ def initialize(response)
9
+ if response.nil? or response.body.nil? or response.code.nil? or response.headers.nil?
10
+ # this response is broken, raise an error.
11
+ raise NilApiResponse.new(500, {}, {"errors" => [{"code" => 990, "message" => "API Server sent back an empty response."}]})
12
+ end
13
+
14
+ self._body = ((response.body.is_a? String) ? JSON.parse(response.body) : response.body)
15
+ self._status = response.code
16
+ self._headers = response.headers
17
+
18
+ case _status
19
+ when 200, 201
20
+ self._entity = Devtunnel::Entity.new(_body)
21
+ when 400,401,403,404,500
22
+ if _body["errors"]
23
+ self.errors = _body["errors"]
24
+ # quickly look to see if we have authentication errors. We want to raise
25
+ # exceptions on api key errors.
26
+ case errors[0]["code"]
27
+ when "002"
28
+ raise InvalidApiKey.new(_status, _hearders, _body)
29
+ when "007"
30
+ raise ExpiredApiKey.new(_status, _hearders, _body)
31
+ end
32
+ end
33
+ else
34
+ # TODO: test unknown resonse and make sure the client can deal with it.
35
+ raise UnknownResponse.new(_status, _headers, _body)
36
+ end
37
+ end
38
+
39
+ # enumeratable methods and such
40
+ def length
41
+ _entity.list.length if _entity.list
42
+ end
43
+ def first
44
+ if _entity.list
45
+ _entity.list.first
46
+ else
47
+ # TODO: some sort of exception
48
+ end
49
+ end
50
+
51
+ def success?
52
+ errors.nil? or errors.empty?
53
+ end
54
+
55
+ def total_results
56
+ return nil unless _entity.meta
57
+ _entity.meta["total_results"]
58
+ end
59
+ def offset
60
+ return nil unless _entity.meta
61
+ _entity.meta["offset"]
62
+ end
63
+ def limit
64
+ return nil unless _entity.meta
65
+ _entity.meta["limit"]
66
+ end
67
+
68
+ # if we're trying to access a method directly on the ApiResponse,
69
+ # the user is probably trying to get an attribute directly from
70
+ # the single entity that was returned. In this case, we'll simply
71
+ # look to see if the
72
+ def method_missing(meth, *args, &block)
73
+ return nil unless _entity
74
+ if _entity.object
75
+ if _entity.object.respond_to? meth
76
+ _entity.object.send(meth, *args, &block)
77
+ else
78
+ raise NoMethodError.new("Unknown attribute #{meth} on ApiResponse or #{_entity.object.class} entity.")
79
+ end
80
+ elsif _entity.list
81
+ if _entity.list.respond_to? meth
82
+ _entity.list.send(meth, *args, &block)
83
+ else
84
+ raise NoMethodError.new("Unknown attribute #{meth} on ApiResponse or #{_entity.list.class} list.")
85
+ end
86
+ end
87
+ end
88
+
89
+ end
90
+
91
+ class JsonParseError < StandardError; end
92
+ end
@@ -0,0 +1,49 @@
1
+ require_relative "account"
2
+ require_relative "success_response"
3
+
4
+ module Devtunnel
5
+ class Entity
6
+ attr_accessor :list, :object, :meta
7
+
8
+ def initialize(json)
9
+ self.meta = json["meta"] if json["meta"]
10
+ if json["list"]
11
+ self.list = Entity.build_from_list(json["list"])
12
+ else
13
+ self.object = Entity.build_from_hash(json)
14
+ end
15
+ end
16
+
17
+ def self.build_from_list(list)
18
+ list.map do |e|
19
+ if e.is_a? Hash
20
+ self.build_from_hash(e)
21
+ elsif e.is_a? Array
22
+ self.build_from_list(e)
23
+ else
24
+ # TODO: throw some weird entity error
25
+ end
26
+ end
27
+ end
28
+
29
+ def self.build_from_hash(hash)
30
+ # our hash should always include a class attribute that we can use
31
+ # to map back to a proper Entity type
32
+ #
33
+ if !hash["success"].nil? and hash.keys.length == 1
34
+ return Devtunnel::SuccessResponse.new hash["success"]
35
+ end
36
+
37
+ hash["new_record"] = false
38
+ case hash["class"]
39
+ when "account"
40
+ Devtunnel::Account.new hash
41
+ else
42
+ raise NoSuchEntity.new("Unknown return type in API response data: #{hash["class"]}")
43
+ end
44
+ end
45
+ end
46
+
47
+ class NoSuchEntity < StandardError;
48
+ end
49
+ end
@@ -0,0 +1,16 @@
1
+ require_relative "api_model"
2
+
3
+ module Devtunnel
4
+ class SuccessResponse
5
+ attr_accessor :success
6
+
7
+ def initialize success
8
+ self.success = success
9
+ end
10
+
11
+ def success?
12
+ success
13
+ end
14
+
15
+ end
16
+ end
@@ -1,3 +1,3 @@
1
1
  module Devtunnel
2
- VERSION = "0.0.1"
2
+ VERSION = "1.0.0"
3
3
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: devtunnel
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.1
4
+ version: 1.0.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Bryan Thompson
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2013-07-04 00:00:00.000000000 Z
11
+ date: 2013-07-18 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -43,7 +43,8 @@ description: Uses your DevTunnel account to quickly and easily tunnel a local de
43
43
  random NAT
44
44
  email:
45
45
  - bryan@madebymarket.com
46
- executables: []
46
+ executables:
47
+ - devtunnel
47
48
  extensions: []
48
49
  extra_rdoc_files: []
49
50
  files:
@@ -52,8 +53,16 @@ files:
52
53
  - LICENSE.txt
53
54
  - README.md
54
55
  - Rakefile
56
+ - bin/devtunnel
55
57
  - devtunnel.gemspec
56
58
  - lib/devtunnel.rb
59
+ - lib/devtunnel/account.rb
60
+ - lib/devtunnel/api.rb
61
+ - lib/devtunnel/api_error.rb
62
+ - lib/devtunnel/api_model.rb
63
+ - lib/devtunnel/api_response.rb
64
+ - lib/devtunnel/entity.rb
65
+ - lib/devtunnel/success_response.rb
57
66
  - lib/devtunnel/version.rb
58
67
  homepage: http://devtunnel.labs.madebymarket.com
59
68
  licenses: