openstack-keystone-client 0.0.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,6 @@
1
+ coverage
2
+ .DS_Store
3
+ *.gem
4
+ .bundle
5
+ Gemfile.lock
6
+ pkg/*
data/.rspec ADDED
@@ -0,0 +1 @@
1
+ --colour
data/Gemfile ADDED
@@ -0,0 +1,4 @@
1
+ source 'https://rubygems.org'
2
+
3
+ # Specify your gem's dependencies in openstack-keystone-client.gemspec
4
+ gemspec
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.
@@ -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.
@@ -0,0 +1,8 @@
1
+ #!/usr/bin/env rake
2
+ require "bundler/gem_tasks"
3
+ require 'rspec/core/rake_task'
4
+
5
+ RSpec::Core::RakeTask.new(:spec)
6
+
7
+ task :default => :spec
8
+
@@ -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,5 @@
1
+ module Keystone
2
+ class Client
3
+ VERSION = "0.0.2"
4
+ end
5
+ 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,10 @@
1
+
2
+ shared_examples 'Openstack::Client::Resource#initialize' do |attributes|
3
+
4
+ attributes.each do |k, v|
5
+ it "should have #{k} attribute" do
6
+ should be_respond_to(k.to_sym)
7
+ should be_respond_to("#{k}=".to_sym)
8
+ end
9
+ end
10
+ 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
@@ -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