right_api_client 1.5.9 → 1.5.12
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.
- 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
|
|