right_api_client 1.5.1
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 +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
|
+
|