rainforest 1.0.7 → 2.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 +4 -4
- data/.gitignore +3 -6
- data/.travis.yml +13 -1
- data/Gemfile +6 -0
- data/README.md +72 -21
- data/Rakefile +6 -13
- data/VERSION +1 -1
- data/bin/rainforest-console +1 -1
- data/gemfiles/default-with-activesupport.gemfile +8 -1
- data/gemfiles/json.gemfile +10 -2
- data/gemfiles/yajl.gemfile +10 -2
- data/lib/rainforest.rb +61 -240
- data/lib/rainforest/apibits/api_client.rb +28 -0
- data/lib/rainforest/apibits/api_endpoint.rb +11 -0
- data/lib/rainforest/apibits/api_list.rb +88 -0
- data/lib/rainforest/apibits/api_method.rb +95 -0
- data/lib/rainforest/apibits/api_object.rb +52 -0
- data/lib/rainforest/apibits/api_resource.rb +153 -0
- data/lib/rainforest/apibits/headers_builder.rb +47 -0
- data/lib/rainforest/apibits/params_builder.rb +27 -0
- data/lib/rainforest/apibits/path_builder.rb +38 -0
- data/lib/rainforest/apibits/requester.rb +104 -0
- data/lib/rainforest/apibits/util.rb +51 -0
- data/lib/rainforest/clients/default_client.rb +59 -0
- data/lib/rainforest/endpoints/client_stats_endpoint.rb +11 -0
- data/lib/rainforest/endpoints/environment_runs_endpoint.rb +11 -0
- data/lib/rainforest/endpoints/environments_endpoint.rb +48 -0
- data/lib/rainforest/endpoints/generator_rows_endpoint.rb +17 -0
- data/lib/rainforest/endpoints/generators_endpoint.rb +48 -0
- data/lib/rainforest/endpoints/integrations_endpoint.rb +33 -0
- data/lib/rainforest/endpoints/run_tests_endpoint.rb +20 -0
- data/lib/rainforest/endpoints/runs_endpoint.rb +48 -0
- data/lib/rainforest/endpoints/schedules_endpoint.rb +48 -0
- data/lib/rainforest/endpoints/site_environments_endpoint.rb +24 -0
- data/lib/rainforest/endpoints/sites_endpoint.rb +39 -0
- data/lib/rainforest/endpoints/tests_endpoint.rb +57 -0
- data/lib/rainforest/endpoints/users_endpoint.rb +48 -0
- data/lib/rainforest/errors/api_connection_error.rb +1 -1
- data/lib/rainforest/errors/api_error.rb +32 -1
- data/lib/rainforest/errors/authentication_error.rb +1 -1
- data/lib/rainforest/errors/rainforest_error.rb +2 -9
- data/lib/rainforest/resources/client_stats.rb +31 -0
- data/lib/rainforest/resources/environment.rb +73 -0
- data/lib/rainforest/resources/generator.rb +73 -0
- data/lib/rainforest/resources/integration.rb +52 -0
- data/lib/rainforest/resources/run.rb +93 -0
- data/lib/rainforest/resources/schedule.rb +63 -0
- data/lib/rainforest/resources/site.rb +53 -0
- data/lib/rainforest/resources/site_environment.rb +40 -0
- data/lib/rainforest/resources/test.rb +98 -0
- data/lib/rainforest/resources/user.rb +75 -0
- data/rainforest.gemspec +8 -6
- data/test/rainforest/api_client_test.rb +51 -0
- data/test/rainforest/api_endpoint_test.rb +13 -0
- data/test/rainforest/api_list_test.rb +49 -0
- data/test/rainforest/api_method_test.rb +78 -0
- data/test/rainforest/headers_builder_test.rb +28 -0
- data/test/rainforest/params_builder_test.rb +57 -0
- data/test/rainforest/path_builder_test.rb +50 -0
- data/test/rainforest/requester_test.rb +86 -0
- data/test/rainforest/util_test.rb +41 -19
- data/test/test_data.rb +72 -0
- data/test/test_helper.rb +17 -134
- metadata +99 -59
- data/CONTRIBUTORS +0 -2
- data/History.txt +0 -4
- data/LICENSE +0 -21
- data/lib/data/ca-certificates.crt +0 -3918
- data/lib/rainforest/api_operations/create.rb +0 -16
- data/lib/rainforest/api_operations/delete.rb +0 -11
- data/lib/rainforest/api_operations/list.rb +0 -16
- data/lib/rainforest/api_operations/update.rb +0 -61
- data/lib/rainforest/api_resource.rb +0 -33
- data/lib/rainforest/errors/invalid_request_error.rb +0 -10
- data/lib/rainforest/json.rb +0 -21
- data/lib/rainforest/list_object.rb +0 -35
- data/lib/rainforest/rainforest_object.rb +0 -165
- data/lib/rainforest/run.rb +0 -8
- data/lib/rainforest/singleton_api_resource.rb +0 -20
- data/lib/rainforest/util.rb +0 -100
- data/test/rainforest/api_resource_test.rb +0 -11
- data/test/rainforest/auth_test.rb +0 -24
- data/test/rainforest/list_object_test.rb +0 -7
- data/test/rainforest/metadata_test.rb +0 -7
- 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,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
|