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
@@ -0,0 +1,30 @@
|
|
1
|
+
|
2
|
+
module RightApi
|
3
|
+
|
4
|
+
class ApiError < RuntimeError
|
5
|
+
|
6
|
+
def initialize(request, response)
|
7
|
+
|
8
|
+
@request, @response = request, response
|
9
|
+
|
10
|
+
super(
|
11
|
+
prefix +
|
12
|
+
"HTTP Code: #{response.code.to_s}, " +
|
13
|
+
"Response body: #{response.body}")
|
14
|
+
end
|
15
|
+
|
16
|
+
def prefix
|
17
|
+
|
18
|
+
'Error: '
|
19
|
+
end
|
20
|
+
end
|
21
|
+
|
22
|
+
class UnknownRouteError < ApiError
|
23
|
+
|
24
|
+
def prefix
|
25
|
+
|
26
|
+
'Unknown action or route. '
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
|
@@ -58,7 +58,9 @@ module RightApi::Helper
|
|
58
58
|
# get the resource_type
|
59
59
|
# Special case: calling .data you don't want a resources object back
|
60
60
|
# but rather all its details since you cannot do a show
|
61
|
-
return RightApi::ResourceDetail.new(
|
61
|
+
return RightApi::ResourceDetail.new(
|
62
|
+
client, *client.send(:do_get, hrefs.first, *args)
|
63
|
+
) if rel == :data
|
62
64
|
|
63
65
|
if is_singular?(rel)
|
64
66
|
# Then the href will be: /resource_type/:id
|
@@ -150,8 +152,19 @@ module RightApi::Helper
|
|
150
152
|
|
151
153
|
# Helper method that checks whether the string is singular
|
152
154
|
def is_singular?(str)
|
153
|
-
|
154
|
-
|
155
|
+
test_str = str.to_s
|
156
|
+
return true if ['data'].include?(test_str)
|
157
|
+
|
158
|
+
case test_str
|
159
|
+
when "audit_entry"
|
160
|
+
return true
|
161
|
+
when "ip_address"
|
162
|
+
return true
|
163
|
+
when "process"
|
164
|
+
return true
|
165
|
+
else
|
166
|
+
(test_str)[-1, 1] != 's' # use legacy syntax for Ruby 1.8.7
|
167
|
+
end
|
155
168
|
end
|
156
169
|
|
157
170
|
# Does not modify links
|
@@ -172,10 +185,58 @@ module RightApi::Helper
|
|
172
185
|
return nil
|
173
186
|
end
|
174
187
|
|
188
|
+
# HACK: instead of pulling in activesupport gem, just hardcode some words
|
189
|
+
def simple_singularize(word)
|
190
|
+
case word
|
191
|
+
when "audit_entries"
|
192
|
+
"audit_entry"
|
193
|
+
when "ip_addresses"
|
194
|
+
"ip_address"
|
195
|
+
when "processes"
|
196
|
+
"process"
|
197
|
+
else
|
198
|
+
word.chop
|
199
|
+
end
|
200
|
+
end
|
201
|
+
|
175
202
|
# Will not change obj
|
176
203
|
def get_singular(obj)
|
177
|
-
|
178
|
-
return 'audit_entry' if str == 'audit_entries'
|
179
|
-
str.chomp('s')
|
204
|
+
simple_singularize(obj.to_s.downcase)
|
180
205
|
end
|
206
|
+
|
207
|
+
# rest client does not post params correctly for all the keys whose values are arrays of hashes
|
208
|
+
# @see http://stackoverflow.com/questions/6436110/restclient-strips-out-the-array-of-hashes-parameter-with-just-the-last-hash
|
209
|
+
#
|
210
|
+
def fix_array_of_hashes(args, top_level = true)
|
211
|
+
|
212
|
+
if args.is_a?(Array)
|
213
|
+
|
214
|
+
#recursively fix each element of the array
|
215
|
+
#
|
216
|
+
args.collect{|a| fix_array_of_hashes(a, false)}
|
217
|
+
elsif args.is_a?(Hash)
|
218
|
+
|
219
|
+
args.inject({}) do |res, (k, v)|
|
220
|
+
key = k
|
221
|
+
if v.is_a?(Array) && !v.empty? && v.first.is_a?(Hash)
|
222
|
+
if top_level
|
223
|
+
# signal to Rails that this really is an array
|
224
|
+
key = k.to_s + '[]'
|
225
|
+
value = fix_array_of_hashes(v, false)
|
226
|
+
else
|
227
|
+
# signal to Rails that this really is an array
|
228
|
+
value = {'' => fix_array_of_hashes(v, false)}
|
229
|
+
end
|
230
|
+
else
|
231
|
+
value = fix_array_of_hashes(v, false)
|
232
|
+
end
|
233
|
+
res[key] = value
|
234
|
+
res
|
235
|
+
end
|
236
|
+
else
|
237
|
+
|
238
|
+
args
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
181
242
|
end
|
@@ -40,26 +40,25 @@ module RightApi
|
|
40
40
|
@href = href
|
41
41
|
|
42
42
|
# Add destroy method to relevant resources
|
43
|
-
define_instance_method('destroy') do
|
44
|
-
client.do_delete
|
43
|
+
define_instance_method('destroy') do |*args|
|
44
|
+
client.send(:do_delete, href, *args)
|
45
45
|
end
|
46
46
|
|
47
47
|
# Add update method to relevant resources
|
48
48
|
define_instance_method('update') do |*args|
|
49
|
-
client.do_put
|
49
|
+
client.send(:do_put, href, *args)
|
50
50
|
end
|
51
51
|
|
52
52
|
# Add show method to relevant resources
|
53
53
|
define_instance_method('show') do |*args|
|
54
|
-
RightApi::ResourceDetail.new(client, *client.do_get
|
54
|
+
RightApi::ResourceDetail.new(client, *client.send(:do_get, href, *args))
|
55
55
|
end
|
56
56
|
end
|
57
57
|
|
58
|
-
#Any other method other than standard actions(show,update,destroy)
|
59
|
-
#called with a POST.
|
58
|
+
# Any other method other than standard actions(show,update,destroy)
|
59
|
+
# is simply appended to the href and called with a POST.
|
60
60
|
def method_missing(m, *args)
|
61
|
-
|
62
|
-
client.do_post(action_href, *args)
|
61
|
+
client.send(:do_post, [ href, m.to_s ].join('/'), *args)
|
63
62
|
end
|
64
63
|
end
|
65
64
|
end
|
@@ -41,8 +41,7 @@ module RightApi
|
|
41
41
|
actions << action_name.to_sym
|
42
42
|
|
43
43
|
define_instance_method(action_name.to_sym) do |*args|
|
44
|
-
|
45
|
-
client.do_post(action_href, *args)
|
44
|
+
client.send(:do_post, "#{hash['href']}/#{action['rel']}", *args)
|
46
45
|
end
|
47
46
|
end
|
48
47
|
|
@@ -91,13 +90,19 @@ module RightApi
|
|
91
90
|
end
|
92
91
|
|
93
92
|
# Add destroy method to relevant resources
|
94
|
-
define_instance_method('destroy') do
|
95
|
-
client.do_delete
|
93
|
+
define_instance_method('destroy') do |*args|
|
94
|
+
client.send(:do_delete, href, *args)
|
96
95
|
end
|
97
96
|
|
98
97
|
# Add update method to relevant resources
|
99
98
|
define_instance_method('update') do |*args|
|
100
|
-
|
99
|
+
|
100
|
+
if resource_type == 'account' # HACK: handle child_account update specially
|
101
|
+
update_href = href.sub(/account/, 'child_account')
|
102
|
+
client.send(:do_put, update_href, *args)
|
103
|
+
else
|
104
|
+
client.send(:do_put, href, *args)
|
105
|
+
end
|
101
106
|
end
|
102
107
|
|
103
108
|
# Add show method to relevant resources
|
@@ -106,12 +111,11 @@ module RightApi
|
|
106
111
|
end
|
107
112
|
end
|
108
113
|
|
109
|
-
#Any other method other than standard actions(show,update,destroy)
|
110
|
-
#called with a POST.
|
114
|
+
# Any other method other than standard actions(show,update,destroy)
|
115
|
+
# is simply appended to the href and called with a POST.
|
111
116
|
def method_missing(m, *args)
|
112
|
-
#
|
113
|
-
|
114
|
-
client.do_post(action_href, *args)
|
117
|
+
# the method href would have been defined while initializing the object
|
118
|
+
client.send(:do_post, [ href, m.to_s ].join('/'), *args)
|
115
119
|
end
|
116
120
|
end
|
117
121
|
end
|
@@ -25,16 +25,16 @@ module RightApi
|
|
25
25
|
@resource_type = resource_type
|
26
26
|
# Add create methods for the relevant root RightApi::Resources
|
27
27
|
self.define_instance_method('create') do |*args|
|
28
|
-
client.do_post
|
28
|
+
client.send(:do_post, path, *args)
|
29
29
|
end
|
30
30
|
|
31
31
|
# Add in index methods for the relevant root RightApi::Resources
|
32
32
|
self.define_instance_method('index') do |*args|
|
33
33
|
# Session uses .index like a .show (so need to treat it as a special case)
|
34
34
|
if resource_type == 'session'
|
35
|
-
ResourceDetail.new(client, *client.do_get
|
35
|
+
ResourceDetail.new(client, *client.send(:do_get, path, *args))
|
36
36
|
else
|
37
|
-
RightApi::Resource.process(client, *client.do_get
|
37
|
+
RightApi::Resource.process(client, *client.send(:do_get, path, *args))
|
38
38
|
end
|
39
39
|
end
|
40
40
|
|
@@ -43,16 +43,15 @@ module RightApi
|
|
43
43
|
# Insert_in_path will NOT modify path
|
44
44
|
action_path = insert_in_path(path, meth)
|
45
45
|
self.define_instance_method(meth) do |*args|
|
46
|
-
client.send
|
46
|
+
client.send(action, action_path, *args)
|
47
47
|
end
|
48
48
|
end if Helper::RESOURCE_SPECIAL_ACTIONS[resource_type]
|
49
49
|
end
|
50
50
|
|
51
|
-
#Any other method other than standard actions(create, index)
|
52
|
-
#called with a POST.
|
51
|
+
# Any other method other than standard actions (create, index)
|
52
|
+
# is simply appended to the href and called with a POST.
|
53
53
|
def method_missing(m, *args)
|
54
|
-
|
55
|
-
client.do_post(action_path, *args)
|
54
|
+
client.send(:do_post, [ href, m.to_s ].join('/'), *args)
|
56
55
|
end
|
57
56
|
end
|
58
57
|
end
|
data/login_to_client_irb.rb
CHANGED
@@ -8,8 +8,7 @@ require 'irb'
|
|
8
8
|
|
9
9
|
begin
|
10
10
|
@client = RightApi::Client.new(YAML.load_file(File.expand_path('../config/login.yml', __FILE__)))
|
11
|
-
puts "logged-in to the API, use the '@client' variable to use the client
|
12
|
-
puts @client.session.index.message
|
11
|
+
puts "logged-in to the API, use the '@client' variable to use the client"
|
13
12
|
end
|
14
13
|
|
15
14
|
IRB.start
|
data/right_api_client.gemspec
CHANGED
@@ -1,29 +1,29 @@
|
|
1
1
|
require File.expand_path('../lib/right_api_client/version', __FILE__)
|
2
2
|
|
3
3
|
Gem::Specification.new do |s|
|
4
|
-
s.name
|
5
|
-
s.version
|
6
|
-
s.platform
|
7
|
-
s.date
|
8
|
-
s.require_path
|
9
|
-
s.authors
|
10
|
-
s.email
|
11
|
-
s.homepage
|
12
|
-
s.summary
|
13
|
-
s.description
|
14
|
-
The right_api_client gem simplifies the use of RightScale's MultiCloud API.
|
15
|
-
a simple object model of the API resources, and handles all of the
|
16
|
-
in making HTTP calls and translating their responses.
|
4
|
+
s.name = 'right_api_client'
|
5
|
+
s.version = RightApi::Client::VERSION
|
6
|
+
s.platform = Gem::Platform::RUBY
|
7
|
+
s.date = Time.now.utc.strftime("%Y-%m-%d")
|
8
|
+
s.require_path = 'lib'
|
9
|
+
s.authors = [ 'RightScale, Inc.' ]
|
10
|
+
s.email = [ 'rubygems@rightscale.com' ]
|
11
|
+
s.homepage = 'https://github.com/rightscale/right_api_client'
|
12
|
+
s.summary = 'RightScale MultiCloud API HTTP Client'
|
13
|
+
s.description = %{
|
14
|
+
The right_api_client gem simplifies the use of RightScale's MultiCloud API.
|
15
|
+
It provides a simple object model of the API resources, and handles all of the
|
16
|
+
fine details involved in making HTTP calls and translating their responses.
|
17
17
|
}
|
18
|
-
s.files
|
19
|
-
s.test_files
|
18
|
+
s.files = `git ls-files`.split(' ')
|
19
|
+
s.test_files = `git ls-files spec config`.split(' ')
|
20
20
|
s.rubygems_version = '1.8.17'
|
21
21
|
|
22
22
|
s.add_runtime_dependency 'json'
|
23
|
-
s.add_runtime_dependency 'rest-client', '1.6
|
23
|
+
s.add_runtime_dependency 'rest-client', '~> 1.6'
|
24
24
|
|
25
25
|
s.add_development_dependency 'rake', '0.8.7'
|
26
|
-
s.add_development_dependency 'rspec', '
|
26
|
+
s.add_development_dependency 'rspec', '2.9.0'
|
27
27
|
s.add_development_dependency 'flexmock', '0.8.7'
|
28
28
|
s.add_development_dependency 'simplecov', '0.4.2'
|
29
29
|
s.add_development_dependency 'bundler'
|
data/right_api_client.rconf
CHANGED
@@ -1,12 +1,10 @@
|
|
1
|
-
|
2
|
-
require 'spec_helper'
|
3
|
-
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
4
2
|
|
5
3
|
describe RightApi::Client do
|
6
4
|
|
7
5
|
before(:all) do
|
8
6
|
|
9
|
-
creds = File.expand_path('
|
7
|
+
creds = File.expand_path('../../../config/login.yml', __FILE__)
|
10
8
|
|
11
9
|
begin
|
12
10
|
@client = RightApi::Client.new(YAML.load_file(creds))
|
@@ -1,31 +1,33 @@
|
|
1
|
-
require File.expand_path('
|
2
|
-
require 'yaml'
|
1
|
+
require File.expand_path('../../spec_helper', __FILE__)
|
3
2
|
|
4
3
|
describe RightApi::Client do
|
5
|
-
|
4
|
+
|
5
|
+
context "given a valid set of credentials in the config/login.yml file" do
|
6
|
+
|
6
7
|
before(:all) do
|
7
|
-
@creds = '
|
8
|
+
@creds = '../../../config/login.yml'
|
8
9
|
begin
|
9
10
|
@client = RightApi::Client.new(YAML.load_file(File.expand_path(@creds, __FILE__)))
|
10
|
-
rescue
|
11
|
+
rescue => e
|
11
12
|
puts "WARNING: The following specs need a valid set of credentials as they are integration tests that can only be done by calling the API server"
|
12
13
|
puts e.message
|
14
|
+
puts e.backtrace
|
13
15
|
end
|
14
16
|
end
|
15
17
|
|
16
|
-
it "
|
17
|
-
@client.headers[:cookies].should_not be_nil
|
18
|
+
it "logs in" do
|
19
|
+
@client.send(:headers)[:cookies].should_not be_nil
|
18
20
|
@client.session.index.message.should == 'You have successfully logged into the RightScale API.'
|
19
21
|
end
|
20
22
|
|
21
|
-
it "
|
23
|
+
it "returns valid cookies" do
|
22
24
|
@client.cookies.class.should == Hash
|
23
25
|
@client.cookies['_session_id'].should_not be_nil
|
24
26
|
@client.cookies['domain'].should match /rightscale.com$/
|
25
27
|
@client.cookies.keys.sort.last.should match /^rs_gbl/ # HACK: not quite sane sanity check
|
26
28
|
end
|
27
29
|
|
28
|
-
it "
|
30
|
+
it "accepts a cookie argument when creating a new client" do
|
29
31
|
my_hash = YAML.load_file(File.expand_path(@creds, __FILE__))
|
30
32
|
my_hash.delete(:email)
|
31
33
|
my_hash.delete(:password)
|
@@ -35,12 +37,27 @@ describe RightApi::Client do
|
|
35
37
|
client1.cookies.should == @client.cookies
|
36
38
|
end
|
37
39
|
|
38
|
-
it "
|
40
|
+
it "timestamps cookies" do
|
41
|
+
|
42
|
+
@client.cookies.timestamp.should_not == nil
|
43
|
+
end
|
44
|
+
|
45
|
+
it "keeps track of the cookies all the time" do
|
46
|
+
|
47
|
+
t0 = @client.cookies.timestamp
|
48
|
+
|
49
|
+
@client.deployments.index
|
50
|
+
t1 = @client.cookies.timestamp
|
51
|
+
|
52
|
+
t0.to_f.should < t1.to_f
|
53
|
+
end
|
54
|
+
|
55
|
+
it "accepts a YAML argument when creating a new client" do
|
39
56
|
client2 = RightApi::Client.new(YAML.load_file(File.expand_path(@creds, __FILE__)))
|
40
57
|
client2.cookies.should_not == @client.cookies
|
41
58
|
end
|
42
59
|
|
43
|
-
it "
|
60
|
+
it "sends post/get/put/delete requests to the server correctly" do
|
44
61
|
new_deployment = @client.deployments.create(:deployment => {:name => 'test'})
|
45
62
|
new_deployment2 = @client.deployments.create(:deployment => {:name => 'test2'})
|
46
63
|
new_deployment.class.should == RightApi::Resource
|
@@ -55,7 +72,7 @@ describe RightApi::Client do
|
|
55
72
|
deployment.show.name.should == 'test2'
|
56
73
|
|
57
74
|
# Tags are a bit special as they use POST and return content type so they need specific tests
|
58
|
-
@client.tags.multi_add("resource_hrefs[]=#{deployment.show.href}&resource_hrefs[]=#{new_deployment2.show.href}&tags[]=tag1").should ==
|
75
|
+
@client.tags.multi_add("resource_hrefs[]=#{deployment.show.href}&resource_hrefs[]=#{new_deployment2.show.href}&tags[]=tag1").should == nil
|
59
76
|
tags = @client.tags.by_resource("resource_hrefs[]=#{deployment.show.href}&resource_hrefs[]=#{new_deployment2.show.href}")
|
60
77
|
tags.class.should == Array
|
61
78
|
tags.first.class.should == RightApi::ResourceDetail
|
@@ -65,5 +82,61 @@ describe RightApi::Client do
|
|
65
82
|
deployment.destroy.should be_nil
|
66
83
|
new_deployment2.destroy.should be_nil
|
67
84
|
end
|
85
|
+
|
86
|
+
it "singularizes resource_types correctly" do
|
87
|
+
@client.get_singular('servers').should == 'server'
|
88
|
+
@client.get_singular('deployments').should == 'deployment'
|
89
|
+
@client.get_singular('audit_entries').should == 'audit_entry'
|
90
|
+
@client.get_singular('processes').should == 'process'
|
91
|
+
@client.get_singular('ip_addresses').should == 'ip_address'
|
92
|
+
end
|
93
|
+
|
94
|
+
it "returns the resource when calling #resource(href)" do
|
95
|
+
|
96
|
+
d0 = @client.deployments.index.first
|
97
|
+
|
98
|
+
d1 = @client.resource(d0.href)
|
99
|
+
|
100
|
+
d1.href.should == d0.href
|
101
|
+
end
|
102
|
+
|
103
|
+
it "raises meaningful errors" do
|
104
|
+
|
105
|
+
err = begin
|
106
|
+
@client.resource('/api/nada')
|
107
|
+
rescue => e
|
108
|
+
e
|
109
|
+
end
|
110
|
+
|
111
|
+
err.class.should ==
|
112
|
+
RightApi::UnknownRouteError
|
113
|
+
err.message.should ==
|
114
|
+
"Unknown action or route. HTTP Code: 404, Response body: " +
|
115
|
+
"NotFound: No route matches \"/api/nada\" with {:method=>:get}"
|
116
|
+
end
|
117
|
+
|
118
|
+
it "wraps errors with _details" do
|
119
|
+
|
120
|
+
err = begin
|
121
|
+
@client.deployments(:id => 'nada').show
|
122
|
+
rescue => e
|
123
|
+
e
|
124
|
+
end
|
125
|
+
|
126
|
+
#p err
|
127
|
+
#puts err.backtrace
|
128
|
+
|
129
|
+
err._details.method.should == :get
|
130
|
+
err._details.path.should == '/api/deployments/nada'
|
131
|
+
err._details.params.should == {}
|
132
|
+
|
133
|
+
err._details.request.class.should == RestClient::Request
|
134
|
+
|
135
|
+
err._details.response.code.should == 422
|
136
|
+
err._details.response.class.should == String
|
137
|
+
err._details.response.should == "ResourceNotFound: Couldn't find Deployment with ID=nada "
|
138
|
+
|
139
|
+
err._details.code.should == 422
|
140
|
+
end
|
68
141
|
end
|
69
142
|
end
|