rainforest 1.0.7 → 2.0.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (85) hide show
  1. checksums.yaml +4 -4
  2. data/.gitignore +3 -6
  3. data/.travis.yml +13 -1
  4. data/Gemfile +6 -0
  5. data/README.md +72 -21
  6. data/Rakefile +6 -13
  7. data/VERSION +1 -1
  8. data/bin/rainforest-console +1 -1
  9. data/gemfiles/default-with-activesupport.gemfile +8 -1
  10. data/gemfiles/json.gemfile +10 -2
  11. data/gemfiles/yajl.gemfile +10 -2
  12. data/lib/rainforest.rb +61 -240
  13. data/lib/rainforest/apibits/api_client.rb +28 -0
  14. data/lib/rainforest/apibits/api_endpoint.rb +11 -0
  15. data/lib/rainforest/apibits/api_list.rb +88 -0
  16. data/lib/rainforest/apibits/api_method.rb +95 -0
  17. data/lib/rainforest/apibits/api_object.rb +52 -0
  18. data/lib/rainforest/apibits/api_resource.rb +153 -0
  19. data/lib/rainforest/apibits/headers_builder.rb +47 -0
  20. data/lib/rainforest/apibits/params_builder.rb +27 -0
  21. data/lib/rainforest/apibits/path_builder.rb +38 -0
  22. data/lib/rainforest/apibits/requester.rb +104 -0
  23. data/lib/rainforest/apibits/util.rb +51 -0
  24. data/lib/rainforest/clients/default_client.rb +59 -0
  25. data/lib/rainforest/endpoints/client_stats_endpoint.rb +11 -0
  26. data/lib/rainforest/endpoints/environment_runs_endpoint.rb +11 -0
  27. data/lib/rainforest/endpoints/environments_endpoint.rb +48 -0
  28. data/lib/rainforest/endpoints/generator_rows_endpoint.rb +17 -0
  29. data/lib/rainforest/endpoints/generators_endpoint.rb +48 -0
  30. data/lib/rainforest/endpoints/integrations_endpoint.rb +33 -0
  31. data/lib/rainforest/endpoints/run_tests_endpoint.rb +20 -0
  32. data/lib/rainforest/endpoints/runs_endpoint.rb +48 -0
  33. data/lib/rainforest/endpoints/schedules_endpoint.rb +48 -0
  34. data/lib/rainforest/endpoints/site_environments_endpoint.rb +24 -0
  35. data/lib/rainforest/endpoints/sites_endpoint.rb +39 -0
  36. data/lib/rainforest/endpoints/tests_endpoint.rb +57 -0
  37. data/lib/rainforest/endpoints/users_endpoint.rb +48 -0
  38. data/lib/rainforest/errors/api_connection_error.rb +1 -1
  39. data/lib/rainforest/errors/api_error.rb +32 -1
  40. data/lib/rainforest/errors/authentication_error.rb +1 -1
  41. data/lib/rainforest/errors/rainforest_error.rb +2 -9
  42. data/lib/rainforest/resources/client_stats.rb +31 -0
  43. data/lib/rainforest/resources/environment.rb +73 -0
  44. data/lib/rainforest/resources/generator.rb +73 -0
  45. data/lib/rainforest/resources/integration.rb +52 -0
  46. data/lib/rainforest/resources/run.rb +93 -0
  47. data/lib/rainforest/resources/schedule.rb +63 -0
  48. data/lib/rainforest/resources/site.rb +53 -0
  49. data/lib/rainforest/resources/site_environment.rb +40 -0
  50. data/lib/rainforest/resources/test.rb +98 -0
  51. data/lib/rainforest/resources/user.rb +75 -0
  52. data/rainforest.gemspec +8 -6
  53. data/test/rainforest/api_client_test.rb +51 -0
  54. data/test/rainforest/api_endpoint_test.rb +13 -0
  55. data/test/rainforest/api_list_test.rb +49 -0
  56. data/test/rainforest/api_method_test.rb +78 -0
  57. data/test/rainforest/headers_builder_test.rb +28 -0
  58. data/test/rainforest/params_builder_test.rb +57 -0
  59. data/test/rainforest/path_builder_test.rb +50 -0
  60. data/test/rainforest/requester_test.rb +86 -0
  61. data/test/rainforest/util_test.rb +41 -19
  62. data/test/test_data.rb +72 -0
  63. data/test/test_helper.rb +17 -134
  64. metadata +99 -59
  65. data/CONTRIBUTORS +0 -2
  66. data/History.txt +0 -4
  67. data/LICENSE +0 -21
  68. data/lib/data/ca-certificates.crt +0 -3918
  69. data/lib/rainforest/api_operations/create.rb +0 -16
  70. data/lib/rainforest/api_operations/delete.rb +0 -11
  71. data/lib/rainforest/api_operations/list.rb +0 -16
  72. data/lib/rainforest/api_operations/update.rb +0 -61
  73. data/lib/rainforest/api_resource.rb +0 -33
  74. data/lib/rainforest/errors/invalid_request_error.rb +0 -10
  75. data/lib/rainforest/json.rb +0 -21
  76. data/lib/rainforest/list_object.rb +0 -35
  77. data/lib/rainforest/rainforest_object.rb +0 -165
  78. data/lib/rainforest/run.rb +0 -8
  79. data/lib/rainforest/singleton_api_resource.rb +0 -20
  80. data/lib/rainforest/util.rb +0 -100
  81. data/test/rainforest/api_resource_test.rb +0 -11
  82. data/test/rainforest/auth_test.rb +0 -24
  83. data/test/rainforest/list_object_test.rb +0 -7
  84. data/test/rainforest/metadata_test.rb +0 -7
  85. data/test/rainforest/run_test.rb +0 -36
