openstack-keystone-client 0.0.2
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +6 -0
- data/.rspec +1 -0
- data/Gemfile +4 -0
- data/LICENSE +22 -0
- data/README.md +82 -0
- data/Rakefile +8 -0
- data/lib/keystone/v2_0/client.rb +105 -0
- data/lib/keystone/v2_0/ec2.rb +17 -0
- data/lib/keystone/v2_0/tokens.rb +60 -0
- data/lib/keystone/version.rb +5 -0
- data/lib/openstack-client.rb +75 -0
- data/lib/openstack-client/base.rb +130 -0
- data/lib/openstack-client/service_catalog.rb +77 -0
- data/openstack-keystone-client.gemspec +24 -0
- data/spec/examples/client.rb +25 -0
- data/spec/examples/resource.rb +10 -0
- data/spec/keystone/v2_0/client_spec.rb +74 -0
- data/spec/lib/keystone_double.rb +17 -0
- data/spec/openstack-client/base_spec.rb +45 -0
- data/spec/openstack-client/service_catalog_spec.rb +73 -0
- data/spec/openstack-client_spec.rb +58 -0
- data/spec/spec_helper.rb +22 -0
- metadata +149 -0
data/.gitignore
ADDED
data/.rspec
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
--colour
|
data/Gemfile
ADDED
data/LICENSE
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
Copyright (c) 2012 yuanying, atoato88
|
2
|
+
|
3
|
+
MIT License
|
4
|
+
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining
|
6
|
+
a copy of this software and associated documentation files (the
|
7
|
+
"Software"), to deal in the Software without restriction, including
|
8
|
+
without limitation the rights to use, copy, modify, merge, publish,
|
9
|
+
distribute, sublicense, and/or sell copies of the Software, and to
|
10
|
+
permit persons to whom the Software is furnished to do so, subject to
|
11
|
+
the following conditions:
|
12
|
+
|
13
|
+
The above copyright notice and this permission notice shall be
|
14
|
+
included in all copies or substantial portions of the Software.
|
15
|
+
|
16
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
17
|
+
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
18
|
+
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
|
19
|
+
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
|
20
|
+
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
|
21
|
+
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
22
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
data/README.md
ADDED
@@ -0,0 +1,82 @@
|
|
1
|
+
Ruby Openstack Keystone Client
|
2
|
+
=============================================================
|
3
|
+
|
4
|
+
## Description
|
5
|
+
|
6
|
+
This is a client for the OpenStack Keystone/Glance/Nova API. There's a Ruby API.
|
7
|
+
|
8
|
+
## Keystone API
|
9
|
+
|
10
|
+
The Keystone 2.0 API is still a moving target, so this module will remain in
|
11
|
+
"Beta" status until the API is finalized and fully implemented.
|
12
|
+
|
13
|
+
### Examples
|
14
|
+
|
15
|
+
See the class definitions for documentation on specific methods and operations.
|
16
|
+
|
17
|
+
require 'keystone/v2_0/client'
|
18
|
+
require 'pp'
|
19
|
+
|
20
|
+
begin
|
21
|
+
client = Keystone::V2_0::Client.new(
|
22
|
+
:username => 'username',
|
23
|
+
:password => 'password',
|
24
|
+
:tenant_name => 'demo',
|
25
|
+
:auth_url => 'http://example.com:5000/v2.0/'
|
26
|
+
)
|
27
|
+
|
28
|
+
pp client.user
|
29
|
+
# =>
|
30
|
+
# {"id"=>"9",
|
31
|
+
# "roles"=>
|
32
|
+
# [{"tenantId"=>"2", "id"=>"1", "name"=>"Admin"},
|
33
|
+
# {"tenantId"=>"2", "id"=>"2", "name"=>"Member"}],
|
34
|
+
# "name"=>"forecast_user"}
|
35
|
+
|
36
|
+
pp client.service_catalog.token
|
37
|
+
# =>
|
38
|
+
# {"id"=>"2a0996fe-e070-4a73-b6b5-6e940070f207",
|
39
|
+
# "expires"=>2012-03-01 13:57:42 +0900,
|
40
|
+
# "tenant"=>"2"}
|
41
|
+
|
42
|
+
pp client.service_catalog.endpoints
|
43
|
+
# =>
|
44
|
+
# {"compute"=>
|
45
|
+
# [{"adminURL"=>"http://example.com:8774/v1.1/2",
|
46
|
+
# "region"=>"RegionOne",
|
47
|
+
# "internalURL"=>"http://example.com:8774/v1.1/2",
|
48
|
+
# "publicURL"=>"http://example.com:8774/v1.1/2"}],
|
49
|
+
# "image"=>
|
50
|
+
# [{"adminURL"=>"http://example.com:9292/v1.1/2",
|
51
|
+
# "region"=>"RegionOne",
|
52
|
+
# "internalURL"=>"http://example.com:9292/v1.1/2",
|
53
|
+
# "publicURL"=>"http://example.com:9292/v1.1/2"}],
|
54
|
+
# "identity"=>
|
55
|
+
# [{"adminURL"=>"http://example.com:35357/v2.0",
|
56
|
+
# "region"=>"RegionOne",
|
57
|
+
# "internalURL"=>"http://example.com:5000/v2.0",
|
58
|
+
# "publicURL"=>"http://example.com:5000/v2.0"}],
|
59
|
+
# "object-store"=>
|
60
|
+
# [{"adminURL"=>"http://example.com:8080/",
|
61
|
+
# "region"=>"RegionOne",
|
62
|
+
# "internalURL"=>"http://example.com:8080/v1/AUTH_2",
|
63
|
+
# "publicURL"=>"http://example.com:8080/v1/AUTH_2"}],
|
64
|
+
# "ec2"=>
|
65
|
+
# [{"adminURL"=>"http://example.com:8773/services/Admin",
|
66
|
+
# "region"=>"RegionOne",
|
67
|
+
# "internalURL"=>"http://example.com:8773/services/Cloud",
|
68
|
+
# "publicURL"=>"http://example.com:8773/services/Cloud"}]}
|
69
|
+
rescue
|
70
|
+
puts 'Authentication Failed.'
|
71
|
+
end
|
72
|
+
|
73
|
+
|
74
|
+
## Authors
|
75
|
+
|
76
|
+
- Yuanying <yuanying@fraction.jp>
|
77
|
+
- atoato88 <atoato88@gmail.com>
|
78
|
+
|
79
|
+
|
80
|
+
## License
|
81
|
+
|
82
|
+
See LICENSE for license information.
|
data/Rakefile
ADDED
@@ -0,0 +1,105 @@
|
|
1
|
+
require 'openstack-client'
|
2
|
+
require 'openstack-client/service_catalog'
|
3
|
+
require 'keystone/v2_0/tokens'
|
4
|
+
require 'keystone/v2_0/ec2'
|
5
|
+
|
6
|
+
module Keystone
|
7
|
+
module V2_0
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
class Keystone::V2_0::Client < ::Openstack::Client
|
12
|
+
attr_accessor :username
|
13
|
+
attr_accessor :tenant_id
|
14
|
+
attr_accessor :tenant_name
|
15
|
+
attr_accessor :password
|
16
|
+
attr_accessor :auth_url
|
17
|
+
attr_accessor :version
|
18
|
+
attr_accessor :region_name
|
19
|
+
|
20
|
+
attr_accessor :roles
|
21
|
+
attr_accessor :services
|
22
|
+
attr_accessor :tenants
|
23
|
+
attr_accessor :tokens
|
24
|
+
attr_accessor :users
|
25
|
+
attr_accessor :service_catalog
|
26
|
+
|
27
|
+
attr_accessor :user
|
28
|
+
|
29
|
+
attr_accessor :ec2
|
30
|
+
|
31
|
+
# Creates a new Keystone::V2_0::Client object.
|
32
|
+
#
|
33
|
+
# The constructor takes a hash of options, including:
|
34
|
+
#
|
35
|
+
# :username - Your Openstack username (optional)
|
36
|
+
# :password - Your Openstack password (optional)
|
37
|
+
# :tenant_id - Your Openstack tenantId (optional)
|
38
|
+
# :tenant_name - Your Openstack tenantName (optional)
|
39
|
+
# :auth_url - Keystone service endpoint for authorization.
|
40
|
+
# :region_name - The specific service region to use. Defaults to first returned region.
|
41
|
+
# :token - Whether to retry if your auth token expires (defaults to true)
|
42
|
+
# :endpoint - A user-supplied endpoint URL for the keystone
|
43
|
+
# service. Lazy-authentication is possible for API
|
44
|
+
# service calls if endpoint is set at instantiation.(optional)
|
45
|
+
#
|
46
|
+
def initialize options={}
|
47
|
+
self.username = options[:username]
|
48
|
+
self.tenant_id = options[:tenant_id]
|
49
|
+
self.tenant_name = options[:tenant_name]
|
50
|
+
self.password = options[:password]
|
51
|
+
self.auth_url = options[:auth_url].chomp('/') if options[:auth_url]
|
52
|
+
self.version = 'v2.0'
|
53
|
+
self.region_name = options[:region_name]
|
54
|
+
self.auth_token = options[:token]
|
55
|
+
|
56
|
+
# self.roles = roles.RoleManager(self)
|
57
|
+
# self.services = services.ServiceManager(self)
|
58
|
+
# self.tenants = tenants.TenantManager(self)
|
59
|
+
self.tokens = Keystone::V2_0::TokenManager.new(self)
|
60
|
+
# self.users = users.UserManager(self)
|
61
|
+
|
62
|
+
# extensions
|
63
|
+
self.ec2 = Keystone::V2_0::EC2CredentialsManager.new(self)
|
64
|
+
|
65
|
+
unless options[:endpoint]
|
66
|
+
authenticate
|
67
|
+
else
|
68
|
+
self.management_url = options[:endpoint]
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
# Authenticate against the Keystone API.
|
73
|
+
#
|
74
|
+
# Uses the data provided at instantiation to authenticate against
|
75
|
+
# the Keystone server. This may use either a username and password
|
76
|
+
# or token for authentication. If a tenant id was provided
|
77
|
+
# then the resulting authenticated client will be scoped to that
|
78
|
+
# tenant and contain a service catalog of available endpoints.
|
79
|
+
#
|
80
|
+
# Returns ``true`` if authentication was successful.
|
81
|
+
def authenticate
|
82
|
+
self.management_url = self.auth_url
|
83
|
+
raw_token = self.tokens.authenticate(
|
84
|
+
username: username,
|
85
|
+
tenant_id: tenant_id,
|
86
|
+
tenant_name: tenant_name,
|
87
|
+
password: password,
|
88
|
+
token: auth_token,
|
89
|
+
return_raw: true
|
90
|
+
)
|
91
|
+
self._extract_service_catalog(self.auth_url, raw_token)
|
92
|
+
self.user = raw_token['user']
|
93
|
+
return true
|
94
|
+
end
|
95
|
+
|
96
|
+
def _extract_service_catalog url, body
|
97
|
+
self.service_catalog = Openstack::Client::ServiceCatalog.new(body)
|
98
|
+
self.auth_token = self.service_catalog.token['id']
|
99
|
+
self.management_url = self.service_catalog.url_for(
|
100
|
+
attribute: 'region',
|
101
|
+
filter_value: self.region_name,
|
102
|
+
endpoint_type: 'adminURL'
|
103
|
+
)
|
104
|
+
end
|
105
|
+
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'openstack-client/base'
|
2
|
+
|
3
|
+
module Keystone
|
4
|
+
module V2_0
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
class Keystone::V2_0::EC2 < Openstack::Client::Resource
|
9
|
+
end
|
10
|
+
|
11
|
+
class Keystone::V2_0::EC2CredentialsManager < Openstack::Client::Manager
|
12
|
+
RESOURCE_CLASS = Keystone::V2_0::EC2
|
13
|
+
|
14
|
+
def list user_id
|
15
|
+
self._list("/users/#{user_id}/credentials/OS-EC2", "credentials")
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,60 @@
|
|
1
|
+
require 'openstack-client/base'
|
2
|
+
|
3
|
+
module Keystone
|
4
|
+
module V2_0
|
5
|
+
|
6
|
+
class Token < Openstack::Client::Resource
|
7
|
+
def initialize manager, info, loaded=nil
|
8
|
+
super
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
class TokenManager < Openstack::Client::Manager
|
13
|
+
RESOURCE_CLASS = Token
|
14
|
+
|
15
|
+
def initialize api
|
16
|
+
super
|
17
|
+
end
|
18
|
+
|
19
|
+
# options:
|
20
|
+
# :username
|
21
|
+
# :tenant_id
|
22
|
+
# :tenant_name
|
23
|
+
# :password
|
24
|
+
# :token
|
25
|
+
# :return_raw
|
26
|
+
def authenticate options
|
27
|
+
if options[:token]
|
28
|
+
params = { 'auth' => { 'token' => { 'id' => options[:token] } } }
|
29
|
+
elsif options[:username] and options[:password]
|
30
|
+
params = {
|
31
|
+
'auth' => {
|
32
|
+
'passwordCredentials' => {
|
33
|
+
'username' => options[:username],
|
34
|
+
'password' => options[:password]
|
35
|
+
}}}
|
36
|
+
else
|
37
|
+
raise 'A username and password or token is required.'
|
38
|
+
end
|
39
|
+
|
40
|
+
if options[:tenant_id]
|
41
|
+
params['auth']['tenantId'] = options[:tenant_id]
|
42
|
+
elsif options[:tenant_name]
|
43
|
+
params['auth']['tenantName'] = options[:tenant_name]
|
44
|
+
end
|
45
|
+
|
46
|
+
return self._create('/tokens', params, 'access', options[:return_raw])
|
47
|
+
end
|
48
|
+
|
49
|
+
def delete token
|
50
|
+
self._delete("/tokens/#{token.id}")
|
51
|
+
end
|
52
|
+
|
53
|
+
def endpoints token
|
54
|
+
self._get("/tokens/#{token.id}/endpoints", 'token')
|
55
|
+
end
|
56
|
+
|
57
|
+
end
|
58
|
+
|
59
|
+
end
|
60
|
+
end
|
@@ -0,0 +1,75 @@
|
|
1
|
+
require 'rest_client'
|
2
|
+
require 'json'
|
3
|
+
|
4
|
+
module Openstack
|
5
|
+
|
6
|
+
end
|
7
|
+
|
8
|
+
class Openstack::Client
|
9
|
+
attr_accessor :auth_token
|
10
|
+
|
11
|
+
attr_accessor :management_url
|
12
|
+
|
13
|
+
def initialize keystone_or_options, options={}
|
14
|
+
require 'keystone/v2_0/client'
|
15
|
+
if keystone_or_options.kind_of?(Keystone::V2_0::Client)
|
16
|
+
keystone = keystone_or_options
|
17
|
+
endpoint_type = options[:endpoint_type] || 'publicURL'
|
18
|
+
self.management_url = keystone.service_catalog.url_for(service_type: 'image', endpoint_type: endpoint_type)
|
19
|
+
self.auth_token = keystone.service_catalog.token['id']
|
20
|
+
else
|
21
|
+
options = keystone_or_options
|
22
|
+
self.management_url = options[:endpoint]
|
23
|
+
self.auth_token = options[:token]
|
24
|
+
end
|
25
|
+
|
26
|
+
raise 'endpoint or Keystone::V2_0::Client object was required.' unless self.management_url
|
27
|
+
end
|
28
|
+
|
29
|
+
def get url, headers={}
|
30
|
+
res = self.execute(method: :get, url: url, headers: headers)
|
31
|
+
end
|
32
|
+
|
33
|
+
def post url, payload, headers={}
|
34
|
+
self.execute(method: :post, url: url, headers: headers, payload: payload)
|
35
|
+
end
|
36
|
+
|
37
|
+
def put url, payload, headers={}
|
38
|
+
self.execute(method: :put, url: url, headers: headers, payload: payload)
|
39
|
+
end
|
40
|
+
|
41
|
+
def delete url, headers={}
|
42
|
+
self.execute(method: :delete, url: url, headers: headers)
|
43
|
+
end
|
44
|
+
|
45
|
+
def execute options
|
46
|
+
self.authenticate unless self.management_url
|
47
|
+
|
48
|
+
options = options.dup
|
49
|
+
|
50
|
+
options[:url] = self.management_url + options[:url]
|
51
|
+
|
52
|
+
options[:headers] ||= {}
|
53
|
+
options[:headers]['X-Auth-Token'] = self.auth_token if self.auth_token
|
54
|
+
options[:headers]['User-Agent'] = 'ruby-openstack-client'
|
55
|
+
options[:headers][:accept] = :json
|
56
|
+
|
57
|
+
if options[:payload]
|
58
|
+
options[:headers][:content_type] = :json
|
59
|
+
options[:payload] = options[:payload].to_json
|
60
|
+
end
|
61
|
+
|
62
|
+
response = RestClient::Request.execute(options)
|
63
|
+
|
64
|
+
if [400, 401, 403, 404, 408, 409, 413, 500, 501].include?(response.code)
|
65
|
+
raise "HTTP Error: #{response.code}"
|
66
|
+
elsif [301, 302, 305].include?(response.code)
|
67
|
+
options[:url] = response.headers[:location]
|
68
|
+
return self.execute(options)
|
69
|
+
end
|
70
|
+
|
71
|
+
return [response, JSON.parse(response.to_s)]
|
72
|
+
end
|
73
|
+
|
74
|
+
end
|
75
|
+
|
@@ -0,0 +1,130 @@
|
|
1
|
+
require 'openstack-client'
|
2
|
+
require 'uri'
|
3
|
+
|
4
|
+
class Openstack::Client::Resource
|
5
|
+
attr_accessor :manager
|
6
|
+
attr_accessor :info
|
7
|
+
attr_accessor :loaded
|
8
|
+
|
9
|
+
def self.delegate methods, options
|
10
|
+
to = options[:to]
|
11
|
+
methods.each do |method|
|
12
|
+
module_eval <<-EOM
|
13
|
+
def #{method.to_s} *args, &block
|
14
|
+
args.unshift(self)
|
15
|
+
self.#{to.to_s}.send(:#{method.to_s}, *args, &block)
|
16
|
+
end
|
17
|
+
EOM
|
18
|
+
end
|
19
|
+
end
|
20
|
+
|
21
|
+
def initialize manager, info, loaded=nil
|
22
|
+
self.manager = manager
|
23
|
+
self.info = info
|
24
|
+
self.loaded = loaded
|
25
|
+
|
26
|
+
self.add_details(info)
|
27
|
+
end
|
28
|
+
|
29
|
+
def add_details info
|
30
|
+
info.each do |name, v|
|
31
|
+
unless self.respond_to?(name.to_sym)
|
32
|
+
self.instance_eval <<-EOM
|
33
|
+
def #{name}
|
34
|
+
self.info['#{name}']
|
35
|
+
end
|
36
|
+
def #{name}= val
|
37
|
+
self.info['#{name}'] = val
|
38
|
+
end
|
39
|
+
EOM
|
40
|
+
end
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
def method_missing(name, *args)
|
45
|
+
self.get unless self.loaded
|
46
|
+
|
47
|
+
return self.send(name, *args) if self.respond_to?(name)
|
48
|
+
super
|
49
|
+
end
|
50
|
+
|
51
|
+
def get
|
52
|
+
self.loaded = true
|
53
|
+
|
54
|
+
res = self.manager.get(self.id)
|
55
|
+
self.info = res.info
|
56
|
+
self.add_details(self.info)
|
57
|
+
return self
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
class Openstack::Client::Manager
|
62
|
+
RESOURCE_CLASS = Openstack::Client::Resource
|
63
|
+
attr_accessor :resource_class
|
64
|
+
attr_accessor :api
|
65
|
+
|
66
|
+
def initialize api
|
67
|
+
self.api = api
|
68
|
+
self.resource_class = self.class::RESOURCE_CLASS
|
69
|
+
end
|
70
|
+
|
71
|
+
def get_id obj
|
72
|
+
if obj.kind_of? String
|
73
|
+
return obj
|
74
|
+
elsif obj.respond_to?(:id)
|
75
|
+
return obj.id
|
76
|
+
elsif obj.respond_to?(:uuid)
|
77
|
+
return obj.uuid
|
78
|
+
end
|
79
|
+
raise "No ID of this object: #{obj}"
|
80
|
+
end
|
81
|
+
|
82
|
+
def url_with_params url, options={}
|
83
|
+
unless options.nil? || options.empty?
|
84
|
+
url += ('?' + options.to_a.map{|a| "#{a.first}=#{a.last}"}.join('&'))
|
85
|
+
end
|
86
|
+
URI.escape(url)
|
87
|
+
end
|
88
|
+
|
89
|
+
def _list url, response_key, body=nil
|
90
|
+
if body
|
91
|
+
resp, body = self.api.post(url, body)
|
92
|
+
else
|
93
|
+
resp, body = self.api.get(url)
|
94
|
+
end
|
95
|
+
|
96
|
+
data = body[response_key]
|
97
|
+
data = data['values'] if data.kind_of? Hash
|
98
|
+
|
99
|
+
return [].tap do |rtn|
|
100
|
+
data.each do |d|
|
101
|
+
rtn << self.resource_class.new(self, d, true)
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
def _get url, response_key
|
107
|
+
resp, body = self.api.get(url)
|
108
|
+
return self.resource_class.new(self, body[response_key])
|
109
|
+
end
|
110
|
+
|
111
|
+
def _create url, body, response_key, return_raw=false
|
112
|
+
resp, body = self.api.post(url, body)
|
113
|
+
if return_raw
|
114
|
+
return body[response_key]
|
115
|
+
else
|
116
|
+
return self.resource_class.new(self, body[response_key])
|
117
|
+
end
|
118
|
+
end
|
119
|
+
|
120
|
+
def _delete url
|
121
|
+
self.api.delete(url)
|
122
|
+
end
|
123
|
+
|
124
|
+
def _update url, body, response_key=nil, method=:put
|
125
|
+
resp, body = self.api.send(method, url, body)
|
126
|
+
return self.resource_class.new(self, body[response_key]) if body
|
127
|
+
return resp
|
128
|
+
end
|
129
|
+
|
130
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
require 'time'
|
2
|
+
|
3
|
+
module Openstack
|
4
|
+
class Client
|
5
|
+
end
|
6
|
+
end
|
7
|
+
|
8
|
+
# Helper class for dealing with a Keystone Service Catalog.
|
9
|
+
class Openstack::Client::ServiceCatalog
|
10
|
+
attr_accessor :catalog
|
11
|
+
|
12
|
+
def initialize resource_dict
|
13
|
+
self.catalog = resource_dict
|
14
|
+
end
|
15
|
+
|
16
|
+
# Fetch token details fron service catalog
|
17
|
+
def token
|
18
|
+
unless defined?(@token)
|
19
|
+
@token = {
|
20
|
+
'id' => self.catalog['token']['id'],
|
21
|
+
'expires' => Time.parse(self.catalog['token']['expires'])
|
22
|
+
}
|
23
|
+
@token['tenant'] = self.catalog['token']['tenant']['id'] if self.catalog['token']['tenant']
|
24
|
+
end
|
25
|
+
@token
|
26
|
+
end
|
27
|
+
|
28
|
+
# Fetch an endpoint from the service catalog.
|
29
|
+
#
|
30
|
+
# Fetch the specified endpoint from the service catalog for
|
31
|
+
# a particular endpoint attribute. If no attribute is given, return
|
32
|
+
# the first endpoint of the specified type.
|
33
|
+
#
|
34
|
+
# See tests for a sample service catalog.
|
35
|
+
def url_for options={}
|
36
|
+
service_type = options[:service_type] || 'identity'
|
37
|
+
endpoint_type = options[:endpoint_type] || 'publicURL'
|
38
|
+
attribute = options[:attribute]
|
39
|
+
filter_value = options[:filter_value]
|
40
|
+
|
41
|
+
catalog = self.catalog['serviceCatalog'] || []
|
42
|
+
|
43
|
+
catalog.each do |service|
|
44
|
+
next unless service['type'] == service_type
|
45
|
+
|
46
|
+
service['endpoints'].each do |endpoint|
|
47
|
+
return endpoint[endpoint_type] if filter_value.nil? || endpoint[attribute] == filter_value
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
raise 'Endpoint not found.'
|
52
|
+
end
|
53
|
+
|
54
|
+
# Fetch and filter endpoints for the specified service(s)
|
55
|
+
#
|
56
|
+
# Returns endpoints for the specified service (or all) and
|
57
|
+
# that contain the specified type (or all).
|
58
|
+
def endpoints options={}
|
59
|
+
service_type = options[:service_type]
|
60
|
+
endpoint_type = options[:endpoint_type]
|
61
|
+
|
62
|
+
return {}.tap do |rtn|
|
63
|
+
catalog = self.catalog['serviceCatalog'] || []
|
64
|
+
|
65
|
+
catalog.each do |service|
|
66
|
+
next if service_type and service_type != service['type']
|
67
|
+
|
68
|
+
rtn[service['type']] = []
|
69
|
+
service['endpoints'].each do |endpoint|
|
70
|
+
next if endpoint_type and endpoint.include?(endpoint_type)
|
71
|
+
rtn[service['type']] << endpoint
|
72
|
+
end
|
73
|
+
end
|
74
|
+
end
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|
@@ -0,0 +1,24 @@
|
|
1
|
+
# -*- encoding: utf-8 -*-
|
2
|
+
require File.expand_path('../lib/keystone/version', __FILE__)
|
3
|
+
|
4
|
+
Gem::Specification.new do |gem|
|
5
|
+
gem.authors = ["yuanying", "atoato88"]
|
6
|
+
gem.email = ["yuanying@fraction.jp", "atoato88@gmail.com"]
|
7
|
+
gem.description = %q{OpenStack Keystone client for Ruby.}
|
8
|
+
gem.summary = %q{This is a client for the OpenStack Keystone API. There's a Ruby API.}
|
9
|
+
gem.homepage = "https://github.com/yuanying/ruby-openstack-keystone-client"
|
10
|
+
|
11
|
+
gem.files = `git ls-files`.split($\)
|
12
|
+
gem.test_files = `git ls-files -- {test,spec,features}/*`.split("\n")
|
13
|
+
gem.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
|
14
|
+
gem.name = "openstack-keystone-client"
|
15
|
+
gem.require_paths = ["lib"]
|
16
|
+
gem.version = Keystone::Client::VERSION
|
17
|
+
|
18
|
+
gem.add_development_dependency "rake"
|
19
|
+
gem.add_development_dependency "rspec"
|
20
|
+
gem.add_development_dependency "webmock"
|
21
|
+
gem.add_development_dependency "activesupport"
|
22
|
+
gem.add_development_dependency "simplecov-rcov"
|
23
|
+
gem.add_runtime_dependency "rest-client"
|
24
|
+
end
|
@@ -0,0 +1,25 @@
|
|
1
|
+
require 'active_support/core_ext'
|
2
|
+
|
3
|
+
shared_examples 'Openstack::Client#initialize' do |manager_klasses|
|
4
|
+
|
5
|
+
subject { described_class.new(initializing_param) }
|
6
|
+
|
7
|
+
it "should be kind of #{described_class}" do
|
8
|
+
should be_kind_of(described_class)
|
9
|
+
end
|
10
|
+
|
11
|
+
it 'should have management_url' do
|
12
|
+
subject.management_url.should_not be_nil
|
13
|
+
end
|
14
|
+
|
15
|
+
it 'should have auth_token' do
|
16
|
+
subject.auth_token.should_not be_nil
|
17
|
+
end
|
18
|
+
|
19
|
+
manager_klasses.each do |manager_klass|
|
20
|
+
it "should have #{manager_klass}" do
|
21
|
+
subject.send(manager_klass.to_s.split('::').last.gsub(/Manager$/, '').tableize.to_sym).should be_kind_of(manager_klass)
|
22
|
+
end
|
23
|
+
end
|
24
|
+
|
25
|
+
end
|
@@ -0,0 +1,74 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'keystone/v2_0/client'
|
3
|
+
|
4
|
+
describe Keystone::V2_0::Client do
|
5
|
+
let(:valid_initialize_params) { {
|
6
|
+
:username => 'valid',
|
7
|
+
:password => 'valid_password',
|
8
|
+
:tenant_name => 'it',
|
9
|
+
:auth_url => 'http://auth.example.com:5000/v2.0/'
|
10
|
+
}
|
11
|
+
}
|
12
|
+
before do
|
13
|
+
stub_request(:post, 'http://auth.example.com:5000/v2.0/tokens').to_return(
|
14
|
+
status: 200,
|
15
|
+
body: "{\"access\": {\"token\": {\"expires\": \"2012-02-29T17:23:54\", \"id\": \"c8bb4eeb-4234-4c4a-b4b8-eddf7ed0862f\", \"tenant\": {\"id\": \"3\", \"name\": \"it\"}}, \"serviceCatalog\": [{\"endpoints\": [{\"adminURL\": \"http://10.21.55.30:8774/v1.1/3\", \"region\": \"RegionOne\", \"internalURL\": \"http://10.21.55.30:8774/v1.1/3\", \"publicURL\": \"http://10.21.55.30:8774/v1.1/3\"}], \"type\": \"compute\", \"name\": \"nova\"}, {\"endpoints\": [{\"adminURL\": \"http://10.21.55.30:9292/v1.1/3\", \"region\": \"RegionOne\", \"internalURL\": \"http://10.21.55.30:9292/v1.1/3\", \"publicURL\": \"http://10.21.55.30:9292/v1.1/3\"}], \"type\": \"image\", \"name\": \"glance\"}, {\"endpoints\": [{\"adminURL\": \"http://10.21.55.30:35357/v2.0\", \"region\": \"RegionOne\", \"internalURL\": \"http://10.21.55.30:5000/v2.0\", \"publicURL\": \"http://10.21.55.30:5000/v2.0\"}], \"type\": \"identity\", \"name\": \"keystone\"}, {\"endpoints\": [{\"adminURL\": \"https://10.21.55.30:8080/\", \"region\": \"RegionOne\", \"internalURL\": \"https://10.21.55.30:8080/v1/AUTH_3\", \"publicURL\": \"https://10.21.55.30:8080/v1/AUTH_3\"}], \"type\": \"object-store\", \"name\": \"swift\"}], \"user\": {\"id\": \"3\", \"roles\": [], \"name\": \"itadmin\"}}}",
|
16
|
+
headers: {:content_type=>"application/json; charset=UTF-8", :content_length=>"1098", :date=>"Wed, 29 Feb 2012 06:45:10 GMT"}
|
17
|
+
)
|
18
|
+
end
|
19
|
+
let(:client) { Keystone::V2_0::Client.new(valid_initialize_params) }
|
20
|
+
|
21
|
+
describe '#initialize' do
|
22
|
+
context 'with valid auth_url and username and password and tenant_name' do
|
23
|
+
|
24
|
+
it 'should return Openstack::Client' do
|
25
|
+
client.should be_kind_of Openstack::Client
|
26
|
+
end
|
27
|
+
|
28
|
+
it 'should extract service catalog' do
|
29
|
+
client.service_catalog.should_not be_nil
|
30
|
+
end
|
31
|
+
|
32
|
+
it 'should have user' do
|
33
|
+
client.user.should == {"id"=>"3", "roles"=>[], "name"=>"itadmin"}
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with valid auth_url and invalid username or password' do
|
38
|
+
let(:params) { {
|
39
|
+
:username => 'invalid',
|
40
|
+
:password => 'XXXXXXX',
|
41
|
+
:tenant_name => 'it',
|
42
|
+
:auth_url => 'http://auth.example.com:5000/v2.0/'
|
43
|
+
}
|
44
|
+
}
|
45
|
+
|
46
|
+
before do
|
47
|
+
stub_request(:post, 'http://auth.example.com:5000/v2.0/tokens').to_return(
|
48
|
+
status: 401,
|
49
|
+
body: "{\"unauthorized\": {\"message\": \"Unauthorized\", \"code\": \"401\"}}",
|
50
|
+
headers: {:content_type=>"application/json; charset=UTF-8", :content_length=>"60", :date=>"Wed, 29 Feb 2012 07:03:14 GMT"}
|
51
|
+
)
|
52
|
+
end
|
53
|
+
|
54
|
+
it 'should raise 401 HTTP Error.' do
|
55
|
+
lambda {
|
56
|
+
Keystone::V2_0::Client.new(params)
|
57
|
+
}.should raise_error(RestClient::Unauthorized)
|
58
|
+
end
|
59
|
+
end
|
60
|
+
|
61
|
+
end
|
62
|
+
|
63
|
+
describe '#auth_url' do
|
64
|
+
context 'when auth_url which has slash at end of url' do
|
65
|
+
let(:auth_url) { valid_initialize_params[:auth_url] }
|
66
|
+
it 'should not have slash at end of url' do
|
67
|
+
client.auth_url.should == 'http://auth.example.com:5000/v2.0'
|
68
|
+
end
|
69
|
+
end
|
70
|
+
end
|
71
|
+
|
72
|
+
end
|
73
|
+
|
74
|
+
|
@@ -0,0 +1,17 @@
|
|
1
|
+
require 'keystone/v2_0/client'
|
2
|
+
|
3
|
+
module KeystoneDouble
|
4
|
+
def service_catalog
|
5
|
+
double('service_catalog').tap do |sc|
|
6
|
+
sc.stub(:token) { { 'id' => 'token' } }
|
7
|
+
sc.stub(:url_for) { 'http://url' }
|
8
|
+
end
|
9
|
+
end
|
10
|
+
|
11
|
+
def keystone_client
|
12
|
+
double('keystone').tap do |k|
|
13
|
+
k.stub(:kind_of?) { true }
|
14
|
+
k.stub(:service_catalog) { service_catalog }
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
@@ -0,0 +1,45 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'openstack-client/base'
|
3
|
+
|
4
|
+
describe Openstack::Client::Resource do
|
5
|
+
let(:manager) { double('manager') }
|
6
|
+
let(:info) { { 'test' => 'test' } }
|
7
|
+
|
8
|
+
context 'when info was given' do
|
9
|
+
let(:resource) { Openstack::Client::Resource.new(manager, info) }
|
10
|
+
|
11
|
+
it 'should have "test" attribute' do
|
12
|
+
resource.should be_respond_to(:test)
|
13
|
+
resource.should be_respond_to(:'test=')
|
14
|
+
end
|
15
|
+
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
describe Openstack::Client::Manager do
|
20
|
+
let(:api) { double{'api'} }
|
21
|
+
let(:manager) { Openstack::Client::Manager.new(api) }
|
22
|
+
|
23
|
+
describe '#resource_class' do
|
24
|
+
it 'should have resource_class' do
|
25
|
+
manager.resource_class.should == manager.class::RESOURCE_CLASS
|
26
|
+
end
|
27
|
+
end
|
28
|
+
|
29
|
+
describe '#url_with_params' do
|
30
|
+
context 'with blank params' do
|
31
|
+
it 'should return simply url' do
|
32
|
+
manager.url_with_params( '/test', { } ).
|
33
|
+
should == '/test'
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
context 'with params' do
|
38
|
+
it 'should construct url with query from params' do
|
39
|
+
manager.url_with_params( '/test', { 'name' => 'NAME', 'status' => 'stat us' } ).
|
40
|
+
should == '/test?name=NAME&status=stat%20us'
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
end
|
45
|
+
end
|
@@ -0,0 +1,73 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'openstack-client/service_catalog'
|
3
|
+
|
4
|
+
describe Openstack::Client::ServiceCatalog do
|
5
|
+
|
6
|
+
let(:resource_dict) do
|
7
|
+
{"token"=>{"expires"=>"2012-02-29T17:23:54", "id"=>"c8bb4eeb-4234-4c4a-b4b8-eddf7ed0862f", "tenant"=>{"id"=>"3", "name"=>"it"}}, "serviceCatalog"=>[{"endpoints"=>[{"adminURL"=>"http://10.21.55.30:8774/v1.1/3", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:8774/v1.1/3", "publicURL"=>"http://10.21.55.30:8774/v1.1/3"}], "type"=>"compute", "name"=>"nova"}, {"endpoints"=>[{"adminURL"=>"http://10.21.55.30:9292/v1.1/3", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:9292/v1.1/3", "publicURL"=>"http://10.21.55.30:9292/v1.1/3"}], "type"=>"image", "name"=>"glance"}, {"endpoints"=>[{"adminURL"=>"http://10.21.55.30:35357/v2.0", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:5000/v2.0", "publicURL"=>"http://10.21.55.30:5000/v2.0"}], "type"=>"identity", "name"=>"keystone"}, {"endpoints"=>[{"adminURL"=>"https://10.21.55.30:8080/", "region"=>"RegionOne", "internalURL"=>"https://10.21.55.30:8080/v1/AUTH_3", "publicURL"=>"https://10.21.55.30:8080/v1/AUTH_3"}], "type"=>"object-store", "name"=>"swift"}], "user"=>{"id"=>"3", "roles"=>[], "name"=>"itadmin"}}
|
8
|
+
end
|
9
|
+
let(:client) { Openstack::Client::ServiceCatalog.new(resource_dict) }
|
10
|
+
|
11
|
+
describe '#catalog' do
|
12
|
+
it 'should equal resource_dict' do
|
13
|
+
client.catalog.should == resource_dict
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
describe '#token' do
|
18
|
+
|
19
|
+
it 'should contains resource_dict value' do
|
20
|
+
client.token.should == {
|
21
|
+
'id' => "c8bb4eeb-4234-4c4a-b4b8-eddf7ed0862f",
|
22
|
+
'expires' => Time.parse("2012-02-29T17:23:54"),
|
23
|
+
'tenant' => "3"
|
24
|
+
}
|
25
|
+
end
|
26
|
+
end
|
27
|
+
|
28
|
+
describe '#url_for' do
|
29
|
+
|
30
|
+
context 'when no filtering,' do
|
31
|
+
it 'should return identity service\'s publicURL' do
|
32
|
+
client.url_for.should == "http://10.21.55.30:5000/v2.0"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
context 'when endpoint_type is adminURL' do
|
37
|
+
it 'should return identity service\'s adminURL' do
|
38
|
+
client.url_for(endpoint_type: 'adminURL').should == "http://10.21.55.30:35357/v2.0"
|
39
|
+
end
|
40
|
+
end
|
41
|
+
|
42
|
+
context 'when service_type is compute' do
|
43
|
+
it 'should return compute service\'s publicURL' do
|
44
|
+
client.url_for(service_type: 'compute').should == "http://10.21.55.30:8774/v1.1/3"
|
45
|
+
end
|
46
|
+
end
|
47
|
+
|
48
|
+
end
|
49
|
+
|
50
|
+
describe '#endpoints' do
|
51
|
+
|
52
|
+
context 'when no filtering' do
|
53
|
+
it 'should return all endpoints' do
|
54
|
+
client.endpoints.should == {
|
55
|
+
'compute' => [{"adminURL"=>"http://10.21.55.30:8774/v1.1/3", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:8774/v1.1/3", "publicURL"=>"http://10.21.55.30:8774/v1.1/3"}],
|
56
|
+
'image' => [{"adminURL"=>"http://10.21.55.30:9292/v1.1/3", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:9292/v1.1/3", "publicURL"=>"http://10.21.55.30:9292/v1.1/3"}],
|
57
|
+
'identity' => [{"adminURL"=>"http://10.21.55.30:35357/v2.0", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:5000/v2.0", "publicURL"=>"http://10.21.55.30:5000/v2.0"}],
|
58
|
+
'object-store' => [{"adminURL"=>"https://10.21.55.30:8080/", "region"=>"RegionOne", "internalURL"=>"https://10.21.55.30:8080/v1/AUTH_3", "publicURL"=>"https://10.21.55.30:8080/v1/AUTH_3"}]
|
59
|
+
}
|
60
|
+
end
|
61
|
+
end
|
62
|
+
|
63
|
+
context 'when service_type is compute' do
|
64
|
+
it 'should return compute service\'s endpoints' do
|
65
|
+
client.endpoints(service_type: 'compute').should == {
|
66
|
+
'compute' => [{"adminURL"=>"http://10.21.55.30:8774/v1.1/3", "region"=>"RegionOne", "internalURL"=>"http://10.21.55.30:8774/v1.1/3", "publicURL"=>"http://10.21.55.30:8774/v1.1/3"}]
|
67
|
+
}
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
require 'spec_helper'
|
2
|
+
require 'openstack-client'
|
3
|
+
|
4
|
+
describe Openstack::Client do
|
5
|
+
|
6
|
+
describe '#execute' do
|
7
|
+
let(:endpoint) { 'http://www.example.com/v2.0' }
|
8
|
+
let(:url) { '/url' }
|
9
|
+
let(:auth_token) { nil }
|
10
|
+
let(:init_params) {{
|
11
|
+
endpoint: endpoint,
|
12
|
+
token: auth_token
|
13
|
+
}}
|
14
|
+
let(:exec_params) {{
|
15
|
+
url: url
|
16
|
+
}}
|
17
|
+
let(:rest_client_params) {{
|
18
|
+
url: endpoint + url,
|
19
|
+
headers: {
|
20
|
+
'User-Agent' => 'ruby-openstack-client',
|
21
|
+
accept: :json
|
22
|
+
}
|
23
|
+
}}
|
24
|
+
let(:response) { double('response').tap { |res| res.stub(:code){200}; res.stub(:to_s) { '{"a":1}' } } }
|
25
|
+
let(:client) { Openstack::Client.new(init_params)}
|
26
|
+
|
27
|
+
context 'when execute url: "/url"' do
|
28
|
+
it 'should call RestClient::Request.execute with rest_client_params' do
|
29
|
+
RestClient::Request.should_receive(:execute).with(rest_client_params).and_return(response)
|
30
|
+
client.execute(exec_params)
|
31
|
+
end
|
32
|
+
end
|
33
|
+
|
34
|
+
context 'when token was given at initializing' do
|
35
|
+
let(:auth_token) { 'AUTH_TOKEN______' }
|
36
|
+
|
37
|
+
it 'should call RestClient::Request.execute with X-Auth-Token header' do
|
38
|
+
rest_client_params[:headers]['X-Auth-Token'] = auth_token
|
39
|
+
RestClient::Request.should_receive(:execute).with(rest_client_params).and_return(response)
|
40
|
+
client.execute(exec_params)
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
context 'when payload was given at executing' do
|
45
|
+
let(:payload) { '{}' }
|
46
|
+
|
47
|
+
it 'should call RestClient::Request.execute with content-type: json header' do
|
48
|
+
rest_client_params[:headers][:content_type] = :json
|
49
|
+
rest_client_params[:payload] = payload.to_json
|
50
|
+
exec_params[:payload] = payload
|
51
|
+
RestClient::Request.should_receive(:execute).with(rest_client_params).and_return(response)
|
52
|
+
client.execute(exec_params)
|
53
|
+
end
|
54
|
+
end
|
55
|
+
|
56
|
+
end
|
57
|
+
|
58
|
+
end
|
data/spec/spec_helper.rb
ADDED
@@ -0,0 +1,22 @@
|
|
1
|
+
# coding: utf-8
|
2
|
+
require 'webmock/rspec'
|
3
|
+
|
4
|
+
require 'simplecov'
|
5
|
+
require 'simplecov-rcov'
|
6
|
+
class SimpleCov::Formatter::MergedFormatter
|
7
|
+
def format(result)
|
8
|
+
SimpleCov::Formatter::HTMLFormatter.new.format(result)
|
9
|
+
SimpleCov::Formatter::RcovFormatter.new.format(result)
|
10
|
+
end
|
11
|
+
end
|
12
|
+
SimpleCov.formatter = SimpleCov::Formatter::MergedFormatter
|
13
|
+
SimpleCov.start do
|
14
|
+
add_filter '/spec/'
|
15
|
+
end
|
16
|
+
|
17
|
+
RSpec.configure do |config|
|
18
|
+
config.mock_with :rspec
|
19
|
+
end
|
20
|
+
|
21
|
+
require 'examples/resource'
|
22
|
+
require 'examples/client'
|
metadata
ADDED
@@ -0,0 +1,149 @@
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
2
|
+
name: openstack-keystone-client
|
3
|
+
version: !ruby/object:Gem::Version
|
4
|
+
version: 0.0.2
|
5
|
+
prerelease:
|
6
|
+
platform: ruby
|
7
|
+
authors:
|
8
|
+
- yuanying
|
9
|
+
- atoato88
|
10
|
+
autorequire:
|
11
|
+
bindir: bin
|
12
|
+
cert_chain: []
|
13
|
+
date: 2012-03-29 00:00:00.000000000Z
|
14
|
+
dependencies:
|
15
|
+
- !ruby/object:Gem::Dependency
|
16
|
+
name: rake
|
17
|
+
requirement: &70297282418740 !ruby/object:Gem::Requirement
|
18
|
+
none: false
|
19
|
+
requirements:
|
20
|
+
- - ! '>='
|
21
|
+
- !ruby/object:Gem::Version
|
22
|
+
version: '0'
|
23
|
+
type: :development
|
24
|
+
prerelease: false
|
25
|
+
version_requirements: *70297282418740
|
26
|
+
- !ruby/object:Gem::Dependency
|
27
|
+
name: rspec
|
28
|
+
requirement: &70297282417420 !ruby/object:Gem::Requirement
|
29
|
+
none: false
|
30
|
+
requirements:
|
31
|
+
- - ! '>='
|
32
|
+
- !ruby/object:Gem::Version
|
33
|
+
version: '0'
|
34
|
+
type: :development
|
35
|
+
prerelease: false
|
36
|
+
version_requirements: *70297282417420
|
37
|
+
- !ruby/object:Gem::Dependency
|
38
|
+
name: webmock
|
39
|
+
requirement: &70297282416120 !ruby/object:Gem::Requirement
|
40
|
+
none: false
|
41
|
+
requirements:
|
42
|
+
- - ! '>='
|
43
|
+
- !ruby/object:Gem::Version
|
44
|
+
version: '0'
|
45
|
+
type: :development
|
46
|
+
prerelease: false
|
47
|
+
version_requirements: *70297282416120
|
48
|
+
- !ruby/object:Gem::Dependency
|
49
|
+
name: activesupport
|
50
|
+
requirement: &70297282414940 !ruby/object:Gem::Requirement
|
51
|
+
none: false
|
52
|
+
requirements:
|
53
|
+
- - ! '>='
|
54
|
+
- !ruby/object:Gem::Version
|
55
|
+
version: '0'
|
56
|
+
type: :development
|
57
|
+
prerelease: false
|
58
|
+
version_requirements: *70297282414940
|
59
|
+
- !ruby/object:Gem::Dependency
|
60
|
+
name: simplecov-rcov
|
61
|
+
requirement: &70297282414160 !ruby/object:Gem::Requirement
|
62
|
+
none: false
|
63
|
+
requirements:
|
64
|
+
- - ! '>='
|
65
|
+
- !ruby/object:Gem::Version
|
66
|
+
version: '0'
|
67
|
+
type: :development
|
68
|
+
prerelease: false
|
69
|
+
version_requirements: *70297282414160
|
70
|
+
- !ruby/object:Gem::Dependency
|
71
|
+
name: rest-client
|
72
|
+
requirement: &70297282412940 !ruby/object:Gem::Requirement
|
73
|
+
none: false
|
74
|
+
requirements:
|
75
|
+
- - ! '>='
|
76
|
+
- !ruby/object:Gem::Version
|
77
|
+
version: '0'
|
78
|
+
type: :runtime
|
79
|
+
prerelease: false
|
80
|
+
version_requirements: *70297282412940
|
81
|
+
description: OpenStack Keystone client for Ruby.
|
82
|
+
email:
|
83
|
+
- yuanying@fraction.jp
|
84
|
+
- atoato88@gmail.com
|
85
|
+
executables: []
|
86
|
+
extensions: []
|
87
|
+
extra_rdoc_files: []
|
88
|
+
files:
|
89
|
+
- .gitignore
|
90
|
+
- .rspec
|
91
|
+
- Gemfile
|
92
|
+
- LICENSE
|
93
|
+
- README.md
|
94
|
+
- Rakefile
|
95
|
+
- lib/keystone/v2_0/client.rb
|
96
|
+
- lib/keystone/v2_0/ec2.rb
|
97
|
+
- lib/keystone/v2_0/tokens.rb
|
98
|
+
- lib/keystone/version.rb
|
99
|
+
- lib/openstack-client.rb
|
100
|
+
- lib/openstack-client/base.rb
|
101
|
+
- lib/openstack-client/service_catalog.rb
|
102
|
+
- openstack-keystone-client.gemspec
|
103
|
+
- spec/examples/client.rb
|
104
|
+
- spec/examples/resource.rb
|
105
|
+
- spec/keystone/v2_0/client_spec.rb
|
106
|
+
- spec/lib/keystone_double.rb
|
107
|
+
- spec/openstack-client/base_spec.rb
|
108
|
+
- spec/openstack-client/service_catalog_spec.rb
|
109
|
+
- spec/openstack-client_spec.rb
|
110
|
+
- spec/spec_helper.rb
|
111
|
+
homepage: https://github.com/yuanying/ruby-openstack-keystone-client
|
112
|
+
licenses: []
|
113
|
+
post_install_message:
|
114
|
+
rdoc_options: []
|
115
|
+
require_paths:
|
116
|
+
- lib
|
117
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
118
|
+
none: false
|
119
|
+
requirements:
|
120
|
+
- - ! '>='
|
121
|
+
- !ruby/object:Gem::Version
|
122
|
+
version: '0'
|
123
|
+
segments:
|
124
|
+
- 0
|
125
|
+
hash: -3300449888302740402
|
126
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
127
|
+
none: false
|
128
|
+
requirements:
|
129
|
+
- - ! '>='
|
130
|
+
- !ruby/object:Gem::Version
|
131
|
+
version: '0'
|
132
|
+
segments:
|
133
|
+
- 0
|
134
|
+
hash: -3300449888302740402
|
135
|
+
requirements: []
|
136
|
+
rubyforge_project:
|
137
|
+
rubygems_version: 1.8.10
|
138
|
+
signing_key:
|
139
|
+
specification_version: 3
|
140
|
+
summary: This is a client for the OpenStack Keystone API. There's a Ruby API.
|
141
|
+
test_files:
|
142
|
+
- spec/examples/client.rb
|
143
|
+
- spec/examples/resource.rb
|
144
|
+
- spec/keystone/v2_0/client_spec.rb
|
145
|
+
- spec/lib/keystone_double.rb
|
146
|
+
- spec/openstack-client/base_spec.rb
|
147
|
+
- spec/openstack-client/service_catalog_spec.rb
|
148
|
+
- spec/openstack-client_spec.rb
|
149
|
+
- spec/spec_helper.rb
|