right_api_client 1.5.9 → 1.5.12
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +0 -1
- data/{CHANGELOG.rdoc → CHANGELOG.md} +14 -1
- data/Gemfile +1 -1
- data/{README.rdoc → README.md} +97 -58
- data/config/login.yml.example +9 -4
- data/lib/right_api_client/client.rb +196 -51
- data/lib/right_api_client/errors.rb +30 -0
- data/lib/right_api_client/helper.rb +67 -6
- data/lib/right_api_client/resource.rb +7 -8
- data/lib/right_api_client/resource_detail.rb +14 -10
- data/lib/right_api_client/resources.rb +7 -8
- data/lib/right_api_client/version.rb +1 -1
- data/login_to_client_irb.rb +1 -2
- data/right_api_client.gemspec +17 -17
- data/right_api_client.rconf +1 -1
- data/spec/{audit_entries_spec.rb → functional/audit_entries_spec.rb} +2 -4
- data/spec/{client_spec.rb → functional/client_spec.rb} +85 -12
- data/spec/spec_helper.rb +16 -34
- data/spec/support/mock_spec_helper.rb +36 -0
- data/spec/unit/helper_spec.rb +78 -0
- data/spec/{instance_facing_spec.rb → unit/instance_facing_spec.rb} +6 -7
- data/spec/{resource_detail_spec.rb → unit/resource_detail_spec.rb} +8 -9
- data/spec/unit/resource_spec.rb +24 -0
- data/spec/unit/resources_spec.rb +27 -0
- metadata +49 -46
- data/lib/right_api_client/exceptions.rb +0 -15
- data/spec/resource_spec.rb +0 -25
- data/spec/resources_spec.rb +0 -25
data/.gitignore
CHANGED
@@ -1,4 +1,17 @@
|
|
1
|
-
= right_api_client - CHANGELOG.
|
1
|
+
= right_api_client - CHANGELOG.md
|
2
|
+
|
3
|
+
== right_api_client - 1.5.11
|
4
|
+
#33 fix specs and readme
|
5
|
+
#32 fix nested array of hashes params
|
6
|
+
#30 README markdown formatting
|
7
|
+
#29 rename read me and add mandatory ownership string
|
8
|
+
|
9
|
+
== right_api_client - 1.5.10
|
10
|
+
#28 Preserve the last request made and make it accessible
|
11
|
+
#26 Fix specs. Separate out unit tests and functional tests.
|
12
|
+
#25 Add support for other RightScale endpoints.
|
13
|
+
#23 Fix child_account update href.
|
14
|
+
#20 Always store the latest cookies. Also includes a jump from rspec 1.3.0 to 2.9.0 and spec infrastructure reorganization.
|
2
15
|
|
3
16
|
== right_api_client - 1.5.9
|
4
17
|
Downgrade even further to Ruby 1.8.7. Should still work in Ruby 1.9.x.
|
data/Gemfile
CHANGED
data/{README.rdoc → README.md}
RENAMED
@@ -1,40 +1,46 @@
|
|
1
|
-
|
1
|
+
# RightScale API Client
|
2
2
|
|
3
|
-
The
|
3
|
+
The right\_api\_client gem simplifies the use of RightScale's MultiCloud API. It provides
|
4
4
|
a simple object model of the API resources, and handles all of the fine details involved
|
5
5
|
in making HTTP calls and translating their responses.
|
6
6
|
It is assumed that users are already familiar with the RightScale API:
|
7
|
-
- API Documentation: http://support.rightscale.com/12-Guides/RightScale_API_1.5
|
8
|
-
- API Reference Docs: http://support.rightscale.com/api1.5
|
9
7
|
|
10
|
-
|
8
|
+
- API Documentation: http://support.rightscale.com/12-Guides/RightScale_API_1.5
|
9
|
+
- API Reference Docs: http://reference.rightscale.com/api1.5
|
10
|
+
|
11
|
+
Maintained by the RightScale "Yellow_team"
|
12
|
+
|
13
|
+
## Installation
|
11
14
|
Ruby 1.8.7 or higher is required.
|
15
|
+
|
12
16
|
gem install right_api_client
|
13
17
|
|
14
|
-
|
15
|
-
The
|
18
|
+
## Versioning
|
19
|
+
The right\_api\_client gem is versioned using the usual X.Y.Z notation, where X.Y is the
|
16
20
|
RightScale API version, and Z is the client version. For example, if you want to use
|
17
21
|
RightScale API 1.5, you should use the latest version of the 1.5 gem. This will ensure
|
18
22
|
that you get the latest bug fixes for the client that is compatible with that API version.
|
19
23
|
|
20
|
-
|
24
|
+
## Usage Instructions
|
21
25
|
New users can start with the following few lines of code and navigate their way around the API by following
|
22
26
|
the available methods. You can find your account id by logging into the RightScale dashboard (https://my.rightscale.com),
|
23
27
|
navigate to the Settings > Account Settings page. The account is is at the end of the browser address bar.
|
28
|
+
|
24
29
|
require 'right_api_client'
|
25
30
|
@client = RightApi::Client.new(:email => 'my@email.com', :password => 'my_password', :account_id => 'my_account_id')
|
26
31
|
puts "Available methods: #{@client.api_methods}"
|
27
32
|
|
28
33
|
The client makes working with and getting to know the API much easier. It spiders the API dynamically to
|
29
34
|
discover its resources on the fly. At every step, the user has the ability to query api_methods(), which
|
30
|
-
indicates the potential methods that can be called. The
|
35
|
+
indicates the potential methods that can be called. The ```config/login.yml.example``` file provides
|
31
36
|
details of different login parameters.
|
32
37
|
|
33
|
-
|
38
|
+
### Making API calls
|
34
39
|
Essentially, just follow the RightScale API documentation (available from http://support.rightscale.com)
|
35
40
|
and treat every resource in the paths as objects that can call other objects using the dot (.) operator:
|
36
41
|
|
37
42
|
Examples:
|
43
|
+
|
38
44
|
- Index: /api/clouds/:cloud_id/datacenters => @client.clouds(:id => :cloud_id).show.datacenters.index
|
39
45
|
- Show: /api/clouds/:cloud_id/datacenters/:id => @client.clouds(:id => :cloud_id).show.datacenters(:id => :datacenter_id).show
|
40
46
|
- Create: /api/deployments/:deployment_id/servers => @client.deployments(:id => :deployment_id).show.servers.create
|
@@ -44,29 +50,38 @@ Examples:
|
|
44
50
|
|
45
51
|
As seen above, whenever you need to chain methods, you must call .show before specifying the next method.
|
46
52
|
|
47
|
-
|
53
|
+
### Parameters
|
48
54
|
Pass-in parameters to the method that they belong to. Lets say you want to filter on the index for deployments:
|
55
|
+
|
49
56
|
@client.deployments.index(:filter => ['name==my_deployment'])
|
57
|
+
|
50
58
|
The filter is the parameter for the index call and not the deployment call.
|
51
59
|
|
52
|
-
|
53
|
-
The HTTP calls made by
|
60
|
+
### Logging HTTP Requests
|
61
|
+
The HTTP calls made by right\_api\_client can be logged in two ways:
|
54
62
|
1. Log to a file
|
63
|
+
|
55
64
|
@client.log('~/right_api_client.log')
|
56
|
-
|
65
|
+
|
66
|
+
2. Log to STDOUT
|
67
|
+
|
57
68
|
@client.log(STDOUT)
|
58
69
|
|
59
|
-
|
70
|
+
## Examples
|
60
71
|
Get a list of all servers (aka doing an Index call)
|
72
|
+
|
61
73
|
@client.servers.index
|
62
74
|
|
63
75
|
Get a list of all servers in a deployment
|
76
|
+
|
64
77
|
@client.deployments(:id => 'my_deployment_id').show.servers.index
|
65
78
|
|
66
79
|
Get a particular server (aka doing a Show call)
|
80
|
+
|
67
81
|
@client.servers(:id => 'my_server_id').show
|
68
82
|
|
69
83
|
Creating a server involves setting up the required parameters, then calling the create method
|
84
|
+
|
70
85
|
server_template_href = @client.server_templates.index(:filter => ['name==Base ServerTemplate']).first.href
|
71
86
|
cloud = @client.clouds(:id => 'my_cloud_id').show
|
72
87
|
params = { :server => {
|
@@ -83,48 +98,56 @@ Creating a server involves setting up the required parameters, then calling the
|
|
83
98
|
new_server.api_methods
|
84
99
|
|
85
100
|
Launch the newly created server. Inputs are a bit tricky so they have to be set in a long string
|
101
|
+
|
86
102
|
inputs = "inputs[][name]=NAME1&inputs[][value]=text:VALUE1&inputs[][name]=NAME2&inputs[][value]=text:VALUE2"
|
87
103
|
new_server.show.launch(inputs)
|
88
104
|
|
89
105
|
Run a script on the server. The API does not currently expose right_scripts, hence, the script href has
|
90
106
|
to be retrieved from the dashboard and put in the following href format.
|
107
|
+
|
91
108
|
script_href = "right_script_href=/api/right_scripts/382371"
|
92
109
|
task = new_server.show.current_instance.show.run_executable(script_href + "&inputs[][name]=TEST_NAME&inputs[][value]=text:VALUE1")
|
93
110
|
task.show.api_methods
|
94
111
|
|
95
112
|
Update the server's name
|
113
|
+
|
96
114
|
params = { :server => {:name => 'New Server Name'}}
|
97
115
|
@client.servers(:id => 'my_server_id').update(params)
|
98
116
|
|
99
117
|
Terminate the server (i.e. shutdown its current_instance)
|
118
|
+
|
100
119
|
@client.servers(:id => 'my_server_id').show.terminate
|
101
120
|
|
102
121
|
Destroy the server (i.e. delete it)
|
122
|
+
|
103
123
|
@client.servers(:id => 'my_server_id').destroy
|
104
124
|
|
105
|
-
|
125
|
+
## Object Types
|
106
126
|
The client returns 3 types of objects:
|
107
|
-
|
108
|
-
- <b>
|
109
|
-
|
110
|
-
- When the content-type is
|
111
|
-
-
|
112
|
-
<b>
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
-
|
118
|
-
-
|
127
|
+
|
128
|
+
- <b>Resources</b>: returned when you are querying a collection of resources, e.g.: ```client.deployments```
|
129
|
+
- <b>Resource</b>: returned when you specify an id and therefore a specific resource, e.g.: ```@client.deployments(:id => :deployment_id)```
|
130
|
+
- When the content-type is type=collection then an array of Resource objects will be returned, e.g.: ```@client.deployments.index```
|
131
|
+
- When the content-type is not a collection then a Resource object will be returned, e.g.: ```@client.deployments(:id => deployment_id).show```
|
132
|
+
- <b>ResourceDetail</b>: returned when you do a .show on a Resource, e.g.: ```client.deployments(:id => deployment_id).show```
|
133
|
+
|
134
|
+
<b>On all 3 types of objects you can query ```.api_methods``` to see a list of available methods, e.g.: ```client.deployments.api_methods```</b>
|
135
|
+
|
136
|
+
### Exceptions:
|
137
|
+
- ```inputs.index``` will return an array of ResourceDetail objects since you cannot do a .show on an input
|
138
|
+
- ```session.index``` will return a ResourceDetail object since you cannot do a .show on a session
|
139
|
+
- ```tags.by_resource, tags.by_tag``` will return an array of ResourceDetail objects since you cannot do a .show on a resource_tag
|
140
|
+
- ```monitoring_metrics(:id => :m_m_id).show.data``` will return a ResourceDetail object since you cannot do
|
119
141
|
a .show on a monitoring_metric_data
|
120
142
|
|
121
|
-
|
122
|
-
The client also supports 'instance facing calls', which use the
|
143
|
+
## Instance Facing Calls:
|
144
|
+
The client also supports 'instance facing calls', which use the instance\_token to login.
|
123
145
|
Unlike regular email-password logins, instance-facing-calls are limited in the amount of allowable calls.
|
124
146
|
Since in most of the cases, the calls are scoped to the instance's cloud (or the instance itself), the cloud_id and
|
125
147
|
the instance_id will be automatically recorded by the client, so that the user does not need to specify it.
|
126
148
|
|
127
|
-
|
149
|
+
### Examples
|
150
|
+
|
128
151
|
@instance_client = RightApi::Client.new(:instance_token => 'my_token', :account_id => 'my_account_id')
|
129
152
|
@instance_client.volume_attachments links to /api/clouds/:cloud_id/volume_attachments
|
130
153
|
@instance_client.volumes_snapshots links to /api/clouds/:cloud_id/volumes_snapshots
|
@@ -133,27 +156,30 @@ the instance_id will be automatically recorded by the client, so that the user d
|
|
133
156
|
@instance_client.backups links to /api/backups
|
134
157
|
@instance_client.live_tasks(:id) links to /api/clouds/:cloud_id/instances/:instance_id/live/tasks/:id
|
135
158
|
|
136
|
-
|
137
|
-
For
|
159
|
+
### Notes
|
160
|
+
For volume\_attachments and volumes\_snapshots you can also go through the volume:
|
161
|
+
|
138
162
|
@instance_client.volumes(:id => :volume_id).show.volume_attachments
|
139
163
|
which maps to:
|
140
164
|
/api/clouds/:cloud_id/volumes/:volume_id/volume_attachment
|
141
|
-
|
165
|
+
|
166
|
+
The instance's volume\_attachments can be accessed using:
|
167
|
+
|
142
168
|
@instance_client.get_instance.volume_attachments
|
143
169
|
which maps to:
|
144
170
|
/api/clouds/:cloud_id/instances/:instance_id/volume_attachments
|
145
171
|
|
146
|
-
Because the cloud_id and the instance_id are automatically added by the client, scripts that work for regular
|
172
|
+
Because the ```cloud_id``` and the ```instance_id``` are automatically added by the client, scripts that work for regular
|
147
173
|
email-password logins will have to be modified for instance-facing calls. The main reason behind this is the
|
148
|
-
inability for instance-facing calls to access the clouds resource (i.e.:
|
174
|
+
inability for instance-facing calls to access the clouds resource (i.e.: ```@instance_client.clouds(:id=> :cloud_id).show``` will fail)
|
149
175
|
|
150
|
-
When you query
|
176
|
+
When you query ```api_methods```, it will list all of the methods that one sees with regular email-password logins.
|
151
177
|
Due to the limiting scope of the instance-facing calls, only a subset of these methods can be called
|
152
178
|
(see the API Reference Docs for valid methods). If you call a method that instance's are not authorized to access,
|
153
179
|
you will get a 403 Permission Denied error.
|
154
180
|
|
155
181
|
|
156
|
-
|
182
|
+
# Design Decisions
|
157
183
|
In the code, we only hard-code CRUD operations for resources. We use the .show and .index methods to make the client
|
158
184
|
more efficient. Since it dynamically creates methods it needs to query the API at times. The .show and the .index make
|
159
185
|
it explicit that querying needs to take place. Without them a GET would have to be queried every step of the way
|
@@ -163,16 +189,16 @@ first do an index call).
|
|
163
189
|
<b>In general, when a new API resource is added, you need to indicate in the client whether index, show, create, update
|
164
190
|
and delete methods are allowed for that resource.</b>
|
165
191
|
|
166
|
-
|
167
|
-
|
192
|
+
## Special Cases
|
193
|
+
### Returning resource\_types that are not actual API resources:
|
168
194
|
- tags:
|
169
|
-
-
|
195
|
+
- by\_resource, by\_tag: both return a COLLECTION of resource\_type = RESOURCE\_TAG
|
170
196
|
- no show or index is defined for that resource_type, therefore return a collection of ResourceDetail objects
|
171
197
|
- data:
|
172
|
-
- querying .data for
|
173
|
-
- no show is defined for that
|
198
|
+
- querying .data for monitoring\_metrics:
|
199
|
+
- no show is defined for that resource\_type, therefore return a ResourceDetail object
|
174
200
|
|
175
|
-
|
201
|
+
### Index call does not act like an index call
|
176
202
|
- session:
|
177
203
|
- session.index should act like a show call and not like an index call (since you cannot query show).
|
178
204
|
Therefore it should return a ResourceDetail object
|
@@ -180,23 +206,36 @@ and delete methods are allowed for that resource.</b>
|
|
180
206
|
- inputs.index cannot return a collection of Resource objects since .show is not allowed. Therefore it should
|
181
207
|
return a collection of ResourceDetail objects
|
182
208
|
|
183
|
-
|
184
|
-
- In
|
185
|
-
- In
|
186
|
-
|
209
|
+
### Having a resource\_type that cannot be accurately determined from the URL:
|
210
|
+
- In server\_arrays.show: resource\_type = current\_instance(s) (although it should be instance(s))
|
211
|
+
- In multi\_cloud\_images.show: resource\_type = setting(s) (although it should be multi\_cloud\_image\_setting)
|
212
|
+
|
213
|
+
Put these special cases in the ```RightApi::Helper::INCONSISTENT_RESOURCE_TYPES``` hash.
|
214
|
+
|
215
|
+
### Method defined on the generic resource\_type itself
|
216
|
+
- 'instances' => {:multi\_terminate => 'do\_post', :multi\_run\_executable => 'do\_post'},
|
217
|
+
- 'inputs' => {:multi\_update => 'do\_put'},
|
218
|
+
- 'tags' => {:by\_tag => 'do\_post', :by\_resource => 'do\_post', :multi\_add => 'do\_post', :multi\_delete =>'do\_post'},
|
219
|
+
- 'backups' => {:cleanup => 'do\_post'}
|
220
|
+
|
221
|
+
Put these special cases in the ```RightApi::Helper::RESOURCE_TYPE_SPECIAL_ACTIONS``` hash.
|
222
|
+
|
223
|
+
### Resources are not linked together
|
224
|
+
- In ResourceDetail, resource\_type = Instance, need live\_tasks as a method.
|
225
|
+
|
226
|
+
|
227
|
+
# Testing
|
228
|
+
|
229
|
+
## Unit Testing
|
230
|
+
bundle exec rspec spec/unit
|
187
231
|
|
188
|
-
|
189
|
-
|
190
|
-
- 'inputs' => {:multi_update => 'do_put'},
|
191
|
-
- 'tags' => {:by_tag => 'do_post', :by_resource => 'do_post', :multi_add => 'do_post', :multi_delete =>'do_post'},
|
192
|
-
- 'backups' => {:cleanup => 'do_post'}
|
193
|
-
Put these special cases in the <tt>RightApi::Helper::RESOURCE_TYPE_SPECIAL_ACTIONS</tt> hash.
|
232
|
+
## Functional Testing
|
233
|
+
See Usage Instructions for how to configure functional testing.
|
194
234
|
|
195
|
-
|
196
|
-
- In ResourceDetail, resource_type = Instance, need live_tasks as a method.
|
235
|
+
bundle exec rspec spec/functional
|
197
236
|
|
198
|
-
|
237
|
+
# Troubleshooting
|
199
238
|
|
200
|
-
|
239
|
+
## Wrong ruby version
|
201
240
|
|
202
241
|
Ruby 1.8.7 or higher is required.
|
data/config/login.yml.example
CHANGED
@@ -6,9 +6,14 @@
|
|
6
6
|
# The account number is at the end of the browser address bar.
|
7
7
|
:account_id: my_account_id
|
8
8
|
|
9
|
-
# There are
|
9
|
+
# There are four login mechanisms:
|
10
10
|
|
11
|
-
#
|
11
|
+
# 0. Use the following parameters to login with your email and base64-encoded password:
|
12
|
+
# To encode your plaintext password, use 'base64' command or similar.
|
13
|
+
:email: my@email.com
|
14
|
+
:password_base64: my_password_encoded_as_base64
|
15
|
+
|
16
|
+
# 1. Use the following parameters to login with your email and plaintext password:
|
12
17
|
:email: my@email.com
|
13
18
|
:password: my_password
|
14
19
|
|
@@ -20,6 +25,6 @@
|
|
20
25
|
# 3. Use the following parameter to login with pre-authenticated cookies:
|
21
26
|
:cookies: my_cookie_string
|
22
27
|
|
23
|
-
# The following are optional parameters
|
28
|
+
# The following are optional parameters:
|
24
29
|
:api_url: (this defaults to https://my.rightscale.com)
|
25
|
-
:api_version: (this defaults to 1.5)
|
30
|
+
:api_version: (this defaults to 1.5)
|
@@ -2,13 +2,14 @@ require 'rest_client'
|
|
2
2
|
require 'json'
|
3
3
|
require 'set'
|
4
4
|
require 'cgi'
|
5
|
+
require 'base64'
|
5
6
|
|
6
7
|
require File.expand_path('../version', __FILE__) unless defined?(RightApi::Client::VERSION)
|
7
8
|
require File.expand_path('../helper', __FILE__)
|
8
9
|
require File.expand_path('../resource', __FILE__)
|
9
10
|
require File.expand_path('../resource_detail', __FILE__)
|
10
11
|
require File.expand_path('../resources', __FILE__)
|
11
|
-
require File.expand_path('../
|
12
|
+
require File.expand_path('../errors', __FILE__)
|
12
13
|
|
13
14
|
# RightApiClient has the generic get/post/delete/put calls that are used by resources
|
14
15
|
module RightApi
|
@@ -20,23 +21,35 @@ module RightApi
|
|
20
21
|
DEFAULT_API_URL = 'https://my.rightscale.com'
|
21
22
|
|
22
23
|
# permitted parameters for initializing
|
23
|
-
AUTH_PARAMS = %w
|
24
|
-
|
24
|
+
AUTH_PARAMS = %w[
|
25
|
+
email password_base64 password account_id api_url api_version
|
26
|
+
cookies instance_token
|
27
|
+
]
|
28
|
+
|
29
|
+
attr_reader :cookies, :instance_token, :last_request
|
25
30
|
|
26
31
|
def initialize(args)
|
32
|
+
|
27
33
|
raise 'This API client is only compatible with Ruby 1.8.7 and upwards.' if (RUBY_VERSION < '1.8.7')
|
34
|
+
|
28
35
|
@api_url, @api_version = DEFAULT_API_URL, API_VERSION
|
36
|
+
|
29
37
|
# Initializing all instance variables from hash
|
30
38
|
args.each { |key,value|
|
31
39
|
instance_variable_set("@#{key}", value) if value && AUTH_PARAMS.include?(key.to_s)
|
32
40
|
} if args.is_a? Hash
|
33
41
|
|
34
42
|
raise 'This API client is only compatible with the RightScale API 1.5 and upwards.' if (Float(@api_version) < 1.5)
|
43
|
+
|
35
44
|
@rest_client = RestClient::Resource.new(@api_url, :timeout => -1)
|
45
|
+
@last_request = {}
|
46
|
+
|
47
|
+
# There are three options for login: credentials, instance token,
|
48
|
+
# or if the user already has the cookies they can just use those.
|
49
|
+
# See config/login.yml.example for more info.
|
50
|
+
login() unless @cookies
|
36
51
|
|
37
|
-
|
38
|
-
# has the cookies they can just use those. See config/login.yml.example for more info.
|
39
|
-
@cookies ||= login()
|
52
|
+
timestamp_cookies
|
40
53
|
|
41
54
|
# Add the top level links for instance_facing_calls
|
42
55
|
if @instance_token
|
@@ -82,32 +95,48 @@ module RightApi
|
|
82
95
|
RestClient.log = file
|
83
96
|
end
|
84
97
|
|
85
|
-
#
|
98
|
+
# Given a path returns a RightApiClient::Resource instance.
|
99
|
+
#
|
100
|
+
def resource(path)
|
101
|
+
|
102
|
+
r = Resource.process(self, *do_get(path))
|
103
|
+
|
104
|
+
r.respond_to?(:show) ? r.show : r
|
105
|
+
end
|
106
|
+
|
107
|
+
# Seems #resource tends to expand (call index) on Resources instances,
|
108
|
+
# so this is a workaround.
|
109
|
+
#
|
110
|
+
def resources(type, path)
|
111
|
+
|
112
|
+
Resources.new(self, path, type)
|
113
|
+
end
|
114
|
+
|
115
|
+
protected
|
86
116
|
|
87
117
|
def login
|
88
|
-
if @instance_token
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
118
|
+
params, path = if @instance_token
|
119
|
+
[ { 'instance_token' => @instance_token },
|
120
|
+
ROOT_INSTANCE_RESOURCE ]
|
121
|
+
elsif @password_base64
|
122
|
+
[ { 'email' => @email, 'password' => Base64.decode64(@password_base64) },
|
123
|
+
ROOT_RESOURCE ]
|
93
124
|
else
|
94
|
-
|
95
|
-
|
96
|
-
'password' => @password,
|
97
|
-
}
|
98
|
-
path = ROOT_RESOURCE
|
125
|
+
[ { 'email' => @email, 'password' => @password },
|
126
|
+
ROOT_RESOURCE ]
|
99
127
|
end
|
100
128
|
params['account_href'] = "/api/accounts/#{@account_id}"
|
101
129
|
|
102
|
-
response = @rest_client[path].post(params, 'X_API_VERSION' => @api_version) do |response, request, result|
|
103
|
-
|
104
|
-
|
105
|
-
response
|
130
|
+
response = @rest_client[path].post(params, 'X_API_VERSION' => @api_version) do |response, request, result, &block|
|
131
|
+
if response.code == 302
|
132
|
+
update_api_url(response)
|
133
|
+
response.follow_redirection(request, result, &block)
|
106
134
|
else
|
107
135
|
response.return!(request, result)
|
108
136
|
end
|
109
137
|
end
|
110
|
-
|
138
|
+
|
139
|
+
update_cookies(response)
|
111
140
|
end
|
112
141
|
|
113
142
|
# Returns the request headers
|
@@ -115,39 +144,57 @@ module RightApi
|
|
115
144
|
{'X_API_VERSION' => @api_version, :cookies => @cookies, :accept => :json}
|
116
145
|
end
|
117
146
|
|
147
|
+
def update_last_request(request, response)
|
148
|
+
@last_request[:request] = request
|
149
|
+
@last_request[:response] = response
|
150
|
+
end
|
151
|
+
|
118
152
|
# Generic get
|
119
153
|
# params are NOT read only
|
120
154
|
def do_get(path, params={})
|
155
|
+
|
121
156
|
# Resource id is a special param as it needs to be added to the path
|
122
157
|
path = add_id_and_params_to_path(path, params)
|
123
158
|
|
159
|
+
req, res = nil
|
160
|
+
|
124
161
|
begin
|
125
162
|
# Return content type so the resulting resource object knows what kind of resource it is.
|
126
|
-
resource_type, body = @rest_client[path].get(headers) do |response, request, result|
|
163
|
+
resource_type, body = @rest_client[path].get(headers) do |response, request, result, &block|
|
164
|
+
req, res = request, response
|
165
|
+
update_cookies(response)
|
166
|
+
update_last_request(request, response)
|
167
|
+
|
127
168
|
case response.code
|
128
169
|
when 200
|
129
|
-
# Get the resource_type from the content_type, the resource_type
|
130
|
-
# be used later to add relevant methods to relevant resources
|
131
|
-
type = ''
|
132
|
-
|
133
|
-
|
170
|
+
# Get the resource_type from the content_type, the resource_type
|
171
|
+
# will be used later to add relevant methods to relevant resources
|
172
|
+
type = if result.content_type.index('rightscale')
|
173
|
+
get_resource_type(result.content_type)
|
174
|
+
else
|
175
|
+
''
|
134
176
|
end
|
135
177
|
|
136
178
|
[type, response.body]
|
179
|
+
when 301, 302
|
180
|
+
update_api_url(response)
|
181
|
+
response.follow_redirection(request, result, &block)
|
137
182
|
when 404
|
138
|
-
raise
|
183
|
+
raise UnknownRouteError.new(request, response)
|
139
184
|
else
|
140
|
-
raise
|
185
|
+
raise ApiError.new(request, response)
|
141
186
|
end
|
142
187
|
end
|
143
|
-
rescue
|
188
|
+
rescue ApiError => e
|
144
189
|
if re_login?(e)
|
145
|
-
#
|
146
|
-
|
190
|
+
# session cookie is expired or invalid
|
191
|
+
login()
|
147
192
|
retry
|
148
193
|
else
|
149
|
-
raise e
|
194
|
+
raise wrap(e, :get, path, params, req, res)
|
150
195
|
end
|
196
|
+
rescue => e
|
197
|
+
raise wrap(e, :get, path, params, req, res)
|
151
198
|
end
|
152
199
|
|
153
200
|
data = if resource_type == 'text'
|
@@ -161,8 +208,16 @@ module RightApi
|
|
161
208
|
|
162
209
|
# Generic post
|
163
210
|
def do_post(path, params={})
|
211
|
+
params = fix_array_of_hashes(params)
|
212
|
+
|
213
|
+
req, res = nil
|
214
|
+
|
164
215
|
begin
|
165
216
|
@rest_client[path].post(params, headers) do |response, request, result|
|
217
|
+
req, res = request, response
|
218
|
+
update_cookies(response)
|
219
|
+
update_last_request(request, response)
|
220
|
+
|
166
221
|
case response.code
|
167
222
|
when 201, 202
|
168
223
|
# Create and return the resource
|
@@ -173,6 +228,8 @@ module RightApi
|
|
173
228
|
# This is based on the assumption that we can determine the resource_type without doing a do_get
|
174
229
|
resource_type = get_singular(relative_href.split('/')[-2])
|
175
230
|
RightApi::Resource.process(self, resource_type, relative_href)
|
231
|
+
when 204
|
232
|
+
nil
|
176
233
|
when 200..299
|
177
234
|
# This is needed for the tags Resource -- which returns a 200 and has a content type
|
178
235
|
# therefore, ResourceDetail objects needs to be returned
|
@@ -188,62 +245,89 @@ module RightApi
|
|
188
245
|
response.return!(request, result)
|
189
246
|
end
|
190
247
|
when 404
|
191
|
-
raise
|
248
|
+
raise UnknownRouteError.new(request, response)
|
192
249
|
else
|
193
|
-
raise
|
250
|
+
raise ApiError.new(request, response)
|
194
251
|
end
|
195
252
|
end
|
196
|
-
|
253
|
+
|
254
|
+
rescue ApiError => e
|
197
255
|
if re_login?(e)
|
198
|
-
|
256
|
+
login()
|
199
257
|
retry
|
200
258
|
else
|
201
|
-
raise e
|
259
|
+
raise wrap(e, :post, path, params, req, res)
|
202
260
|
end
|
261
|
+
rescue => e
|
262
|
+
raise wrap(e, :post, path, params, req, res)
|
203
263
|
end
|
204
264
|
end
|
205
265
|
|
206
266
|
# Generic delete
|
207
|
-
def do_delete(path)
|
267
|
+
def do_delete(path, params={})
|
268
|
+
# Resource id is a special param as it needs to be added to the path
|
269
|
+
path = add_id_and_params_to_path(path, params)
|
270
|
+
|
271
|
+
req, res = nil
|
272
|
+
|
208
273
|
begin
|
209
274
|
@rest_client[path].delete(headers) do |response, request, result|
|
275
|
+
req, res = request, response
|
276
|
+
update_cookies(response)
|
277
|
+
update_last_request(request, response)
|
278
|
+
|
210
279
|
case response.code
|
211
|
-
when 200
|
280
|
+
when 200
|
281
|
+
when 204
|
282
|
+
nil
|
212
283
|
when 404
|
213
|
-
raise
|
284
|
+
raise UnknownRouteError.new(request, response)
|
214
285
|
else
|
215
|
-
raise
|
286
|
+
raise ApiError.new(request, response)
|
216
287
|
end
|
217
288
|
end
|
218
|
-
rescue
|
289
|
+
rescue ApiError => e
|
219
290
|
if re_login?(e)
|
220
|
-
|
291
|
+
login()
|
221
292
|
retry
|
222
293
|
else
|
223
|
-
raise e
|
294
|
+
raise wrap(e, :delete, path, params, req, res)
|
224
295
|
end
|
296
|
+
rescue => e
|
297
|
+
raise wrap(e, :delete, path, params, req, res)
|
225
298
|
end
|
226
299
|
end
|
227
300
|
|
228
301
|
# Generic put
|
229
302
|
def do_put(path, params={})
|
303
|
+
params = fix_array_of_hashes(params)
|
304
|
+
|
305
|
+
req, res = nil
|
306
|
+
|
230
307
|
begin
|
231
308
|
@rest_client[path].put(params, headers) do |response, request, result|
|
309
|
+
req, res = request, response
|
310
|
+
update_cookies(response)
|
311
|
+
update_last_request(request, response)
|
312
|
+
|
232
313
|
case response.code
|
233
314
|
when 204
|
315
|
+
nil
|
234
316
|
when 404
|
235
|
-
raise
|
317
|
+
raise UnknownRouteError.new(request, response)
|
236
318
|
else
|
237
|
-
raise
|
319
|
+
raise ApiError.new(request, response)
|
238
320
|
end
|
239
321
|
end
|
240
|
-
rescue
|
322
|
+
rescue ApiError => e
|
241
323
|
if re_login?(e)
|
242
|
-
|
324
|
+
login()
|
243
325
|
retry
|
244
326
|
else
|
245
|
-
raise e
|
327
|
+
raise wrap(e, :put, path, params, req, res)
|
246
328
|
end
|
329
|
+
rescue => e
|
330
|
+
raise wrap(e, :put, path, params, req, res)
|
247
331
|
end
|
248
332
|
end
|
249
333
|
|
@@ -255,6 +339,67 @@ module RightApi
|
|
255
339
|
def get_resource_type(content_type)
|
256
340
|
content_type.scan(/\.rightscale\.(.*)\+json/)[0][0]
|
257
341
|
end
|
342
|
+
|
343
|
+
# Makes sure the @cookies have a timestamp.
|
344
|
+
#
|
345
|
+
def timestamp_cookies
|
346
|
+
|
347
|
+
return unless @cookies
|
348
|
+
|
349
|
+
class << @cookies; attr_accessor :timestamp; end
|
350
|
+
@cookies.timestamp = Time.now
|
351
|
+
end
|
352
|
+
|
353
|
+
# Sets the @cookies (and timestamp it).
|
354
|
+
#
|
355
|
+
def update_cookies(response)
|
356
|
+
|
357
|
+
return unless response.cookies
|
358
|
+
|
359
|
+
(@cookies ||= {}).merge!(response.cookies)
|
360
|
+
timestamp_cookies
|
361
|
+
end
|
362
|
+
|
363
|
+
#
|
364
|
+
# A helper class for error details
|
365
|
+
#
|
366
|
+
class ErrorDetails
|
367
|
+
|
368
|
+
attr_reader :method, :path, :params, :request, :response
|
369
|
+
|
370
|
+
def initialize(me, pt, ps, rq, rs)
|
371
|
+
|
372
|
+
@method = me
|
373
|
+
@path = pt
|
374
|
+
@params = ps
|
375
|
+
@request = rq
|
376
|
+
@response = rs
|
377
|
+
end
|
378
|
+
|
379
|
+
def code
|
380
|
+
|
381
|
+
@response ? @response.code : nil
|
382
|
+
end
|
383
|
+
end
|
384
|
+
|
385
|
+
# Adds details (path, params) to an error. Returns the error.
|
386
|
+
#
|
387
|
+
def wrap(error, method, path, params, request, response)
|
388
|
+
|
389
|
+
class << error; attr_accessor :_details; end
|
390
|
+
error._details = ErrorDetails.new(method, path, params, request, response)
|
391
|
+
|
392
|
+
error
|
393
|
+
end
|
394
|
+
|
395
|
+
private
|
396
|
+
|
397
|
+
def update_api_url(response)
|
398
|
+
# Update the rest client url if we are redirected to another endpoint
|
399
|
+
uri = URI.parse(response.headers[:location])
|
400
|
+
@api_url = "#{uri.scheme}://#{uri.host}"
|
401
|
+
@rest_client = RestClient::Resource.new(@api_url, :timeout => -1)
|
402
|
+
end
|
258
403
|
end
|
259
404
|
end
|
260
405
|
|