@@ -0,0 +1,28 @@
1
+ module Rainforest
2
+ class ApiClient
3
+ attr_accessor :headers, :params
4
+
5
+ def initialize(headers, params)
6
+ self.refresh_from(headers, params)
7
+ end
8
+
9
+ def refresh_from(headers, params)
10
+ @headers = headers
11
+ @params = params
12
+ self
13
+ end
14
+
15
+ def execute(api_method)
16
+ api_method.headers = ParamsBuilder.merge(api_method.headers, @headers)
17
+ api_method.params = ParamsBuilder.merge(api_method.params, @params)
18
+ api_method.execute
19
+ end
20
+
21
+ def inspect
22
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}> Headers: " +
23
+ JSON.pretty_generate(@headers) + ", Params: " +
24
+ JSON.pretty_generate(@params)
25
+ end
26
+
27
+ end
28
+ end
@@ -0,0 +1,11 @@
1
+ module Rainforest
2
+ class ApiEndpoint
3
+ attr_accessor :client
4
+ attr_accessor :parent
5
+
6
+ def initialize(client, parent=nil)
7
+ @client = client
8
+ @parent = parent
9
+ end
10
+ end
11
+ end
@@ -0,0 +1,88 @@
1
+ module Rainforest
2
+ class ApiList < ApiResource
3
+ include Enumerable
4
+
5
+ attr_reader :api_method
6
+ attr_reader :client
7
+ attr_reader :data
8
+ attr_reader :klass
9
+
10
+ def initialize(klass, json={}, api_method=nil, client=nil)
11
+ if klass.is_a?(Class)
12
+ @klass = klass
13
+ else
14
+ @klass = Util.constantize(klass)
15
+ end
16
+
17
+ refresh_from(json, api_method, client)
18
+ end
19
+
20
+ def refresh_from(json, api_method=nil, client=nil)
21
+ unless json.is_a?(Hash)
22
+ json = {
23
+ :data => json
24
+ }
25
+ end
26
+ json = Util.symbolize_keys(json)
27
+
28
+ clear_api_attributes
29
+ @api_method = api_method
30
+ @client = client
31
+ @data = []
32
+ @json = Util.sorta_deep_clone(json)
33
+
34
+ json.each do |k, v|
35
+ if k.to_sym == :data
36
+ if v.respond_to?(:map)
37
+ instance_variable_set("@#{k}", v.map{ |i| @klass.new(i, @api_method, @client) })
38
+ else
39
+ instance_variable_set("@#{k}", v || [])
40
+ end
41
+ elsif self.class.api_attribute_names.include?(k)
42
+ instance_variable_set("@#{k}", determine_api_attribute_value(k, v))
43
+ end
44
+ end
45
+ self
46
+ end
47
+
48
+ def [](k)
49
+ data[k]
50
+ end
51
+
52
+ def []=(k, v)
53
+ data[k]=v
54
+ end
55
+
56
+ def last
57
+ data.last
58
+ end
59
+
60
+ def length
61
+ data.length
62
+ end
63
+
64
+ def each(&blk)
65
+ data.each(&blk)
66
+ end
67
+
68
+ def inspect
69
+ "#<#{self.class}[#{@klass}]:0x#{self.object_id.to_s(16)}> Data: " + JSON.pretty_generate(inspect_data)
70
+ end
71
+
72
+ def inspect_data
73
+ ret = []
74
+ data.each do |d|
75
+ if d.respond_to?(:inspect_nested)
76
+ ret << d.inspect_nested
77
+ else
78
+ ret << d
79
+ end
80
+ end
81
+ ret
82
+ end
83
+
84
+ @api_attributes = {
85
+ :data => { :readonly => true }
86
+ }
87
+ end
88
+ end
@@ -0,0 +1,95 @@
1
+ module Rainforest
2
+ class ApiMethod
3
+
4
+ attr_accessor :path
5
+ attr_accessor :method
6
+ attr_accessor :params
7
+ attr_accessor :headers
8
+
9
+ attr_accessor :response_body
10
+ attr_accessor :response_code
11
+ attr_accessor :error
12
+
13
+ attr_accessor :api_base
14
+
15
+ def initialize(method, path, params, headers, object)
16
+ @api_base = api_base || Rainforest.api_base
17
+
18
+ @method = method.to_sym
19
+ @path = PathBuilder.build(path, object, params)
20
+ @params = ParamsBuilder.build(params)
21
+ @headers = HeadersBuilder.build(headers)
22
+ end
23
+
24
+ def execute
25
+ begin
26
+ response = Requester.request(method, url, params, headers)
27
+ @response_body = response.body
28
+ @response_code = response.code
29
+ rescue StandardError => e
30
+ @response_body = e.http_body if e.respond_to?(:http_body)
31
+ @response_code = e.http_code if e.respond_to?(:http_code)
32
+ @error = compose_error(e)
33
+ raise @error
34
+ end
35
+
36
+
37
+ response_json
38
+ end
39
+
40
+ def url
41
+ "#{api_base}#{@path}"
42
+ end
43
+
44
+ def response_json
45
+ return @json if @json
46
+ begin
47
+ @json = Util.symbolize_keys(JSON.parse(@response_body))
48
+ rescue JSON::ParserError
49
+ @error = ApiError.new("Unable to parse the server response as JSON.", self)
50
+ raise @error
51
+ end
52
+ @json
53
+ end
54
+
55
+ def compose_error(error)
56
+ msg = "An error occured while making the API call."
57
+
58
+ case error
59
+ when RestClient::ExceptionWithResponse
60
+ return error_with_response(error)
61
+
62
+ when RestClient::RequestTimeout
63
+ msg = "The request timed out while making the API call."
64
+
65
+ when RestClient::ServerBrokeConnection
66
+ msg = "The connection to the server broke before the request completed."
67
+
68
+ when SocketError
69
+ msg = "An unexpected error occured while trying to connect to " \
70
+ "the API. You may be seeing this message because your DNS is " \
71
+ "not working. To check, try running 'host #{Rainforest.api_base}' "\
72
+ "from the command line."
73
+
74
+ else
75
+ msg = "An unexpected error occured. If this problem persists let us " \
76
+ "know at #{Rainforest.support_email}."
77
+ end
78
+
79
+ return ApiConnectionError.new(msg, self)
80
+ end
81
+
82
+ # Handle a few common cases.
83
+ def error_with_response(error)
84
+ case @response_code
85
+ when 400, 404
86
+ return ApiError.new(@response_body || "Invalid request. Please check the URL and parameters.", self)
87
+ when 401
88
+ return AuthenticationError.new(@response_body || "Authentication failed.", self)
89
+ else
90
+ return ApiError.new(@response_body || "An error occured while making the API call.", self)
91
+ end
92
+ end
93
+
94
+ end
95
+ end
@@ -0,0 +1,52 @@
1
+ module Rainforest
2
+ class ApiObject
3
+ include Enumerable
4
+ attr_reader :json
5
+
6
+ def self.construct(json)
7
+ if json.is_a?(Array)
8
+ return json.map{ |a| ApiObject.construct(a) }
9
+ elsif json.is_a?(Hash)
10
+ return ApiObject.new(json)
11
+ else
12
+ return json
13
+ end
14
+ end
15
+
16
+ def initialize(json=nil)
17
+ refresh_from(json)
18
+ end
19
+
20
+ def refresh_from(json={})
21
+ @json = Util.sorta_deep_clone(json)
22
+ @json.each do |k, v|
23
+ @json[k] = ApiObject.construct(v)
24
+ end
25
+ self
26
+ end
27
+
28
+ def inspect
29
+ @json.inspect
30
+ end
31
+
32
+ def to_json(*args)
33
+ JSON.generate(@json)
34
+ end
35
+
36
+ def method_missing(name, *args, &blk)
37
+ if name.to_s.end_with?('=')
38
+ attr = name.to_s[0...-1].to_sym
39
+ @json[attr] = args[0]
40
+ else
41
+ if @json.respond_to?(name)
42
+ @json.send(name, *args, &blk)
43
+ elsif @json.has_key?(name.to_sym)
44
+ return @json[name.to_sym]
45
+ else
46
+ super
47
+ end
48
+ end
49
+ end
50
+
51
+ end
52
+ end
@@ -0,0 +1,153 @@
1
+ module Rainforest
2
+ class ApiResource
3
+ attr_reader :api_method
4
+ attr_reader :json
5
+ attr_reader :client
6
+
7
+ def initialize(json=nil, api_method=nil, client=nil)
8
+ refresh_from(json, api_method, client)
9
+ end
10
+
11
+ def refresh_from(json={}, api_method=nil, client=nil)
12
+ unless json.is_a?(Hash)
13
+ json = { :id => json }
14
+ end
15
+ json = Util.symbolize_keys(json)
16
+
17
+ # Clear or write over any old data
18
+ clear_api_attributes
19
+ @api_method = api_method
20
+ @json = Util.sorta_deep_clone(json)
21
+ @client = client || Rainforest.default_client
22
+
23
+ # Use json (not the @json, the cloned copy)
24
+ json.each do |k, v|
25
+ unless self.class.api_attribute_names.include?(k.to_sym)
26
+ self.class.add_api_attribute(k.to_sym)
27
+ end
28
+ instance_variable_set("@#{k}", determine_api_attribute_value(k, v))
29
+ end
30
+ self
31
+ end
32
+
33
+ # Returns the default client
34
+ def self.client
35
+ Rainforest.default_client
36
+ end
37
+
38
+ def inspect
39
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
40
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}> Attributes: " + JSON.pretty_generate(inspect_api_attributes)
41
+ end
42
+
43
+ def inspect_nested
44
+ id_string = (self.respond_to?(:id) && !self.id.nil?) ? " id=#{self.id}" : ""
45
+ "#<#{self.class}:0x#{self.object_id.to_s(16)}#{id_string}>"
46
+ end
47
+
48
+ def to_json(*args)
49
+ JSON.generate(api_attributes)
50
+ end
51
+
52
+ def self.api_attribute_names
53
+ @api_attributes.map(&:first)
54
+ end
55
+
56
+ def self.editable_api_attribute_names
57
+ @api_attributes.select do |k, v|
58
+ v[:editable]
59
+ end.map(&:first)
60
+ end
61
+
62
+ def self.add_api_attribute(name)
63
+ attr_accessor name.to_sym
64
+ @api_attributes[name.to_sym] = {}
65
+ end
66
+
67
+ def api_attributes
68
+ ret = {}
69
+ self.class.api_attribute_names.each do |attribute|
70
+ ret[attribute] = self.send(attribute)
71
+ end
72
+ ret
73
+ end
74
+
75
+ def inspect_api_attributes
76
+ ret = {}
77
+ api_attributes.each do |k, v|
78
+ if v.is_a?(ApiResource)
79
+ ret[k] = v.inspect_nested
80
+ else
81
+ ret[k] = v
82
+ end
83
+ end
84
+ ret
85
+ end
86
+
87
+ # TODO(joncalhoun): Fix this (not currently working as intended)
88
+ def changed_api_attributes
89
+ ret = {}
90
+ self.api_attributes.each do |name, value|
91
+ if @json[name] != value
92
+ ret[name] = value
93
+ end
94
+ end
95
+ ret
96
+ end
97
+
98
+ def editable_api_attributes
99
+ ret = {}
100
+ self.class.editable_api_attribute_names.each do |name|
101
+ ret[name] = self.send(name)
102
+ end
103
+ ret
104
+ end
105
+
106
+ def clear_api_attributes
107
+ self.class.api_attribute_names.each do |name|
108
+ instance_variable_set("@#{name}", nil)
109
+ end
110
+ end
111
+
112
+ def self.determine_api_attribute_value(name, raw_value)
113
+ if @api_attributes[name] && @api_attributes[name].has_key?(:constructor)
114
+ klass = Util.constantize(@api_attributes[name][:constructor])
115
+ if(klass.respond_to?(:construct))
116
+ klass.construct(raw_value)
117
+ else
118
+ klass.new(raw_value)
119
+ end
120
+ else
121
+ ApiObject.construct(raw_value)
122
+ end
123
+ end
124
+
125
+ def determine_api_attribute_value(name, raw_value)
126
+ self.class.determine_api_attribute_value(name, raw_value)
127
+ end
128
+
129
+
130
+ def self.api_subclasses
131
+ return @api_subclasses ||= Set.new
132
+ end
133
+
134
+ def self.api_subclass_fetch(name)
135
+ @api_subclasses_hash ||= {}
136
+ if @api_subclasses_hash.has_key?(name)
137
+ @api_subclasses_hash[name]
138
+ end
139
+ end
140
+
141
+ def self.register_api_subclass(subclass, name=nil)
142
+ @api_subclasses ||= Set.new
143
+ @api_subclasses << subclass
144
+
145
+ unless name.nil?
146
+ @api_subclasses_hash ||= {}
147
+ @api_subclasses_hash[name] = subclass
148
+ end
149
+ end
150
+
151
+ @api_attributes = {}
152
+ end
153
+ end
@@ -0,0 +1,47 @@
1
+ module Rainforest
2
+ module HeadersBuilder
3
+
4
+ def self.build(headers)
5
+ headers ||= {}
6
+ default_headers.merge(headers)
7
+ end
8
+
9
+ def self.default_headers
10
+ headers = {
11
+ :user_agent => "Rainforest/#{Rainforest.api_version} RubyBindings/#{Rainforest::VERSION}",
12
+ }
13
+
14
+ begin
15
+ headers.update({
16
+ :x_rainforest_client_user_agent => JSON.generate(user_agent)
17
+ })
18
+ rescue => e
19
+ headers.update({
20
+ :x_rainforest_client_raw_user_agent => user_agent.inspect,
21
+ :error => "#{e} (#{e.class})"
22
+ })
23
+ end
24
+ headers
25
+ end
26
+
27
+ def self.user_agent
28
+ lang_version = "#{RUBY_VERSION} p#{RUBY_PATCHLEVEL} (#{RUBY_RELEASE_DATE})"
29
+
30
+ {
31
+ :bindings_version => Rainforest::VERSION,
32
+ :lang => 'ruby',
33
+ :lang_version => lang_version,
34
+ :platform => RUBY_PLATFORM,
35
+ :publisher => 'rainforest',
36
+ :uname => get_uname
37
+ }
38
+ end
39
+
40
+ def self.get_uname
41
+ `uname -a 2>/dev/null`.strip if RUBY_PLATFORM =~ /linux|darwin/i
42
+ rescue Errno::ENOMEM => ex # couldn't create subprocess
43
+ "uname lookup failed"
44
+ end
45
+
46
+ end
47
+ end