mod 'nanliu/staging', '1.0.3'
# OpenStack
-mod 'stackforge/keystone', '5.1.0'
+mod 'openstack/keystone', '6.0.0'
mod 'puppetlabs/apache', '1.5.0'
mod 'stackforge/openstacklib', '5.1.0'
mod 'aimonb/aviator', '0.5.1'
+##2015-07-08 - 6.0.0
+###Summary
+
+This is a backwards-incompatible major release for OpenStack Kilo.
+
+####Backwards-incompatible changes
+- Remove deprecated parameters
+- MySQL: change default MySQL collate to utf8_general_ci
+- Move openstackclient to openstacklib
+
+####Features
+- Puppet 4.x support
+- Support Keystone v3 API
+- Allow disabling or delaying the token_flush cron
+- Migrate postgresql backend to use openstacklib::db::postgresql
+- Add max_token_size optional parameter
+- Add admin_workers and public_workers configuration options
+- Add support for LDAP connection pools
+- Add a package ensure for openstackclient
+- Enable setting the revoke/token driver
+- Add manage_service feature
+- Makes distinct use of url vs auth_url
+- Create a sync_db boolean for Keystone
+- LDAP: add support to configure credential driver
+- Support notification_format
+- Allow custom file source for wsgi scripts
+- Decouple sync_db from enabled
+- Add support for Fernet Tokens
+
+####Bugfixes
+- Crontab: ensure the script is run with bash shell
+- Copy latest keystone.py from Keystone upstream
+- Fix deprecated LDAP config options
+- Fix service keystone conflict when running in apache
+
+####Maintenance
+- Acceptance tests with Beaker
+- Fix spec tests for RSpec 3.x and Puppet 4.x
+- Restructures authentication for resource providers
+
##2015-06-17 - 5.1.0
###Summary
keystone
=======
-5.1.0 - 2014.2 - Juno
+6.0.0 - 2015.1 - Kilo
#### Table of Contents
--- /dev/null
+{
+ "CHANGELOG.md": "88b3a502ab7375f624d0c0e01a8adaac",
+ "Gemfile": "2aec9171226d42dc7e5cf1f6bfc977bf",
+ "LICENSE": "12a15a9ebddda7d856c783f745e5ee47",
+ "README.md": "4d9691f4e7f088cad9baa3ef6737b32e",
+ "Rakefile": "68e2a46cd546eeb34bab6dc1512b549d",
+ "examples/apache_dropin.pp": "219828fe274f52e57b69b4b32f24b423",
+ "examples/apache_with_paths.pp": "b2122f0f4eefe9eedec2cade44d4dcc3",
+ "examples/ldap_full.pp": "943f823c8a45cacbbe65f0d8711d6287",
+ "examples/ldap_identity.pp": "ee258356235ece8a0416e747f3d1fd80",
+ "examples/v3_basic.pp": "f1cae4f18d02993b61ab5a63c1a06ed0",
+ "ext/keystone_test.rb": "d403c8c80616f94d0cac9ff12c327b9a",
+ "ext/keystone_test_v3.rb": "e6121a38bfe902ca8d3c00669b41af4e",
+ "files/httpd/keystone.py": "775825c125975916ef6bad1ef9faf44c",
+ "lib/puppet/provider/keystone/util.rb": "f1618166fbc1df3e160e795d5734428d",
+ "lib/puppet/provider/keystone.rb": "b1aa3281c13dcb476f0d4241d18609f2",
+ "lib/puppet/provider/keystone_config/ini_setting.rb": "b3c3813be1c155f49fedf0a1178fe905",
+ "lib/puppet/provider/keystone_domain/openstack.rb": "8e69b3404d2dc8b5a9a55ba13b7b40bd",
+ "lib/puppet/provider/keystone_endpoint/openstack.rb": "d69201cbcef5da1b588d356416841f75",
+ "lib/puppet/provider/keystone_paste_ini/ini_setting.rb": "df7e671676104f090a07942f774844e3",
+ "lib/puppet/provider/keystone_role/openstack.rb": "71d44ad4123acd8cb8a756e19e6d5017",
+ "lib/puppet/provider/keystone_service/openstack.rb": "f30c932bb9ab74fa6591302033e8f5f9",
+ "lib/puppet/provider/keystone_tenant/openstack.rb": "37407bf69e6097d89565b12257707cb4",
+ "lib/puppet/provider/keystone_user/openstack.rb": "d79c17b8c9f5ad3ae99e14f953ba958b",
+ "lib/puppet/provider/keystone_user_role/openstack.rb": "d6dddb86df2d8e3a9acde3f97d2bc60b",
+ "lib/puppet/type/keystone_config.rb": "01069a89da581af00fed130fc373c2c3",
+ "lib/puppet/type/keystone_domain.rb": "3c2791fdc77585edc0d56b25a9bd6b72",
+ "lib/puppet/type/keystone_endpoint.rb": "0e72c165df04b2efc73e1676871e0785",
+ "lib/puppet/type/keystone_paste_ini.rb": "5429630bad1a33ab3f14b45f403ed2af",
+ "lib/puppet/type/keystone_role.rb": "421ee2247a50fb2eacac5cdd1bcc7382",
+ "lib/puppet/type/keystone_service.rb": "09f5241ca8fede306018b36454deedd3",
+ "lib/puppet/type/keystone_tenant.rb": "9bdf0763ba4d610ad262437751cde59e",
+ "lib/puppet/type/keystone_user.rb": "2ed3068a9bdd3a19168a6cb09ff09bf3",
+ "lib/puppet/type/keystone_user_role.rb": "82f5d6d1c1fba9023adfaf11104bffe9",
+ "manifests/client.pp": "8520e2209d84d0ba92b5e6a429938731",
+ "manifests/config.pp": "5e27a3b503cd4931e410a2d41d89fda1",
+ "manifests/cron/token_flush.pp": "4f2ce0209fbb9696eda2758ef84c18d2",
+ "manifests/db/mysql.pp": "d91fa7b9a3ecf574af6a9f11d1d2c147",
+ "manifests/db/postgresql.pp": "caacd8b3028d50c37c61be6626d92673",
+ "manifests/db/sync.pp": "3caf7ccd37b6f62714bf3b77d0dbf0f9",
+ "manifests/dev/install.pp": "54e58af21326dcb21603096500e63be1",
+ "manifests/endpoint.pp": "d24b24be405b7b1e5fb1af76ecfa6500",
+ "manifests/init.pp": "e1c57737e028155032a6a4c06264a864",
+ "manifests/ldap.pp": "5c8b5b4937a59cc835ddb3609c3ba97e",
+ "manifests/logging.pp": "5774990dea77d17dfceaad4a8777824c",
+ "manifests/params.pp": "0b8b7920a55a5ac26af221ee9cd162c5",
+ "manifests/policy.pp": "c8a8998316ea42f611a1c7ae6a563461",
+ "manifests/python.pp": "88ef5e5584df349640335a1227f924d7",
+ "manifests/resource/authtoken.pp": "120f71ab6e1a6f27bf44a6c92cd5ab24",
+ "manifests/resource/service_identity.pp": "d0e6c682727ce2109f42300a35b0988e",
+ "manifests/roles/admin.pp": "b74dd7fa2a292f6e37ea3018c064c7e2",
+ "manifests/service.pp": "d043301073416c442d0a0fba36c85c0d",
+ "manifests/wsgi/apache.pp": "54606fca35ca48104799da62bd1799e4",
+ "metadata.json": "2aa859fa6bc99b9503bd05a01f5dd233",
+ "spec/acceptance/basic_keystone_spec.rb": "bb23467e03bd97fee508fa7f3a975174",
+ "spec/acceptance/nodesets/default.yml": "2bb6248df2563b1d871aeb2ab16e077a",
+ "spec/acceptance/nodesets/nodepool-centos7.yml": "cc37eb9368fb2bd889b3f6830cabcfc7",
+ "spec/acceptance/nodesets/nodepool-trusty.yml": "f68590f582842cdd73233946829644a3",
+ "spec/classes/keystone_client_spec.rb": "7f97bacdc6b372b160e1234ec576968a",
+ "spec/classes/keystone_cron_token_flush_spec.rb": "3aefefa8e13597338b329d0be9121c77",
+ "spec/classes/keystone_db_mysql_spec.rb": "025306de49f8f83823eff98321f18172",
+ "spec/classes/keystone_db_postgresql_spec.rb": "53fb2d31e72446e51feb6da576656d19",
+ "spec/classes/keystone_endpoint_spec.rb": "f9f73dd21b83b2334d4d1dee701c511f",
+ "spec/classes/keystone_ldap_spec.rb": "9c9cd5d034294c27f3739cd89509c471",
+ "spec/classes/keystone_logging_spec.rb": "662d3c86e9c2f5225a2ad1ef3721327d",
+ "spec/classes/keystone_policy_spec.rb": "179d795895cfabe3c3d02615b9e9a04a",
+ "spec/classes/keystone_python_spec.rb": "59d13966a2f62c0884f2d6375c0c0135",
+ "spec/classes/keystone_roles_admin_spec.rb": "1ff0a367ba5a130727e55c6aad985533",
+ "spec/classes/keystone_service_spec.rb": "15c4446865070015904ea9550eec2fa5",
+ "spec/classes/keystone_spec.rb": "0b73d50f264bbb6da25c6df078eb8407",
+ "spec/classes/keystone_wsgi_apache_spec.rb": "d0f9bf67abe16ece501758079a17a081",
+ "spec/defines/keystone_resource_authtoken_spec.rb": "f83a5fa982d4e4435e4c84bcc50bf52e",
+ "spec/defines/keystone_resource_service_identity_spec.rb": "216982cb0eb16ed223429440bc52bbfe",
+ "spec/shared_examples.rb": "c23e32dbc37052ae071bc729abfeeda5",
+ "spec/spec.opts": "a600ded995d948e393fbe2320ba8e51c",
+ "spec/spec_helper.rb": "ab7c5df754690421cdfe9770c3057cc8",
+ "spec/spec_helper_acceptance.rb": "a2737eefbf7340478b048aeb19de4cae",
+ "spec/unit/provider/keystone/util_spec.rb": "a1f871eef2314861a5e61769068eb0bb",
+ "spec/unit/provider/keystone_domain/openstack_spec.rb": "ad941a88ec119a6ed65e0751f74e978a",
+ "spec/unit/provider/keystone_endpoint/openstack_spec.rb": "1b54246797f8a9d245c90d4b2a6bc069",
+ "spec/unit/provider/keystone_paste_ini/ini_setting_spec.rb": "8b583280cfc7c67d64a2dfd8caa7a130",
+ "spec/unit/provider/keystone_role/openstack_spec.rb": "ea02769d8d105648a28ae829e19ecaeb",
+ "spec/unit/provider/keystone_service/openstack_spec.rb": "072e985f1f4b90524af9a73ee99e435e",
+ "spec/unit/provider/keystone_spec.rb": "0ff6ddcf326413510cd4a015cdaf64ea",
+ "spec/unit/provider/keystone_tenant/openstack_spec.rb": "62b15ac89ad356a0498a9f341e3531d4",
+ "spec/unit/provider/keystone_user/openstack_spec.rb": "fc442a5e039f1318dbe3c63df39c1f4d",
+ "spec/unit/provider/keystone_user_role/openstack_spec.rb": "708bdfa669e6db8f6b90d158dd6bb4da",
+ "spec/unit/type/keystone_endpoint_spec.rb": "5dbd0b540a452bae36218b2a8794a41e",
+ "spec/unit/type/keystone_paste_ini_spec.rb": "9037113f96850d5567e9dc7c540915ae",
+ "spec/unit/type/keystone_tenant_spec.rb": "b9c9319e924537191b596a03c5c1443d",
+ "spec/unit/type/keystone_user_role_spec.rb": "a03cfa9f55028d6a7f2a351582c1a93d",
+ "spec/unit/type/keystone_user_spec.rb": "d7e2fc65663ecc487f2f046a668dc505",
+ "tests/site.pp": "f0f28af600251f61f3f1299dd0749d24"
+}
\ No newline at end of file
--- /dev/null
+# Example using v3 domains. The admin user is created in the domain
+# named 'admin_domain', and assigned the role 'admin' in the 'admin'
+# project in the domain 'admin_domain'. The keystone service account is
+# created in default domain, and assigned the
+# role 'admin' in the project 'services' in the default domain.
+# NOTE: Until all of the other services support using Keystone v3
+# with keystone_authtoken middleware that supports v3, they cannot
+# specify a domain for authentication, and so have to be in the
+# default domain.
+#
+# To be sure everything is working, run:
+# $ export OS_IDENTITY_API_VERSION=3
+# $ export OS_USERNAME=admin
+# $ export OS_USER_DOMAIN_NAME=admin_domain
+# $ export OS_PASSWORD=ChangeMe
+# $ export OS_PROJECT_NAME=admin
+# $ export OS_PROJECT_DOMAIN_NAME=admin_domain
+# $ export OS_AUTH_URL=http://keystone.local:35357/v3
+# $ openstack user list
+#
+
+Exec { logoutput => 'on_failure' }
+
+
+class { '::mysql::server': }
+class { '::keystone::db::mysql':
+ password => 'keystone',
+}
+class { '::keystone':
+ verbose => true,
+ debug => true,
+ database_connection => 'mysql://keystone:keystone@127.0.0.1/keystone',
+ admin_token => 'admin_token',
+ enabled => true,
+}
+class { '::keystone::roles::admin':
+ email => 'test@example.tld',
+ password => 'a_big_secret',
+ admin => 'admin', # username
+ admin_tenant => 'admin', # project name
+ admin_user_domain => 'admin', # domain for user
+ admin_tenant_domain => 'admin', # domain for project
+}
+class { '::keystone::endpoint':
+ public_url => 'http://127.0.0.1:5000/',
+ admin_url => 'http://127.0.0.1:35357/',
+}
--- /dev/null
+#!/usr/bin/env ruby
+# this script verifies that keystone has
+# been successfully installed using the instructions
+# found here: http://keystone.openstack.org/configuration.html
+# and can use the v3 api http://developer.openstack.org/api-ref-identity-v3.html
+
+begin
+ require 'rubygems'
+rescue
+ puts 'Could not require rubygems. This assumes puppet is not installed as a gem'
+end
+require 'open3'
+require 'fileutils'
+require 'puppet'
+require 'pp'
+
+username='admin'
+password='a_big_secret'
+# required to get a real services catalog
+project='openstack'
+user_domain='admin'
+project_domain='admin'
+
+# shared secret
+service_token='admin_token'
+
+def run_command(cmd)
+ Open3.popen3(cmd) do |stdin, stdout, stderr|
+ begin
+ stdout = stdout.read
+ puts "Response from token request:#{stdout}"
+ return stdout
+ rescue Exception => e
+ puts "Request failed, this sh*t is borked :( : details: #{e}"
+ exit 1
+ end
+ end
+end
+
+puts `puppet apply -e "package {curl: ensure => present }"`
+get_token = %(curl -D - -d '{"auth":{"identity":{"methods":["password"],"password":{"user":{"domain":{"name":"#{user_domain}"},"name":"#{username}","password": "#{password}"}}},"scope":{"project":{"domain":{"name":"#{project_domain}"},"name": "#{project}"}}}}' -H "Content-type: application/json" http://localhost:35357/v3/auth/tokens)
+token = nil
+
+puts "Running auth command: #{get_token}"
+rawoutput = run_command(get_token)
+if rawoutput =~ /X-Subject-Token: ([\w]+)/
+ token = $1
+else
+ puts "No token in output! #{rawoutput}"
+ exit 1
+end
+
+if token
+ puts "We were able to retrieve a token"
+ puts token
+ verify_token = "curl -H 'X-Auth-Token: #{service_token}' 'X-Subject-Token: #{token}' http://localhost:35357/v3/auth/tokens"
+ puts 'verifying token'
+ run_command(verify_token)
+ ['endpoints', 'projects', 'users'].each do |x|
+ puts "getting #{x}"
+ get_keystone_data = "curl -H 'X-Auth-Token: #{token}' http://localhost:35357/v3/#{x}"
+ pp PSON.load(run_command(get_keystone_data))
+ end
+end
# under the License.
#
-# This file was copied from https://github.com/openstack/keystone/raw/a4f29db2b8cde1b445b86218fb5543295da2092c/httpd/keystone.py
+# This file was copied from
+# raw.githubusercontent.com/openstack/keystone/a4f29db/httpd/keystone.py
# It's only required for platforms on which it is not packaged yet.
# It should be removed when available everywhere in a package.
#
require 'puppet/provider/openstack'
require 'puppet/provider/openstack/auth'
require 'puppet/provider/openstack/credentials'
+require 'puppet/provider/keystone/util'
class Puppet::Provider::Keystone < Puppet::Provider::Openstack
@admin_endpoint ||= get_admin_endpoint
end
+ # use the domain in this order:
+ # 1 - the domain name specified in the resource definition - resource[:domain]
+ # 2 - the domain name part of the resource name/title e.g. user_name::user_domain
+ # if passed in by name_and_domain above
+ # 3 - use the specified default_domain_name
+ # 4 - lookup the default domain
+ # 5 - use 'Default' - the "default" default domain if no other one is configured
+ # Usage: name_and_domain(resource[:name], resource[:domain], default_domain_name)
+ def self.name_and_domain(namedomstr, domain_from_resource=nil, default_domain_name=nil)
+ name, domain = Util.split_domain(namedomstr)
+ ret = [name]
+ if domain_from_resource
+ ret << domain_from_resource
+ elsif domain
+ ret << domain
+ elsif default_domain_name
+ ret << default_domain_name
+ elsif default_domain
+ ret << default_domain
+ else
+ ret << 'Default'
+ end
+ ret
+ end
+
def self.admin_token
@admin_token ||= get_admin_token
end
def self.request(service, action, properties=nil)
super
- rescue Puppet::Error::OpenstackAuthInputError => error
- request_by_service_token(service, action, error, properties)
+ rescue Puppet::Error::OpenstackAuthInputError => error
+ request_by_service_token(service, action, error, properties)
end
def self.request_by_service_token(service, action, error, properties=nil)
INI_FILENAME
end
+ def self.default_domain
+ domain_hash[default_domain_id]
+ end
+
+ def self.domain_hash
+ return @domain_hash if @domain_hash
+ list = request('domain', 'list')
+ @domain_hash = Hash[list.collect{|domain| [domain[:id], domain[:name]]}]
+ @domain_hash
+ end
+
+ def self.domain_name_from_id(id)
+ domain_hash[id]
+ end
+
+ def self.default_domain_id
+ return @default_domain_id if @default_domain_id
+ if keystone_file and keystone_file['identity'] and keystone_file['identity']['default_domain_id']
+ @default_domain_id = "#{keystone_file['identity']['default_domain_id'].strip}"
+ else
+ @default_domain_id = 'default'
+ end
+ @default_domain_id
+ end
+
def self.keystone_file
return @keystone_file if @keystone_file
if File.exists?(ini_filename)
--- /dev/null
+module Util
+ # Splits the rightmost part of a string using '::' as delimiter
+ # Returns an array of both parts or nil if either is empty.
+ # An empty rightmost part is ignored and converted as 'string::' => 'string'
+ #
+ # Examples:
+ # "foo" -> ["foo", nil]
+ # "foo::" -> ["foo", nil]
+ # "foo::bar" -> ["foo", "bar"]
+ # "foo::bar::" -> ["foo", "bar"]
+ # "::foo" -> [nil, "foo"]
+ # "::foo::" -> [nil, "foo"]
+ # "foo::bar::baz" -> ["foo::bar", "baz"]
+ # "foo::bar::baz::" -> ["foo::bar", "baz"]
+ #
+ def self.split_domain(str)
+ left, right = nil, nil
+ unless str.nil?
+ left, delimiter, right = str.gsub(/::$/, '').rpartition('::')
+ left, right = right, nil if delimiter.empty?
+ left = nil if left.empty?
+ end
+ return [left, right]
+ end
+end
--- /dev/null
+require 'puppet/provider/keystone'
+require 'puppet/util/inifile'
+
+Puppet::Type.type(:keystone_domain).provide(
+ :openstack,
+ :parent => Puppet::Provider::Keystone
+) do
+
+ desc 'Provider that manages keystone domains'
+
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
+
+ def initialize(value={})
+ super(value)
+ @property_flush = {}
+ end
+
+ def create
+ properties = [resource[:name]]
+ if resource[:enabled] == :true
+ properties << '--enable'
+ elsif resource[:enabled] == :false
+ properties << '--disable'
+ end
+ if resource[:description]
+ properties << '--description'
+ properties << resource[:description]
+ end
+ @property_hash = self.class.request('domain', 'create', properties)
+ @property_hash[:is_default] = sym_to_bool(resource[:is_default])
+ @property_hash[:ensure] = :present
+ ensure_default_domain(true)
+ end
+
+ def exists?
+ @property_hash[:ensure] == :present
+ end
+
+ def destroy
+ # have to disable first - Keystone does not allow you to delete an
+ # enabled domain
+ self.class.request('domain', 'set', [resource[:name], '--disable'])
+ self.class.request('domain', 'delete', resource[:name])
+ @property_hash[:ensure] == :absent
+ ensure_default_domain(false, true)
+ @property_hash.clear
+ end
+
+ def enabled=(value)
+ @property_flush[:enabled] = value
+ end
+
+ def enabled
+ bool_to_sym(@property_hash[:enabled])
+ end
+
+ def description=(value)
+ @property_flush[:description] = value
+ end
+
+ def description
+ @property_hash[:description]
+ end
+
+ def id
+ @property_hash[:id]
+ end
+
+ def is_default
+ bool_to_sym(@property_hash[:is_default])
+ end
+
+ def is_default=(value)
+ @property_flush[:is_default] = value
+ end
+
+ def ensure_default_domain(create, destroy=false, value=nil)
+ if !self.class.keystone_file
+ return
+ end
+ changed = false
+ curid = self.class.default_domain_id
+ newid = id
+ default = (is_default == :true)
+ if (default && create) || (!default && (value == :true))
+ # new default domain, or making existing domain the default domain
+ if curid != newid
+ self.class.keystone_file['identity']['default_domain_id'] = newid
+ changed = true
+ end
+ elsif (default && destroy) || (default && (value == :false))
+ # removing default domain, or making this domain not the default
+ if curid == newid
+ # can't delete from inifile, so just reset to default 'default'
+ self.class.keystone_file['identity']['default_domain_id'] = 'default'
+ changed = true
+ newid = 'default'
+ end
+ end
+ if changed
+ self.class.keystone_file.store
+ debug("The default_domain_id was changed from #{curid} to #{newid}")
+ end
+ end
+
+ def self.instances
+ request('domain', 'list').collect do |domain|
+ new(
+ :name => domain[:name],
+ :ensure => :present,
+ :enabled => domain[:enabled].downcase.chomp == 'true' ? true : false,
+ :description => domain[:description],
+ :id => domain[:id],
+ :is_default => domain[:id] == default_domain_id
+ )
+ end
+ end
+
+ def self.prefetch(resources)
+ domains = instances
+ resources.keys.each do |name|
+ if provider = domains.find{ |domain| domain.name == name }
+ resources[name].provider = provider
+ end
+ end
+ end
+
+ def flush
+ options = []
+ if @property_flush && !@property_flush.empty?
+ options << '--enable' if @property_flush[:enabled] == :true
+ options << '--disable' if @property_flush[:enabled] == :false
+ if @property_flush[:description]
+ options << '--description' << resource[:description]
+ end
+ self.class.request('domain', 'set', [resource[:name]] + options) unless options.empty?
+ if @property_flush[:is_default]
+ ensure_default_domain(false, false, @property_flush[:is_default])
+ end
+ @property_flush.clear
+ end
+ end
+end
desc 'Provider for keystone roles.'
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
desc "Provider to manage keystone services."
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
end
def create
- properties = []
- if resource[:description]
- properties << '--description'
- properties << resource[:description]
+ if resource[:type]
+ properties = [resource[:type]]
+ properties << '--name' << resource[:name]
+ if resource[:description]
+ properties << '--description' << resource[:description]
+ end
+ self.class.request('service', 'create', properties)
+ @property_hash[:ensure] = :present
+ else
+ raise(Puppet::Error, 'The type is mandatory for creating a keystone service')
end
- raise(Puppet::Error, 'The service type is mandatory') unless resource[:type]
- properties << '--type'
- properties << resource[:type]
- properties << resource[:name]
- self.class.request('service', 'create', properties)
- @property_hash[:ensure] = :present
- end
-
- def exists?
- @property_hash[:ensure] == :present
end
def destroy
@property_hash.clear
end
- def description=(value)
- @property_flush[:description] = value
+ def exists?
+ @property_hash[:ensure] == :present
end
def description
@property_hash[:description]
end
- def type=(value)
- @property_flush[:type] = value
+ def description=(value)
+ @property_flush[:description] = value
end
def type
@property_hash[:type]
end
+ def type=(value)
+ @property_flush[:type] = value
+ end
+
def id
@property_hash[:id]
end
end
def flush
- if ! @property_flush.empty?
- destroy
- create
+ options = []
+ if @property_flush && !@property_flush.empty?
+ options << "--description=#{resource[:description]}" if @property_flush[:description]
+ options << "--type=#{resource[:type]}" if @property_flush[:type]
+ self.class.request('service', 'set', [@property_hash[:id]] + options) unless options.empty?
@property_flush.clear
end
end
desc "Provider to manage keystone tenants/projects."
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
end
def create
- properties = [resource[:name]]
+ # see if resource[:domain], or project_name::project_domain
+ project_name, project_domain = self.class.name_and_domain(resource[:name], resource[:domain])
+ properties = [project_name]
if resource[:enabled] == :true
properties << '--enable'
elsif resource[:enabled] == :false
properties << '--description'
properties << resource[:description]
end
- self.class.request('project', 'create', properties)
- @property_hash[:ensure] = :present
+ if project_domain
+ properties << '--domain'
+ properties << project_domain
+ end
+ @property_hash = self.class.request('project', 'create', properties)
+ @property_hash[:ensure] = :present
end
def exists?
end
def destroy
- self.class.request('project', 'delete', @property_hash[:id])
+ self.class.request('project', 'delete', id)
@property_hash.clear
end
@property_hash[:description]
end
+ def domain
+ @property_hash[:domain]
+ end
+
def id
@property_hash[:id]
end
def self.instances
+ instance_hash = {}
list = request('project', 'list', '--long')
- list.collect do |project|
+ list.each do |project|
+ domname = domain_name_from_id(project[:domain_id])
+ if instance_hash.include?(project[:name]) # not unique
+ curdomid = instance_hash[project[:name]][:domain_id]
+ if curdomid != default_domain_id
+ # Move the project from the short name slot to the long name slot
+ # because it is not in the default domain.
+ curdomname = domain_name_from_id(curdomid)
+ instance_hash["#{project[:name]}::#{curdomname}"] = instance_hash[project[:name]]
+ # Use the short name slot for the new project
+ instance_hash[project[:name]] = project
+ else
+ # Use the long name for the new project
+ instance_hash["#{project[:name]}::#{domname}"] = project
+ end
+ else
+ # Unique (for now) - store in short name slot
+ instance_hash[project[:name]] = project
+ end
+ end
+ instance_hash.keys.collect do |project_name|
+ project = instance_hash[project_name]
+ domname = domain_name_from_id(project[:domain_id])
new(
- :name => project[:name],
+ :name => project_name,
:ensure => :present,
:enabled => project[:enabled].downcase.chomp == 'true' ? true : false,
:description => project[:description],
+ :domain => domname,
+ :domain_id => project[:domain_id],
:id => project[:id]
)
end
end
def self.prefetch(resources)
- tenants = instances
- resources.keys.each do |name|
- if provider = tenants.find{ |tenant| tenant.name == name }
- resources[name].provider = provider
+ project_hash = {}
+ projects = instances
+ resources.each do |resname, resource|
+ # resname may be specified as just "name" or "name::domain"
+ name, resdomain = name_and_domain(resname, resource[:domain])
+ provider = projects.find do |project|
+ # have a match if the full instance name matches the full resource name, OR
+ # the base resource name matches the base instance name, and the
+ # resource domain matches the instance domain
+ project_name, project_domain = name_and_domain(project.name, project.domain)
+ (project.name == resname) ||
+ ((project_name == name) && (project_domain == resdomain))
end
+ resource.provider = provider if provider
end
end
options << '--disable'
end
(options << "--description=#{resource[:description]}") if @property_flush[:description]
- options << @property_hash[:id]
- self.class.request('project', 'set', options) unless options.empty?
+ self.class.request('project', 'set', [id] + options) unless options.empty?
@property_flush.clear
end
end
desc "Provider to manage keystone users."
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
end
def create
- properties = [resource[:name]]
+ # see if resource[:domain], or user specified as user::domain
+ user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
+ properties = [user_name]
if resource[:enabled] == :true
properties << '--enable'
elsif resource[:enabled] == :false
if resource[:password]
properties << '--password' << resource[:password]
end
- if resource[:tenant]
- properties << '--project' << resource[:tenant]
- end
if resource[:email]
properties << '--email' << resource[:email]
end
- self.class.request('user', 'create', properties)
+ if user_domain
+ properties << '--domain'
+ properties << user_domain
+ end
+ @property_hash = self.class.request('user', 'create', properties)
+ @property_hash[:domain] = user_domain
+ if resource[:tenant]
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{resource[:tenant]}")[:id]
+ set_project(resource[:tenant], project_id)
+ end
@property_hash[:ensure] = :present
end
def destroy
- self.class.request('user', 'delete', @property_hash[:id])
+ self.class.request('user', 'delete', id)
@property_hash.clear
end
res = resource[:password]
else
# Password validation
- credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
- credentials.auth_url = self.class.get_endpoint
- credentials.password = resource[:password]
- credentials.project_name = resource[:tenant]
- credentials.username = resource[:name]
+ credentials = Puppet::Provider::Openstack::CredentialsV3.new
+ credentials.auth_url = self.class.get_endpoint
+ credentials.password = resource[:password]
+ credentials.user_id = id
+ # NOTE: The only reason we use username is so that the openstack provider
+ # will know we are doing v3password auth - otherwise, it is not used. The
+ # user_id uniquely identifies the user including domain.
+ credentials.username, unused = self.class.name_and_domain(resource[:name], domain)
+ # Need to specify a project id to get a project scoped token. List
+ # all of the projects for the user, and use the id from the first one.
+ projects = self.class.request('project', 'list', ['--user', id, '--long'])
+ if projects && projects[0] && projects[0][:id]
+ credentials.project_id = projects[0][:id]
+ else
+ # last chance - try a domain scoped token
+ credentials.domain_name = domain
+ end
begin
token = Puppet::Provider::Openstack.request('token', 'issue', ['--format', 'value'], credentials)
rescue Puppet::Error::OpenstackUnauthorizedError
@property_flush[:replace_password] = value
end
+ def find_project_for_user(projname, project_id = nil)
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
+ project_name, project_domain = self.class.name_and_domain(projname, nil, user_domain)
+ self.class.request('project', 'list', ['--user', id, '--long']).each do |project|
+ if (project_id == project[:id]) ||
+ ((projname == project_name) && (project_domain == self.class.domain_name_from_id(project[:domain_id])))
+ return project[:name]
+ end
+ end
+ return nil
+ end
+
+ def set_project(newproject, project_id = nil)
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ unless project_id
+ project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{newproject}")[:id]
+ end
+ # Currently the only way to assign a user to a tenant not using user-create
+ # is to use role-add - this means we also need a role - there is usual
+ # a default role called _member_ which can be used for this purpose. What
+ # usually happens in a puppet module is that immediately after calling
+ # keystone_user, the module will then assign a role to that user. It is
+ # ok for a user to have the _member_ role and another role.
+ default_role = "_member_"
+ begin
+ self.class.request('role', 'show', default_role)
+ rescue
+ self.class.request('role', 'create', default_role)
+ end
+ # finally, assign the user to the project with the role
+ self.class.request('role', 'add', [default_role, '--project', project_id, '--user', id])
+ newproject
+ end
+
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ def tenant=(value)
+ @property_hash[:tenant] = set_project(value)
+ end
+
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
def tenant
return resource[:tenant] if sym_to_bool(resource[:ignore_default_tenant])
# use the one returned from instances
if tenant_name.nil? or tenant_name.empty?
return nil # nothing found, nothing given
end
- # If the user list command doesn't report the project, it might still be there
- # We don't need to know exactly what it is, we just need to know whether it's
- # the one we're trying to set.
- roles = self.class.request('user role', 'list', [resource[:name], '--project', tenant_name])
- if roles.empty?
- return nil
- else
- return tenant_name
- end
+ project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{tenant_name}")[:id]
+ find_project_for_user(tenant_name, project_id)
end
- def tenant=(value)
- self.class.request('user', 'set', [resource[:name], '--project', value])
- rescue Puppet::ExecutionFailure => e
- if e.message =~ /You are not authorized to perform the requested action: LDAP user update/
- # read-only LDAP identity backend - just fall through
- else
- raise e
- end
- # note: read-write ldap will silently fail, not raise an exception
- else
- @property_hash[:tenant] = self.class.set_project(value, resource[:name])
+ def domain
+ @property_hash[:domain]
+ end
+
+ def domain_id
+ @property_hash[:domain_id]
end
def self.instances
- list = request('user', 'list', '--long')
- list.collect do |user|
+ instance_hash = {}
+ request('user', 'list', ['--long']).each do |user|
+ # The field says "domain" but it is really the domain_id
+ domname = domain_name_from_id(user[:domain])
+ if instance_hash.include?(user[:name]) # not unique
+ curdomid = instance_hash[user[:name]][:domain]
+ if curdomid != default_domain_id
+ # Move the user from the short name slot to the long name slot
+ # because it is not in the default domain.
+ curdomname = domain_name_from_id(curdomid)
+ instance_hash["#{user[:name]}::#{curdomname}"] = instance_hash[user[:name]]
+ # Use the short name slot for the new user
+ instance_hash[user[:name]] = user
+ else
+ # Use the long name for the new user
+ instance_hash["#{user[:name]}::#{domname}"] = user
+ end
+ else
+ # Unique (for now) - store in short name slot
+ instance_hash[user[:name]] = user
+ end
+ end
+ instance_hash.keys.collect do |user_name|
+ user = instance_hash[user_name]
new(
- :name => user[:name],
+ :name => user_name,
:ensure => :present,
:enabled => user[:enabled].downcase.chomp == 'true' ? true : false,
:password => user[:password],
- :project => user[:project],
:email => user[:email],
+ :description => user[:description],
+ :domain => domain_name_from_id(user[:domain]),
+ :domain_id => user[:domain],
:id => user[:id]
)
end
def self.prefetch(resources)
users = instances
- resources.keys.each do |name|
- if provider = users.find{ |user| user.name == name }
- resources[name].provider = provider
+ resources.each do |resname, resource|
+ # resname may be specified as just "name" or "name::domain"
+ name, resdomain = name_and_domain(resname, resource[:domain])
+ provider = users.find do |user|
+ # have a match if the full instance name matches the full resource name, OR
+ # the base resource name matches the base instance name, and the
+ # resource domain matches the instance domain
+ username, user_domain = name_and_domain(user.name, user.domain)
+ (user.name == resname) ||
+ ((username == name) && (user_domain == resdomain))
end
+ resource.provider = provider if provider
end
end
- def self.set_project(newproject, name)
- # some backends do not store the project/tenant in the user object, so we have to
- # to modify the project/tenant instead
- # First, see if the project actually needs to change
- roles = request('user role', 'list', [name, '--project', newproject])
- unless roles.empty?
- return # if already set, just skip
- end
- # Currently the only way to assign a user to a tenant not using user-create
- # is to use user-role-add - this means we also need a role - there is usual
- # a default role called _member_ which can be used for this purpose. What
- # usually happens in a puppet module is that immediately after calling
- # keystone_user, the module will then assign a role to that user. It is
- # ok for a user to have the _member_ role and another role.
- default_role = "_member_"
- begin
- request('role', 'show', [default_role])
- rescue
- debug("Keystone role #{default_role} does not exist - creating")
- request('role', 'create', [default_role])
- end
- request('role', 'add', [default_role, '--project', newproject, '--user', name])
- end
end
require 'puppet/provider/keystone'
+require 'puppet/provider/keystone/util'
Puppet::Type.type(:keystone_user_role).provide(
:openstack,
desc "Provider to manage keystone role assignments to users."
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
end
def create
- properties = []
- properties << '--project' << get_project
- properties << '--user' << get_user
if resource[:roles]
resource[:roles].each do |role|
self.class.request('role', 'add', [role] + properties)
end
def destroy
- properties = []
- properties << '--project' << get_project
- properties << '--user' << get_user
if @property_hash[:roles]
@property_hash[:roles].each do |role|
self.class.request('role', 'remove', [role] + properties)
end
def exists?
- if @user_role_hash
- return ! @property_hash[:name].empty?
- else
- roles = self.class.request('user role', 'list', [get_user, '--project', get_project])
+ if self.class.user_role_hash.nil? || self.class.user_role_hash.empty?
+ roles = self.class.request('role', 'list', properties)
# Since requesting every combination of users, roles, and
# projects is so expensive, construct the property hash here
# instead of in self.instances so it can be used in the role
role[:name]
end
end
- return @property_hash[:ensure] == :present
end
+ return @property_hash[:ensure] == :present
end
def roles
# determine the roles to be added and removed
remove = current_roles - Array(value)
add = Array(value) - current_roles
- user = get_user
- project = get_project
add.each do |role_name|
- self.class.request('role', 'add', [role_name, '--project', project, '--user', user])
+ self.class.request('role', 'add', [role_name] + properties)
end
remove.each do |role_name|
- self.class.request('role', 'remove', [role_name, '--project', project, '--user', user])
+ self.class.request('role', 'remove', [role_name] + properties)
end
end
private
+ def properties
+ properties = []
+ if get_project_id
+ properties << '--project' << get_project_id
+ elsif get_domain
+ properties << '--domain' << get_domain
+ else
+ error("No project or domain specified for role")
+ end
+ properties << '--user' << get_user_id
+ properties
+ end
+
def get_user
resource[:name].rpartition('@').first
end
resource[:name].rpartition('@').last
end
+ # if the role is for a domain, it will be specified as
+ # user@::domain - the "project" part will be empty
+ def get_domain
+ # use defined because @domain may be nil
+ return @domain if defined?(@domain)
+ projname, domname = Util.split_domain(get_project)
+ if projname.nil?
+ @domain = domname # no project specified, so must be a domain
+ else
+ @domain = nil # not a domain specific role
+ end
+ @domain
+ end
+
+ def get_user_id
+ @user_id ||= Puppet::Resource.indirection.find("Keystone_user/#{get_user}")[:id]
+ end
+
+ def get_project_id
+ # use defined because @project_id may be nil
+ return @project_id if defined?(@project_id)
+ projname, domname = Util.split_domain(get_project)
+ if projname.nil?
+ @project_id = nil
+ else
+ @project_id ||= Puppet::Resource.indirection.find("Keystone_tenant/#{get_project}")[:id]
+ end
+ @project_id
+ end
+
def self.get_projects
- request('project', 'list').collect { |project| project[:name] }
+ request('project', 'list', '--long').collect do |project|
+ {
+ :id => project[:id],
+ :name => project[:name],
+ :domain_id => project[:domain_id],
+ :domain => domain_name_from_id(project[:domain_id])
+ }
+ end
end
- def self.get_users(project)
- request('user', 'list', ['--project', project]).collect { |user| user[:name] }
+ def self.get_users(project_id=nil, domain_id=nil)
+ properties = ['--long']
+ if project_id
+ properties << '--project' << project_id
+ elsif domain_id
+ properties << '--domain' << domain_id
+ end
+ request('user', 'list', properties).collect do |user|
+ {
+ :id => user[:id],
+ :name => user[:name],
+ # note - column is "Domain" but it is really the domain id
+ :domain_id => user[:domain],
+ :domain => domain_name_from_id(user[:domain])
+ }
+ end
+ end
+
+ def self.user_role_hash
+ @user_role_hash
end
def self.set_user_role_hash(user_role_hash)
end
def self.build_user_role_hash
- hash = @user_role_hash || {}
+ # The new hash will have the property that if the
+ # given key does not exist, create it with an empty
+ # array as the value for the hash key
+ hash = @user_role_hash || Hash.new{|h,k| h[k] = []}
return hash unless hash.empty?
- projects = get_projects
- projects.each do |project|
- users = get_users(project)
- users.each do |user|
- user_roles = request('user role', 'list', [user, '--project', project])
- hash["#{user}@#{project}"] = []
- user_roles.each do |role|
- hash["#{user}@#{project}"] << role[:name]
+ # Need a mapping of project id to names.
+ project_hash = {}
+ Puppet::Type.type(:keystone_tenant).provider(:openstack).instances.each do |project|
+ project_hash[project.id] = project.name
+ end
+ # Need a mapping of user id to names.
+ user_hash = {}
+ Puppet::Type.type(:keystone_user).provider(:openstack).instances.each do |user|
+ user_hash[user.id] = user.name
+ end
+ # need a mapping of role id to name
+ role_hash = {}
+ request('role', 'list').each {|role| role_hash[role[:id]] = role[:name]}
+ # now, get all role assignments
+ request('role assignment', 'list').each do |assignment|
+ if assignment[:user]
+ if assignment[:project]
+ hash["#{user_hash[assignment[:user]]}@#{project_hash[assignment[:project]]}"] << role_hash[assignment[:role]]
+ else
+ domainname = domain_id_to_name(assignment[:domain])
+ hash["#{user_hash[assignment[:user]]}@::#{domainname}"] << role_hash[assignment[:role]]
end
end
end
--- /dev/null
+# LP#1408531
+File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
+File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
+
+Puppet::Type.newtype(:keystone_domain) do
+
+ desc <<-EOT
+ This type can be used to manage
+ keystone domains.
+ EOT
+
+ ensurable
+
+ newparam(:name, :namevar => true) do
+ newvalues(/\w+/)
+ end
+
+ newproperty(:enabled) do
+ newvalues(/(t|T)rue/, /(f|F)alse/, true, false )
+ defaultto(true)
+ munge do |value|
+ value.to_s.downcase.to_sym
+ end
+ end
+
+ newproperty(:description)
+
+ newproperty(:is_default) do
+ desc <<-EOT
+ If this is true, this is the default domain used for v2.0 requests when the domain
+ is not specified, or used by v3 providers if no other domain is specified. The id
+ of this domain will be written to the keystone config identity/default_domain_id
+ value.
+ EOT
+ newvalues(/(t|T)rue/, /(f|F)alse/, true, false )
+ defaultto(false)
+ munge do |value|
+ value.to_s.downcase.to_sym
+ end
+ end
+
+ newproperty(:id) do
+ validate do |v|
+ raise(Puppet::Error, 'This is a read only property')
+ end
+ end
+
+ # we should not do anything until the keystone service is started
+ autorequire(:service) do
+ 'keystone'
+ end
+
+
+end
# LP#1408531
File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
+require 'puppet/provider/keystone/util'
Puppet::Type.newtype(:keystone_tenant) do
end
end
+ newproperty(:domain) do
+ desc 'Domain for tenant.'
+ newvalues(nil, /\S+/)
+ def insync?(is)
+ raise(Puppet::Error, "The domain cannot be changed from #{self.should} to #{is}") unless self.should == is
+ true
+ end
+ end
+
+ autorequire(:keystone_domain) do
+ # use the domain parameter if given, or the one from name if any
+ self[:domain] || Util.split_domain(self[:name])[1]
+ end
+
# This ensures the service is started and therefore the keystone
# config is configured IF we need them for authentication.
# If there is no keystone config, authentication credentials
File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
+require 'puppet/provider/keystone/util'
+
Puppet::Type.newtype(:keystone_user) do
desc 'Type for managing keystone users.'
end
newparam(:ignore_default_tenant) do
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ validate do |v|
+ Puppet.warning('The ignore_default_tenant parameter is deprecated and will be removed in the future.')
+ end
newvalues(/(t|T)rue/, /(f|F)alse/, true, false)
defaultto(false)
munge do |value|
end
newproperty(:tenant) do
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ validate do |v|
+ Puppet.warning('The tenant parameter is deprecated and will be removed in the future. Please use keystone_user_role to assign a user to a project.')
+ end
newvalues(/\S+/)
end
end
end
+ newproperty(:domain) do
+ newvalues(nil, /\S+/)
+ def insync?(is)
+ raise(Puppet::Error, "The domain cannot be changed from #{self.should} to #{is}") unless self.should == is
+ true
+ end
+ end
+
autorequire(:keystone_tenant) do
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
self[:tenant]
end
+ autorequire(:keystone_domain) do
+ # use the domain parameter if given, or the one from name if any
+ self[:domain] or Util.split_domain(self[:name])[1]
+ end
+
# we should not do anything until the keystone service is started
autorequire(:service) do
['keystone']
File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
+require 'puppet/provider/keystone/util'
+
Puppet::Type.newtype(:keystone_user_role) do
desc <<-EOT
end
autorequire(:keystone_tenant) do
- self[:name].rpartition('@').last
+ proj, dom = Util.split_domain(self[:name].rpartition('@').last)
+ rv = nil
+ if proj # i.e. not ::domain
+ rv = self[:name].rpartition('@').last
+ end
+ rv
end
autorequire(:keystone_role) do
self[:roles]
end
+ autorequire(:keystone_domain) do
+ rv = []
+ userdom = Util.split_domain(self[:name].rpartition('@').first)[1]
+ if userdom
+ rv << userdom
+ end
+ projectdom = Util.split_domain(self[:name].rpartition('@').last)[1]
+ if projectdom
+ rv << projectdom
+ end
+ rv
+ end
+
# we should not do anything until the keystone service is started
autorequire(:service) do
['keystone']
# [*version*]
# (optional) API version for endpoint. Appended to all endpoint urls. (Defaults to 'v2.0')
#
+# [*user_domain*]
+# (Optional) Domain for $auth_name
+# Defaults to undef (use the keystone server default domain)
+#
+# [*project_domain*]
+# (Optional) Domain for $tenant (project)
+# Defaults to undef (use the keystone server default domain)
+#
+# [*default_domain*]
+# (Optional) Domain for $auth_name and $tenant (project)
+# If keystone_user_domain is not specified, use $keystone_default_domain
+# If keystone_project_domain is not specified, use $keystone_default_domain
+# Defaults to undef
+#
# === Examples
#
# class { 'keystone::endpoint':
$admin_url = 'http://127.0.0.1:35357',
$version = 'v2.0',
$region = 'RegionOne',
+ $user_domain = undef,
+ $project_domain = undef,
+ $default_domain = undef,
) {
$public_url_real = "${public_url}/${version}"
admin_url => $admin_url_real,
internal_url => $internal_url_real,
region => $region,
+ user_domain => $user_domain,
+ project_domain => $project_domain,
+ default_domain => $default_domain,
}
}
# (Optional) Number of maximum active Fernet keys. Integer > 0.
# Defaults to undef
#
+# [*default_domain*]
+# (optional) When Keystone v3 support is enabled, v2 clients will need
+# to have a domain assigned for certain operations. For example,
+# doing a user create operation must have a domain associated with it.
+# This is the domain which will be used if a domain is needed and not
+# explicitly set in the request.
+# Defaults to undef (will use built-in Keystone default)
+#
# == Dependencies
# None
#
$enable_fernet_setup = false,
$fernet_key_repository = '/etc/keystone/fernet-keys',
$fernet_max_active_keys = undef,
+ $default_domain = undef,
# DEPRECATED PARAMETERS
$mysql_module = undef,
$compute_port = undef,
}
}
+ if $default_domain {
+ keystone_domain { $default_domain:
+ ensure => present,
+ enabled => true,
+ is_default => true,
+ require => File['/etc/keystone/keystone.conf'],
+ notify => Exec['restart_keystone'],
+ }
+ # Update this code when https://bugs.launchpad.net/keystone/+bug/1472285 is addressed.
+ # 1/ Keystone needs to be started before creating the default domain
+ # 2/ Once the default domain is created, we can query Keystone to get the default domain ID
+ # 3/ The Keystone_domain provider has in charge of doing the query and configure keystone.conf
+ # 4/ After such a change, we need to restart Keystone service.
+ # restart_keystone exec is doing 4/, it restart Keystone if we have a new default domain setted
+ # and if we manage the service to be enabled.
+ if $manage_service and $enabled {
+ exec { 'restart_keystone':
+ path => ['/usr/sbin', '/usr/bin', '/sbin', '/bin/'],
+ command => "service ${service_name} restart",
+ refreshonly => true,
+ }
+ }
+ }
}
--- /dev/null
+# == Definition: keystone::resource::authtoken
+#
+# This resource configures Keystone authentication resources for an OpenStack
+# service. It will manage the [keystone_authtoken] section in the given
+# config resource. It supports all of the authentication parameters specified
+# at http://www.jamielennox.net/blog/2015/02/17/loading-authentication-plugins/
+# with the addition of the default domain for user and project.
+#
+# The username and project_name parameters may be given in the form
+# "name::domainname". The authtoken resource will use the domains in
+# the following order:
+# 1) The given domain parameter (user_domain_name or project_domain_name)
+# 2) The domain given as the "::domainname" part of username or project_name
+# 3) The default_domain_name
+#
+# For example, instead of doing this::
+#
+# glance_api_config {
+# 'keystone_authtoken/admin_tenant_name': value => $keystone_tenant;
+# 'keystone_authtoken/admin_user' : value => $keystone_user;
+# 'keystone_authtoken/admin_password' : value => $keystone_password;
+# secret => true;
+# ...
+# }
+#
+# manifests should do this instead::
+#
+# keystone::resource::authtoken { 'glance_api_config':
+# username => $keystone_user,
+# password => $keystone_password,
+# auth_url => $real_identity_uri,
+# project_name => $keystone_tenant,
+# user_domain_name => $keystone_user_domain,
+# project_domain_name => $keystone_project_domain,
+# default_domain_name => $keystone_default_domain,
+# cacert => $ca_file,
+# ...
+# }
+#
+# The use of `keystone::resource::authtoken` makes it easy to avoid mistakes,
+# and makes it easier to support some of the newer authentication types coming
+# with Keystone Kilo and later, such as Kerberos, Federation, etc.
+#
+# == Parameters:
+#
+# [*name*]
+# The name of the resource corresponding to the config file. For example,
+# keystone::resource::authtoken { 'glance_api_config': ... }
+# Where 'glance_api_config' is the name of the resource used to manage
+# the glance api configuration.
+# string; required
+#
+# [*username*]
+# The name of the service user;
+# string; required
+#
+# [*password*]
+# Password to create for the service user;
+# string; required
+#
+# [*auth_url*]
+# The URL to use for authentication.
+# string; required
+#
+# [*auth_plugin*]
+# The plugin to use for authentication.
+# string; optional: default to 'password'
+#
+# [*user_id*]
+# The ID of the service user;
+# string; optional: default to undef
+#
+# [*user_domain_name*]
+# (Optional) Name of domain for $username
+# Defaults to undef
+#
+# [*user_domain_id*]
+# (Optional) ID of domain for $username
+# Defaults to undef
+#
+# [*project_name*]
+# Service project name;
+# string; optional: default to undef
+#
+# [*project_id*]
+# Service project ID;
+# string; optional: default to undef
+#
+# [*project_domain_name*]
+# (Optional) Name of domain for $project_name
+# Defaults to undef
+#
+# [*project_domain_id*]
+# (Optional) ID of domain for $project_name
+# Defaults to undef
+#
+# [*domain_name*]
+# (Optional) Use this for auth to obtain a domain-scoped token.
+# If using this option, do not specify $project_name or $project_id.
+# Defaults to undef
+#
+# [*domain_id*]
+# (Optional) Use this for auth to obtain a domain-scoped token.
+# If using this option, do not specify $project_name or $project_id.
+# Defaults to undef
+#
+# [*default_domain_name*]
+# (Optional) Name of domain for $username and $project_name
+# If user_domain_name is not specified, use $default_domain_name
+# If project_domain_name is not specified, use $default_domain_name
+# Defaults to undef
+#
+# [*default_domain_id*]
+# (Optional) ID of domain for $user_id and $project_id
+# If user_domain_id is not specified, use $default_domain_id
+# If project_domain_id is not specified, use $default_domain_id
+# Defaults to undef
+#
+# [*trust_id*]
+# (Optional) Trust ID
+# Defaults to undef
+#
+# [*cacert*]
+# (Optional) CA certificate file for TLS (https)
+# Defaults to undef
+#
+# [*cert*]
+# (Optional) Certificate file for TLS (https)
+# Defaults to undef
+#
+# [*key*]
+# (Optional) Key file for TLS (https)
+# Defaults to undef
+#
+# [*insecure*]
+# If true, explicitly allow TLS without checking server cert against any
+# certificate authorities. WARNING: not recommended. Use with caution.
+# boolean; Defaults to false (which means be secure)
+#
+define keystone::resource::authtoken(
+ $username,
+ $password,
+ $auth_url,
+ $auth_plugin = 'password',
+ $user_id = undef,
+ $user_domain_name = undef,
+ $user_domain_id = undef,
+ $project_name = undef,
+ $project_id = undef,
+ $project_domain_name = undef,
+ $project_domain_id = undef,
+ $domain_name = undef,
+ $domain_id = undef,
+ $default_domain_name = undef,
+ $default_domain_id = undef,
+ $trust_id = undef,
+ $cacert = undef,
+ $cert = undef,
+ $key = undef,
+ $insecure = false,
+) {
+
+ if !$project_name and !$project_id and !$domain_name and !$domain_id {
+ fail('Must specify either a project (project_name or project_id, for a project scoped token) or a domain (domain_name or domain_id, for a domain scoped token)')
+ }
+
+ if ($project_name or $project_id) and ($domain_name or $domain_id) {
+ fail('Cannot specify both a project (project_name or project_id) and a domain (domain_name or domain_id)')
+ }
+
+ $user_and_domain_array = split($username, '::')
+ $real_username = $user_and_domain_array[0]
+ $real_user_domain_name = pick($user_domain_name, $user_and_domain_array[1], $default_domain_name, '__nodomain__')
+
+ $project_and_domain_array = split($project_name, '::')
+ $real_project_name = $project_and_domain_array[0]
+ $real_project_domain_name = pick($project_domain_name, $project_and_domain_array[1], $default_domain_name, '__nodomain__')
+
+ create_resources($name, {'keystone_authtoken/auth_plugin' => {'value' => $auth_plugin}})
+ create_resources($name, {'keystone_authtoken/auth_url' => {'value' => $auth_url}})
+ create_resources($name, {'keystone_authtoken/username' => {'value' => $real_username}})
+ create_resources($name, {'keystone_authtoken/password' => {'value' => $password, 'secret' => true}})
+ if $user_id {
+ create_resources($name, {'keystone_authtoken/user_id' => {'value' => $user_id}})
+ } else {
+ create_resources($name, {'keystone_authtoken/user_id' => {'ensure' => 'absent'}})
+ }
+ if $real_user_domain_name == '__nodomain__' {
+ create_resources($name, {'keystone_authtoken/user_domain_name' => {'ensure' => 'absent'}})
+ } else {
+ create_resources($name, {'keystone_authtoken/user_domain_name' => {'value' => $real_user_domain_name}})
+ }
+ if $user_domain_id {
+ create_resources($name, {'keystone_authtoken/user_domain_id' => {'value' => $user_domain_id}})
+ } elsif $default_domain_id {
+ create_resources($name, {'keystone_authtoken/user_domain_id' => {'value' => $default_domain_id}})
+ } else {
+ create_resources($name, {'keystone_authtoken/user_domain_id' => {'ensure' => 'absent'}})
+ }
+ if $project_name {
+ create_resources($name, {'keystone_authtoken/project_name' => {'value' => $real_project_name}})
+ } else {
+ create_resources($name, {'keystone_authtoken/project_name' => {'ensure' => 'absent'}})
+ }
+ if $project_id {
+ create_resources($name, {'keystone_authtoken/project_id' => {'value' => $project_id}})
+ } else {
+ create_resources($name, {'keystone_authtoken/project_id' => {'ensure' => 'absent'}})
+ }
+ if $real_project_domain_name == '__nodomain__' {
+ create_resources($name, {'keystone_authtoken/project_domain_name' => {'ensure' => 'absent'}})
+ } else {
+ create_resources($name, {'keystone_authtoken/project_domain_name' => {'value' => $real_project_domain_name}})
+ }
+ if $project_domain_id {
+ create_resources($name, {'keystone_authtoken/project_domain_id' => {'value' => $project_domain_id}})
+ } elsif $default_domain_id {
+ create_resources($name, {'keystone_authtoken/project_domain_id' => {'value' => $default_domain_id}})
+ } else {
+ create_resources($name, {'keystone_authtoken/project_domain_id' => {'ensure' => 'absent'}})
+ }
+ if $domain_name {
+ create_resources($name, {'keystone_authtoken/domain_name' => {'value' => $domain_name}})
+ } else {
+ create_resources($name, {'keystone_authtoken/domain_name' => {'ensure' => 'absent'}})
+ }
+ if $domain_id {
+ create_resources($name, {'keystone_authtoken/domain_id' => {'value' => $domain_id}})
+ } else {
+ create_resources($name, {'keystone_authtoken/domain_id' => {'ensure' => 'absent'}})
+ }
+ if $trust_id {
+ create_resources($name, {'keystone_authtoken/trust_id' => {'value' => $trust_id}})
+ } else {
+ create_resources($name, {'keystone_authtoken/trust_id' => {'ensure' => 'absent'}})
+ }
+ if $cacert {
+ create_resources($name, {'keystone_authtoken/cacert' => {'value' => $cacert}})
+ } else {
+ create_resources($name, {'keystone_authtoken/cacert' => {'ensure' => 'absent'}})
+ }
+ if $cert {
+ create_resources($name, {'keystone_authtoken/cert' => {'value' => $cert}})
+ } else {
+ create_resources($name, {'keystone_authtoken/cert' => {'ensure' => 'absent'}})
+ }
+ if $key {
+ create_resources($name, {'keystone_authtoken/key' => {'value' => $key}})
+ } else {
+ create_resources($name, {'keystone_authtoken/key' => {'ensure' => 'absent'}})
+ }
+ create_resources($name, {'keystone_authtoken/insecure' => {'value' => $insecure}})
+}
# List of roles;
# string; optional: default to ['admin']
#
-# [*domain*]
-# User domain (keystone v3), not implemented yet.
-# string; optional: default to undef
-#
# [*email*]
# Service email;
# string; optional: default to '$auth_name@localhost'
# Whether to create the service.
# string; optional: default to True
#
+# [*user_domain*]
+# (Optional) Domain for $auth_name
+# Defaults to undef (use the keystone server default domain)
+#
+# [*project_domain*]
+# (Optional) Domain for $tenant (project)
+# Defaults to undef (use the keystone server default domain)
+#
+# [*default_domain*]
+# (Optional) Domain for $auth_name and $tenant (project)
+# If keystone_user_domain is not specified, use $keystone_default_domain
+# If keystone_project_domain is not specified, use $keystone_default_domain
+# Defaults to undef
+#
define keystone::resource::service_identity(
$admin_url = false,
$internal_url = false,
$configure_user = true,
$configure_user_role = true,
$configure_service = true,
- $domain = undef,
$email = "${name}@localhost",
$region = 'RegionOne',
$service_name = undef,
$tenant = 'services',
$ignore_default_tenant = false,
$roles = ['admin'],
+ $user_domain = undef,
+ $project_domain = undef,
+ $default_domain = undef,
) {
-
- if $domain {
- warning('Keystone domains are not yet managed by puppet-keystone.')
- }
-
if $service_name == undef {
$service_name_real = $auth_name
} else {
$service_name_real = $service_name
}
+ if $user_domain == undef {
+ $user_domain_real = $default_domain
+ } else {
+ $user_domain_real = $user_domain
+ }
+
if $configure_user {
+ if $user_domain_real {
+ # We have to use ensure_resource here and hope for the best, because we have
+ # no way to know if the $user_domain is the same domain passed as the
+ # $default_domain parameter to class keystone.
+ ensure_resource('keystone_domain', $user_domain_real, {
+ 'ensure' => 'present',
+ 'enabled' => true,
+ })
+ }
ensure_resource('keystone_user', $auth_name, {
'ensure' => 'present',
'enabled' => true,
'email' => $email,
'tenant' => $tenant,
'ignore_default_tenant' => $ignore_default_tenant,
+ 'domain' => $user_domain_real,
})
}
'ensure' => 'present',
'roles' => $roles,
})
- if $configure_user {
- Keystone_user[$auth_name] -> Keystone_user_role["${auth_name}@${tenant}"]
- }
}
if $configure_service {
#
# [*configure_user_role*]
# Optional. Should the admin role be configured for the admin user?
-# Defaulst to 'true'.
+# Defaults to 'true'.
+#
+# [*admin_user_domain*]
+# Optional. Domain of the admin user
+# Defaults to undef (undef will resolve to class keystone $default_domain)
+#
+# [*admin_project_domain*]
+# Optional. Domain of the admin tenant
+# Defaults to undef (undef will resolve to class keystone $default_domain)
+#
+# [*service_project_domain*]
+# Optional. Domain for $service_tenant
+# Defaults to undef (undef will resolve to class keystone $default_domain)
#
# == Dependencies
# == Examples
$service_tenant_desc = 'Tenant for the openstack services',
$configure_user = true,
$configure_user_role = true,
+ $admin_user_domain = undef,
+ $admin_project_domain = undef,
+ $service_project_domain = undef,
) {
+ if $service_project_domain {
+ if $service_project_domain != $admin_user_domain {
+ if $service_project_domain != $admin_project_domain {
+ keystone_domain { $service_project_domain:
+ ensure => present,
+ enabled => true,
+ }
+ }
+ }
+ }
+
+ if $admin_project_domain {
+ if $admin_project_domain != $admin_user_domain {
+ if $service_project_domain != $admin_project_domain {
+ keystone_domain { $admin_project_domain:
+ ensure => present,
+ enabled => true,
+ }
+ }
+ }
+ }
+
+ if $admin_user_domain {
+ if $admin_project_domain != $admin_user_domain {
+ if $service_project_domain != $admin_user_domain {
+ keystone_domain { $admin_user_domain:
+ ensure => present,
+ enabled => true,
+ }
+ }
+ }
+ }
+
keystone_tenant { $service_tenant:
ensure => present,
enabled => true,
description => $service_tenant_desc,
+ domain => $service_project_domain,
}
keystone_tenant { $admin_tenant:
ensure => present,
enabled => true,
description => $admin_tenant_desc,
+ domain => $admin_project_domain,
}
keystone_role { 'admin':
ensure => present,
tenant => $admin_tenant,
email => $email,
password => $password,
+ domain => $admin_user_domain,
ignore_default_tenant => $ignore_default_tenant,
}
}
{
- "name": "stackforge-keystone",
- "version": "5.1.0",
+ "name": "openstack-keystone",
+ "version": "6.0.0",
"author": "Puppet Labs and OpenStack Contributors",
"summary": "Puppet module for OpenStack Keystone",
"license": "Apache-2.0",
"source": "git://github.com/openstack/puppet-keystone.git",
"project_page": "https://launchpad.net/puppet-keystone",
"issues_url": "https://bugs.launchpad.net/puppet-keystone",
+ "dependencies": [
+ {"name":"puppetlabs/apache","version_requirement":">=1.0.0 <2.0.0"},
+ {"name":"puppetlabs/inifile","version_requirement":">=1.0.0 <2.0.0"},
+ {"name":"puppetlabs/stdlib","version_requirement":">=4.0.0 <5.0.0"},
+ {"name":"openstack/openstacklib","version_requirement":">=6.0.0 <7.0.0"}
+ ],
"requirements": [
- { "name": "pe","version_requirement": "3.x" },
- { "name": "puppet","version_requirement": "3.x" }
+ {
+ "name": "pe",
+ "version_requirement": "3.x"
+ },
+ {
+ "name": "puppet",
+ "version_requirement": "3.x"
+ }
],
"operatingsystem_support": [
{
"operatingsystem": "Debian",
- "operatingsystemrelease": ["7"]
+ "operatingsystemrelease": [
+ "7"
+ ]
},
{
"operatingsystem": "Fedora",
- "operatingsystemrelease": ["20"]
+ "operatingsystemrelease": [
+ "20"
+ ]
},
{
"operatingsystem": "RedHat",
- "operatingsystemrelease": ["6.5","7"]
+ "operatingsystemrelease": [
+ "6.5",
+ "7"
+ ]
},
{
"operatingsystem": "Ubuntu",
- "operatingsystemrelease": ["12.04","14.04"]
+ "operatingsystemrelease": [
+ "12.04",
+ "14.04"
+ ]
}
],
- "description": "Installs and configures OpenStack Keystone (Identity).",
- "dependencies": [
- { "name": "puppetlabs/apache", "version_requirement": ">=1.0.0 <2.0.0" },
- { "name": "puppetlabs/inifile", "version_requirement": ">=1.0.0 <2.0.0" },
- { "name": "puppetlabs/stdlib", "version_requirement": ">=4.0.0 <5.0.0" },
- { "name": "stackforge/openstacklib", "version_requirement": ">=5.0.0 <6.0.0" }
- ]
+ "description": "Installs and configures OpenStack Keystone (Identity)."
}
admin_token => 'admin_token',
enabled => true,
}
+ # "v2" admin and service
class { '::keystone::roles::admin':
- email => 'test@example.tld',
- password => 'a_big_secret',
+ email => 'test@example.tld',
+ password => 'a_big_secret',
}
class { '::keystone::endpoint':
- public_url => "http://127.0.0.1:5000/",
- admin_url => "http://127.0.0.1:35357/",
+ public_url => "http://127.0.0.1:5000/",
+ admin_url => "http://127.0.0.1:35357/",
+ default_domain => 'admin',
}
::keystone::resource::service_identity { 'beaker-ci':
service_type => 'beaker',
admin_url => 'http://127.0.0.1:1234',
internal_url => 'http://127.0.0.1:1234',
}
+ # v3 admin
+ # we don't use ::keystone::roles::admin but still create resources manually:
+ keystone_domain { 'admin_domain':
+ ensure => present,
+ enabled => true,
+ description => 'Domain for admin v3 users',
+ }
+ keystone_domain { 'service_domain':
+ ensure => present,
+ enabled => true,
+ description => 'Domain for admin v3 users',
+ }
+ keystone_tenant { 'servicesv3':
+ ensure => present,
+ enabled => true,
+ description => 'Tenant for the openstack services',
+ domain => 'service_domain',
+ }
+ keystone_tenant { 'openstackv3':
+ ensure => present,
+ enabled => true,
+ description => 'admin tenant',
+ domain => 'admin_domain',
+ }
+ keystone_user { 'adminv3':
+ ensure => present,
+ enabled => true,
+ tenant => 'openstackv3', # note: don't have to use 'openstackv3::admin_domain' here since the tenant name 'openstackv3' is unique among all domains
+ email => 'test@example.tld',
+ password => 'a_big_secret',
+ domain => 'admin_domain',
+ }
+ keystone_user_role { 'adminv3@openstackv3':
+ ensure => present,
+ roles => ['admin'],
+ }
+ # service user exists only in the service_domain - must
+ # use v3 api
+ ::keystone::resource::service_identity { 'beaker-civ3':
+ service_type => 'beakerv3',
+ service_description => 'beakerv3 service',
+ service_name => 'beakerv3',
+ password => 'secret',
+ tenant => 'servicesv3',
+ public_url => 'http://127.0.0.1:1234/v3',
+ admin_url => 'http://127.0.0.1:1234/v3',
+ internal_url => 'http://127.0.0.1:1234/v3',
+ user_domain => 'service_domain',
+ project_domain => 'service_domain',
+ }
EOS
it { should have_entry('1 0 * * * keystone-manage token_flush >>/var/log/keystone/keystone-tokenflush.log 2>&1').with_user('keystone') }
end
- describe 'test keystone user/tenant/service/role/endpoint resources' do
+ shared_examples_for 'keystone user/tenant/service/role/endpoint resources using v2 API' do |auth_creds|
+ it 'should find users in the default domain' do
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 user list") do |r|
+ expect(r.stdout).to match(/admin/)
+ expect(r.stderr).to be_empty
+ end
+ end
+ it 'should find tenants in the default domain' do
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 project list") do |r|
+ expect(r.stdout).to match(/openstack/)
+ expect(r.stderr).to be_empty
+ end
+ end
+ it 'should find beaker service' do
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 service list") do |r|
+ expect(r.stdout).to match(/beaker/)
+ expect(r.stderr).to be_empty
+ end
+ end
+ it 'should find admin role' do
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 role list") do |r|
+ expect(r.stdout).to match(/admin/)
+ expect(r.stderr).to be_empty
+ end
+ end
+ it 'should find beaker endpoints' do
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v2.0 --os-identity-api-version 2 endpoint list --long") do |r|
+ expect(r.stdout).to match(/1234/)
+ expect(r.stderr).to be_empty
+ end
+ end
+ end
+ shared_examples_for 'keystone user/tenant/service/role/endpoint resources using v3 API' do |auth_creds|
it 'should find beaker user' do
- shell('openstack --os-username admin --os-password a_big_secret --os-tenant-name openstack --os-auth-url http://127.0.0.1:5000/v2.0 user list') do |r|
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 user list") do |r|
expect(r.stdout).to match(/beaker/)
expect(r.stderr).to be_empty
end
end
it 'should find services tenant' do
- shell('openstack --os-username admin --os-password a_big_secret --os-tenant-name openstack --os-auth-url http://127.0.0.1:5000/v2.0 project list') do |r|
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 project list") do |r|
expect(r.stdout).to match(/services/)
expect(r.stderr).to be_empty
end
end
it 'should find beaker service' do
- shell('openstack --os-username admin --os-password a_big_secret --os-tenant-name openstack --os-auth-url http://127.0.0.1:5000/v2.0 service list') do |r|
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 service list") do |r|
expect(r.stdout).to match(/beaker/)
expect(r.stderr).to be_empty
end
end
it 'should find admin role' do
- shell('openstack --os-username admin --os-password a_big_secret --os-tenant-name openstack --os-auth-url http://127.0.0.1:5000/v2.0 role list') do |r|
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 role list") do |r|
expect(r.stdout).to match(/admin/)
expect(r.stderr).to be_empty
end
end
it 'should find beaker endpoints' do
- shell('openstack --os-username admin --os-password a_big_secret --os-tenant-name openstack --os-auth-url http://127.0.0.1:5000/v2.0 endpoint list --long') do |r|
+ shell("openstack #{auth_creds} --os-auth-url http://127.0.0.1:5000/v3 --os-identity-api-version 3 endpoint list") do |r|
expect(r.stdout).to match(/1234/)
expect(r.stderr).to be_empty
end
end
end
+ describe 'with v2 admin with v2 credentials' do
+ include_examples 'keystone user/tenant/service/role/endpoint resources using v2 API',
+ '--os-username admin --os-password a_big_secret --os-project-name openstack'
+ end
+ describe 'with v2 service with v2 credentials' do
+ include_examples 'keystone user/tenant/service/role/endpoint resources using v2 API',
+ '--os-username beaker-ci --os-password secret --os-project-name services'
+ end
+ describe 'with v2 admin with v3 credentials' do
+ include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API',
+ '--os-username admin --os-password a_big_secret --os-project-name openstack --os-user-domain-name Default --os-project-domain-name Default'
+ end
+ describe "with v2 service with v3 credentials" do
+ include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API',
+ '--os-username beaker-ci --os-password secret --os-project-name services --os-user-domain-name Default --os-project-domain-name Default'
+ end
+ describe 'with v3 admin with v3 credentials' do
+ include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API',
+ '--os-username adminv3 --os-password a_big_secret --os-project-name openstackv3 --os-user-domain-name admin_domain --os-project-domain-name admin_domain'
+ end
+ describe "with v3 service with v3 credentials" do
+ include_examples 'keystone user/tenant/service/role/endpoint resources using v3 API',
+ '--os-username beaker-civ3 --os-password secret --os-project-name servicesv3 --os-user-domain-name service_domain --os-project-domain-name service_domain'
+ end
+
end
end
)
end
end
+
+ describe 'with domain parameters' do
+
+ let :params do
+ { :user_domain => 'userdomain',
+ :project_domain => 'projectdomain',
+ :default_domain => 'defaultdomain' }
+ end
+
+ it { is_expected.to contain_keystone__resource__service_identity('keystone').with(
+ :user_domain => 'userdomain',
+ :project_domain => 'projectdomain',
+ :default_domain => 'defaultdomain'
+ )}
+ end
end
end
it { is_expected.to contain_keystone_tenant('foobar').with(
- :ensure => 'present',
- :enabled => true,
+ :ensure => 'present',
+ :enabled => true,
:description => 'foobar description'
)}
it { is_expected.to contain_keystone_tenant('admin').with(
before do
let :params do
{
- :configure_user => false,
- :configure_user_role => false
+ :configure_user => false,
+ :configure_user_role => false
}
end
end
end
+ describe 'when specifying admin_user_domain and admin_project_domain' do
+ let :params do
+ {
+ :email => 'foo@bar',
+ :password => 'ChangeMe',
+ :admin_tenant => 'admin_tenant',
+ :admin_user_domain => 'admin_user_domain',
+ :admin_project_domain => 'admin_project_domain',
+ }
+ end
+ it { is_expected.to contain_keystone_user('admin').with(
+ :domain => 'admin_user_domain',
+ :tenant => 'admin_tenant'
+ )}
+ it { is_expected.to contain_keystone_tenant('admin_tenant').with(:domain => 'admin_project_domain') }
+ it { is_expected.to contain_keystone_domain('admin_user_domain') }
+ it { is_expected.to contain_keystone_domain('admin_project_domain') }
+
+ end
+
+ describe 'when specifying admin_user_domain and admin_project_domain' do
+ let :params do
+ {
+ :email => 'foo@bar',
+ :password => 'ChangeMe',
+ :admin_tenant => 'admin_tenant::admin_project_domain',
+ :admin_user_domain => 'admin_user_domain',
+ :admin_project_domain => 'admin_project_domain',
+ }
+ end
+ it { is_expected.to contain_keystone_user('admin').with(
+ :domain => 'admin_user_domain',
+ :tenant => 'admin_tenant::admin_project_domain'
+ )}
+ it { is_expected.to contain_keystone_tenant('admin_tenant::admin_project_domain').with(:domain => 'admin_project_domain') }
+ it { is_expected.to contain_keystone_domain('admin_user_domain') }
+ it { is_expected.to contain_keystone_domain('admin_project_domain') }
+
+ end
+
+ describe 'when specifying a service domain' do
+ let :params do
+ {
+ :email => 'foo@bar',
+ :password => 'ChangeMe',
+ :service_tenant => 'service_project',
+ :service_project_domain => 'service_domain'
+ }
+ end
+ it { is_expected.to contain_keystone_tenant('service_project').with(:domain => 'service_domain') }
+ it { is_expected.to contain_keystone_domain('service_domain') }
+
+ end
+
+ describe 'when specifying a service domain and service tenant domain' do
+ let :params do
+ {
+ :email => 'foo@bar',
+ :password => 'ChangeMe',
+ :service_tenant => 'service_project::service_domain',
+ :service_project_domain => 'service_domain'
+ }
+ end
+ it { is_expected.to contain_keystone_tenant('service_project::service_domain').with(:domain => 'service_domain') }
+ it { is_expected.to contain_keystone_domain('service_domain') }
+
+ end
+
end
'rabbit_host' => '127.0.0.1',
'rabbit_password' => 'openstack',
'rabbit_userid' => 'admin',
+ 'default_domain' => 'other_domain',
}
httpd_params = {'service_name' => 'httpd'}.merge(default_params)
is_expected.to contain_keystone_config('DEFAULT/public_workers').with_value('2')
end
end
+
+ if param_hash['default_domain']
+ it { is_expected.to contain_keystone_domain(param_hash['default_domain']).with(:is_default => true) }
+ end
end
[default_params, override_params].each do |param_hash|
end
end
+ describe 'when configuring default domain' do
+ describe 'with default config' do
+ let :params do
+ default_params
+ end
+ it { is_expected.to_not contain_exec('restart_keystone') }
+ end
+ describe 'with default domain and service is managed and enabled' do
+ let :params do
+ default_params.merge({
+ 'default_domain'=> 'test',
+ })
+ end
+ it { is_expected.to contain_exec('restart_keystone') }
+ end
+ describe 'with default domain and service is not managed' do
+ let :params do
+ default_params.merge({
+ 'default_domain' => 'test',
+ 'manage_service' => false,
+ })
+ end
+ it { is_expected.to_not contain_exec('restart_keystone') }
+ end
+ end
+
context 'on RedHat platforms' do
let :facts do
global_facts.merge({
--- /dev/null
+require 'spec_helper'
+
+describe 'keystone::resource::authtoken' do
+
+ let (:title) { 'keystone_config' }
+
+ let :required_params do
+ { :username => 'keystone',
+ :password => 'secret',
+ :auth_url => 'http://127.0.0.1:35357/',
+ :project_name => 'services' }
+ end
+
+ shared_examples 'shared examples' do
+
+ context 'with only required parameters' do
+ let :params do
+ required_params
+ end
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/username').with(
+ :value => 'keystone',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/user_id').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/password').with(
+ :value => 'secret',
+ :secret => true,
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/auth_plugin').with(
+ :value => 'password',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/auth_url').with(
+ :value => 'http://127.0.0.1:35357/',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_name').with(
+ :value => 'services',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_id').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/user_domain_name').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_domain_name').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/user_domain_id').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_domain_id').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/domain_name').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/domain_id').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/trust_id').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/cacert').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/cert').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/key').with(
+ :ensure => 'absent',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/insecure').with(
+ :value => 'false',
+ )}
+
+ end
+
+ context 'when omitting a required parameter password' do
+ let :params do
+ required_params.delete(:password)
+ end
+ it { expect { is_expected.to raise_error(Puppet::Error) } }
+ end
+
+ context 'when specifying auth_url' do
+ let :params do
+ required_params.merge({:auth_url => 'https://host:11111/v3/'})
+ end
+ it { is_expected.to contain_keystone_config('keystone_authtoken/auth_url').with(
+ :value => 'https://host:11111/v3/',
+ )}
+
+ end
+
+ context 'when specifying project and scope_domain' do
+ let :params do
+ required_params.merge({:domain_name => 'domain'})
+ end
+ it { expect { is_expected.to raise_error(Puppet::Error, 'Cannot specify both a project (project_name or project_id) and a domain (domain_name or domain_id)') } }
+ end
+
+ context 'when specifying neither project nor domain' do
+ let :params do
+ required_params.delete(:project_name)
+ end
+ it { expect { is_expected.to raise_error(Puppet::Error, 'Must specify either a project (project_name or project_id, for a project scoped token) or a domain (domain_name or domain_id, for a domain scoped token)') } }
+ end
+
+ context 'when specifying domain in name' do
+ let :params do
+ required_params.merge({
+ :username => 'keystone::userdomain',
+ :project_name => 'services::projdomain',
+ :default_domain_name => 'shouldnotuse'
+ })
+ end
+ it { is_expected.to contain_keystone_config('keystone_authtoken/user_domain_name').with(
+ :value => 'userdomain',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_domain_name').with(
+ :value => 'projdomain',
+ )}
+
+ end
+
+ context 'when specifying domain in parameters' do
+ let :params do
+ required_params.merge({
+ :username => 'keystone::userdomain',
+ :user_domain_name => 'realuserdomain',
+ :project_name => 'services::projdomain',
+ :project_domain_name => 'realprojectdomain',
+ :default_domain_name => 'shouldnotuse'
+ })
+ end
+ it { is_expected.to contain_keystone_config('keystone_authtoken/user_domain_name').with(
+ :value => 'realuserdomain',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_domain_name').with(
+ :value => 'realprojectdomain',
+ )}
+
+ end
+
+ context 'when specifying only default domain' do
+ let :params do
+ required_params.merge({
+ :default_domain_name => 'defaultdomain'
+ })
+ end
+ it { is_expected.to contain_keystone_config('keystone_authtoken/user_domain_name').with(
+ :value => 'defaultdomain',
+ )}
+
+ it { is_expected.to contain_keystone_config('keystone_authtoken/project_domain_name').with(
+ :value => 'defaultdomain',
+ )}
+
+ end
+
+ end
+
+ context 'on a Debian osfamily' do
+ let :facts do
+ { :osfamily => "Debian" }
+ end
+
+ include_examples 'shared examples'
+ end
+
+ context 'on a RedHat osfamily' do
+ let :facts do
+ { :osfamily => 'RedHat' }
+ end
+
+ include_examples 'shared examples'
+ end
+end
it { expect { is_expected.to raise_error(Puppet::Error) } }
end
+ context 'with user domain' do
+ let :params do
+ required_params.merge({:user_domain => 'userdomain'})
+ end
+ it { is_expected.to contain_keystone_domain('userdomain').with(
+ :ensure => 'present',
+ )}
+ it { is_expected.to contain_keystone_user(title).with(
+ :ensure => 'present',
+ :password => 'secrete',
+ :email => 'neutron@localhost',
+ :tenant => 'services',
+ :domain => 'userdomain',
+ )}
+ it { is_expected.to contain_keystone_user_role("#{title}@services").with(
+ :ensure => 'present',
+ :roles => ['admin'],
+ )}
+ end
+ context 'with user and project domain' do
+ let :params do
+ required_params.merge({
+ :user_domain => 'userdomain',
+ :project_domain => 'projdomain',
+ })
+ end
+ it { is_expected.to contain_keystone_user(title).with(
+ :ensure => 'present',
+ :password => 'secrete',
+ :email => 'neutron@localhost',
+ :tenant => 'services',
+ :domain => 'userdomain',
+ )}
+ it { is_expected.to contain_keystone_domain('userdomain').with(
+ :ensure => 'present',
+ )}
+ it { is_expected.to contain_keystone_user_role("#{title}@services").with(
+ :ensure => 'present',
+ :roles => ['admin'],
+ )}
+ end
+ context 'with default domain only' do
+ let :params do
+ required_params.merge({
+ :default_domain => 'defaultdomain',
+ })
+ end
+ it { is_expected.to contain_keystone_user(title).with(
+ :ensure => 'present',
+ :password => 'secrete',
+ :email => 'neutron@localhost',
+ :tenant => 'services',
+ :domain => 'defaultdomain',
+ )}
+ it { is_expected.to contain_keystone_domain('defaultdomain').with(
+ :ensure => 'present',
+ )}
+ it { is_expected.to contain_keystone_user_role("#{title}@services").with(
+ :ensure => 'present',
+ :roles => ['admin'],
+ )}
+ end
+
end
context 'on a Debian osfamily' do
RSpec.configure do |c|
# Project root
proj_root = File.expand_path(File.join(File.dirname(__FILE__), '..'))
+ modname = JSON.parse(open('metadata.json').read)['name'].split('-')[1]
# Readable test descriptions
c.formatter = :documentation
# install git
install_package host, 'git'
- # clean out any module cruft
- shell('rm -fr /etc/puppet/modules/*')
+ zuul_ref = ENV['ZUUL_REF']
+ zuul_branch = ENV['ZUUL_BRANCH']
+ zuul_url = ENV['ZUUL_URL']
+
+ repo = 'openstack/puppet-openstack-integration'
+
+ # Start out with clean moduledir, don't trust r10k to purge it
+ on host, "rm -rf /etc/puppet/modules/*"
+ # Install dependent modules via git or zuul
+ r = on host, "test -e /usr/zuul-env/bin/zuul-cloner", { :acceptable_exit_codes => [0,1] }
+ if r.exit_code == 0
+ zuul_clone_cmd = '/usr/zuul-env/bin/zuul-cloner '
+ zuul_clone_cmd += '--cache-dir /opt/git '
+ zuul_clone_cmd += "--zuul-ref #{zuul_ref} "
+ zuul_clone_cmd += "--zuul-branch #{zuul_branch} "
+ zuul_clone_cmd += "--zuul-url #{zuul_url} "
+ zuul_clone_cmd += "git://git.openstack.org #{repo}"
+ on host, zuul_clone_cmd
+ else
+ on host, "git clone https://git.openstack.org/#{repo} #{repo}"
+ end
+
+ on host, "ZUUL_REF=#{zuul_ref} ZUUL_BRANCH=#{zuul_branch} ZUUL_URL=#{zuul_url} bash #{repo}/install_modules.sh"
- # install library modules from the forge
- on host, puppet('module','install','puppetlabs-mysql'), { :acceptable_exit_codes => 0 }
- on host, puppet('module','install','dprince/qpid'), { :acceptable_exit_codes => 0 }
- on host, puppet('module','install','duritong/sysctl'), { :acceptable_exit_codes => 0 }
- on host, puppet('module','install','puppetlabs-inifile'), { :acceptable_exit_codes => 0 }
- on host, puppet('module','install','puppetlabs-rabbitmq'), { :acceptable_exit_codes => 0 }
- on host, puppet('module','install','puppetlabs-apache'), { :acceptable_exit_codes => 0 }
+ # Install the module being tested
+ on host, "rm -fr /etc/puppet/modules/#{modname}"
+ puppet_module_install(:source => proj_root, :module_name => modname)
- # install puppet modules from git, use master
- shell('git clone https://git.openstack.org/openstack/puppet-openstacklib /etc/puppet/modules/openstacklib')
- shell('git clone https://git.openstack.org/openstack/puppet-openstack_extras /etc/puppet/modules/openstack_extras')
+ on host, "rm -fr #{repo}"
- # Install the module being tested
- puppet_module_install(:source => proj_root, :module_name => 'keystone')
# List modules installed to help with debugging
- on hosts[0], puppet('module','list'), { :acceptable_exit_codes => 0 }
+ on host, puppet('module','list'), { :acceptable_exit_codes => 0 }
end
end
end
--- /dev/null
+require 'puppet'
+require 'spec_helper'
+require 'puppet/provider/keystone'
+require 'puppet/provider/keystone/util'
+
+describe "split_domain method" do
+ it 'should handle nil and empty strings' do
+ expect(Util.split_domain('')).to eq([nil, nil])
+ expect(Util.split_domain(nil)).to eq([nil, nil])
+ end
+ it 'should return name and no domain' do
+ expect(Util.split_domain('foo')).to eq(['foo', nil])
+ expect(Util.split_domain('foo::')).to eq(['foo', nil])
+ end
+ it 'should return name and domain' do
+ expect(Util.split_domain('foo::bar')).to eq(['foo', 'bar'])
+ expect(Util.split_domain('foo::bar::')).to eq(['foo', 'bar'])
+ expect(Util.split_domain('::foo::bar')).to eq(['::foo', 'bar'])
+ expect(Util.split_domain('::foo::bar::')).to eq(['::foo', 'bar'])
+ expect(Util.split_domain('foo::bar::baz')).to eq(['foo::bar', 'baz'])
+ expect(Util.split_domain('foo::bar::baz::')).to eq(['foo::bar', 'baz'])
+ expect(Util.split_domain('::foo::bar::baz')).to eq(['::foo::bar', 'baz'])
+ expect(Util.split_domain('::foo::bar::baz::')).to eq(['::foo::bar', 'baz'])
+ end
+ it 'should return domain only' do
+ expect(Util.split_domain('::foo')).to eq([nil, 'foo'])
+ expect(Util.split_domain('::foo::')).to eq([nil, 'foo'])
+ end
+end
--- /dev/null
+require 'puppet'
+require 'spec_helper'
+require 'puppet/provider/keystone_domain/openstack'
+
+provider_class = Puppet::Type.type(:keystone_domain).provider(:openstack)
+
+class Puppet::Provider::Keystone
+ def self.reset
+ @admin_endpoint = nil
+ @tenant_hash = nil
+ @admin_token = nil
+ @keystone_file = nil
+ @domain_id_to_name = nil
+ @default_domain_id = nil
+ @domain_hash = nil
+ end
+end
+
+describe provider_class do
+
+ after :each do
+ provider_class.reset
+ end
+
+ shared_examples 'authenticated with environment variables' do
+ ENV['OS_USERNAME'] = 'test'
+ ENV['OS_PASSWORD'] = 'abc123'
+ ENV['OS_PROJECT_NAME'] = 'test'
+ ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v2.0'
+ end
+
+ describe 'when managing a domain' do
+
+ let(:domain_attrs) do
+ {
+ :name => 'foo',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ }
+ end
+
+ let(:resource) do
+ Puppet::Type::Keystone_domain.new(domain_attrs)
+ end
+
+ let(:provider) do
+ provider_class.new(resource)
+ end
+
+ it_behaves_like 'authenticated with environment variables' do
+ describe '#create' do
+ it 'creates a domain' do
+ # keystone.conf
+ File.expects(:exists?).returns(true)
+ kcmock = {
+ 'identity' => {'default_domain_id' => ' default'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(kcmock)
+ kcmock.expects(:read).with('/etc/keystone/keystone.conf')
+ provider.class.expects(:openstack)
+ .with('domain', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo'])
+ .returns('id="1cb05cfed7c24279be884ba4f6520262"
+name="foo"
+description="foo"
+enabled=True
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ end
+
+ end
+
+ describe '#destroy' do
+ it 'destroys a domain' do
+ provider.instance_variable_get('@property_hash')[:id] = 'my-domainid'
+ # keystone.conf
+ File.expects(:exists?).returns(true)
+ kcmock = {
+ 'identity' => {'default_domain_id' => ' default'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(kcmock)
+ kcmock.expects(:read).with('/etc/keystone/keystone.conf')
+ provider.class.expects(:openstack)
+ .with('domain', 'set', ['foo', '--disable'])
+ provider.class.expects(:openstack)
+ .with('domain', 'delete', 'foo')
+ provider.destroy
+ expect(provider.exists?).to be_falsey
+ end
+
+ end
+
+ describe '#instances' do
+ it 'finds every domain' do
+ provider.class.expects(:openstack)
+ .with('domain', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo",True
+')
+ instances = provider_class.instances
+ expect(instances.count).to eq(1)
+ end
+ end
+
+ describe '#create default' do
+ let(:domain_attrs) do
+ {
+ :name => 'foo',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ :is_default => 'True',
+ }
+ end
+
+ it 'creates a default domain' do
+ File.expects(:exists?).returns(true)
+ mock = {
+ 'identity' => {'default_domain_id' => ' default'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(mock)
+ mock.expects(:read).with('/etc/keystone/keystone.conf')
+ mock.expects(:store)
+ provider.class.expects(:openstack)
+ .with('domain', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo'])
+ .returns('id="1cb05cfed7c24279be884ba4f6520262"
+name="foo"
+description="foo"
+enabled=True
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(mock['identity']['default_domain_id']).to eq('1cb05cfed7c24279be884ba4f6520262')
+ end
+ end
+
+ describe '#destroy default' do
+ it 'destroys a default domain' do
+ provider.instance_variable_get('@property_hash')[:is_default] = true
+ provider.instance_variable_get('@property_hash')[:id] = 'my-domainid'
+ # keystone.conf
+ File.expects(:exists?).returns(true)
+ kcmock = {
+ 'identity' => {'default_domain_id' => ' my-domainid'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(kcmock)
+ kcmock.expects(:read).with('/etc/keystone/keystone.conf')
+ kcmock.expects(:store)
+ provider.class.expects(:openstack)
+ .with('domain', 'set', ['foo', '--disable'])
+ provider.class.expects(:openstack)
+ .with('domain', 'delete', 'foo')
+ provider.destroy
+ expect(provider.exists?).to be_falsey
+ expect(kcmock['identity']['default_domain_id']).to eq('default')
+ end
+ end
+
+ describe '#flush' do
+ let(:domain_attrs) do
+ {
+ :name => 'foo',
+ :description => 'new description',
+ :ensure => 'present',
+ :enabled => 'True',
+ :is_default => 'True',
+ }
+ end
+
+ it 'changes the description' do
+ provider.class.expects(:openstack)
+ .with('domain', 'set', ['foo', '--description', 'new description'])
+ provider.description=('new description')
+ provider.flush
+ end
+
+ it 'changes is_default' do
+ # keystone.conf
+ File.expects(:exists?).returns(true)
+ kcmock = {
+ 'identity' => {'default_domain_id' => ' my-domainid'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(kcmock)
+ kcmock.expects(:read).with('/etc/keystone/keystone.conf')
+ provider.is_default=(true)
+ provider.flush
+ end
+ end
+ end
+ end
+end
describe '#create' do
it 'creates a role' do
- provider.class.stubs(:openstack)
- .with('role', 'list', '--quiet', '--format', 'csv', [])
- .returns('"ID","Name"
-"1cb05cfed7c24279be884ba4f6520262","foo"
-')
- provider.class.stubs(:openstack)
+ provider.class.expects(:openstack)
.with('role', 'create', '--format', 'shell', 'foo')
.returns('name="foo"')
provider.create
describe '#destroy' do
it 'destroys a role' do
- provider.class.stubs(:openstack)
- .with('role', 'list', '--quiet', '--format', 'csv', [])
- .returns('"ID","Name"')
- provider.class.stubs(:openstack)
+ provider.class.expects(:openstack)
.with('role', 'delete', [])
provider.destroy
expect(provider.exists?).to be_falsey
describe '#exists' do
context 'when role does not exist' do
subject(:response) do
- provider.class.stubs(:openstack)
- .with('role', 'list', '--quiet', '--format', 'csv', [])
- .returns('"ID","Name"')
response = provider.exists?
end
it { is_expected.to be_falsey }
describe '#instances' do
it 'finds every role' do
- provider.class.stubs(:openstack)
+ provider.class.expects(:openstack)
.with('role', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name"
"1cb05cfed7c24279be884ba4f6520262","foo"
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test'
- ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v2.0'
+ ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000/v3'
end
describe 'when managing a service' do
"1cb05cfed7c24279be884ba4f6520262","foo","foo","foo"
')
provider.class.stubs(:openstack)
- .with('service', 'create', '--format', 'shell', ['--description', 'foo', '--type', 'foo', 'foo'])
+ .with('service', 'create', '--format', 'shell', ['foo', '--name', 'foo', '--description', 'foo'])
.returns('description="foo"
enabled="True"
id="8f0dd4c0abc44240998fbb3f5089ecbf"
klass = Puppet::Provider::Keystone
class Puppet::Provider::Keystone
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def self.reset
@admin_endpoint = nil
@tenant_hash = nil
@admin_token = nil
@keystone_file = nil
+ @domain_id_to_name = nil
+ @default_domain_id = nil
+ @domain_hash = nil
end
end
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('http://192.168.56.210:35357/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('http://192.168.56.210:35357/v3/')
end
it 'should use localhost in the admin endpoint if bind_host is 0.0.0.0' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('http://127.0.0.1:35357/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('http://127.0.0.1:35357/v3/')
end
it 'should use [::1] in the admin endpoint if bind_host is ::0' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('http://[::1]:35357/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('http://[::1]:35357/v3/')
end
it 'should use localhost in the admin endpoint if bind_host is unspecified' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('http://127.0.0.1:35357/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('http://127.0.0.1:35357/v3/')
end
it 'should use https if ssl is enabled' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('https://192.168.56.210:35357/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('https://192.168.56.210:35357/v3/')
end
it 'should use http if ssl is disabled' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('http://192.168.56.210:35357/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('http://192.168.56.210:35357/v3/')
end
it 'should use the defined admin_endpoint if available' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('https://keystone.example.com/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('https://keystone.example.com/v3/')
end
it 'should handle an admin_endpoint with a trailing slash' do
File.expects(:exists?).with("/etc/keystone/keystone.conf").returns(true)
Puppet::Util::IniConfig::File.expects(:new).returns(mock)
mock.expects(:read).with('/etc/keystone/keystone.conf')
- expect(klass.get_admin_endpoint).to eq('https://keystone.example.com/v2.0/')
+ expect(klass.get_admin_endpoint).to eq('https://keystone.example.com/v3/')
end
end
+ describe 'when using domains' do
+ it 'name_and_domain should return the resource domain' do
+ expect(klass.name_and_domain('foo::in_name', 'from_resource', 'default')).to eq(['foo', 'from_resource'])
+ end
+ it 'name_and_domain should return the default domain' do
+ expect(klass.name_and_domain('foo', nil, 'default')).to eq(['foo', 'default'])
+ end
+ it 'name_and_domain should return the domain part of the name' do
+ expect(klass.name_and_domain('foo::in_name', nil, 'default')).to eq(['foo', 'in_name'])
+ end
+ it 'should return the default domain name using the default_domain_id from keystone.conf' do
+ ENV['OS_USERNAME'] = 'test'
+ ENV['OS_PASSWORD'] = 'abc123'
+ ENV['OS_PROJECT_NAME'] = 'test'
+ ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v3'
+ mock = {
+ 'DEFAULT' => {
+ 'admin_endpoint' => 'http://127.0.0.1:35357',
+ 'admin_token' => 'admin_token'
+ },
+ 'identity' => {'default_domain_id' => 'somename'}
+ }
+ File.expects(:exists?).with('/etc/keystone/keystone.conf').returns(true)
+ Puppet::Util::IniConfig::File.expects(:new).returns(mock)
+ mock.expects(:read).with('/etc/keystone/keystone.conf')
+ klass.expects(:openstack)
+ .with('domain', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name","Enabled","Description"
+"somename","SomeName",True,"default domain"
+')
+ expect(klass.name_and_domain('foo')).to eq(['foo', 'SomeName'])
+ end
+ it 'should return Default if default_domain_id is not configured' do
+ ENV['OS_USERNAME'] = 'test'
+ ENV['OS_PASSWORD'] = 'abc123'
+ ENV['OS_PROJECT_NAME'] = 'test'
+ ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v3'
+ mock = {}
+ Puppet::Util::IniConfig::File.expects(:new).returns(mock)
+ File.expects(:exists?).with('/etc/keystone/keystone.conf').returns(true)
+ mock.expects(:read).with('/etc/keystone/keystone.conf')
+ klass.expects(:openstack)
+ .with('domain', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name","Enabled","Description"
+"default","Default",True,"default domain"
+')
+ expect(klass.name_and_domain('foo')).to eq(['foo', 'Default'])
+ end
+ end
end
provider_class = Puppet::Type.type(:keystone_tenant).provider(:openstack)
+class Puppet::Provider::Keystone
+ def self.reset
+ @admin_endpoint = nil
+ @tenant_hash = nil
+ @admin_token = nil
+ @keystone_file = nil
+ @domain_id_to_name = nil
+ @default_domain_id = nil
+ @domain_hash = nil
+ end
+end
+
describe provider_class do
+ after :each do
+ provider_class.reset
+ end
+
+ let(:tenant_attrs) do
+ {
+ :name => 'foo',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ }
+ end
+
+ let(:resource) do
+ Puppet::Type::Keystone_tenant.new(tenant_attrs)
+ end
+
+ let(:provider) do
+ provider_class.new(resource)
+ end
+
+ def before_hook(domainlist)
+ if domainlist
+ provider.class.expects(:openstack).once
+ .with('domain', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name","Enabled","Description"
+"foo_domain_id","foo_domain",True,"foo domain"
+"bar_domain_id","bar_domain",True,"bar domain"
+"another_domain_id","another_domain",True,"another domain"
+"disabled_domain_id","disabled_domain",False,"disabled domain"
+"default","Default",True,"the default domain"
+')
+ end
+ end
+
+ before :each, :domainlist => true do
+ before_hook(true)
+ end
+
+ before :each, :domainlist => false do
+ before_hook(false)
+ end
+
shared_examples 'authenticated with environment variables' do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
ENV['OS_PROJECT_NAME'] = 'test'
- ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v2.0'
+ ENV['OS_AUTH_URL'] = 'http://127.0.0.1:35357/v3'
end
describe 'when managing a tenant' do
- let(:tenant_attrs) do
- {
- :name => 'foo',
- :description => 'foo',
- :ensure => 'present',
- :enabled => 'True',
- }
- end
-
- let(:resource) do
- Puppet::Type::Keystone_tenant.new(tenant_attrs)
- end
-
- let(:provider) do
- provider_class.new(resource)
- end
-
it_behaves_like 'authenticated with environment variables' do
- describe '#create' do
+ describe '#create', :domainlist => true do
it 'creates a tenant' do
- provider.class.stubs(:openstack)
- .with('project', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Description","Enabled"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo",True
-')
- provider.class.stubs(:openstack)
- .with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo'])
+ provider.class.expects(:openstack)
+ .with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo', '--domain', 'Default'])
.returns('description="foo"
enabled="True"
name="foo"
+id="foo"
+domain_id="foo_domain_id"
')
provider.create
expect(provider.exists?).to be_truthy
end
end
- describe '#destroy' do
+ describe '#destroy', :domainlist => false do
it 'destroys a tenant' do
- provider.class.stubs(:openstack)
- .with('project', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Description","Enabled"')
- provider.class.stubs(:openstack)
- .with('project', 'delete', [])
+ provider.instance_variable_get('@property_hash')[:id] = 'my-project-id'
+ provider.class.expects(:openstack)
+ .with('project', 'delete', 'my-project-id')
provider.destroy
expect(provider.exists?).to be_falsey
end
end
- context 'when tenant does not exist' do
+ context 'when tenant does not exist', :domainlist => false do
subject(:response) do
- provider.class.stubs(:openstack)
- .with('project', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Description","Enabled"')
response = provider.exists?
end
- it { is_expected.to be_falsey }
+ it { expect(response).to be_falsey }
end
- describe '#instances' do
+ describe '#instances', :domainlist => true do
it 'finds every tenant' do
- provider.class.stubs(:openstack)
+ provider.class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Description","Enabled"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo",True
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","foo","bar_domain_id","foo",True
')
- instances = Puppet::Type::Keystone_tenant::ProviderOpenstack.instances
- expect(instances.count).to eq(1)
+ instances = provider.class.instances
+ expect(instances[0].name).to eq('foo')
+ expect(instances[0].domain).to eq('bar_domain')
+ expect(instances[1].name).to eq('foo::foo_domain')
end
end
end
+
+ describe 'v3 domains with no domain in resource', :domainlist => true do
+
+ let(:tenant_attrs) do
+ {
+ :name => 'foo',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True'
+ }
+ end
+
+ it 'adds default domain to commands' do
+ mock = {
+ 'identity' => {'default_domain_id' => 'foo_domain_id'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(mock)
+ File.expects(:exists?).with('/etc/keystone/keystone.conf').returns(true)
+ mock.expects(:read).with('/etc/keystone/keystone.conf')
+ provider.class.expects(:openstack)
+ .with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo', '--domain', 'foo_domain'])
+ .returns('description="foo"
+enabled="True"
+name="foo"
+id="project-id"
+domain_id="foo_domain_id"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("project-id")
+ end
+
+ end
+
+ describe 'v3 domains with domain in resource', :domainlist => false do
+
+ let(:tenant_attrs) do
+ {
+ :name => 'foo',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ :domain => 'foo_domain'
+ }
+ end
+
+ it 'uses given domain in commands' do
+ provider.class.expects(:openstack)
+ .with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo', '--domain', 'foo_domain'])
+ .returns('description="foo"
+enabled="True"
+name="foo"
+id="project-id"
+domain_id="foo_domain_id"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("project-id")
+ end
+ end
+
+ describe 'v3 domains with domain in name/title', :domainlist => false do
+
+ let(:tenant_attrs) do
+ {
+ :name => 'foo::foo_domain',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True'
+ }
+ end
+
+ it 'uses given domain in commands' do
+ provider.class.expects(:openstack)
+ .with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo', '--domain', 'foo_domain'])
+ .returns('description="foo"
+enabled="True"
+name="foo"
+id="project-id"
+domain_id="foo_domain_id"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("project-id")
+ end
+ end
+
+ describe 'v3 domains with domain in name/title and in resource', :domainlist => false do
+
+ let(:tenant_attrs) do
+ {
+ :name => 'foo::bar_domain',
+ :description => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ :domain => 'foo_domain'
+ }
+ end
+
+ it 'uses given domain in commands' do
+ provider.class.expects(:openstack)
+ .with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo', '--domain', 'foo_domain'])
+ .returns('description="foo"
+enabled="True"
+name="foo"
+id="project-id"
+domain_id="foo_domain_id"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("project-id")
+ end
+ end
end
end
require 'puppet'
require 'spec_helper'
require 'puppet/provider/keystone_user/openstack'
+require 'puppet/provider/openstack'
provider_class = Puppet::Type.type(:keystone_user).provider(:openstack)
+def project_class
+ Puppet::Type.type(:keystone_tenant).provider(:openstack)
+end
+
describe provider_class do
shared_examples 'authenticated with environment variables' do
:password => 'foo',
:tenant => 'foo',
:email => 'foo@example.com',
+ :domain => 'foo_domain',
}
end
provider_class.new(resource)
end
+ def before_hook(delete, missing, noproject, user_cached)
+ provider.class.expects(:openstack).once
+ .with('domain', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name","Enabled","Description"
+"foo_domain_id","foo_domain",True,"foo domain"
+"bar_domain_id","bar_domain",True,"bar domain"
+"another_domain_id","another_domain",True,"another domain"
+"disabled_domain_id","disabled_domain",False,"disabled domain"
+')
+ if user_cached
+ return # using cached user, so no user list
+ end
+ if noproject
+ project = ''
+ else
+ project = 'foo'
+ end
+ # delete will call the search again and should not return the deleted user
+ foo_returns = ['"ID","Name","Project Id","Domain","Description","Email","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo",' + project + ',"foo_domain_id","foo description","foo@example.com",True
+"2cb05cfed7c24279be884ba4f6520262","foo",' + project + ',"bar_domain_id","foo description","foo@example.com",True
+"3cb05cfed7c24279be884ba4f6520262","foo",' + project + ',"another_domain_id","foo description","foo@example.com",True
+']
+ nn = 1
+ if delete
+ nn = 2
+ foo_returns << ''
+ end
+ if missing
+ foo_returns = ['']
+ end
+ provider.class.expects(:openstack).times(nn)
+ .with('user', 'list', '--quiet', '--format', 'csv', ['--long'])
+ .returns(*foo_returns)
+ end
+
+ before :each, :default => true do
+ before_hook(false, false, false, false)
+ end
+ before :each, :delete => true do
+ before_hook(true, false, false, false)
+ end
+ before :each, :missing => true do
+ before_hook(false, true, false, false)
+ end
+ before :each, :noproject => true do
+ before_hook(false, false, true, false)
+ end
+ before :each, :default_https => true do
+ before_hook(false, false, false, false)
+ end
+ before :each, :user_cached => true do
+ before_hook(false, false, false, true)
+ end
+ before :each, :nohooks => true do
+ # do nothing
+ end
+
describe 'when managing a user' do
it_behaves_like 'authenticated with environment variables' do
describe '#create' do
it 'creates a user' do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Project","Email","Enabled"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo","foo@example.com",True
+ project_class.expects(:openstack).once
+ .with('domain', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name","Enabled","Description"
+"foo_domain_id","foo_domain",True,"foo domain"
+"bar_domain_id","bar_domain",True,"bar domain"
+"another_domain_id","another_domain",True,"another domain"
+"disabled_domain_id","disabled_domain",False,"disabled domain"
+')
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","foo","bar_domain_id","foo",True
+')
+ provider.class.expects(:openstack)
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('
+name="_member_"
')
- provider.class.stubs(:openstack)
- .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--project', 'foo', '--email', 'foo@example.com'])
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '12b23f07d4a3448d8189521ab09610b0'])
+ provider.class.expects(:openstack)
+ .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
.returns('email="foo@example.com"
enabled="True"
id="12b23f07d4a3448d8189521ab09610b0"
name="foo"
-project_id="5e2001b2248540f191ff22627dc0c2d7"
username="foo"
')
provider.create
describe '#destroy' do
it 'destroys a user' do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Project","Email","Enabled"')
- provider.class.stubs(:openstack)
- .with('user', 'delete', [])
+ provider.instance_variable_get('@property_hash')[:id] = 'my-user-id'
+ provider.class.expects(:openstack)
+ .with('user', 'delete', 'my-user-id')
provider.destroy
expect(provider.exists?).to be_falsey
end
describe '#exists' do
context 'when user does not exist' do
subject(:response) do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Project","Email","Enabled"')
response = provider.exists?
end
end
end
- describe '#instances' do
+ describe '#instances', :default => true do
it 'finds every user' do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Project","Email","Enabled"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo","foo@example.com",True
-')
- instances = Puppet::Type::Keystone_user::ProviderOpenstack.instances
- expect(instances.count).to eq(1)
+ instances = provider.class.instances
+ expect(instances.count).to eq(3)
+ expect(instances[0].name).to eq('foo')
+ expect(instances[0].domain).to eq('another_domain')
+ expect(instances[1].name).to eq('foo::foo_domain')
+ expect(instances[2].name).to eq('foo::bar_domain')
end
end
describe '#tenant' do
- it 'gets the tenant with default backend' do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Project","Email","Enabled"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo","foo@example.com",True
+ it 'gets the tenant with default backend', :nohooks => true do
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
')
- provider.class.stubs(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'foo'])
- .returns('"ID","Name","Project","User"
-"9fe2ff9ee4384b1894a90878d3e92bab","_member_","foo","foo"
+ provider.class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"foo_project_id1","foo","foo_domain_id","",True
')
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
tenant = provider.tenant
expect(tenant).to eq('foo')
end
- it 'gets the tenant with LDAP backend' do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', '--long')
- .returns('"ID","Name","Project","Email","Enabled"
-"1cb05cfed7c24279be884ba4f6520262","foo","","foo@example.com",True
+ it 'gets the tenant with LDAP backend', :nohooks => true do
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
')
provider.class.expects(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'foo'])
- .returns('"ID","Name","Project","User"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo","foo"
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"foo_project_id1","foo","foo_domain_id","",True
+"bar_project_id2","bar","bar_domain_id","",True
+"foo_project_id2","foo","another_domain_id","",True
')
tenant = provider.tenant
expect(tenant).to eq('foo')
end
end
-
describe '#tenant=' do
- context 'when using default backend' do
+ context 'when using default backend', :nohooks => true do
it 'sets the tenant' do
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
+')
provider.class.expects(:openstack)
- .with('user', 'set', ['foo', '--project', 'bar'])
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('name="_member_"')
provider.class.expects(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'bar'])
- .returns('"ID","Name","Project","User"
-"9fe2ff9ee4384b1894a90878d3e92bab","_member_","bar","foo"
-')
+ .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
provider.tenant=('bar')
end
end
-
- context 'when using LDAP read-write backend' do
+ context 'when using LDAP read-write backend', :nohooks => true do
it 'sets the tenant when _member_ role exists' do
- provider.class.expects(:openstack)
- .with('user', 'set', ['foo', '--project', 'bar'])
- provider.class.expects(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'bar'])
- .returns('')
- provider.class.expects(:openstack)
- .with('role', 'show', '--format', 'shell', ['_member_'])
- .returns('id="9fe2ff9ee4384b1894a90878d3e92bab"
-name="_member_"
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
')
provider.class.expects(:openstack)
- .with('role', 'add', ['_member_', '--project', 'bar', '--user', 'foo'])
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('name="_member_"')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
provider.tenant=('bar')
end
it 'sets the tenant when _member_ role does not exist' do
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
+')
provider.class.expects(:openstack)
- .with('user', 'set', ['foo', '--project', 'bar'])
- provider.class.expects(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'bar'])
- .returns('')
- provider.class.expects(:openstack)
- .with('role', 'show', '--format', 'shell', ['_member_'])
+ .with('role', 'show', '--format', 'shell', '_member_')
.raises(Puppet::ExecutionFailure, 'no such role _member_')
provider.class.expects(:openstack)
- .with('role', 'create', '--format', 'shell', ['_member_'])
+ .with('role', 'create', '--format', 'shell', '_member_')
.returns('name="_member_"')
provider.class.expects(:openstack)
- .with('role', 'add', ['_member_', '--project', 'bar', '--user', 'foo'])
- .returns('id="8wr2ff9ee4384b1894a90878d3e92bab"
-name="_member_"
-')
+ .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
provider.tenant=('bar')
end
end
-
-# This doesn't make sense, need to clarify what's happening with LDAP mock
-=begin
- context 'when using LDAP read-only backend' do
+ context 'when using LDAP read-only backend', :nohooks => true do
it 'sets the tenant when _member_ role exists' do
- provider.class.expects(:openstack)
- .with('user', 'set', [['foo', '--project', 'bar']])
- .raises(Puppet::ExecutionFailure, 'You are not authorized to perform the requested action: LDAP user update')
- provider.class.expects(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', [['foo', '--project', 'bar']])
- .returns('')
- provider.class.expects(:openstack)
- .with('role', 'show', '--format', 'shell', [['_member_']])
- .returns('id="9fe2ff9ee4384b1894a90878d3e92bab"
-name="_member_"
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
')
provider.class.expects(:openstack)
- .with('role', 'add', [['_member_', '--project', 'bar', '--user', 'foo']])
- provider.tenant=('bar')
- end
-
- it 'sets the tenant and gets an unexpected exception message' do
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('name="_member_"')
provider.class.expects(:openstack)
- .with('user', 'set', [['foo', '--project', 'bar']])
- .raises(Puppet::ExecutionFailure, 'unknown error message')
- expect{ provider.tenant=('bar') }.to raise_error(Puppet::ExecutionFailure, /unknown error message/)
+ .with('role', 'add', ['_member_', '--project', '2cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
+ provider.tenant=('bar')
end
end
-=end
end
end
end
- describe "#password" do
+ describe "#password", :nohooks => true do
let(:user_attrs) do
{
:name => 'foo',
:password => 'foo',
:tenant => 'foo',
:email => 'foo@example.com',
+ :domain => 'foo_domain',
}
end
end
shared_examples 'with auth-url environment variable' do
- ENV['OS_AUTH_URL'] = 'http://localhost:5000'
+ ENV['OS_AUTH_URL'] = 'http://127.0.0.1:5000'
end
it_behaves_like 'with auth-url environment variable' do
it 'checks the password' do
- Puppet::Provider::Openstack.stubs(:openstack)
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ mockcreds = {}
+ Puppet::Provider::Openstack::CredentialsV3.expects(:new).returns(mockcreds)
+ mockcreds.expects(:auth_url=).with('http://127.0.0.1:5000')
+ mockcreds.expects(:password=).with('foo')
+ mockcreds.expects(:username=).with('foo')
+ mockcreds.expects(:user_id=).with('1cb05cfed7c24279be884ba4f6520262')
+ mockcreds.expects(:project_id=).with('project-id-1')
+ mockcreds.expects(:to_env).returns(mockcreds)
+ Puppet::Provider::Openstack.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"project-id-1","foo","foo_domain_id","foo",True
+')
+ Puppet::Provider::Openstack.expects(:openstack)
.with('token', 'issue', ['--format', 'value'])
.returns('2015-05-14T04:06:05Z
e664a386befa4a30878dcef20e79f167
end
it 'fails the password check' do
- Puppet::Provider::Openstack.stubs(:openstack)
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ Puppet::Provider::Openstack.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"project-id-1","foo","foo_domain_id","foo",True
+')
+ Puppet::Provider::Openstack.expects(:openstack)
.with('token', 'issue', ['--format', 'value'])
.raises(Puppet::ExecutionFailure, 'HTTP 401 invalid authentication')
password = provider.password
expect(password).to eq(nil)
end
+
+ it 'checks the password with domain scoped token' do
+ provider.instance_variable_get('@property_hash')[:id] = '1cb05cfed7c24279be884ba4f6520262'
+ provider.instance_variable_get('@property_hash')[:domain] = 'foo_domain'
+ mockcreds = {}
+ Puppet::Provider::Openstack::CredentialsV3.expects(:new).returns(mockcreds)
+ mockcreds.expects(:auth_url=).with('http://127.0.0.1:5000')
+ mockcreds.expects(:password=).with('foo')
+ mockcreds.expects(:username=).with('foo')
+ mockcreds.expects(:user_id=).with('1cb05cfed7c24279be884ba4f6520262')
+ mockcreds.expects(:domain_name=).with('foo_domain')
+ mockcreds.expects(:to_env).returns(mockcreds)
+ Puppet::Provider::Openstack.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+')
+ Puppet::Provider::Openstack.expects(:openstack)
+ .with('token', 'issue', ['--format', 'value'])
+ .returns('2015-05-14T04:06:05Z
+e664a386befa4a30878dcef20e79f167
+8dce2ae9ecd34c199d2877bf319a3d06
+ac43ec53d5a74a0b9f51523ae41a29f0
+')
+ password = provider.password
+ expect(password).to eq('foo')
+ end
end
+ end
+
+ describe 'when updating a user with unmanaged password', :nohooks => true do
describe 'when updating a user with unmanaged password' do
:replace_password => 'False',
:tenant => 'foo',
:email => 'foo@example.com',
+ :domain => 'foo_domain',
}
end
+ let(:resource) do
+ Puppet::Type::Keystone_user.new(user_attrs)
+ end
+
+ let :provider do
+ provider_class.new(resource)
+ end
+
it 'should not try to check password' do
expect(provider.password).to eq('foo')
end
end
+ end
+ it_behaves_like 'authenticated with environment variables' do
+ describe 'v3 domains with no domain in resource', :nohooks => true do
+ let(:user_attrs) do
+ {
+ :name => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ :password => 'foo',
+ :tenant => 'foo',
+ :email => 'foo@example.com',
+ }
+ end
+
+ it 'adds default domain to commands' do
+ provider_class.class_exec {
+ @default_domain_id = nil
+ }
+ mock = {
+ 'identity' => {'default_domain_id' => 'foo_domain_id'}
+ }
+ Puppet::Util::IniConfig::File.expects(:new).returns(mock)
+ File.expects(:exists?).with('/etc/keystone/keystone.conf').returns(true)
+ mock.expects(:read).with('/etc/keystone/keystone.conf')
+ provider.class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--user', '1cb05cfed7c24279be884ba4f6520262', '--long'])
+ .returns('"ID","Name"
+')
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
+')
+ provider.class.expects(:openstack)
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('
+name="_member_"
+')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '1cb05cfed7c24279be884ba4f6520262'])
+ provider.class.expects(:openstack)
+ .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
+ .returns('email="foo@example.com"
+enabled="True"
+id="1cb05cfed7c24279be884ba4f6520262"
+name="foo"
+username="foo"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("1cb05cfed7c24279be884ba4f6520262")
+ end
+ end
+
+ describe 'v3 domains with domain in resource' do
+ let(:user_attrs) do
+ {
+ :name => 'foo',
+ :ensure => 'present',
+ :enabled => 'True',
+ :password => 'foo',
+ :tenant => 'foo',
+ :email => 'foo@example.com',
+ :domain => 'bar_domain',
+ }
+ end
+
+ it 'uses given domain in commands' do
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
+')
+ provider.class.expects(:openstack)
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('
+name="_member_"
+')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
+ provider.class.expects(:openstack)
+ .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'bar_domain'])
+ .returns('email="foo@example.com"
+enabled="True"
+id="2cb05cfed7c24279be884ba4f6520262"
+name="foo"
+username="foo"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
+ end
+ end
+
+ describe 'v3 domains with domain in name/title' do
+ let(:user_attrs) do
+ {
+ :name => 'foo::bar_domain',
+ :ensure => 'present',
+ :enabled => 'True',
+ :password => 'foo',
+ :tenant => 'foo',
+ :email => 'foo@example.com',
+ }
+ end
+
+ it 'uses given domain in commands' do
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
+')
+ provider.class.expects(:openstack)
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('
+name="_member_"
+')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
+ provider.class.expects(:openstack)
+ .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'bar_domain'])
+ .returns('email="foo@example.com"
+enabled="True"
+id="2cb05cfed7c24279be884ba4f6520262"
+name="foo"
+username="foo"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
+ end
+ end
+
+ describe 'v3 domains with domain in name/title and in resource' do
+ let(:user_attrs) do
+ {
+ :name => 'foo::bar_domain',
+ :ensure => 'present',
+ :enabled => 'True',
+ :password => 'foo',
+ :tenant => 'foo',
+ :email => 'foo@example.com',
+ :domain => 'foo_domain',
+ }
+ end
+
+ it 'uses the resource domain in commands' do
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","bar","bar_domain_id","bar",True
+')
+ provider.class.expects(:openstack)
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('
+name="_member_"
+')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
+ provider.class.expects(:openstack)
+ .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
+ .returns('email="foo@example.com"
+enabled="True"
+id="2cb05cfed7c24279be884ba4f6520262"
+name="foo"
+username="foo"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
+ end
+ end
+
+ describe 'v3 domains with domain in name/title and in resource and in tenant' do
+ let(:user_attrs) do
+ {
+ :name => 'foo::bar_domain',
+ :ensure => 'present',
+ :enabled => 'True',
+ :password => 'foo',
+ :tenant => 'foo::foo_domain',
+ :email => 'foo@example.com',
+ :domain => 'foo_domain',
+ }
+ end
+
+ it 'uses the resource domain in commands' do
+ project_class.expects(:openstack)
+ .with('project', 'list', '--quiet', '--format', 'csv', '--long')
+ .returns('"ID","Name","Domain ID","Description","Enabled"
+"1cb05cfed7c24279be884ba4f6520262","foo","foo_domain_id","foo",True
+"2cb05cfed7c24279be884ba4f6520262","foo","bar_domain_id","foo",True
+')
+ provider.class.expects(:openstack)
+ .with('role', 'show', '--format', 'shell', '_member_')
+ .returns('
+name="_member_"
+')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['_member_', '--project', '1cb05cfed7c24279be884ba4f6520262', '--user', '2cb05cfed7c24279be884ba4f6520262'])
+ provider.class.expects(:openstack)
+ .with('user', 'create', '--format', 'shell', ['foo', '--enable', '--password', 'foo', '--email', 'foo@example.com', '--domain', 'foo_domain'])
+ .returns('email="foo@example.com"
+enabled="True"
+id="2cb05cfed7c24279be884ba4f6520262"
+name="foo"
+username="foo"
+')
+ provider.create
+ expect(provider.exists?).to be_truthy
+ expect(provider.id).to eq("2cb05cfed7c24279be884ba4f6520262")
+ end
+ end
end
end
require 'puppet/provider/keystone_user_role/openstack'
provider_class = Puppet::Type.type(:keystone_user_role).provider(:openstack)
+def user_class
+ Puppet::Type.type(:keystone_user).provider(:openstack)
+end
+def project_class
+ Puppet::Type.type(:keystone_tenant).provider(:openstack)
+end
describe provider_class do
+ # assumes Enabled is the last column - no quotes
+ def list_to_csv(thelist)
+ if thelist.is_a?(String)
+ return ''
+ end
+ str=""
+ thelist.each do |rec|
+ if rec.is_a?(String)
+ return ''
+ end
+ rec.each do |xx|
+ if xx.equal?(rec.last)
+ # True/False have no quotes
+ if xx == 'True' or xx == 'False'
+ str = str + xx + "\n"
+ else
+ str = str + '"' + xx + '"' + "\n"
+ end
+ else
+ str = str + '"' + xx + '",'
+ end
+ end
+ end
+ str
+ end
+
+ def before_need_instances
+ provider.class.expects(:openstack).once
+ .with('domain', 'list', '--quiet', '--format', 'csv')
+ .returns('"ID","Name","Enabled","Description"
+"foo_domain_id","foo_domain",True,"foo domain"
+"bar_domain_id","bar_domain",True,"bar domain"
+"another_domain_id","another_domain",True,"another domain"
+"disabled_domain_id","disabled_domain",False,"disabled domain"
+')
+ project_list = [['project-id-1','foo','foo_domain_id','foo project in foo domain','True'],
+ ['project-id-2','foo','bar_domain_id','foo project in bar domain','True'],
+ ['project-id-3','bar','foo_domain_id','bar project in foo domain','True'],
+ ['project-id-4','etc','another_domain_id','another project','True']]
+
+ user_list_for_project = {
+ 'project-id-1' => [['user-id-1','foo@example.com','foo','foo_domain','foo user','foo@foo_domain','True'],
+ ['user-id-2','bar@example.com','foo','foo_domain','bar user','bar@foo_domain','True']],
+ 'project-id-2' => [['user-id-3','foo@bar.com','foo','bar_domain','foo user','foo@bar_domain','True'],
+ ['user-id-4','bar@bar.com','foo','bar_domain','bar user','bar@bar_domain','True']]
+ }
+ user_list_for_project.default = ''
+
+ user_list_for_domain = {
+ 'foo_domain_id' => [['user-id-1','foo@example.com','foo','foo_domain','foo user','foo@foo_domain','True'],
+ ['user-id-2','bar@example.com','foo','foo_domain','bar user','bar@foo_domain','True']],
+ 'bar_domain_id' => [['user-id-3','foo@bar.com','foo','bar_domain','foo user','foo@bar_domain','True'],
+ ['user-id-4','bar@bar.com','foo','bar_domain','bar user','bar@bar_domain','True']]
+ }
+ user_list_for_domain.default = ''
+
+ role_list_for_project_user = {
+ 'project-id-1' => {
+ 'user-id-1' => [['role-id-1','foo','foo','foo'],
+ ['role-id-2','bar','foo','foo']]
+ },
+ 'project-id-2' => {
+ 'user-id-3' => [['role-id-1','foo','foo','foo'],
+ ['role-id-2','bar','foo','foo']]
+ }
+ }
+ role_list_for_project_user.default = ''
+
+ role_list_for_domain_user = {
+ 'foo_domain_id' => {
+ 'user-id-2' => [['role-id-1','foo','foo_domain','foo'],
+ ['role-id-2','bar','foo_domain','foo']]
+ },
+ 'bar_domain_id' => {
+ 'user-id-4' => [['role-id-1','foo','bar_domain','foo'],
+ ['role-id-2','bar','bar_domain','foo']]
+ }
+ }
+ role_list_for_project_user.default = ''
+
+ provider.class.expects(:openstack).once
+ .with('project', 'list', '--quiet', '--format', 'csv', ['--long'])
+ .returns('"ID","Name","Domain ID","Description","Enabled"' + "\n" + list_to_csv(project_list))
+ project_list.each do |rec|
+ csvlist = list_to_csv(user_list_for_project[rec[0]])
+ provider.class.expects(:openstack)
+ .with('user', 'list', '--quiet', '--format', 'csv', ['--long', '--project', rec[0]])
+ .returns('"ID","Name","Project","Domain","Description","Email","Enabled"' + "\n" + csvlist)
+ next if csvlist == ''
+ user_list_for_project[rec[0]].each do |urec|
+ csvlist = ''
+ if role_list_for_project_user.has_key?(rec[0]) and
+ role_list_for_project_user[rec[0]].has_key?(urec[0])
+ csvlist = list_to_csv(role_list_for_project_user[rec[0]][urec[0]])
+ end
+ provider.class.expects(:openstack)
+ .with('role', 'list', '--quiet', '--format', 'csv', ['--project', rec[0], '--user', urec[0]])
+ .returns('"ID","Name","Project","User"' + "\n" + csvlist)
+ end
+ end
+ ['foo_domain_id', 'bar_domain_id'].each do |domid|
+ csvlist = list_to_csv(user_list_for_domain[domid])
+ provider.class.expects(:openstack)
+ .with('user', 'list', '--quiet', '--format', 'csv', ['--long', '--domain', domid])
+ .returns('"ID","Name","Project","Domain","Description","Email","Enabled"' + "\n" + csvlist)
+ next if csvlist == ''
+ user_list_for_domain[domid].each do |urec|
+ csvlist = ''
+ if role_list_for_domain_user.has_key?(domid) and
+ role_list_for_domain_user[domid].has_key?(urec[0])
+ csvlist = list_to_csv(role_list_for_domain_user[domid][urec[0]])
+ end
+ provider.class.expects(:openstack)
+ .with('role', 'list', '--quiet', '--format', 'csv', ['--domain', domid, '--user', urec[0]])
+ .returns('"ID","Name","Domain","User"' + "\n" + csvlist)
+ end
+ end
+ end
+
+ def before_common(destroy, nolist=false, instances=false)
+ rolelistprojectuser = [['role-id-1','foo','foo','foo'],
+ ['role-id-2','bar','foo','foo']]
+ csvlist = list_to_csv(rolelistprojectuser)
+ rolelistreturns = ['"ID","Name","Project","User"' + "\n" + csvlist]
+ nn = 1
+ if destroy
+ rolelistreturns = ['']
+ nn = 1
+ end
+ unless nolist
+ provider.class.expects(:openstack).times(nn)
+ .with('role', 'list', '--quiet', '--format', 'csv', ['--project', 'project-id-1', '--user', 'user-id-1'])
+ .returns(*rolelistreturns)
+ end
+
+ userhash = {:id => 'user-id-1', :name => 'foo@example.com'}
+ usermock = user_class.new(userhash)
+ unless instances
+ usermock.expects(:exists?).with(any_parameters).returns(true)
+ user_class.expects(:new).twice.with(any_parameters).returns(usermock)
+ end
+ user_class.expects(:instances).with(any_parameters).returns([usermock])
+
+ projecthash = {:id => 'project-id-1', :name => 'foo'}
+ projectmock = project_class.new(projecthash)
+ unless instances
+ projectmock.expects(:exists?).with(any_parameters).returns(true)
+ project_class.expects(:new).with(any_parameters).returns(projectmock)
+ end
+ project_class.expects(:instances).with(any_parameters).returns([projectmock])
+ end
+
+ before :each, :default => true do
+ before_common(false)
+ end
+
+ before :each, :destroy => true do
+ before_common(true)
+ end
+
+ before :each, :nolist => true do
+ before_common(true, true)
+ end
+
+ before :each, :instances => true do
+ before_common(true, true, true)
+ end
+
shared_examples 'authenticated with environment variables' do
ENV['OS_USERNAME'] = 'test'
ENV['OS_PASSWORD'] = 'abc123'
provider_class.new(resource)
end
- before(:each) do
- provider.class.stubs(:openstack)
- .with('user', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'foo'])
- .returns('"ID","Name","Project","User"
-"1cb05cfed7c24279be884ba4f6520262","foo","foo","foo"
-')
- end
-
- describe '#create' do
+ describe '#create', :default => true do
it 'adds all the roles to the user' do
- provider.class.stubs(:openstack)
- .with('role', 'add', ['foo', '--project', 'foo', '--user', 'foo'])
- provider.class.stubs(:openstack)
- .with('role', 'add', ['bar', '--project', 'foo', '--user', 'foo'])
- provider.class.stubs(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'foo'])
- .returns('"ID","Name","Project","User"
-"1cb05ed7c24279be884ba4f6520262","foo","foo","foo"
-"2cb05ed7c24279be884ba4f6520262","bar","foo","foo"
-')
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['foo', '--project', 'project-id-1', '--user', 'user-id-1'])
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['bar', '--project', 'project-id-1', '--user', 'user-id-1'])
provider.create
expect(provider.exists?).to be_truthy
end
end
- describe '#destroy' do
+ describe '#destroy', :destroy => true do
it 'removes all the roles from a user' do
- provider.class.stubs(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'foo'])
- .returns('"ID","Name","Project","User"')
- provider.class.stubs(:openstack)
- .with('role', 'remove', ['foo', '--project', 'foo', '--user', 'foo'])
- provider.class.stubs(:openstack)
- .with('role', 'remove', ['bar', '--project', 'foo', '--user', 'foo'])
+ provider.instance_variable_get('@property_hash')[:roles] = ['foo', 'bar']
+ provider.class.expects(:openstack)
+ .with('role', 'remove', ['foo', '--project', 'project-id-1', '--user', 'user-id-1'])
+ provider.class.expects(:openstack)
+ .with('role', 'remove', ['bar', '--project', 'project-id-1', '--user', 'user-id-1'])
provider.destroy
expect(provider.exists?).to be_falsey
end
end
- describe '#exists' do
+ describe '#exists', :default => true do
subject(:response) do
- provider.class.stubs(:openstack)
- .with('user role', 'list', '--quiet', '--format', 'csv', ['foo', '--project', 'foo'])
- .returns('"ID","Name","Project","User"
-"1cb05ed7c24279be884ba4f6520262","foo","foo","foo"
-')
response = provider.exists?
end
it { is_expected.to be_truthy }
end
+
+ describe '#instances', :instances => true do
+ it 'finds every user role' do
+ provider.class.expects(:openstack)
+ .with('role', 'list', '--quiet', '--format', 'csv', [])
+ .returns('"ID","Name"
+"foo-role-id","foo"
+"bar-role-id","bar"
+')
+ provider.class.expects(:openstack)
+ .with('role assignment', 'list', '--quiet', '--format', 'csv', [])
+ .returns('
+"Role","User","Group","Project","Domain"
+"foo-role-id","user-id-1","","project-id-1",""
+"bar-role-id","user-id-1","","project-id-1",""
+')
+ instances = provider.class.instances
+ expect(instances.count).to eq(1)
+ expect(instances[0].name).to eq('foo@example.com@foo')
+ expect(instances[0].roles).to eq(['foo', 'bar'])
+ end
+ end
+
+ describe '#roles=', :nolist => true do
+ let(:user_role_attrs) do
+ {
+ :name => 'foo@foo',
+ :ensure => 'present',
+ :roles => ['one', 'two'],
+ }
+ end
+
+ it 'applies the new roles' do
+ provider.instance_variable_get('@property_hash')[:roles] = ['foo', 'bar']
+ provider.class.expects(:openstack)
+ .with('role', 'remove', ['foo', '--project', 'project-id-1', '--user', 'user-id-1'])
+ provider.class.expects(:openstack)
+ .with('role', 'remove', ['bar', '--project', 'project-id-1', '--user', 'user-id-1'])
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['one', '--project', 'project-id-1', '--user', 'user-id-1'])
+ provider.class.expects(:openstack)
+ .with('role', 'add', ['two', '--project', 'project-id-1', '--user', 'user-id-1'])
+ provider.roles=(['one', 'two'])
+ end
+ end
end
end
end
--- /dev/null
+require 'spec_helper'
+require 'puppet'
+require 'puppet/type/keystone_tenant'
+
+describe Puppet::Type.type(:keystone_tenant) do
+
+ before :each do
+ @project = Puppet::Type.type(:keystone_tenant).new(
+ :name => 'foo',
+ :domain => 'foo-domain',
+ )
+
+ @domain = @project.parameter('domain')
+ end
+
+ it 'should not be in sync for domain changes' do
+ expect { @domain.insync?('not-the-domain') }.to raise_error(Puppet::Error, /The domain cannot be changed from/)
+ expect { @domain.insync?(nil) }.to raise_error(Puppet::Error, /The domain cannot be changed from/)
+ end
+
+ it 'should be in sync if domain is the same' do
+ expect(@domain.insync?('foo-domain')).to be true
+ end
+
+end
--- /dev/null
+require 'spec_helper'
+require 'puppet'
+require 'puppet/type/keystone_user'
+
+describe Puppet::Type.type(:keystone_user) do
+
+ before :each do
+ @project = Puppet::Type.type(:keystone_user).new(
+ :name => 'foo',
+ :domain => 'foo-domain',
+ )
+
+ @domain = @project.parameter('domain')
+ end
+
+ it 'should not be in sync for domain changes' do
+ expect { @domain.insync?('not-the-domain') }.to raise_error(Puppet::Error, /The domain cannot be changed from/)
+ expect { @domain.insync?(nil) }.to raise_error(Puppet::Error, /The domain cannot be changed from/)
+ end
+
+ it 'should be in sync if domain is the same' do
+ expect(@domain.insync?('foo-domain')).to be true
+ end
+
+end