right_api_client 1.5.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,13 @@
1
+ .DS_Store
2
+ nbproject
3
+ *login.yml
4
+ .loadpath
5
+ .project
6
+ .rvmrc
7
+ .bundle
8
+ .idea
9
+ .idea/*
10
+ coverage/*
11
+ doc/*
12
+ *~
13
+ Gemfile.lock
@@ -0,0 +1,5 @@
1
+ = right_api_client - CHANGELOG.rdoc
2
+
3
+
4
+ == right_api_client - 1.5.1
5
+ Initial public release, supports all of the calls in RightScale API 1.5.
data/Gemfile ADDED
@@ -0,0 +1,3 @@
1
+ source 'http://rubygems.org'
2
+
3
+ gemspec
@@ -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.
@@ -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.
@@ -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
+