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.
@@ -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