openstack-keystone-client 0.0.2
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 +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
|