From: Martin Zobel-Helas Date: Wed, 19 Aug 2015 13:25:51 +0000 (+0000) Subject: Update to Kilo X-Git-Url: https://git.donarmstrong.com/?p=dsa-puppet.git;a=commitdiff_plain;h=2dc39f2a756f82040d82cba324b21f44fad8ef3f Update to Kilo Signed-off-by: Martin Zobel-Helas --- diff --git a/3rdparty/Puppetfile b/3rdparty/Puppetfile index 29f9da5e..722a92e6 100644 --- a/3rdparty/Puppetfile +++ b/3rdparty/Puppetfile @@ -12,7 +12,7 @@ mod 'elasticsearch/elasticsearch', '0.9.5' 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' diff --git a/3rdparty/modules/keystone/CHANGELOG.md b/3rdparty/modules/keystone/CHANGELOG.md index 973da8fb..33c0f5e0 100644 --- a/3rdparty/modules/keystone/CHANGELOG.md +++ b/3rdparty/modules/keystone/CHANGELOG.md @@ -1,3 +1,43 @@ +##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 diff --git a/3rdparty/modules/keystone/README.md b/3rdparty/modules/keystone/README.md index df602374..e35380cc 100644 --- a/3rdparty/modules/keystone/README.md +++ b/3rdparty/modules/keystone/README.md @@ -1,7 +1,7 @@ keystone ======= -5.1.0 - 2014.2 - Juno +6.0.0 - 2015.1 - Kilo #### Table of Contents diff --git a/3rdparty/modules/keystone/checksums.json b/3rdparty/modules/keystone/checksums.json new file mode 100644 index 00000000..a39bc591 --- /dev/null +++ b/3rdparty/modules/keystone/checksums.json @@ -0,0 +1,94 @@ +{ + "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 diff --git a/3rdparty/modules/keystone/examples/v3_basic.pp b/3rdparty/modules/keystone/examples/v3_basic.pp new file mode 100644 index 00000000..5777d2b5 --- /dev/null +++ b/3rdparty/modules/keystone/examples/v3_basic.pp @@ -0,0 +1,47 @@ +# 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/', +} diff --git a/3rdparty/modules/keystone/ext/keystone_test_v3.rb b/3rdparty/modules/keystone/ext/keystone_test_v3.rb new file mode 100644 index 00000000..0d7550e3 --- /dev/null +++ b/3rdparty/modules/keystone/ext/keystone_test_v3.rb @@ -0,0 +1,64 @@ +#!/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 diff --git a/3rdparty/modules/keystone/files/httpd/keystone.py b/3rdparty/modules/keystone/files/httpd/keystone.py index 81c5348a..a893d268 100644 --- a/3rdparty/modules/keystone/files/httpd/keystone.py +++ b/3rdparty/modules/keystone/files/httpd/keystone.py @@ -13,7 +13,8 @@ # 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. # diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone.rb index 4e7815ab..849868bc 100644 --- a/3rdparty/modules/keystone/lib/puppet/provider/keystone.rb +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone.rb @@ -2,6 +2,7 @@ require 'puppet/util/inifile' 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 @@ -30,6 +31,31 @@ 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 @@ -80,8 +106,8 @@ class Puppet::Provider::Keystone < Puppet::Provider::Openstack 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) @@ -96,6 +122,31 @@ class Puppet::Provider::Keystone < Puppet::Provider::Openstack 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) diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone/util.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone/util.rb new file mode 100644 index 00000000..7eadf98b --- /dev/null +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone/util.rb @@ -0,0 +1,25 @@ +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 diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone_domain/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone_domain/openstack.rb new file mode 100644 index 00000000..14a8f698 --- /dev/null +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone_domain/openstack.rb @@ -0,0 +1,143 @@ +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 diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone_role/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone_role/openstack.rb index b1546204..799bce0c 100644 --- a/3rdparty/modules/keystone/lib/puppet/provider/keystone_role/openstack.rb +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone_role/openstack.rb @@ -7,7 +7,7 @@ Puppet::Type.type(:keystone_role).provide( desc 'Provider for keystone roles.' - @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new + @credentials = Puppet::Provider::Openstack::CredentialsV3.new def initialize(value={}) super(value) diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone_service/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone_service/openstack.rb index 40aa8e2f..4ac76469 100644 --- a/3rdparty/modules/keystone/lib/puppet/provider/keystone_service/openstack.rb +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone_service/openstack.rb @@ -7,7 +7,7 @@ Puppet::Type.type(:keystone_service).provide( desc "Provider to manage keystone services." - @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new + @credentials = Puppet::Provider::Openstack::CredentialsV3.new def initialize(value={}) super(value) @@ -15,21 +15,17 @@ Puppet::Type.type(:keystone_service).provide( 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 @@ -37,22 +33,26 @@ Puppet::Type.type(:keystone_service).provide( @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 @@ -80,9 +80,11 @@ Puppet::Type.type(:keystone_service).provide( 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 diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone_tenant/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone_tenant/openstack.rb index 19879847..57a299fd 100644 --- a/3rdparty/modules/keystone/lib/puppet/provider/keystone_tenant/openstack.rb +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone_tenant/openstack.rb @@ -7,7 +7,7 @@ Puppet::Type.type(:keystone_tenant).provide( 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) @@ -15,7 +15,9 @@ Puppet::Type.type(:keystone_tenant).provide( 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 @@ -25,8 +27,12 @@ Puppet::Type.type(:keystone_tenant).provide( 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? @@ -34,7 +40,7 @@ Puppet::Type.type(:keystone_tenant).provide( end def destroy - self.class.request('project', 'delete', @property_hash[:id]) + self.class.request('project', 'delete', id) @property_hash.clear end @@ -54,29 +60,67 @@ Puppet::Type.type(:keystone_tenant).provide( @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 @@ -90,8 +134,7 @@ Puppet::Type.type(:keystone_tenant).provide( 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 diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone_user/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone_user/openstack.rb index 98a34cd3..eb1e303f 100644 --- a/3rdparty/modules/keystone/lib/puppet/provider/keystone_user/openstack.rb +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone_user/openstack.rb @@ -7,7 +7,7 @@ Puppet::Type.type(:keystone_user).provide( desc "Provider to manage keystone users." - @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new + @credentials = Puppet::Provider::Openstack::CredentialsV3.new def initialize(value={}) super(value) @@ -15,7 +15,9 @@ Puppet::Type.type(:keystone_user).provide( 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 @@ -24,18 +26,26 @@ Puppet::Type.type(:keystone_user).provide( 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 @@ -89,11 +99,23 @@ Puppet::Type.type(:keystone_user).provide( 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 @@ -117,6 +139,51 @@ Puppet::Type.type(:keystone_user).provide( @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 @@ -130,40 +197,52 @@ Puppet::Type.type(:keystone_user).provide( 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 @@ -171,34 +250,19 @@ Puppet::Type.type(:keystone_user).provide( 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 diff --git a/3rdparty/modules/keystone/lib/puppet/provider/keystone_user_role/openstack.rb b/3rdparty/modules/keystone/lib/puppet/provider/keystone_user_role/openstack.rb index da2b8704..e670a675 100644 --- a/3rdparty/modules/keystone/lib/puppet/provider/keystone_user_role/openstack.rb +++ b/3rdparty/modules/keystone/lib/puppet/provider/keystone_user_role/openstack.rb @@ -1,4 +1,5 @@ require 'puppet/provider/keystone' +require 'puppet/provider/keystone/util' Puppet::Type.type(:keystone_user_role).provide( :openstack, @@ -7,7 +8,7 @@ Puppet::Type.type(:keystone_user_role).provide( 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) @@ -15,9 +16,6 @@ Puppet::Type.type(:keystone_user_role).provide( 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) @@ -26,9 +24,6 @@ Puppet::Type.type(:keystone_user_role).provide( 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) @@ -38,10 +33,8 @@ Puppet::Type.type(:keystone_user_role).provide( 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 @@ -55,8 +48,8 @@ Puppet::Type.type(:keystone_user_role).provide( role[:name] end end - return @property_hash[:ensure] == :present end + return @property_hash[:ensure] == :present end def roles @@ -68,13 +61,11 @@ Puppet::Type.type(:keystone_user_role).provide( # 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 @@ -91,6 +82,19 @@ Puppet::Type.type(:keystone_user_role).provide( 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 @@ -99,12 +103,67 @@ Puppet::Type.type(:keystone_user_role).provide( 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) @@ -112,16 +171,32 @@ Puppet::Type.type(:keystone_user_role).provide( 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 diff --git a/3rdparty/modules/keystone/lib/puppet/type/keystone_domain.rb b/3rdparty/modules/keystone/lib/puppet/type/keystone_domain.rb new file mode 100644 index 00000000..4a2d7773 --- /dev/null +++ b/3rdparty/modules/keystone/lib/puppet/type/keystone_domain.rb @@ -0,0 +1,54 @@ +# 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 diff --git a/3rdparty/modules/keystone/lib/puppet/type/keystone_tenant.rb b/3rdparty/modules/keystone/lib/puppet/type/keystone_tenant.rb index 6195d23f..449ccd04 100644 --- a/3rdparty/modules/keystone/lib/puppet/type/keystone_tenant.rb +++ b/3rdparty/modules/keystone/lib/puppet/type/keystone_tenant.rb @@ -1,6 +1,7 @@ # 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 @@ -34,6 +35,20 @@ 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 diff --git a/3rdparty/modules/keystone/lib/puppet/type/keystone_user.rb b/3rdparty/modules/keystone/lib/puppet/type/keystone_user.rb index b484e7c5..f671f3ed 100644 --- a/3rdparty/modules/keystone/lib/puppet/type/keystone_user.rb +++ b/3rdparty/modules/keystone/lib/puppet/type/keystone_user.rb @@ -2,6 +2,8 @@ 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.' @@ -13,6 +15,11 @@ Puppet::Type.newtype(:keystone_user) do 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| @@ -48,6 +55,11 @@ Puppet::Type.newtype(:keystone_user) do 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 @@ -69,10 +81,25 @@ Puppet::Type.newtype(:keystone_user) do 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'] diff --git a/3rdparty/modules/keystone/lib/puppet/type/keystone_user_role.rb b/3rdparty/modules/keystone/lib/puppet/type/keystone_user_role.rb index 502dc397..d3c78e91 100644 --- a/3rdparty/modules/keystone/lib/puppet/type/keystone_user_role.rb +++ b/3rdparty/modules/keystone/lib/puppet/type/keystone_user_role.rb @@ -2,6 +2,8 @@ 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 @@ -31,13 +33,31 @@ Puppet::Type.newtype(:keystone_user_role) do 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'] diff --git a/3rdparty/modules/keystone/manifests/endpoint.pp b/3rdparty/modules/keystone/manifests/endpoint.pp index 6c821f47..ddf5c136 100644 --- a/3rdparty/modules/keystone/manifests/endpoint.pp +++ b/3rdparty/modules/keystone/manifests/endpoint.pp @@ -22,6 +22,20 @@ # [*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': @@ -36,6 +50,9 @@ 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}" @@ -56,6 +73,9 @@ class keystone::endpoint ( admin_url => $admin_url_real, internal_url => $internal_url_real, region => $region, + user_domain => $user_domain, + project_domain => $project_domain, + default_domain => $default_domain, } } diff --git a/3rdparty/modules/keystone/manifests/init.pp b/3rdparty/modules/keystone/manifests/init.pp index 20c2011e..b406e326 100644 --- a/3rdparty/modules/keystone/manifests/init.pp +++ b/3rdparty/modules/keystone/manifests/init.pp @@ -363,6 +363,14 @@ # (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 # @@ -467,6 +475,7 @@ class keystone( $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, @@ -927,4 +936,27 @@ class keystone( } } + 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, + } + } + } } diff --git a/3rdparty/modules/keystone/manifests/resource/authtoken.pp b/3rdparty/modules/keystone/manifests/resource/authtoken.pp new file mode 100644 index 00000000..5165abb3 --- /dev/null +++ b/3rdparty/modules/keystone/manifests/resource/authtoken.pp @@ -0,0 +1,253 @@ +# == 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}}) +} diff --git a/3rdparty/modules/keystone/manifests/resource/service_identity.pp b/3rdparty/modules/keystone/manifests/resource/service_identity.pp index 9bbd1b13..4ac13220 100644 --- a/3rdparty/modules/keystone/manifests/resource/service_identity.pp +++ b/3rdparty/modules/keystone/manifests/resource/service_identity.pp @@ -69,10 +69,6 @@ # 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' @@ -93,6 +89,20 @@ # 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, @@ -104,7 +114,6 @@ define keystone::resource::service_identity( $configure_user = true, $configure_user_role = true, $configure_service = true, - $domain = undef, $email = "${name}@localhost", $region = 'RegionOne', $service_name = undef, @@ -112,19 +121,32 @@ define keystone::resource::service_identity( $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, @@ -132,6 +154,7 @@ define keystone::resource::service_identity( 'email' => $email, 'tenant' => $tenant, 'ignore_default_tenant' => $ignore_default_tenant, + 'domain' => $user_domain_real, }) } @@ -140,9 +163,6 @@ define keystone::resource::service_identity( 'ensure' => 'present', 'roles' => $roles, }) - if $configure_user { - Keystone_user[$auth_name] -> Keystone_user_role["${auth_name}@${tenant}"] - } } if $configure_service { diff --git a/3rdparty/modules/keystone/manifests/roles/admin.pp b/3rdparty/modules/keystone/manifests/roles/admin.pp index aa5abd72..fe43a0fe 100644 --- a/3rdparty/modules/keystone/manifests/roles/admin.pp +++ b/3rdparty/modules/keystone/manifests/roles/admin.pp @@ -51,7 +51,19 @@ # # [*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 @@ -75,17 +87,55 @@ class keystone::roles::admin( $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, @@ -98,6 +148,7 @@ class keystone::roles::admin( tenant => $admin_tenant, email => $email, password => $password, + domain => $admin_user_domain, ignore_default_tenant => $ignore_default_tenant, } } diff --git a/3rdparty/modules/keystone/metadata.json b/3rdparty/modules/keystone/metadata.json index 77e23afd..c7205b3f 100644 --- a/3rdparty/modules/keystone/metadata.json +++ b/3rdparty/modules/keystone/metadata.json @@ -1,39 +1,55 @@ { - "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)." } diff --git a/3rdparty/modules/keystone/spec/acceptance/basic_keystone_spec.rb b/3rdparty/modules/keystone/spec/acceptance/basic_keystone_spec.rb index b0fd8d39..e5563f09 100644 --- a/3rdparty/modules/keystone/spec/acceptance/basic_keystone_spec.rb +++ b/3rdparty/modules/keystone/spec/acceptance/basic_keystone_spec.rb @@ -43,13 +43,15 @@ describe 'basic keystone server with resources' do 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', @@ -60,6 +62,56 @@ describe 'basic keystone server with resources' do 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 @@ -80,37 +132,94 @@ describe 'basic keystone server with resources' do 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 diff --git a/3rdparty/modules/keystone/spec/classes/keystone_endpoint_spec.rb b/3rdparty/modules/keystone/spec/classes/keystone_endpoint_spec.rb index 217d791f..59390ec9 100644 --- a/3rdparty/modules/keystone/spec/classes/keystone_endpoint_spec.rb +++ b/3rdparty/modules/keystone/spec/classes/keystone_endpoint_spec.rb @@ -48,4 +48,19 @@ describe 'keystone::endpoint' do ) 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 diff --git a/3rdparty/modules/keystone/spec/classes/keystone_roles_admin_spec.rb b/3rdparty/modules/keystone/spec/classes/keystone_roles_admin_spec.rb index bbd6d953..f134d43d 100644 --- a/3rdparty/modules/keystone/spec/classes/keystone_roles_admin_spec.rb +++ b/3rdparty/modules/keystone/spec/classes/keystone_roles_admin_spec.rb @@ -54,8 +54,8 @@ describe 'keystone::roles::admin' do 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( @@ -95,8 +95,8 @@ describe 'keystone::roles::admin' do before do let :params do { - :configure_user => false, - :configure_user_role => false + :configure_user => false, + :configure_user_role => false } end @@ -105,4 +105,72 @@ describe 'keystone::roles::admin' do 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 diff --git a/3rdparty/modules/keystone/spec/classes/keystone_spec.rb b/3rdparty/modules/keystone/spec/classes/keystone_spec.rb index 89c4fc55..c9537dc0 100644 --- a/3rdparty/modules/keystone/spec/classes/keystone_spec.rb +++ b/3rdparty/modules/keystone/spec/classes/keystone_spec.rb @@ -93,6 +93,7 @@ describe 'keystone' do 'rabbit_host' => '127.0.0.1', 'rabbit_password' => 'openstack', 'rabbit_userid' => 'admin', + 'default_domain' => 'other_domain', } httpd_params = {'service_name' => 'httpd'}.merge(default_params) @@ -211,6 +212,10 @@ describe 'keystone' do 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| @@ -845,6 +850,32 @@ describe 'keystone' do 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({ diff --git a/3rdparty/modules/keystone/spec/defines/keystone_resource_authtoken_spec.rb b/3rdparty/modules/keystone/spec/defines/keystone_resource_authtoken_spec.rb new file mode 100644 index 00000000..06894070 --- /dev/null +++ b/3rdparty/modules/keystone/spec/defines/keystone_resource_authtoken_spec.rb @@ -0,0 +1,198 @@ +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 diff --git a/3rdparty/modules/keystone/spec/defines/keystone_resource_service_identity_spec.rb b/3rdparty/modules/keystone/spec/defines/keystone_resource_service_identity_spec.rb index 18979635..63ef98ad 100644 --- a/3rdparty/modules/keystone/spec/defines/keystone_resource_service_identity_spec.rb +++ b/3rdparty/modules/keystone/spec/defines/keystone_resource_service_identity_spec.rb @@ -69,6 +69,69 @@ describe 'keystone::resource::service_identity' do 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 diff --git a/3rdparty/modules/keystone/spec/spec_helper_acceptance.rb b/3rdparty/modules/keystone/spec/spec_helper_acceptance.rb index 2c2634ad..429e807c 100644 --- a/3rdparty/modules/keystone/spec/spec_helper_acceptance.rb +++ b/3rdparty/modules/keystone/spec/spec_helper_acceptance.rb @@ -6,6 +6,7 @@ run_puppet_install_helper 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 @@ -18,25 +19,38 @@ RSpec.configure do |c| # 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 diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone/util_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone/util_spec.rb new file mode 100644 index 00000000..25f7b236 --- /dev/null +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone/util_spec.rb @@ -0,0 +1,29 @@ +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 diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_domain/openstack_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_domain/openstack_spec.rb new file mode 100644 index 00000000..497f09ba --- /dev/null +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_domain/openstack_spec.rb @@ -0,0 +1,192 @@ +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 diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_role/openstack_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_role/openstack_spec.rb index 09e229b8..6765e32e 100644 --- a/3rdparty/modules/keystone/spec/unit/provider/keystone_role/openstack_spec.rb +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_role/openstack_spec.rb @@ -32,12 +32,7 @@ describe provider_class do 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 @@ -47,10 +42,7 @@ describe provider_class do 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 @@ -61,9 +53,6 @@ describe provider_class do 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 } @@ -72,7 +61,7 @@ describe provider_class do 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" diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_service/openstack_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_service/openstack_spec.rb index 5a299a5b..f685a809 100644 --- a/3rdparty/modules/keystone/spec/unit/provider/keystone_service/openstack_spec.rb +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_service/openstack_spec.rb @@ -10,7 +10,7 @@ describe provider_class 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:5000/v3' end describe 'when managing a service' do @@ -41,7 +41,7 @@ describe provider_class 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" diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_spec.rb index 4981f1e5..44d265f5 100644 --- a/3rdparty/modules/keystone/spec/unit/provider/keystone_spec.rb +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_spec.rb @@ -6,13 +6,16 @@ require 'tempfile' 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 @@ -57,7 +60,7 @@ describe Puppet::Provider::Keystone 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 localhost in the admin endpoint if bind_host is 0.0.0.0' do @@ -65,7 +68,7 @@ describe Puppet::Provider::Keystone 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 @@ -73,7 +76,7 @@ describe Puppet::Provider::Keystone 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 @@ -81,7 +84,7 @@ describe Puppet::Provider::Keystone 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 @@ -89,7 +92,7 @@ describe Puppet::Provider::Keystone 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 @@ -97,7 +100,7 @@ describe Puppet::Provider::Keystone 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 @@ -105,7 +108,7 @@ describe Puppet::Provider::Keystone 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 @@ -113,9 +116,58 @@ describe Puppet::Provider::Keystone 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 diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_tenant/openstack_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_tenant/openstack_spec.rb index 1dec49e0..fd17e425 100644 --- a/3rdparty/modules/keystone/spec/unit/provider/keystone_tenant/openstack_spec.rb +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_tenant/openstack_spec.rb @@ -4,87 +4,233 @@ require 'puppet/provider/keystone_tenant/openstack' 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 diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_user/openstack_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_user/openstack_spec.rb index 3f545d0c..d7de008c 100644 --- a/3rdparty/modules/keystone/spec/unit/provider/keystone_user/openstack_spec.rb +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_user/openstack_spec.rb @@ -1,9 +1,14 @@ 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 @@ -21,6 +26,7 @@ describe provider_class do :password => 'foo', :tenant => 'foo', :email => 'foo@example.com', + :domain => 'foo_domain', } end @@ -32,22 +38,95 @@ describe provider_class do 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 @@ -57,11 +136,9 @@ username="foo" 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 @@ -71,9 +148,6 @@ username="foo" 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 @@ -81,134 +155,133 @@ username="foo" 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', @@ -217,6 +290,7 @@ name="_member_" :password => 'foo', :tenant => 'foo', :email => 'foo@example.com', + :domain => 'foo_domain', } end @@ -229,12 +303,26 @@ name="_member_" 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 @@ -246,13 +334,48 @@ ac43ec53d5a74a0b9f51523ae41a29f0 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 @@ -265,13 +388,239 @@ ac43ec53d5a74a0b9f51523ae41a29f0 :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 diff --git a/3rdparty/modules/keystone/spec/unit/provider/keystone_user_role/openstack_spec.rb b/3rdparty/modules/keystone/spec/unit/provider/keystone_user_role/openstack_spec.rb index 2490adc5..7a88d85c 100644 --- a/3rdparty/modules/keystone/spec/unit/provider/keystone_user_role/openstack_spec.rb +++ b/3rdparty/modules/keystone/spec/unit/provider/keystone_user_role/openstack_spec.rb @@ -3,9 +3,183 @@ require 'spec_helper' 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' @@ -31,59 +205,83 @@ describe provider_class do 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 diff --git a/3rdparty/modules/keystone/spec/unit/type/keystone_tenant_spec.rb b/3rdparty/modules/keystone/spec/unit/type/keystone_tenant_spec.rb new file mode 100644 index 00000000..978fa229 --- /dev/null +++ b/3rdparty/modules/keystone/spec/unit/type/keystone_tenant_spec.rb @@ -0,0 +1,25 @@ +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 diff --git a/3rdparty/modules/keystone/spec/unit/type/keystone_user_spec.rb b/3rdparty/modules/keystone/spec/unit/type/keystone_user_spec.rb new file mode 100644 index 00000000..789af435 --- /dev/null +++ b/3rdparty/modules/keystone/spec/unit/type/keystone_user_spec.rb @@ -0,0 +1,25 @@ +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