right_api_client 1.5.9 → 1.5.12
Sign up to get free protection for your applications and to get access to all the features.
- 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
|