right_api_client 1.5.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +13 -0
- data/CHANGELOG.rdoc +5 -0
- data/Gemfile +3 -0
- data/LICENSE.txt +19 -0
- data/README.rdoc +196 -0
- data/Rakefile +17 -0
- data/config/login.yml.example +25 -0
- data/lib/right_api_client.rb +1 -0
- data/lib/right_api_client/client.rb +245 -0
- data/lib/right_api_client/helper.rb +192 -0
- data/lib/right_api_client/resource.rb +61 -0
- data/lib/right_api_client/resource_detail.rb +115 -0
- data/lib/right_api_client/resources.rb +51 -0
- data/lib/right_api_client/version.rb +8 -0
- data/login_to_client_irb.rb +15 -0
- data/right_api_client.gemspec +31 -0
- data/right_api_client.rconf +10 -0
- data/spec/client_spec.rb +60 -0
- data/spec/instance_facing_spec.rb +25 -0
- data/spec/resource_detail_spec.rb +59 -0
- data/spec/resource_spec.rb +25 -0
- data/spec/resources_spec.rb +25 -0
- data/spec/spec_helper.rb +41 -0
- metadata +181 -0
data/.gitignore
ADDED
data/CHANGELOG.rdoc
ADDED
data/Gemfile
ADDED
data/LICENSE.txt
ADDED
@@ -0,0 +1,19 @@
|
|
1
|
+
Copyright (c) 2010-2011, RightScale.com
|
2
|
+
|
3
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
4
|
+
of this software and associated documentation files (the "Software"), to deal
|
5
|
+
in the Software without restriction, including without limitation the rights
|
6
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
7
|
+
copies of the Software, and to permit persons to whom the Software is
|
8
|
+
furnished to do so, subject to the following conditions:
|
9
|
+
|
10
|
+
The above copyright notice and this permission notice shall be included in
|
11
|
+
all copies or substantial portions of the Software.
|
12
|
+
|
13
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
14
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
15
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
16
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
17
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
18
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
19
|
+
THE SOFTWARE.
|
data/README.rdoc
ADDED
@@ -0,0 +1,196 @@
|
|
1
|
+
= RightScale API Client
|
2
|
+
|
3
|
+
The right_api_client gem simplifies the use of RightScale's MultiCloud API. It provides
|
4
|
+
a simple object model of the API resources, and handles all of the fine details involved
|
5
|
+
in making HTTP calls and translating their responses.
|
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
|
+
|
10
|
+
== Installation
|
11
|
+
Ruby 1.9 is required.
|
12
|
+
gem install right_api_client
|
13
|
+
|
14
|
+
== Versioning
|
15
|
+
The right_api_client gem is versioned using the usual X.Y.Z notation, where X.Y is the
|
16
|
+
RightScale API version, and Z is the client version. For example, if you want to use
|
17
|
+
RightScale API 1.5, you should use the latest version of the 1.5 gem. This will ensure
|
18
|
+
that you get the latest bug fixes for the client that is compatible with that API version.
|
19
|
+
|
20
|
+
== Usage Instructions
|
21
|
+
New users can start with the following few lines of code and navigate their way around the API by following
|
22
|
+
the available methods. You can find your account id by logging into the RightScale dashboard (https://my.rightscale.com),
|
23
|
+
navigate to the Settings > Account Settings page. The account is is at the end of the browser address bar.
|
24
|
+
require 'right_api_client'
|
25
|
+
@client = RightApi::Client.new(:email => 'my@email.com', :password => 'my_password', :account_id => 'my_account_id')
|
26
|
+
puts "Available methods: #{@client.api_methods}"
|
27
|
+
|
28
|
+
The client makes working with and getting to know the API much easier. It spiders the API dynamically to
|
29
|
+
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 <tt>config/login.yml.example</tt> file provides
|
31
|
+
details of different login parameters.
|
32
|
+
|
33
|
+
=== Making API calls
|
34
|
+
Essentially, just follow the RightScale API documentation (available from http://support.rightscale.com)
|
35
|
+
and treat every resource in the paths as objects that can call other objects using the dot (.) operator:
|
36
|
+
|
37
|
+
Examples:
|
38
|
+
- Index: /api/clouds/:cloud_id/datacenters => @client.clouds(:id => :cloud_id).show.datacenters.index
|
39
|
+
- Show: /api/clouds/:cloud_id/datacenters/:id => @client.clouds(:id => :cloud_id).show.datacenters(:id => :datacenter_id).show
|
40
|
+
- Create: /api/deployments/:deployment_id/servers => @client.deployments(:id => :deployment_id).show.servers.create
|
41
|
+
- Update: /api/deployments/:deployment_id/servers/:id => @client.deployments(:id => :deployment_id).show.servers(:id => :server_id).update
|
42
|
+
- Destroy: /api/deployments/:deployment_id/servers/:id => @client.deployments(:id => :deployment_id).show.servers(:id => :server_id).destroy
|
43
|
+
- An action: /api/servers/:server_id/launch => @client.servers(:id => :server_id).show.launch
|
44
|
+
|
45
|
+
As seen above, whenever you need to chain methods, you must call .show before specifying the next method.
|
46
|
+
|
47
|
+
=== Parameters
|
48
|
+
Pass-in parameters to the method that they belong to. Lets say you want to filter on the index for deployments:
|
49
|
+
@client.deployments.index(:filter => ['name==my_deployment'])
|
50
|
+
The filter is the parameter for the index call and not the deployment call.
|
51
|
+
|
52
|
+
=== Logging HTTP Requests
|
53
|
+
The HTTP calls made by right_api_client can be logged in two ways:
|
54
|
+
1. Log to a file
|
55
|
+
@client.log('~/right_api_client.log')
|
56
|
+
2. Log to SDTOUT
|
57
|
+
@client.log(STDOUT)
|
58
|
+
|
59
|
+
== Examples
|
60
|
+
Get a list of all servers (aka doing an Index call)
|
61
|
+
@client.servers.index
|
62
|
+
|
63
|
+
Get a list of all servers in a deployment
|
64
|
+
@client.deployments(:id => 'my_deployment_id').show.servers.index
|
65
|
+
|
66
|
+
Get a particular server (aka doing a Show call)
|
67
|
+
@client.servers(:id => 'my_server_id').show
|
68
|
+
|
69
|
+
Creating a server involves setting up the required parameters, then calling the create method
|
70
|
+
server_template_href = @client.server_templates.index(:filter => ['name==Base ServerTemplate']).first.href
|
71
|
+
cloud = @client.clouds(:id => 'my_cloud_id').show
|
72
|
+
params = { :server => {
|
73
|
+
:name => 'Test Server',
|
74
|
+
:deployment_href => @client.deployments(:id => 'my_deployment_id').show.href,
|
75
|
+
:instance => {
|
76
|
+
:server_template_href => server_template_href,
|
77
|
+
:cloud_href => cloud.href,
|
78
|
+
:security_group_hrefs => [cloud.security_groups.index(:filter => ['name==default']).first.href],
|
79
|
+
:ssh_key_href => cloud.ssh_keys.index.first.href,
|
80
|
+
:datacenter_href => cloud.datacenters.index.first.href
|
81
|
+
}}}
|
82
|
+
new_server = @client.servers.create(params)
|
83
|
+
new_server.api_methods
|
84
|
+
|
85
|
+
Launch the newly created server. Inputs are a bit tricky so they have to be set in a long string
|
86
|
+
inputs = "inputs[][name]=NAME1&inputs[][value]=text:VALUE1&inputs[][name]=NAME2&inputs[][value]=text:VALUE2"
|
87
|
+
new_server.show.launch(inputs)
|
88
|
+
|
89
|
+
Run a script on the server. The API does not currently expose right_scripts, hence, the script href has
|
90
|
+
to be retrieved from the dashboard and put in the following href format.
|
91
|
+
script_href = "right_script_href=/api/right_scripts/382371"
|
92
|
+
task = new_server.show.current_instance.show.run_executable(script_href + "&inputs[][name]=TEST_NAME&inputs[][value]=text:VALUE1")
|
93
|
+
task.show.api_methods
|
94
|
+
|
95
|
+
Update the server's name
|
96
|
+
params = { :server => {:name => 'New Server Name'}}
|
97
|
+
@client.servers(:id => 'my_server_id').update(params)
|
98
|
+
|
99
|
+
Terminate the server (i.e. shutdown its current_instance)
|
100
|
+
@client.servers(:id => 'my_server_id').show.terminate
|
101
|
+
|
102
|
+
Destroy the server (i.e. delete it)
|
103
|
+
@client.servers(:id => 'my_server_id').destroy
|
104
|
+
|
105
|
+
== Object Types
|
106
|
+
The client returns 3 types of objects:
|
107
|
+
- <b>Resources</b>: returned when you are querying a collection of resources, e.g.: <tt>client.deployments</tt>
|
108
|
+
- <b>Resource</b>: returned when you specify an id and therefore a specific resource, e.g.: <tt>@client.deployments(:id => :deployment_id)</tt>
|
109
|
+
- When the content-type is type=collection then an array of Resource objects will be returned, e.g.: <tt>@client.deployments.index</tt>
|
110
|
+
- When the content-type is not a collection then a Resource object will be returned, e.g.: <tt>@client.deployments(:id => deployment_id).show</tt>
|
111
|
+
- <b>ResourceDetail</b>: returned when you do a .show on a Resource, e.g.: <tt>client.deployments(:id => deployment_id).show</tt>
|
112
|
+
<b>On all 3 types of objects you can query <tt>.api_methods</tt> to see a list of available methods, e.g.: <tt>client.deployments.api_methods</tt></b>
|
113
|
+
|
114
|
+
=== Exceptions:
|
115
|
+
- <tt>inputs.index</tt> will return an array of ResourceDetail objects since you cannot do a .show on an input
|
116
|
+
- <tt>session.index</tt> will return a ResourceDetail object since you cannot do a .show on a session
|
117
|
+
- <tt>tags.by_resource, tags.by_tag</tt> will return an array of ResourceDetail objects since you cannot do a .show on a resource_tag
|
118
|
+
- <tt>monitoring_metrics(:id => :m_m_id).show.data</tt> will return a ResourceDetail object since you cannot do
|
119
|
+
a .show on a monitoring_metric_data
|
120
|
+
|
121
|
+
== Instance Facing Calls:
|
122
|
+
The client also supports 'instance facing calls', which use the instance_token to login.
|
123
|
+
Unlike regular email-password logins, instance-facing-calls are limited in the amount of allowable calls.
|
124
|
+
Since in most of the cases, the calls are scoped to the instance's cloud (or the instance itself), the cloud_id and
|
125
|
+
the instance_id will be automatically recorded by the client, so that the user does not need to specify it.
|
126
|
+
|
127
|
+
=== Examples
|
128
|
+
@instance_client = RightApi::Client.new(:instance_token => 'my_token', :account_id => 'my_account_id')
|
129
|
+
@instance_client.volume_attachments links to /api/clouds/:cloud_id/volume_attachments
|
130
|
+
@instance_client.volumes_snapshots links to /api/clouds/:cloud_id/volumes_snapshots
|
131
|
+
@instance_client.volumes_types links to /api/clouds/:cloud_id/volumes_types
|
132
|
+
@instance_client.volumes links to /api/clouds/:cloud_id/volumes
|
133
|
+
@instance_client.backups links to /api/backups
|
134
|
+
@instance_client.live_tasks(:id) links to /api/clouds/:cloud_id/instances/:instance_id/live/tasks/:id
|
135
|
+
|
136
|
+
=== Notes
|
137
|
+
For volume_attachments and volumes_snapshots you can also go through the volume:
|
138
|
+
@instance_client.volumes(:id => :volume_id).show.volume_attachments
|
139
|
+
which maps to:
|
140
|
+
/api/clouds/:cloud_id/volumes/:volume_id/volume_attachment
|
141
|
+
The instance's volume_attachments can be accessed using:
|
142
|
+
@instance_client.get_instance.volume_attachments
|
143
|
+
which maps to:
|
144
|
+
/api/clouds/:cloud_id/instances/:instance_id/volume_attachments
|
145
|
+
|
146
|
+
Because the cloud_id and the instance_id are automatically added by the client, scripts that work for regular
|
147
|
+
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.: <tt>@instance_client.clouds(:id=> :cloud_id).show</tt> will fail)
|
149
|
+
|
150
|
+
When you query <tt>api_methods</tt>, it will list all of the methods that one sees with regular email-password logins.
|
151
|
+
Due to the limiting scope of the instance-facing calls, only a subset of these methods can be called
|
152
|
+
(see the API Reference Docs for valid methods). If you call a method that instance's are not authorized to access,
|
153
|
+
you will get a 403 Permission Denied error.
|
154
|
+
|
155
|
+
|
156
|
+
= Design Decisions
|
157
|
+
In the code, we only hard-code CRUD operations for resources. We use the .show and .index methods to make the client
|
158
|
+
more efficient. Since it dynamically creates methods it needs to query the API at times. The .show and the .index make
|
159
|
+
it explicit that querying needs to take place. Without them a GET would have to be queried every step of the way
|
160
|
+
(i.e.: the index call would be client.deployments, and the create call would be client.deployments.create which would
|
161
|
+
first do an index call).
|
162
|
+
|
163
|
+
<b>In general, when a new API resource is added, you need to indicate in the client whether index, show, create, update
|
164
|
+
and delete methods are allowed for that resource.</b>
|
165
|
+
|
166
|
+
== Special Cases
|
167
|
+
=== Returning resource_types that are not actual API resources:
|
168
|
+
- tags:
|
169
|
+
- by_resource, by_tag: both return a COLLECTION of resource_type = RESOURCE_TAG
|
170
|
+
- no show or index is defined for that resource_type, therefore return a collection of ResourceDetail objects
|
171
|
+
- data:
|
172
|
+
- querying .data for monitoring_metrics:
|
173
|
+
- no show is defined for that resource_type, therefore return a ResourceDetail object
|
174
|
+
|
175
|
+
=== Index call does not act like an index call
|
176
|
+
- session:
|
177
|
+
- session.index should act like a show call and not like an index call (since you cannot query show).
|
178
|
+
Therefore it should return a ResourceDetail object
|
179
|
+
- inputs
|
180
|
+
- inputs.index cannot return a collection of Resource objects since .show is not allowed. Therefore it should
|
181
|
+
return a collection of ResourceDetail objects
|
182
|
+
|
183
|
+
=== Having a resource_type that cannot be accurately determined from the URL:
|
184
|
+
- In server_arrays.show: resource_type = current_instance(s) (although it should be instance(s))
|
185
|
+
- In multi_cloud_images.show: resource_type = setting(s) (although it should be multi_cloud_image_setting)
|
186
|
+
Put these special cases in the <tt>RightApi::Helper::INCONSISTENT_RESOURCE_TYPES</tt> hash.
|
187
|
+
|
188
|
+
=== Method defined on the generic resource_type itself
|
189
|
+
- 'instances' => {:multi_terminate => 'do_post', :multi_run_executable => 'do_post'},
|
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.
|
194
|
+
|
195
|
+
=== Resources are not linked together
|
196
|
+
- In ResourceDetail, resource_type = Instance, need live_tasks as a method.
|
data/Rakefile
ADDED
@@ -0,0 +1,17 @@
|
|
1
|
+
require File.expand_path('../lib/right_api_client', __FILE__)
|
2
|
+
require 'rake'
|
3
|
+
require 'spec/rake/spectask'
|
4
|
+
|
5
|
+
task :build do
|
6
|
+
system "gem build right_api_client.gemspec"
|
7
|
+
end
|
8
|
+
|
9
|
+
task :release => :build do
|
10
|
+
system "gem push right_api_client-#{RightApi::Client::VERSION}.gem"
|
11
|
+
end
|
12
|
+
|
13
|
+
Spec::Rake::SpecTask.new('spec') do |t|
|
14
|
+
t.spec_files = Dir.glob('spec/*_spec.rb')
|
15
|
+
t.spec_opts << '--format nested'
|
16
|
+
t.spec_opts << '--colour'
|
17
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
# The API login details can be put in a YAML file. This is an example login.yml which
|
2
|
+
# will be needed for login_to_client_irb.rb quick login method.
|
3
|
+
|
4
|
+
# Account ID is a required parameter. You can find your account number by logging into the
|
5
|
+
# RightScale dashboard (https://my.rightscale.com), navigate to the Settings > Account Settings page.
|
6
|
+
# The account number is at the end of the browser address bar.
|
7
|
+
:account_id: my_account_id
|
8
|
+
|
9
|
+
# There are three login mechanisms:
|
10
|
+
|
11
|
+
# 1. Use the following parameters to login with your email and password:
|
12
|
+
:email: my@email.com
|
13
|
+
:password: my_password
|
14
|
+
|
15
|
+
# 2. Use the following parameter to login with an instance_token:
|
16
|
+
# To find the instance_token, launch a server, navigate to the info page and under
|
17
|
+
# User Data box, you will see RS_API_TOKEN = your_account_id:the_instance_token
|
18
|
+
:instance_token: my_instance_token
|
19
|
+
|
20
|
+
# 3. Use the following parameter to login with pre-authenticated cookies:
|
21
|
+
:cookies: my_cookie_string
|
22
|
+
|
23
|
+
# The following are optional parameters can also be set:
|
24
|
+
:api_url: (this defaults to https://my.rightscale.com)
|
25
|
+
:api_version: (this defaults to 1.5)
|
@@ -0,0 +1 @@
|
|
1
|
+
require File.expand_path('../right_api_client/client', __FILE__)
|
@@ -0,0 +1,245 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'json'
|
3
|
+
require 'set'
|
4
|
+
require 'cgi'
|
5
|
+
|
6
|
+
require File.expand_path('../version', __FILE__) unless defined?(RightApi::Client::VERSION)
|
7
|
+
require File.expand_path('../helper', __FILE__)
|
8
|
+
require File.expand_path('../resource', __FILE__)
|
9
|
+
require File.expand_path('../resource_detail', __FILE__)
|
10
|
+
require File.expand_path('../resources', __FILE__)
|
11
|
+
|
12
|
+
# RightApiClient has the generic get/post/delete/put calls that are used by resources
|
13
|
+
module RightApi
|
14
|
+
class Client
|
15
|
+
include Helper
|
16
|
+
|
17
|
+
ROOT_RESOURCE = '/api/session'
|
18
|
+
ROOT_INSTANCE_RESOURCE = '/api/session/instance'
|
19
|
+
DEFAULT_API_URL = 'https://my.rightscale.com'
|
20
|
+
|
21
|
+
# permitted parameters for initializing
|
22
|
+
AUTH_PARAMS = %w(email password account_id api_url api_version cookies instance_token)
|
23
|
+
attr_reader :cookies, :instance_token
|
24
|
+
|
25
|
+
def initialize(args)
|
26
|
+
@api_url, @api_version = DEFAULT_API_URL, API_VERSION
|
27
|
+
# Initializing all instance variables from hash
|
28
|
+
args.each { |key,value|
|
29
|
+
instance_variable_set("@#{key}", value) if value && AUTH_PARAMS.include?(key.to_s)
|
30
|
+
} if args.is_a? Hash
|
31
|
+
|
32
|
+
raise 'This API client is only compatible with the RightScale API 1.5 and upwards.' if (Float(@api_version) < 1.5)
|
33
|
+
@rest_client = RestClient::Resource.new(@api_url)
|
34
|
+
|
35
|
+
# There are three options for login: credentials, instance token, or if the user already
|
36
|
+
# has the cookies they can just use those. See config/login.yml.example for more info.
|
37
|
+
@cookies ||= login()
|
38
|
+
|
39
|
+
# Add the top level links for instance_facing_calls
|
40
|
+
if @instance_token
|
41
|
+
resource_type, path, data = self.do_get(ROOT_INSTANCE_RESOURCE)
|
42
|
+
instance_href = get_href_from_links(data['links'])
|
43
|
+
cloud_href = instance_href.split('/instances')[0]
|
44
|
+
|
45
|
+
define_instance_method(:get_instance) do |*params|
|
46
|
+
type, instance_path, instance_data = self.do_get(ROOT_INSTANCE_RESOURCE)
|
47
|
+
RightApi::ResourceDetail.new(self, type, instance_path, instance_data)
|
48
|
+
end
|
49
|
+
|
50
|
+
Helper::INSTANCE_FACING_RESOURCES.each do |meth|
|
51
|
+
define_instance_method(meth) do |*args|
|
52
|
+
obj_path = cloud_href + '/' + meth.to_s
|
53
|
+
# Following are special cases that need to over-ride the obj_path
|
54
|
+
obj_path = '/api/backups' if meth == :backups
|
55
|
+
obj_path = instance_href + '/live/tasks' if meth == :live_tasks
|
56
|
+
if has_id(*args)
|
57
|
+
obj_path = add_id_and_params_to_path(obj_path, *args)
|
58
|
+
RightApi::Resource.process(self, get_singular(meth), obj_path)
|
59
|
+
else
|
60
|
+
RightApi::Resources.new(self, obj_path, meth.to_s)
|
61
|
+
end
|
62
|
+
end
|
63
|
+
end
|
64
|
+
else
|
65
|
+
# Session is the root resource that has links to all the base resources
|
66
|
+
define_instance_method(:session) do |*params|
|
67
|
+
RightApi::Resources.new(self, ROOT_RESOURCE, 'session')
|
68
|
+
end
|
69
|
+
# Allow the base resources to be accessed directly
|
70
|
+
get_associated_resources(self, session.index.links, nil)
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
def to_s
|
75
|
+
"#<RightApi::Client>"
|
76
|
+
end
|
77
|
+
|
78
|
+
# Log HTTP calls to file (file can be STDOUT as well)
|
79
|
+
def log(file)
|
80
|
+
RestClient.log = file
|
81
|
+
end
|
82
|
+
|
83
|
+
# Users shouldn't need to call the following methods directly
|
84
|
+
|
85
|
+
def login
|
86
|
+
if @instance_token
|
87
|
+
params = {
|
88
|
+
'instance_token' => @instance_token
|
89
|
+
}
|
90
|
+
path = ROOT_INSTANCE_RESOURCE
|
91
|
+
else
|
92
|
+
params = {
|
93
|
+
'email' => @email,
|
94
|
+
'password' => @password,
|
95
|
+
}
|
96
|
+
path = ROOT_RESOURCE
|
97
|
+
end
|
98
|
+
params['account_href'] = "/api/accounts/#{@account_id}"
|
99
|
+
|
100
|
+
response = @rest_client[path].post(params, 'X_API_VERSION' => @api_version) do |response, request, result|
|
101
|
+
case response.code
|
102
|
+
when 302
|
103
|
+
response
|
104
|
+
else
|
105
|
+
response.return!(request, result)
|
106
|
+
end
|
107
|
+
end
|
108
|
+
response.cookies
|
109
|
+
end
|
110
|
+
|
111
|
+
# Returns the request headers
|
112
|
+
def headers
|
113
|
+
{'X_API_VERSION' => @api_version, :cookies => @cookies, :accept => :json}
|
114
|
+
end
|
115
|
+
|
116
|
+
# Generic get
|
117
|
+
# params are NOT read only
|
118
|
+
def do_get(path, params={})
|
119
|
+
# Resource id is a special param as it needs to be added to the path
|
120
|
+
path = add_id_and_params_to_path(path, params)
|
121
|
+
|
122
|
+
begin
|
123
|
+
# Return content type so the resulting resource object knows what kind of resource it is.
|
124
|
+
resource_type, body = @rest_client[path].get(headers) do |response, request, result|
|
125
|
+
case response.code
|
126
|
+
when 200
|
127
|
+
# Get the resource_type from the content_type, the resource_type will
|
128
|
+
# be used later to add relevant methods to relevant resources.
|
129
|
+
type = ''
|
130
|
+
if result.content_type.index('rightscale')
|
131
|
+
type = get_resource_type(result.content_type)
|
132
|
+
end
|
133
|
+
|
134
|
+
[type, response.body]
|
135
|
+
else
|
136
|
+
raise "Unexpected response #{response.code.to_s}, #{response.body}"
|
137
|
+
end
|
138
|
+
end
|
139
|
+
#Session cookie is expired or invalid
|
140
|
+
rescue RuntimeError => e
|
141
|
+
if re_login?(e)
|
142
|
+
@cookies = login()
|
143
|
+
retry
|
144
|
+
else
|
145
|
+
raise e
|
146
|
+
end
|
147
|
+
end
|
148
|
+
|
149
|
+
data = JSON.parse(body)
|
150
|
+
[resource_type, path, data]
|
151
|
+
end
|
152
|
+
|
153
|
+
# Generic post
|
154
|
+
def do_post(path, params={})
|
155
|
+
begin
|
156
|
+
@rest_client[path].post(params, headers) do |response, request, result|
|
157
|
+
case response.code
|
158
|
+
when 201, 202
|
159
|
+
# Create and return the resource
|
160
|
+
href = response.headers[:location]
|
161
|
+
relative_href = href.split(@api_url)[-1]
|
162
|
+
# Return the resource that was just created
|
163
|
+
# Determine the resource_type from the href (eg. api/clouds/id).
|
164
|
+
# This is based on the assumption that we can determine the resource_type without doing a do_get
|
165
|
+
resource_type = get_singular(relative_href.split('/')[-2])
|
166
|
+
RightApi::Resource.process(self, resource_type, relative_href)
|
167
|
+
when 200..299
|
168
|
+
# This is needed for the tags Resource -- which returns a 200 and has a content type
|
169
|
+
# therefore, ResourceDetail objects needs to be returned
|
170
|
+
if response.code == 200 && response.headers[:content_type].index('rightscale')
|
171
|
+
resource_type = get_resource_type(response.headers[:content_type])
|
172
|
+
data = JSON.parse(response)
|
173
|
+
# Resource_tag is returned after querying tags.by_resource or tags.by_tags.
|
174
|
+
# You cannot do a show on a resource_tag, but that is basically what we want to do
|
175
|
+
data.map { |obj|
|
176
|
+
RightApi::ResourceDetail.new(self, resource_type, path, obj)
|
177
|
+
}
|
178
|
+
else
|
179
|
+
response.return!(request, result)
|
180
|
+
end
|
181
|
+
else
|
182
|
+
raise "Unexpected response #{response.code.to_s}, #{response.body}"
|
183
|
+
end
|
184
|
+
end
|
185
|
+
rescue RuntimeError => e
|
186
|
+
if re_login?(e)
|
187
|
+
@cookies = login()
|
188
|
+
retry
|
189
|
+
else
|
190
|
+
raise e
|
191
|
+
end
|
192
|
+
end
|
193
|
+
end
|
194
|
+
|
195
|
+
# Generic delete
|
196
|
+
def do_delete(path)
|
197
|
+
begin
|
198
|
+
@rest_client[path].delete(headers) do |response|
|
199
|
+
case response.code
|
200
|
+
when 200, 204
|
201
|
+
else
|
202
|
+
raise "Unexpected response #{response.code.to_s}, #{response.body}"
|
203
|
+
end
|
204
|
+
end
|
205
|
+
rescue RuntimeError => e
|
206
|
+
if re_login?(e)
|
207
|
+
@cookies = login()
|
208
|
+
retry
|
209
|
+
else
|
210
|
+
raise e
|
211
|
+
end
|
212
|
+
end
|
213
|
+
end
|
214
|
+
|
215
|
+
# Generic put
|
216
|
+
def do_put(path, params={})
|
217
|
+
begin
|
218
|
+
@rest_client[path].put(params, headers) do |response|
|
219
|
+
case response.code
|
220
|
+
when 204
|
221
|
+
else
|
222
|
+
raise "Unexpected response #{response.code.to_s}, #{response.body}"
|
223
|
+
end
|
224
|
+
end
|
225
|
+
rescue RuntimeError => e
|
226
|
+
if re_login?(e)
|
227
|
+
@cookies = login()
|
228
|
+
retry
|
229
|
+
else
|
230
|
+
raise e
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
|
235
|
+
def re_login?(e)
|
236
|
+
e.message.index('403') && e.message =~ %r(.*Session cookie is expired or invalid)
|
237
|
+
end
|
238
|
+
|
239
|
+
# returns the resource_type
|
240
|
+
def get_resource_type(content_type)
|
241
|
+
content_type.scan(/\.rightscale\.(.*)\+json/)[0][0]
|
242
|
+
end
|
243
|
+
end
|
244
|
+
end
|
245
|
+
|