Add composite namevar for tenant, user, user_role.

There are two sides on this patch, the user facing one, and the
developer's one.

It gives more flexibility for the interface used by the user for the
Keystone_tenant, Keystone_user and Keystone_user_roles resources.  For
instance to specify a user and give the admin role, currently you have
to:

  keystone_user { 'new_admin::admin_domain':
    ensure      => present,
    enabled     => true,
    tenant      => 'openstackv3::admin_domain',
    email       => 'test@example.tld',
    password    => 'a_big_secret',
  }
  keystone_user_role { 'new_admin::admin_domain@openstackv3::admin_domain':
    ensure         => present,
    roles          => ['admin'],
  }

Now you can specify it like this:

  keystone_user { 'new_admin':
    ensure      => present,
    enabled     => true,
    domain      => 'admin_domain',
    tenant      => 'openstackv3::admin_domain',
    email       => 'test@example.tld',
    password    => 'a_big_secret',
  }
  keystone_user_role { 'new_admin@openstackv3':
    ensure         => present,
    user_domain    => 'admin_domain',
    project_domain => 'admin_domain',
    roles          => ['admin'],
  }

For the developer this simplify the code.  Puppet is using composite
namevar to make all the resources unique.  So guessing what pattern is
used in the title is no longer required.  For instance this :

  keystone_tenant { 'project_one': ensure => present }
  keystone_tenant { 'meaningless': name => 'project_one', domain => 'Default', ensure => present }

is detected as the same tenant by puppet.

The same is true for dependencies. This is working correctly:

  keystone_tenant { 'meaningless': name => 'project_one', domain => 'domain_one', ensure => present }
  file {'/tmp/needed': ensure => present, require => Keystone_tenant['project_one::domain_one'] }

In autorequire term in type definition, you just have to pass the fully
qualified name (with the domain suffix for user and tenant) of the
resource and puppet will do the matching, whatever the original title
is.  See the examples in user and tenant in keystone_user_role type.

Change-Id: I4deb27dc6f71fb7a7ec6a9c72bd0e1412c2e9a30
This commit is contained in:
Sofer Athlan-Guyot 2015-09-23 20:17:31 +02:00
parent b195a13918
commit 74799f9e34
29 changed files with 1707 additions and 835 deletions

View File

@ -11,6 +11,7 @@ class Puppet::Provider::Keystone < Puppet::Provider::Openstack
INI_FILENAME = '/etc/keystone/keystone.conf'
@@default_domain_id = nil
@@default_domain = nil
def self.admin_endpoint
@admin_endpoint ||= get_admin_endpoint
@ -32,10 +33,6 @@ class Puppet::Provider::Keystone < Puppet::Provider::Openstack
end
end
def self.default_domain
domain_name_from_id(default_domain_id)
end
def self.default_domain_id
if @@default_domain_id
@@default_domain_id
@ -50,18 +47,40 @@ class Puppet::Provider::Keystone < Puppet::Provider::Openstack
@@default_domain_id = id
end
def self.default_domain_set?
true unless default_domain_id == 'default'
def self.resource_to_name(domain, name, check_for_default=true)
raise Puppet::Error, "Domain cannot be nil for project '#{name}'. " \
'Please report a bug.' if domain.nil?
join_str = '::'
name_display = [name]
unless check_for_default && domain == default_domain
name_display << domain
end
name_display.join(join_str)
end
def self.domain_check(name, domain)
# Ongoing deprecation warning ending after Mitaka
# http://specs.openstack.org/openstack/puppet-openstack-specs/specs/kilo/api-v3-support.html
if (domain.nil? || domain.empty?) && default_domain_set?
warning('In Liberty, not providing a domain name (::domain) for a ' \
"resource name (#{name}) is deprecated when the default_domain_id is " \
"not 'default'")
def self.name_to_resource(name)
uniq = name.split('::')
if uniq.count == 1
uniq.insert(0, default_domain)
else
uniq.reverse!
end
uniq
end
# Prefix with default domain if missing from the name.
def self.make_full_name(name)
resource_to_name(*name_to_resource(name), false)
end
def self.default_domain
# Default domain class variable is filled in by
# user/tenant/user_role type or domain resource.
@@default_domain
end
def self.default_domain=(value)
@@default_domain = value
end
def self.domain_name_from_id(id)
@ -160,31 +179,6 @@ class Puppet::Provider::Keystone < Puppet::Provider::Openstack
end
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.request(service, action, properties=nil)
super
rescue Puppet::Error::OpenstackAuthInputError => error

View File

@ -9,15 +9,15 @@ Puppet::Type.type(:keystone_tenant).provide(
@credentials = Puppet::Provider::Openstack::CredentialsV3.new
include PuppetX::Keystone::CompositeNamevar::Helpers
def initialize(value={})
super(value)
@property_flush = {}
end
def create
# 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]
properties = [resource[:name]]
if resource[:enabled] == :true
properties << '--enable'
elsif resource[:enabled] == :false
@ -27,14 +27,22 @@ Puppet::Type.type(:keystone_tenant).provide(
properties << '--description'
properties << resource[:description]
end
if project_domain
properties << '--domain'
properties << project_domain
end
properties << resource[:domain]
@property_hash = self.class.request('project', 'create', properties)
@property_hash[:name] = resource[:name]
@property_hash[:domain] = resource[:domain]
@property_hash[:ensure] = :present
rescue Puppet::ExecutionFailure => e
if e.message =~ /No domain with a name or ID of/
raise(Puppet::Error, "No project #{resource[:name]} with domain #{resource[:domain]} found")
else
raise
end
end
mk_resource_methods
def exists?
@property_hash[:ensure] == :present
@ -57,25 +65,12 @@ Puppet::Type.type(:keystone_tenant).provide(
@property_flush[:description] = value
end
def description
@property_hash[:description]
end
def domain
@property_hash[:domain]
end
def id
@property_hash[:id]
end
def self.instances
projects = request('project', 'list', '--long')
projects.collect do |project|
domain_name = domain_name_from_id(project[:domain_id])
project_name = set_domain_for_name(project[:name], domain_name)
new(
:name => project_name,
:name => resource_to_name(domain_name, project[:name]),
:ensure => :present,
:enabled => project[:enabled].downcase.chomp == 'true' ? true : false,
:description => project[:description],
@ -87,20 +82,10 @@ Puppet::Type.type(:keystone_tenant).provide(
end
def self.prefetch(resources)
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
prefetch_composite(resources) do |sorted_namevars|
domain = sorted_namevars[0]
name = sorted_namevars[1]
resource_to_name(domain, name)
end
end

View File

@ -9,14 +9,15 @@ Puppet::Type.type(:keystone_user).provide(
@credentials = Puppet::Provider::Openstack::CredentialsV3.new
include PuppetX::Keystone::CompositeNamevar::Helpers
def initialize(value={})
super(value)
@property_flush = {}
end
def create
# see if resource[:domain], or user specified as user::domain
user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
user_name, user_domain = resource[:name], resource[:domain]
properties = [user_name]
if resource[:enabled] == :true
properties << '--enable'
@ -54,13 +55,15 @@ Puppet::Type.type(:keystone_user).provide(
options << '--email' << resource[:email] if @property_flush[:email]
# project handled in tenant= separately
unless options.empty?
options << @property_hash[:id]
options << id
self.class.request('user', 'set', options)
end
@property_flush.clear
end
end
mk_resource_methods
def exists?
@property_hash[:ensure] == :present
end
@ -74,18 +77,10 @@ Puppet::Type.type(:keystone_user).provide(
@property_flush[:enabled] = value
end
def email
@property_hash[:email]
end
def email=(value)
@property_flush[:email] = value
end
def id
@property_hash[:id]
end
def password
passwd = nil
return passwd if resource[:password] == nil
@ -104,7 +99,7 @@ Puppet::Type.type(:keystone_user).provide(
# 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)
credentials.username = resource[:name]
# 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'])
@ -149,9 +144,8 @@ Puppet::Type.type(:keystone_user).provide(
users = request('user', 'list', ['--long'])
users.collect do |user|
domain_name = domain_name_from_id(user[:domain])
user_name = set_domain_for_name(user[:name], domain_name)
new(
:name => user_name,
:name => resource_to_name(domain_name, user[:name]),
:ensure => :present,
:enabled => user[:enabled].downcase.chomp == 'true' ? true : false,
:password => user[:password],
@ -165,19 +159,10 @@ Puppet::Type.type(:keystone_user).provide(
end
def self.prefetch(resources)
users = 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 = 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
prefetch_composite(resources) do |sorted_namevars|
domain = sorted_namevars[0]
name = sorted_namevars[1]
resource_to_name(domain, name)
end
end

View File

@ -1,13 +1,15 @@
require 'puppet/provider/keystone'
require 'puppet/provider/keystone/util'
require 'puppet_x/keystone/composite_namevar'
Puppet::Type.type(:keystone_user_role).provide(
:openstack,
:parent => Puppet::Provider::Keystone
) do
desc "Provider to manage keystone role assignments to users."
include PuppetX::Keystone::CompositeNamevar::Helpers
@credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
@ -36,17 +38,17 @@ Puppet::Type.type(:keystone_user_role).provide(
def exists?
if self.class.user_role_hash.nil? || self.class.user_role_hash.empty?
roles = self.class.request('role', 'list', properties)
roles_db = 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
# and destroy methods
@property_hash[:name] = resource[:name]
if roles.empty?
if roles_db.empty?
@property_hash[:ensure] = :absent
else
@property_hash[:ensure] = :present
@property_hash[:roles] = roles.collect do |role|
@property_hash[:roles] = roles_db.collect do |role|
role[:name]
end
end
@ -54,8 +56,13 @@ Puppet::Type.type(:keystone_user_role).provide(
return @property_hash[:ensure] == :present
end
def roles
@property_hash[:roles]
mk_resource_methods
# Don't want :absent
[:user, :user_domain, :project, :project_domain, :domain].each do |attr|
define_method(attr) do
@property_hash[attr] ||= resource[attr]
end
end
def roles=(value)
@ -74,110 +81,42 @@ Puppet::Type.type(:keystone_user_role).provide(
def self.instances
instances = build_user_role_hash
instances.collect do |title, roles|
new(
new({
:name => title,
:ensure => :present,
:roles => roles
)
}.merge(@user_role_parameters[title]))
end
end
private
def properties
return @properties if @properties
properties = []
if get_project_id
if set?(:project)
properties << '--project' << get_project_id
elsif get_domain
properties << '--domain' << get_domain
elsif set?(:domain)
properties << '--domain' << domain
else
raise(Puppet::Error, 'No project or domain specified for role')
end
properties << '--user' << get_user_id
properties
end
def get_user
resource[:name].rpartition('@').first
end
def get_project
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
@properties = properties
end
def get_user_id
return @user_id if defined?(@user_id)
user_name, domain_name = Util.split_domain(get_user)
if user_name.nil?
@user_id = nil
else
user = self.class.fetch_user(user_name, domain_name)
if user.nil?
raise(Puppet::Error, "No user #{user_name} with domain #{domain_name} found")
else
@user_id = user[:id]
end
end
@user_id
user_db = self.class.fetch_user(user, user_domain)
raise(Puppet::Error, "No user #{user} with domain #{user_domain} found") if user_db.nil?
user_db[:id]
end
def get_project_id
return @project_id if defined?(@project_id)
project_name, domain_name = Util.split_domain(get_project)
if project_name.nil?
@project_id = nil
else
project = self.class.fetch_project(project_name, domain_name)
if project.nil?
raise(Puppet::Error, "No project #{project_name} with domain #{domain_name} found")
else
@project_id = project[:id]
end
end
@project_id
end
def self.get_projects
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_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])
}
project_db = self.class.fetch_project(project, project_domain)
if project_db.nil?
raise(Puppet::Error, "No project #{project} with domain #{project_domain} found")
end
project_db[:id]
end
def self.user_role_hash
@ -193,6 +132,7 @@ Puppet::Type.type(:keystone_user_role).provide(
# 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] = []}
@user_role_parameters = {}
return hash unless hash.empty?
# Need a mapping of project id to names.
project_hash = {}
@ -210,12 +150,21 @@ Puppet::Type.type(:keystone_user_role).provide(
# 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]]
user_str = user_hash[assignment[:user]]
if assignment[:project] && !assignment[:project].empty?
project_str = project_hash[assignment[:project]]
name = "#{user_str}@#{project_str}"
@user_role_parameters[name] = Hash[
[:user_domain, :user, :project_domain, :project]
.zip(name_to_resource(user_str) + name_to_resource(project_str))]
else
domainname = domain_id_to_name(assignment[:domain])
hash["#{user_hash[assignment[:user]]}@::#{domainname}"] << role_hash[assignment[:role]]
domainname = domain_name_from_id(assignment[:domain])
name = "#{user_hash[assignment[:user]]}@::#{domainname}"
@user_role_parameters[name] = Hash[
[:user_domain, :user, :domain]
.zip(name_to_resource(user_str) + [domainname])]
end
hash[name] << role_hash[assignment[:role]]
end
end
set_user_role_hash(hash)

View File

@ -49,6 +49,4 @@ Puppet::Type.newtype(:keystone_domain) do
autorequire(:anchor) do
['keystone_started']
end
end

View File

@ -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'
require 'puppet_x/keystone/composite_namevar'
require 'puppet_x/keystone/type'
Puppet::Type.newtype(:keystone_tenant) do
@ -35,18 +37,15 @@ Puppet::Type.newtype(:keystone_tenant) do
end
end
newproperty(:domain) do
newparam(:domain) do
desc 'Domain for tenant.'
newvalues(nil, /\S+/)
def insync?(is)
raise(Puppet::Error, "[keystone_tenant]: The domain cannot be changed from #{self.should} to #{is}") unless self.should == is
true
end
isnamevar
include PuppetX::Keystone::Type::DefaultDomain
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]
self[:domain]
end
# This ensures the service is started and therefore the keystone
@ -56,4 +55,8 @@ Puppet::Type.newtype(:keystone_tenant) do
autorequire(:anchor) do
['keystone_started', 'default_domain_created']
end
def self.title_patterns
PuppetX::Keystone::CompositeNamevar.basic_split_title_patterns(:name, :domain)
end
end

View File

@ -3,6 +3,8 @@ File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift
File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
require 'puppet/provider/keystone/util'
require 'puppet_x/keystone/composite_namevar'
require 'puppet_x/keystone/type'
Puppet::Type.newtype(:keystone_user) do
@ -26,9 +28,9 @@ Puppet::Type.newtype(:keystone_user) do
newvalues(/\S+/)
def change_to_s(currentvalue, newvalue)
if currentvalue == :absent
return "created password"
return 'created password'
else
return "changed password"
return 'changed password'
end
end
@ -59,21 +61,22 @@ Puppet::Type.newtype(:keystone_user) do
end
end
newproperty(:domain) do
newvalues(nil, /\S+/)
def insync?(is)
raise(Puppet::Error, "[keystone_user]: The domain cannot be changed from #{self.should} to #{is}") unless self.should == is
true
end
newparam(:domain) do
isnamevar
include PuppetX::Keystone::Type::DefaultDomain
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]
self[:domain]
end
# we should not do anything until the keystone service is started
autorequire(:anchor) do
['keystone_started','default_domain_created']
end
def self.title_patterns
PuppetX::Keystone::CompositeNamevar.basic_split_title_patterns(:name, :domain)
end
end

View File

@ -3,40 +3,83 @@ File.expand_path('../..', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift
File.expand_path('../../../../openstacklib/lib', File.dirname(__FILE__)).tap { |dir| $LOAD_PATH.unshift(dir) unless $LOAD_PATH.include?(dir) }
require 'puppet/provider/keystone/util'
require 'puppet_x/keystone/composite_namevar'
require 'puppet_x/keystone/type'
Puppet::Type.newtype(:keystone_user_role) do
desc <<-EOT
This is currently used to model the creation of
keystone users roles.
This is currently used to model the creation of keystone users
roles.
User roles are an assignment of a role to a user on a certain
tenant. The combination of all of these attributes is unique.
The resource's name can be specified like this:
<user(::user_domain)?>@<project(::project_domain)?|::domain>
which means the user is required. Project and domain are mutually
exclusive. User_domain and project_domain are optional.
"user_domain" and "project_domain" resources default to the name
of the "Keystone_domain" resource which has the "is_default"
property set to true in the current catalog, or to "Default" if
such resource doesn't exist in the catalog.
User roles are an assignment of a role to a user on
a certain tenant. The combination of all of these
attributes is unique.
EOT
include PuppetX::Keystone::CompositeNamevar::Helpers
ensurable
newparam(:name, :namevar => true) do
newparam(:name, :namevar => true)
[:user, :project].each do |p|
newparam(p) do
isnamevar
defaultto PuppetX::Keystone::CompositeNamevar::Unset
end
end
[:user_domain, :project_domain].each do |p|
newparam(p) do
isnamevar
include PuppetX::Keystone::Type::DefaultDomain
end
end
newparam(:domain) do
isnamevar
defaultto PuppetX::Keystone::CompositeNamevar::Unset
validate do |v|
if !resource.parameters[:project].nil? &&
resource.parameters[:project].value != PuppetX::Keystone::CompositeNamevar::Unset &&
v != PuppetX::Keystone::CompositeNamevar::Unset
raise(Puppet::ResourceError,
'Cannot define both project and domain for a role.')
end
end
end
newproperty(:roles, :array_matching => :all) do
def insync?(is)
return false unless is.is_a? Array
# order of roles does not matter
is.sort == self.should.sort
is.sort == should.sort
end
end
autorequire(:keystone_user) do
self[:name].rpartition('@').first
# Pass through title parsing for matching resource.
[provider.class.resource_to_name(self[:user_domain], self[:user], false)]
end
autorequire(:keystone_tenant) do
proj, dom = Util.split_domain(self[:name].rpartition('@').last)
rv = nil
if proj # i.e. not ::domain
rv = self[:name].rpartition('@').last
rv = []
unless parameter_set?(:domain)
# Pass through title parsing for matching resource.
rv << provider.class.resource_to_name(self[:project_domain],
self[:project], false)
end
rv
end
@ -46,15 +89,9 @@ Puppet::Type.newtype(:keystone_user_role) do
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 = [self[:user_domain]]
rv << self[:project_domain] if parameter_set?(:project_domain)
rv << self[:domain] if parameter_set?(:domain)
rv
end
@ -62,4 +99,72 @@ Puppet::Type.newtype(:keystone_user_role) do
autorequire(:anchor) do
['keystone_started']
end
def self.title_patterns
user = PuppetX::Keystone::CompositeNamevar.not_two_colon_regex
project_domain = user
domain = user
user_domain = Regexp.new(/(?:[^:@]|:[^:@])+/)
project = user_domain
unset = ->(_) { PuppetX::Keystone::CompositeNamevar::Unset }
[
[
# fully qualified user with fully qualified project
/^(#{user})::(#{user_domain})@(#{project})::(#{project_domain})$/,
[
[:user],
[:user_domain],
[:project],
[:project_domain]
]
],
# fully qualified user with domain
[
/^(#{user})::(#{user_domain})@::(#{domain})($)/,
[
[:user],
[:user_domain],
[:domain],
# Don't want to have project_domain set to default, while
# not used.
[:project_domain, unset]
]
],
# fully qualified user with project
[
/^(#{user})::(#{user_domain})@(#{project})$/,
[
[:user],
[:user_domain],
[:project]
]
],
# user with fully qualified project
[
/^(#{user})@(#{project})::(#{project_domain})$/,
[
[:user],
[:project],
[:project_domain]
]
],
# user with domain
[
/^(#{user})@::(#{domain})($)/,
[
[:user],
[:domain],
[:project_domain, unset]
]
],
# user with project
[
/^(#{user})@(#{project})$/,
[
[:user],
[:project]
]
]
]
end
end

View File

@ -0,0 +1,71 @@
# Cherry pick PUP-1073 from puppetlabs: support of composite namevar for alias.
if Gem::Version.new(Puppet.version) < Gem::Version.new('4.0.0')
Puppet::Resource::Catalog.class_eval do
def create_resource_aliases(resource)
# Skip creating aliases and checking collisions for non-isomorphic resources.
return unless resource.respond_to?(:isomorphic?) and resource.isomorphic?
# Add an alias if the uniqueness key is valid and not the
# title, which has already been checked.
ukey = resource.uniqueness_key
if ukey.any? and ukey != [resource.title]
self.alias(resource, ukey)
end
end
end
Puppet::Resource.class_eval do
def uniqueness_key
# Temporary kludge to deal with inconsistent use patterns; ensure we don't return nil for namevar/:name
h = self.to_hash
name = h[namevar] || h[:name] || self.name
h[namevar] ||= name
h[:name] ||= name
h.values_at(*key_attributes.sort_by { |k| k.to_s })
end
end
end
require 'puppet_x/keystone/composite_namevar/helpers'
module PuppetX
module Keystone
module CompositeNamevar
class Unset; end
def self.not_two_colon_regex
# Anything but 2 consecutive colons.
Regexp.new(/(?:[^:]|:[^:])+/)
end
def self.basic_split_title_patterns(prefix, suffix, separator = '::', *regexps)
associated_regexps = []
if regexps.empty? and separator == '::'
associated_regexps += [not_two_colon_regex, not_two_colon_regex]
else
if regexps.count != 2
raise(Puppet::DevError, 'You must provide two regexps')
else
associated_regexps += regexps
end
end
prefix_re = associated_regexps[0]
suffix_re = associated_regexps[1]
[
[
/^(#{prefix_re})#{separator}(#{suffix_re})$/,
[
[prefix],
[suffix]
]
],
[
/^(#{prefix_re})$/,
[
[prefix]
]
]
]
end
end
end
end

View File

@ -0,0 +1,27 @@
require 'puppet_x/keystone/composite_namevar/helpers/utilities'
module PuppetX
module Keystone
module CompositeNamevar
module Helpers
def set?(param, argument = nil)
value = nil
if argument.nil?
value = send(param.to_sym)
else
value = send(param.to_sym, argument)
end
value != PuppetX::Keystone::CompositeNamevar::Unset
end
def parameter_set?(key)
set?(:'[]', key.to_sym)
end
def self.included(klass)
klass.extend Utilities if klass.to_s.match(/Provider/)
end
end
end
end
end

View File

@ -0,0 +1,44 @@
module PuppetX
module Keystone
module CompositeNamevar
module Helpers
module Utilities
def prefetch_composite(resources)
# cannot trust puppet for correct resources with semantic title
res = resources.values.first
catalog = res.catalog
klass = res.class
required_resources = catalog.resources.find_all do |e|
e.class.to_s == klass.to_s
end
# hash catalog resource by uniq key
required_res = Hash[required_resources.map(&:uniqueness_key)
.zip(required_resources)]
# This is the sort order returned by uniqueness_key.
namevars_ordered = resource_type.key_attributes.map(&:to_s).sort
existings = instances
# uniqueness_key sort by lexical order of the key attributes
required_res.each do |res_key, resource|
provider = existings.find do |existing|
if block_given?
# transformation is done on the name using namevar,
# so we let the user transform it the correct way.
res_transformed_namevar = yield(res_key)
# name in self.instance is assumed to have the same
# transformation than the one given by the user.
exist_transformed_namevar = existing.name
res_transformed_namevar == exist_transformed_namevar
else
res_key == namevars_ordered
.map { |namevar| existing.send(namevar) }
end
end
resource.provider = provider if provider
end
end
end
end
end
end
end

View File

@ -0,0 +1 @@
require 'puppet_x/keystone/type/default_domain'

View File

@ -0,0 +1,82 @@
module PuppetX
module Keystone
module Type
module DefaultDomain
def self.included(klass)
klass.class_eval do
defaultto do
default = PuppetX::Keystone::Type::DefaultDomain
.calculate_using(resource)
deprecation_msg = 'Support for a resource without the domain ' \
'set is deprecated in Liberty cycle. ' \
'It will be dropped in the M-cycle. ' \
"Currently using '#{default}' as default domain."
warning(deprecation_msg)
default
end
# This make sure that @@default_domain is filled no matter
# what.
validate do |_|
PuppetX::Keystone::Type::DefaultDomain
.calculate_using(resource)
true
end
end
end
def self.calculate_using(resource)
current_default = from_keystone_env
return current_default unless current_default.nil?
catalog = resource.catalog
if catalog.nil?
# Running "puppet resource" (resource.catalog is nil)).
# We try to get the default from the keystone.conf file
# and then, the name from the api.
current_default = from_keystone_conf_and_api(resource)
else
current_default = from_catalog(catalog)
end
resource.provider.class.default_domain = current_default
current_default
end
private
def self.from_keystone_env
Puppet::Provider::Keystone.default_domain
rescue Puppet::DevError => e
raise e unless e.message.match(/The default domain should already be filled in/)
end
def self.from_catalog(catalog)
default_res_in_cat = catalog.resources.find do |r|
r.class.to_s == 'Puppet::Type::Keystone_domain' &&
r[:is_default] == :true &&
r[:ensure] == :present
end
default_res_in_cat[:name] rescue 'Default'
end
def self.from_keystone_conf_and_api(resource)
current_default = nil
default_domain_from_conf = Puppet::Resource.indirection
.find('Keystone_config/identity/default_domain_id')
if default_domain_from_conf[:ensure] == :absent
current_default = 'Default'
else
current_default = resource.provider.class
.domain_name_from_id(default_domain_from_conf[:value])
end
current_default
rescue
raise(Puppet::DevError,
'The default domain cannot be guessed from your ' \
'current installation. Please check that keystone ' \
'is working properly')
end
end
end
end
end

View File

@ -67,7 +67,6 @@ describe 'basic keystone server with resources' do
database_connection => 'mysql://keystone:keystone@127.0.0.1/keystone',
admin_token => 'admin_token',
enabled => true,
default_domain => 'default_domain',
}
# "v2" admin and service
class { '::keystone::roles::admin':
@ -99,15 +98,16 @@ describe 'basic keystone server with resources' do
enabled => true,
description => 'Domain for admin v3 users',
}
keystone_tenant { 'servicesv3::service_domain':
keystone_tenant { 'servicesv3':
ensure => present,
enabled => true,
description => 'Tenant for the openstack services',
domain => 'service_domain',
}
keystone_tenant { 'openstackv3::admin_domain':
ensure => present,
enabled => true,
description => 'admin tenant',
description => 'admin tenant'
}
keystone_user { 'adminv3::admin_domain':
ensure => present,
@ -115,7 +115,9 @@ describe 'basic keystone server with resources' do
email => 'test@example.tld',
password => 'a_big_secret',
}
keystone_user_role { 'adminv3::admin_domain@openstackv3::admin_domain':
keystone_user_role { 'adminv3@openstackv3':
project_domain => 'admin_domain',
user_domain => 'admin_domain',
ensure => present,
roles => ['admin'],
}
@ -227,11 +229,11 @@ describe 'basic keystone server with resources' do
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_domain --os-project-domain-name default_domain'
'--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_domain --os-project-domain-name default_domain'
'--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',
@ -241,6 +243,33 @@ describe 'basic keystone server with resources' 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
describe 'composite namevar quick test' do
context 'similar resources different naming' do
let(:pp) do
<<-EOM
keystone_tenant { 'openstackv3':
ensure => present,
enabled => true,
description => 'admin tenant',
domain => 'admin_domain'
}
keystone_user { 'adminv3::useless_when_the_domain_is_set':
ensure => present,
enabled => true,
email => 'test@example.tld',
password => 'a_big_secret',
domain => 'admin_domain'
}
keystone_user_role { 'adminv3::admin_domain@openstackv3::admin_domain':
ensure => present,
roles => ['admin'],
}
EOM
end
it 'should not do any modification' do
apply_manifest(pp, :catch_changes => true)
end
end
end
end

View File

@ -0,0 +1,161 @@
require 'spec_helper_acceptance'
describe 'basic keystone server with changed domain id' do
after(:context) do
clean_up_manifest = <<-EOM
class { '::keystone':
verbose => true,
debug => true,
database_connection => 'mysql://keystone:keystone@127.0.0.1/keystone',
admin_token => 'admin_token',
enabled => true,
}
keystone_config { 'identity/default_domain_id': ensure => absent}
EOM
apply_manifest(clean_up_manifest, :catch_failures => true)
end
context 'new domain id' do
let(:pp) do
<<-EOM
Exec { logoutput => 'on_failure' }
# make sure apache is stopped before keystone eventlet
# in case of wsgi was run before
class { '::apache':
service_ensure => 'stopped',
}
Service['httpd'] -> Service['keystone']
# Common resources
case $::osfamily {
'Debian': {
include ::apt
class { '::openstack_extras::repo::debian::ubuntu':
release => 'liberty',
repo => 'proposed',
package_require => true,
}
}
'RedHat': {
class { '::openstack_extras::repo::redhat::redhat':
manage_rdo => false,
repo_hash => {
'openstack-common-testing' => {
'baseurl' => 'http://cbs.centos.org/repos/cloud7-openstack-common-testing/x86_64/os/',
'descr' => 'openstack-common-testing',
'gpgcheck' => 'no',
},
'openstack-liberty-testing' => {
'baseurl' => 'http://cbs.centos.org/repos/cloud7-openstack-liberty-testing/x86_64/os/',
'descr' => 'openstack-liberty-testing',
'gpgcheck' => 'no',
},
'openstack-liberty-trunk' => {
'baseurl' => 'http://trunk.rdoproject.org/centos7-liberty/current-passed-ci/',
'descr' => 'openstack-liberty-trunk',
'gpgcheck' => 'no',
},
},
}
package { 'openstack-selinux': ensure => 'latest' }
}
default: {
fail("Unsupported osfamily (${::osfamily})")
}
}
class { '::mysql::server': }
# Keystone resources
class { '::keystone::client': }
class { '::keystone::cron::token_flush': }
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,
default_domain => 'my_default_domain'
}
keystone_tenant { 'project_in_my_default_domain':
ensure => present,
enabled => true,
description => 'Project in another default domain',
}
keystone_user { 'user_in_my_default_domain':
ensure => present,
enabled => true,
email => 'test@example.tld',
password => 'a_big_secret',
}
keystone_user_role { 'user_in_my_default_domain@project_in_my_default_domain':
ensure => present,
roles => ['admin'],
}
keystone_domain { 'other_domain': ensure => present }
keystone_user { 'user_in_my_default_domain::other_domain':
ensure => present,
enabled => true,
email => 'test@example.tld',
password => 'a_big_secret',
}
keystone_tenant { 'project_in_my_default_domain::other_domain':
ensure => present,
enabled => true,
description => 'Project in other domain',
}
keystone_user_role { 'user_in_my_default_domain@::other_domain':
ensure => present,
user_domain => 'other_domain',
roles => ['admin'],
}
EOM
end
describe 'puppet apply' do
it 'should work with no errors and catch deprecation warning' do
apply_manifest(pp, :catch_failures => true) do |result|
expect(result.stderr)
.to include_regexp([/Keystone_tenant\[project_in_my_default_domain\]\/domain: Support for a resource without.*. Currently using 'my_default_domain' as default domain/,
/Keystone_user\[user_in_my_default_domain\]\/domain/,
/Keystone_user_role\[user_in_my_default_domain@project_in_my_default_domain\]\/user_domain/,
/Keystone_user_role\[user_in_my_default_domain@project_in_my_default_domain\]\/project_domain/])
end
end
it 'should be idempotent' do
apply_manifest(pp, :catch_changes => true) do |result|
expect(result.stderr)
.to include_regexp(/Warning: \/Keystone_tenant.*Currently using 'my_default_domain'/)
end
end
end
describe 'puppet resources are successful created' do
it 'for tenant' do
shell('puppet resource keystone_tenant') do |result|
expect(result.stdout)
.to include_regexp([/keystone_tenant { 'project_in_my_default_domain::my_default_domain':/,
/keystone_tenant { 'project_in_my_default_domain::other_domain':/])
end
end
it 'for user' do
shell('puppet resource keystone_user') do |result|
expect(result.stdout)
.to include_regexp([/keystone_user { 'user_in_my_default_domain::my_default_domain':/,
/keystone_user { 'user_in_my_default_domain::other_domain':/])
end
end
it 'for role' do
shell('puppet resource keystone_user_role') do |result|
expect(result.stdout)
.to include_regexp([/keystone_user_role { 'user_in_my_default_domain::my_default_domain@project_in_my_default_domain::my_default_domain':/,
/keystone_user_role { 'user_in_my_default_domain::other_domain@::other_domain':/])
end
end
end
end
end

View File

@ -61,7 +61,6 @@ describe 'keystone server running with Apache/WSGI as Identity Provider' do
admin_token => 'admin_token',
enabled => true,
service_name => 'httpd',
default_domain => 'default_domain',
}
include ::apache
class { '::keystone::wsgi::apache':
@ -230,11 +229,11 @@ describe 'keystone server running with Apache/WSGI as Identity Provider' do
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_domain --os-project-domain-name default_domain'
'--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_domain --os-project-domain-name default_domain'
'--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',

View File

@ -61,7 +61,6 @@ describe 'keystone server running with Apache/WSGI with resources' do
admin_token => 'admin_token',
enabled => true,
service_name => 'httpd',
default_domain => 'default_domain',
}
include ::apache
class { '::keystone::wsgi::apache':
@ -226,11 +225,11 @@ describe 'keystone server running with Apache/WSGI with resources' do
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_domain --os-project-domain-name default_domain'
'--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_domain --os-project-domain-name default_domain'
'--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',

View File

@ -3,3 +3,134 @@ shared_examples_for "a Puppet::Error" do |description|
expect { is_expected.to have_class_count(1) }.to raise_error(Puppet::Error, description)
end
end
shared_examples_for 'parse title correctly' do |result|
let(:title) do |example|
example.metadata[:example_group][:description]
end
let(:current_class) do |example|
example.metadata[:described_class]
end
let(:resource) { current_class.new(:title => title) }
it 'should parse this title correctly' do
times = result.delete(:calling_default) || 0
Puppet::Provider::Keystone.expects(:default_domain).times(times).returns('Default')
expect(resource.to_hash).to include(result)
end
end
shared_examples_for 'croak on the title' do
let(:title) do |example|
example.metadata[:example_group][:description]
end
let(:current_class) do |example|
example.metadata[:described_class]
end
let(:user) { current_class.new(:title => title) }
it 'croak on the title' do
expect { user }.to raise_error(Puppet::Error, /No set of title patterns matched the title/)
end
end
# Let resources to [<tested_resource>, <required>, <required>, ..., <not_required>]
shared_examples_for 'autorequire the correct resources' do
let(:catalog) { Puppet::Resource::Catalog.new }
it 'should autorequire correctly' do
resource = resources[0]
resources_good = resources[1...resources.count-1]
catalog.add_resource(*resources)
dependency = resource.autorequire
expect(dependency.size).to eq(resources_good.count)
resources_good.each_with_index do |good, idx|
expect(dependency[idx].target).to eq(resource)
expect(dependency[idx].source).to eq(good)
end
end
end
# Let resources to [<existing>, <non_existing>]
shared_examples_for 'prefetch the resources' do
let(:current_class) do |example|
example.metadata[:described_class]
end
it 'should correctly prefetch the existing resource' do
existing = resources[0]
non_existing = resources[1]
resource = mock
r = []
r << existing
catalog = Puppet::Resource::Catalog.new
r.each { |res| catalog.add_resource(res) }
m_value = mock
m_first = mock
resource.expects(:values).returns(m_value)
m_value.expects(:first).returns(m_first)
m_first.expects(:catalog).returns(catalog)
m_first.expects(:class).returns(current_class.resource_type)
current_class.prefetch(resource)
# found and not found
expect(existing.provider.ensure).to eq(:present)
expect(non_existing.provider.ensure).to eq(:absent)
end
end
# attribute [Array[Hash]]
# - the first hash are the expected result
# - second are parameters to test default domain, required but can be empty
# - the rest are the combination of attributes you want to test
# see examples in user/user_role/tenant
shared_examples_for 'create the correct resource' do |attributes|
expected_results = attributes.shift['expected_results']
default_domain = attributes.shift
context 'domain filled' do
attributes.each do |attribute|
context 'test' do
let(:resource_attrs) { attribute.values[0] }
it "should correctly create the resource when #{attribute.keys[0]}" do
times = resource_attrs.delete(:default_domain)
unless times.nil?
Puppet::Provider::Keystone.expects(:default_domain)
.times(times)
.returns(default_domain[:name])
end
provider.create
expect(provider.exists?).to be_truthy
expected_results.each do |key, value|
expect(provider.send(key)).to eq(value)
end
end
end
end
end
context 'domain not passed, using default' do
with_default_domain = default_domain[:attributes]
if with_default_domain
with_default_domain.each do |attribute|
let(:resource_attrs) { attribute[1] }
it 'should fall into the default domain' do
Puppet::Provider::Keystone.expects(:default_domain)
.times(default_domain[:times])
.returns(default_domain[:name])
provider.create
expect(provider.exists?).to be_truthy
expected_results.each do |key, value|
expect(provider.send(key)).to eq(value)
end
end
end
end
end
end
# Let resources to [<resource_1>, <duplicate>]
shared_examples_for 'detect duplicate resource' do
let(:catalog) { Puppet::Resource::Catalog.new }
it 'should detect the duplicate' do
expect { catalog.add_resource(resources[0]) }.not_to raise_error
expect { catalog.add_resource(resources[1]) }.to raise_error(ArgumentError,/Cannot alias/)
end
end

View File

@ -19,7 +19,10 @@ def setup_provider_tests
@admin_token = nil
@keystone_file = nil
Puppet::Provider::Keystone.default_domain_id = nil
Puppet::Provider::Keystone.default_domain = nil
@domain_hash = nil
end
end
end
Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f }

View File

@ -54,3 +54,5 @@ RSpec.configure do |c|
end
end
end
Dir[File.dirname(__FILE__) + '/support/**/*.rb'].each { |f| require f }

View File

@ -0,0 +1,20 @@
RSpec::Matchers.define :include_regexp do |expected|
regexps = expected.dup
regexps = [regexps] unless regexps.is_a?(Array)
expected_count = regexps.count
count = 0
match do |actual|
output = actual
output = output.split("\n") unless output.is_a?(Array)
output.each do |line|
regexps.each_with_index do |regex, regexp_idx|
if line.match(regex)
count += 1
regexps.delete_at(regexp_idx)
break
end
end
end
expected_count == count
end
end

View File

@ -22,7 +22,7 @@ describe provider_class do
:name => 'foo',
:description => 'foo',
:ensure => 'present',
:enabled => 'True',
:enabled => 'True'
}
end
@ -65,8 +65,7 @@ describe provider_class do
name="foo"
description="foo"
enabled=True
'
)
')
provider.create
expect(provider.exists?).to be_truthy
end
@ -99,8 +98,7 @@ enabled=True
.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
@ -131,8 +129,7 @@ enabled=True
name="foo"
description="foo"
enabled=True
'
)
')
expect(provider.class.default_domain_id).to eq('default')
expect(another_class.default_domain_id).to eq('default')
provider.create

View File

@ -142,41 +142,6 @@ id="the_user_id"
expect(klass.fetch_user('The User', 'Default')).to eq({:name=>"The User", :id=>"the_user_id"})
end
end
describe '#default_domain_set?' do
it 'should be false when default_domain_id is default' do
mock = {'identity' => {'default_domain_id' => 'default'}}
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.default_domain_set?).to be_falsey
end
it 'should be true when default_domain_id is not default' do
mock = {'identity' => {'default_domain_id' => 'not_default'}}
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.default_domain_set?).to be_truthy
end
end
describe '#domain_check' do
it 'should not warn when domain name is provided' do
klass.domain_check('name', 'domain')
expect(klass.domain_check('name', 'domain')).to be_nil
end
it 'should not warn when domain is not provided and default_domain_id is not set' do
klass.domain_check('name', '')
expect(klass.domain_check('name', '')).to be_nil
end
it 'should warn when domain name is empty and default_domain_id is set' do
mock = {'identity' => {'default_domain_id' => 'not_default'}}
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(:warning).with("In Liberty, not providing a domain name (::domain) for a resource name (name) is deprecated when the default_domain_id is not 'default'")
klass.domain_check('name', '')
end
end
describe '#get_admin_endpoint' do
it 'should return nothing if there is no keystone config file' do
@ -367,64 +332,6 @@ id="other_domain_id"
set_env
end
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
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 the default_domain_id from one class set in another class' do
klass.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"default","Default",True,"default domain"
"somename","SomeName",True,"some domain"
')
another_class.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"default","Default",True,"default domain"
"somename","SomeName",True,"some domain"
')
expect(klass.default_domain).to eq('Default')
expect(another_class.default_domain).to eq('Default')
klass.default_domain_id = 'somename'
expect(klass.default_domain).to eq('SomeName')
expect(another_class.default_domain).to eq('SomeName')
end
it 'should return Default if default_domain_id is not configured' do
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
it 'should list all domains when requesting a domain name from an ID' do
klass.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])

View File

@ -12,44 +12,44 @@ describe provider_class do
provider_class.reset
end
let(:tenant_attrs) do
let(:resource_attrs) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
{
:name => 'foo',
:description => 'foo',
:name => 'project_one',
:description => 'Project One',
:ensure => 'present',
:enabled => 'True',
:enabled => 'True'
}
end
let(:resource) do
Puppet::Type::Keystone_tenant.new(tenant_attrs)
Puppet::Type::Keystone_tenant.new(resource_attrs)
end
let(:provider) do
provider_class.new(resource)
end
def before_hook(domainlist)
def before_hook(domainlist, provider_class)
if domainlist
provider.class.expects(:openstack).once
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"
"domain_one_id","domain_one",True,"project_one domain"
"domain_two_id","domain_two",True,"domain_two 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)
before_hook(true, provider_class)
end
before :each, :domainlist => false do
before_hook(false)
before_hook(false, provider_class)
end
let(:set_env) do
@ -65,17 +65,16 @@ describe provider_class do
describe 'when managing a tenant' do
describe '#create', :domainlist => true do
describe '#create', :domainlist => false do
it 'creates a tenant' do
provider.class.expects(:openstack)
.with('project', 'create', '--format', 'shell', ['foo', '--enable', '--description', 'foo', '--domain', 'Default'])
.returns('description="foo"
.with('project', 'create', '--format', 'shell', ['project_one', '--enable', '--description', 'Project One', '--domain', 'Default'])
.returns('description="Project One"
enabled="True"
name="foo"
id="foo"
domain_id="foo_domain_id"
'
)
name="project_one"
id="project_one"
domain_id="domain_one_id"
')
provider.create
expect(provider.exists?).to be_truthy
end
@ -92,159 +91,203 @@ domain_id="foo_domain_id"
end
context 'when tenant does not exist', :domainlist => false do
subject(:response) do
response = provider.exists?
it 'exists? should be false' do
expect(provider.exists?).to be_falsey
end
it { expect(response).to be_falsey }
end
describe '#instances', :domainlist => true do
it 'finds every tenant' do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
provider_class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', '--long')
.returns('"ID","Name","Domain ID","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","project_one","domain_one_id","Project One",True
"2cb05cfed7c24279be884ba4f6520262","project_one","domain_two_id","Project One, domain Two",True
')
instances = provider.class.instances
expect(instances[0].name).to eq('project_one::domain_one')
expect(instances[0].domain).to eq('domain_one')
expect(instances[1].name).to eq('project_one::domain_two')
expect(instances[1].domain).to eq('domain_two')
end
end
describe '#prefetch' do
before(:each) do
provider_class.expects(:domain_name_from_id).with('default').returns('Default')
provider_class.expects(:domain_name_from_id).with('domain_two_id').returns('domain_two')
# There are one for self.instance and one for each Puppet::Type.type calls.
Puppet::Provider::Keystone.expects(:default_domain).times(3).returns('Default')
provider.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
"1cb05cfed7c24279be884ba4f6520262","project_one","default","A project",True
"2cb05cfed7c24279be884ba4f6520262","project_one","domain_two_id","A domain_two",True
')
provider.class.expects(:openstack)
.with('domain', 'show', '--format', 'shell', 'foo_domain')
.returns('description=""
enabled="True"
id="foo_domain_id"
name="foo_domain"
')
provider.class.expects(:openstack)
.with('domain', 'show', '--format', 'shell', 'bar_domain')
.returns('description=""
enabled="True"
id="bar_domain_id"
name="bar_domain"
')
instances = provider.class.instances
expect(instances[0].name).to eq('foo::foo_domain')
expect(instances[0].domain).to eq('foo_domain')
expect(instances[1].name).to eq('foo::bar_domain')
end
let(:resources) do
Puppet::Provider::Keystone.expects(:default_domain).times(4).returns('Default')
[Puppet::Type.type(:keystone_tenant).new(:title => 'project_one', :ensure => :absent),
Puppet::Type.type(:keystone_tenant).new(:title => 'non_existant', :ensure => :absent)]
end
include_examples 'prefetch the resources'
end
describe '#flush' do
context '.enable' do
describe '-> false' do
it 'properly set enable to false' do
provider_class.expects(:openstack)
.with('project', 'set', ['37b7086693ec482389799da5dc546fa4', '--disable'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.enabled = :false
provider.flush
end
end
describe '-> true' do
it 'properly set enable to true' do
provider_class.expects(:openstack)
.with('project', 'set', ['37b7086693ec482389799da5dc546fa4', '--enable'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.enabled = :true
provider.flush
end
end
end
context '.description' do
it 'change the description' do
provider_class.expects(:openstack)
.with('project', 'set', ['37b7086693ec482389799da5dc546fa4',
'--description=new description'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.expects(:resource).returns(:description => 'new description')
provider.description = 'new description'
provider.flush
end
end
context '.enable/description' do
it 'properly change the enable and the description' do
provider_class.expects(:openstack)
.with('project', 'set', ['37b7086693ec482389799da5dc546fa4', '--disable',
'--description=new description'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.expects(:resource).returns(:description => 'new description')
provider.enabled = :false
provider.description = 'new description'
provider.flush
end
end
end
end
describe 'v3 domains with no domain in resource', :domainlist => true do
let(:tenant_attrs) do
context 'when managing a tenant using v3 domain' do
describe '#create' do
describe 'with domain in resource', :domainlist => false do
before(:each) do
provider_class.expects(:openstack)
.with('project', 'create', '--format', 'shell', ['project_one', '--enable', '--description', 'Project One', '--domain', 'domain_one'])
.returns('description="Project One"
enabled="True"
name="project_one"
id="project-id"
domain_id="domain_one_id"
')
end
include_examples 'create the correct resource', [
{
:name => 'foo',
:description => 'foo',
'expected_results' => {
:domain => 'domain_one',
:id => 'project-id',
:name => 'project_one'
}
},
{
:name => 'domain_one',
:times => 2,
:attributes => {
'Default' => {
:title => 'project_one',
:description => 'Project One',
: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',
'domain in parameter' => {
:name => 'project_one',
:description => 'Project One',
:ensure => 'present',
:enabled => 'True',
:domain => 'foo_domain'
:domain => 'domain_one',
:default_domain => 1
}
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")
expect(provider.name).to eq('foo::foo_domain')
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',
'domain in title' => {
:title => 'project_one::domain_one',
:description => 'Project One',
:ensure => 'present',
:enabled => 'True',
:domain => 'foo_domain'
:default_domain => 1
}
},
{
'domain in parameter override domain in title' => {
:title => 'project_one::domain_two',
:description => 'Project One',
:ensure => 'present',
:enabled => 'True',
:domain => 'domain_one',
:default_domain => 1
}
},
]
end
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")
expect(provider.name).to eq('foo::bar_domain')
describe '#prefetch' do
before(:each) do
provider_class.expects(:domain_name_from_id)
.with('domain_one_id').returns('domain_one')
provider_class.expects(:domain_name_from_id)
.with('domain_two_id').returns('domain_two')
provider_class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', '--long')
.returns('"ID","Name","Domain ID","Description","Enabled"
"1cb05cfed7c24279be884ba4f6520262","name","domain_one_id","A project_one",True
"2cb05cfed7c24279be884ba4f6520262","project_one","domain_two_id","A domain_two",True
')
end
let(:resources) do
# 1 by resource + 3 in prefetch and instance list.
Puppet::Provider::Keystone.expects(:default_domain).times(5).returns('Default')
[
Puppet::Type.type(:keystone_tenant)
.new(:title => 'name::domain_one', :ensure => :absent),
Puppet::Type.type(:keystone_tenant)
.new(:title => 'noex::domain_one', :ensure => :absent)
]
end
include_examples 'prefetch the resources'
end
context 'different name, identical resource' do
let(:resources) do
Puppet::Provider::Keystone.expects(:default_domain).times(2).returns('Default')
[
Puppet::Type.type(:keystone_tenant)
.new(:title => 'name::domain_one', :ensure => :present),
Puppet::Type.type(:keystone_tenant)
.new(:title => 'name', :domain => 'domain_one', :ensure => :present)
]
end
include_examples 'detect duplicate resource'
end
end
end

View File

@ -25,19 +25,20 @@ describe provider_class do
project_class.reset
end
let(:user_attrs) do
let(:resource_attrs) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
{
:name => 'user1',
:ensure => :present,
:enabled => 'True',
:password => 'secret',
:email => 'user1@example.com',
:domain => 'domain1',
:domain => 'domain1'
}
end
let(:resource) do
Puppet::Type::Keystone_user.new(user_attrs)
Puppet::Type::Keystone_user.new(resource_attrs)
end
let(:provider) do
@ -84,14 +85,14 @@ username="user1"
describe '#instances' do
it 'finds every user' do
provider.class.expects(:openstack)
provider_class.expects(:openstack)
.with('user', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Project Id","Domain","Description","Email","Enabled"
"user1_id","user1","project1_id","domain1_id","user1 description","user1@example.com",True
"user2_id","user2","project2_id","domain2_id","user2 description","user2@example.com",True
"user3_id","user3","project3_id","domain3_id","user3 description","user3@example.com",True
')
provider.class.expects(:openstack)
provider_class.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"default","Default",True,"default"
@ -99,51 +100,95 @@ username="user1"
"domain2_id","domain2",True,"domain2"
"domain3_id","domain3",True,"domain3"
')
provider.class.expects(:openstack)
.with('domain', 'show', '--format', 'shell', 'domain1')
.returns('description=""
enabled="True"
id="domain1_id"
name="domain1"
')
provider.class.expects(:openstack)
.with('domain', 'show', '--format', 'shell', 'domain2')
.returns('description=""
enabled="True"
id="domain2_id"
name="domain2"
')
provider.class.expects(:openstack)
.with('domain', 'show', '--format', 'shell', 'domain3')
.returns('description=""
enabled="True"
id="domain3_id"
name="domain3"
')
# for self.instances to create the name string in
# resource_to_name
Puppet::Provider::Keystone.expects(:default_domain).times(3).returns('Default')
instances = provider.class.instances
expect(instances.count).to eq(3)
expect(instances[0].name).to eq('user1::domain1')
expect(instances[0].domain).to eq('domain1')
expect(instances[1].name).to eq('user2::domain2')
expect(instances[1].domain).to eq('domain2')
expect(instances[2].name).to eq('user3::domain3')
expect(instances[2].domain).to eq('domain3')
end
end
describe '#prefetch' do
let(:resources) do
[Puppet::Type.type(:keystone_user).new(:title => 'exists', :ensure => :present),
Puppet::Type.type(:keystone_user).new(:title => 'non_exists', :ensure => :present)]
end
before(:each) do
provider_class.expects(:domain_name_from_id).with('default')
.returns('Default')
provider_class.expects(:domain_name_from_id).with('domain2_id')
.returns('bar')
Puppet::Provider::Keystone.expects(:default_domain).times(7)
.returns('Default')
provider_class.expects(:openstack)
.with('user', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Project Id","Domain","Description","Email","Enabled"
"user1_id","exists","project1_id","default","user1 description","user1@example.com",True
"user2_id","user2","project2_id","domain2_id","user2 description","user2@example.com",True
')
end
include_examples 'prefetch the resources'
end
describe '#flush' do
context '.enable' do
describe '-> false' do
it 'properly set enable to false' do
provider_class.expects(:openstack)
.with('user', 'set', ['--disable', '37b7086693ec482389799da5dc546fa4'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.enabled = :false
provider.flush
end
end
describe '-> true' do
it 'properly set enable to true' do
provider_class.expects(:openstack)
.with('user', 'set', ['--enable', '37b7086693ec482389799da5dc546fa4'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.enabled = :true
provider.flush
end
end
end
context '.email' do
it 'change the mail' do
provider_class.expects(:openstack)
.with('user', 'set', ['--email', 'new email',
'37b7086693ec482389799da5dc546fa4'])
.returns('""')
provider.expects(:id).returns('37b7086693ec482389799da5dc546fa4')
provider.expects(:resource).returns(:email => 'new email')
provider.email = 'new email'
provider.flush
end
end
end
end
describe "#password" do
let(:user_attrs) do
describe '#password' do
let(:resource_attrs) do
{
:name => 'foo',
:ensure => 'present',
:enabled => 'True',
:password => 'foo',
:email => 'foo@example.com',
:domain => 'domain1',
:domain => 'domain1'
}
end
let(:resource) do
Puppet::Type::Keystone_user.new(user_attrs)
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type::Keystone_user.new(resource_attrs)
end
let :provider do
@ -151,7 +196,6 @@ name="domain3"
end
it 'checks the password' do
provider.instance_variable_get('@property_hash')[:id] = 'user1_id'
mock_creds = Puppet::Provider::Openstack::CredentialsV3.new
mock_creds.auth_url='http://127.0.0.1:5000'
mock_creds.password='foo'
@ -160,21 +204,9 @@ name="domain3"
mock_creds.project_id='project-id-1'
Puppet::Provider::Openstack::CredentialsV3.expects(:new).returns(mock_creds)
provider.class.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"default","Default",True,"default"
"domain1_id","domain1",True,"domain1"
"domain2_id","domain2",True,"domain2"
')
provider.class.expects(:openstack)
provider_class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--user', 'user1_id', '--long'])
.returns('"ID","Name","Domain ID","Description","Enabled"
"project2_id","project2","domain2_id","",True
')
Puppet::Provider::Openstack.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--user', 'project1_id', '--long'])
.returns('"ID","Name","Domain ID","Description","Enabled"
"project-id-1","foo","domain1_id","foo",True
')
Puppet::Provider::Openstack.expects(:openstack)
@ -184,20 +216,13 @@ e664a386befa4a30878dcef20e79f167
8dce2ae9ecd34c199d2877bf319a3d06
ac43ec53d5a74a0b9f51523ae41a29f0
')
provider.expects(:id).times(2).returns('user1_id')
password = provider.password
expect(password).to eq('foo')
end
it 'fails the password check' do
provider.instance_variable_get('@property_hash')[:id] = 'user1_id'
provider.class.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"default","Default",True,"default"
"domain1_id","domain1",True,"domain1"
"domain2_id","domain2",True,"domain2"
')
Puppet::Provider::Openstack.expects(:openstack)
provider_class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--user', 'user1_id', '--long'])
.returns('"ID","Name","Domain ID","Description","Enabled"
"project-id-1","foo","domain1_id","foo",True
@ -205,6 +230,7 @@ ac43ec53d5a74a0b9f51523ae41a29f0
Puppet::Provider::Openstack.expects(:openstack)
.with('token', 'issue', ['--format', 'value'])
.raises(Puppet::ExecutionFailure, 'HTTP 401 invalid authentication')
provider.expects(:id).times(2).returns('user1_id')
password = provider.password
expect(password).to eq(nil)
end
@ -219,7 +245,7 @@ ac43ec53d5a74a0b9f51523ae41a29f0
mock_creds.user_id='project1_id'
mock_creds.domain_name='domain1'
Puppet::Provider::Openstack::CredentialsV3.expects(:new).returns(mock_creds)
Puppet::Provider::Openstack.expects(:openstack)
provider_class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--user', 'project1_id', '--long'])
.returns('"ID","Name","Domain ID","Description","Enabled"
')
@ -239,7 +265,8 @@ ac43ec53d5a74a0b9f51523ae41a29f0
describe 'when updating a user with unmanaged password' do
let(:user_attrs) do
let(:resource_attrs) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
{
:name => 'user1',
:ensure => 'present',
@ -247,12 +274,12 @@ ac43ec53d5a74a0b9f51523ae41a29f0
:password => 'secret',
:replace_password => 'False',
:email => 'user1@example.com',
:domain => 'domain1',
:domain => 'domain1'
}
end
let(:resource) do
Puppet::Type::Keystone_user.new(user_attrs)
Puppet::Type::Keystone_user.new(resource_attrs)
end
let :provider do
@ -265,42 +292,10 @@ ac43ec53d5a74a0b9f51523ae41a29f0
end
end
describe 'v3 domains with no domain in resource' do
let(:user_attrs) do
{
:name => 'user1',
:ensure => 'present',
:enabled => 'True',
:password => 'secret',
:email => 'user1@example.com',
}
end
it 'adds default domain to commands' do
mock = {
'identity' => {'default_domain_id' => 'domain1_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('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"domain1_id","domain1",True,"domain1"
"domain2_id","domain2",True,"domain2"
')
provider.class.expects(:openstack)
.with('project', 'list', '--quiet', '--format', 'csv', ['--user', 'user1_id', '--long'])
.returns('"ID","Name"
')
provider.class.expects(:openstack)
.with('role', 'show', '--format', 'shell', '_member_')
.returns('
name="_member_"
')
provider.class.expects(:openstack)
.with('role', 'add', ['_member_', '--project', 'project1_id', '--user', 'user1_id'])
provider.class.expects(:openstack)
describe 'when managing an user using v3 domains' do
describe '#create' do
before(:each) do
provider_class.expects(:openstack)
.with('user', 'create', '--format', 'shell', ['user1', '--enable', '--password', 'secret', '--email', 'user1@example.com', '--domain', 'domain1'])
.returns('email="user1@example.com"
enabled="True"
@ -308,96 +303,103 @@ id="user1_id"
name="user1"
username="user1"
')
provider.class.expects(:openstack)
.with('project', 'show', '--format', 'shell', ['project1', '--domain', 'domain2'])
.returns('name="project1"
id="project1_id"
')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.id).to eq("user1_id")
end
end
describe 'v3 domains with domain in resource' do
let(:user_attrs) do
include_examples 'create the correct resource', [
{
'expected_results' => {
:domain => 'domain1',
:id => 'user1_id',
:name => 'user1',
:domain => 'domain1'
}
},
{
:name => 'domain1',
:times => 2,
:attributes => {
'Default' => {
:title => 'user1',
:ensure => 'present',
:enabled => 'True',
:password => 'secret',
:email => 'user1@example.com'
}
}
},
{
'domain in parameter' => {
:name => 'user1',
:ensure => 'present',
:enabled => 'True',
:password => 'secret',
:email => 'user1@example.com',
:domain => 'domain1',
:default_domain => 1
}
end
it 'uses given domain in commands' do
provider.class.expects(:openstack)
.with('user', 'create', '--format', 'shell', ['user1', '--enable', '--password', 'secret', '--email', 'user1@example.com', '--domain', 'domain1'])
.returns('email="user1@example.com"
enabled="True"
id="user1_id"
name="user1"
username="user1"
')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.id).to eq("user1_id")
end
end
describe 'v3 domains with domain in name/title' do
let(:user_attrs) do
},
{
:name => 'user1::domain1',
'domain in title' => {
:title => 'user1::domain1',
:ensure => 'present',
:enabled => 'True',
:password => 'secret',
:email => 'user1@example.com',
:default_domain => 1
}
end
it 'uses given domain in commands' do
provider.class.expects(:openstack)
.with('user', 'create', '--format', 'shell', ['user1', '--enable', '--password', 'secret', '--email', 'user1@example.com', '--domain', 'domain1'])
.returns('email="user1@example.com"
enabled="True"
id="user1_id"
name="user1"
username="user1"
')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.id).to eq("user1_id")
expect(provider.name).to eq('user1::domain1')
end
end
describe 'v3 domains with domain in name/title and in resource' do
let(:user_attrs) do
},
{
:name => 'user1::domain1',
'domain in parameter override domain in title' => {
:title => 'user1::foobar',
:ensure => 'present',
:enabled => 'True',
:password => 'secret',
:email => 'user1@example.com',
:domain => 'domain1',
:default_domain => 1
}
},
]
end
it 'uses the resource domain in commands' do
provider.class.expects(:openstack)
.with('user', 'create', '--format', 'shell', ['user1', '--enable', '--password', 'secret', '--email', 'user1@example.com', '--domain', 'domain1'])
.returns('email="user1@example.com"
enabled="True"
id="user1_id"
name="user1"
username="user1"
describe '#prefetch' do
let(:resources) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
[
Puppet::Type.type(:keystone_user)
.new(:title => 'exists::domain1', :ensure => :present),
Puppet::Type.type(:keystone_user)
.new(:title => 'non_exists::domain1', :ensure => :present)
]
end
before(:each) do
# Used to make the final display name
provider_class.expects(:default_domain).times(3).returns('Default')
provider_class.expects(:domain_name_from_id)
.with('domain1_id').returns('domain1')
provider_class.expects(:domain_name_from_id)
.with('domain2_id').returns('bar')
provider_class.expects(:openstack)
.with('user', 'list', '--quiet', '--format', 'csv', ['--long'])
.returns('"ID","Name","Project Id","Domain","Description","Email","Enabled"
"user1_id","exists","project1_id","domain1_id","user1 description","user1@example.com",True
"user2_id","user2","project2_id","domain2_id","user2 description","user2@example.com",True
')
provider.create
expect(provider.exists?).to be_truthy
expect(provider.id).to eq("user1_id")
expect(provider.name).to eq('user1::domain1')
end
include_examples 'prefetch the resources'
end
context 'different name, identical resource' do
let(:resources) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
[
Puppet::Type.type(:keystone_user)
.new(:title => 'name::domain_one', :ensure => :present),
Puppet::Type.type(:keystone_user)
.new(:title => 'name', :domain => 'domain_one', :ensure => :present)
]
end
include_examples 'detect duplicate resource'
end
end
end

View File

@ -20,16 +20,17 @@ describe provider_class do
after(:each) { provider_class.reset }
describe 'when managing a user\'s role' do
let(:user_role_attrs) do
let(:resource_attrs) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
{
:name => 'user1::domain1@project1::domain1',
:title => 'user1::domain1@project1::domain1',
:ensure => 'present',
:roles => ['role1', 'role2'],
:roles => %w(role1 role2)
}
end
let(:resource) do
Puppet::Type::Keystone_user_role.new(user_role_attrs)
Puppet::Type::Keystone_user_role.new(resource_attrs)
end
let(:provider) do
@ -37,31 +38,86 @@ describe provider_class do
end
describe '#create' do
it 'adds all the roles to the user' do
before(:each) do
provider.class.expects(:openstack)
.with('role', 'list', '--quiet', '--format', 'csv', ['--project', 'project1_id', '--user', 'user1_id' ])
provider_class.expects(:openstack)
.with('role', 'list', '--quiet', '--format', 'csv',
['--project', 'project1_id', '--user', 'user1_id' ])
.returns('"ID","Name","Project","User"
"role1_id","role1","project1","user1"
"role2_id","role2","project1","user1"
')
provider.class.expects(:openstack)
.with('role', 'add', ['role1', '--project', 'project1_id', '--user', 'user1_id'])
provider.class.expects(:openstack)
.with('role', 'add', ['role2', '--project', 'project1_id', '--user', 'user1_id'])
provider.class.expects(:openstack)
.with('project', 'show', '--format', 'shell', ['project1', '--domain', 'domain1'])
provider_class.expects(:openstack)
.with('role', 'add',
['role1', '--project', 'project1_id', '--user', 'user1_id'])
provider_class.expects(:openstack)
.with('role', 'add',
['role2', '--project', 'project1_id', '--user', 'user1_id'])
provider_class.expects(:openstack)
.with('project', 'show', '--format', 'shell',
['project1', '--domain', 'domain1'])
.returns('name="project1"
id="project1_id"
')
provider.class.expects(:openstack)
.with('user', 'show', '--format', 'shell', ['user1', '--domain', 'domain1'])
provider_class.expects(:openstack)
.with('user', 'show', '--format', 'shell',
['user1', '--domain', 'domain1'])
.returns('name="user1"
id="user1_id"
')
provider.create
expect(provider.exists?).to be_truthy
end
include_examples 'create the correct resource', [
{
'expected_results' => {}
},
{
:name => 'domain1',
:times => 4,
:attributes => {
'Default' => {
:title => 'user1@project1',
:ensure => 'present',
:roles => %w(role1 role2)
}
}
},
{
'all in the title' => {
:title => 'user1::domain1@project1::domain1',
:ensure => 'present',
:roles => %w(role1 role2),
:default_domain => 2
}
},
{
'user complete and project in the params' => {
:title => 'user1::domain1@project1',
:ensure => 'present',
:project_domain => 'domain1',
:roles => %w(role1 role2),
:default_domain => 2
}
},
{
'user and project in the params' => {
:title => 'user1@project1',
:ensure => 'present',
:project_domain => 'domain1',
:user_domain => 'domain1',
:roles => %w(role1 role2),
:default_domain => 2
}
},
{
'project complet and user in the params' => {
:title => 'user1@project1::domain1',
:ensure => 'present',
:user_domain => 'domain1',
:roles => %w(role1 role2),
:default_domain => 2
}
},
]
end
describe '#destroy' do
@ -108,7 +164,7 @@ id="project1_id"
.returns('name="user1"
id="user1_id"
')
response = provider.exists?
provider.exists?
end
it { is_expected.to be_truthy }
@ -119,10 +175,12 @@ id="user1_id"
project_class = Puppet::Type.type(:keystone_tenant).provider(:openstack)
user_class = Puppet::Type.type(:keystone_user).provider(:openstack)
usermock = user_class.new({:id => 'user1_id', :name => 'user1'})
usermock = user_class.new(:id => 'user1_id', :name => 'user1')
user_class.expects(:instances).with(any_parameters).returns([usermock])
projectmock = project_class.new({:id => 'project1_id', :name => 'project1'})
projectmock = project_class.new(:id => 'project1_id', :name => 'project1')
# 2 for tenant and user and 2 for user_role
Puppet::Provider::Keystone.expects(:default_domain).times(4).returns('Default')
project_class.expects(:instances).with(any_parameters).returns([projectmock])
provider.class.expects(:openstack)
@ -135,26 +193,31 @@ id="user1_id"
.with('role assignment', 'list', '--quiet', '--format', 'csv', [])
.returns('
"Role","User","Group","Project","Domain"
"role1-id","user1_id","","project1_id",""
"role2-id","user1_id","","project1_id",""
"role1-id","user1_id","","project1_id","Default"
"role2-id","user1_id","","project1_id","Default"
')
instances = provider.class.instances
expect(instances.count).to eq(1)
expect(instances[0].name).to eq('user1@project1')
expect(instances[0].roles).to eq(['role1', 'role2'])
expect(instances[0].user).to eq('user1')
expect(instances[0].user_domain).to eq('Default')
expect(instances[0].project).to eq('project1')
expect(instances[0].project_domain).to eq('Default')
end
end
describe '#roles=' do
let(:user_role_attrs) do
let(:resource_attrs) do
{
:name => 'foo@foo',
:title => 'foo@foo',
:ensure => 'present',
:roles => ['one', 'two'],
}
end
it 'applies new roles' do
Puppet::Provider::Keystone.expects(:default_domain).times(4).returns('Default')
provider.instance_variable_get('@property_hash')[:roles] = ['foo', 'bar']
provider.class.expects(:openstack)
.with('role', 'remove', ['foo', '--project', 'project1_id', '--user', 'user1_id'])
@ -164,11 +227,6 @@ id="user1_id"
.with('role', 'add', ['one', '--project', 'project1_id', '--user', 'user1_id'])
provider.class.expects(:openstack)
.with('role', 'add', ['two', '--project', 'project1_id', '--user', 'user1_id'])
provider.class.expects(:openstack)
.with('domain', 'list', '--quiet', '--format', 'csv', [])
.returns('"ID","Name","Enabled","Description"
"default","Default",True,"Default Domain"
')
provider.class.expects(:openstack)
.with('project', 'show', '--format', 'shell', ['foo', '--domain', 'Default'])
.returns('name="foo"
@ -179,8 +237,25 @@ id="project1_id"
.returns('name="foo"
id="user1_id"
')
provider.roles=(['one', 'two'])
end
provider.roles = %w(one two)
end
end
context 'different name, identical resource' do
let(:resources) do
Puppet::Provider::Keystone.expects(:default_domain).times(4).returns('Default')
[
Puppet::Type.type(:keystone_user_role)
.new(:title => 'user::domain_user@project::domain_project',
:ensure => :present),
Puppet::Type.type(:keystone_user_role)
.new(:title => 'user@project',
:user_domain => 'domain_user',
:project_domain => 'domain_project',
:ensure => :present)
]
end
include_examples 'detect duplicate resource'
end
end
end

View File

@ -4,22 +4,63 @@ 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',
let(:project) do
Puppet::Type.type(:keystone_tenant).new(
:id => 'blah',
:name => 'project',
:domain => 'domain_project'
)
@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/)
it 'should not accept id property' do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
expect { project }.to raise_error(Puppet::Error,
/This is a read only property/)
end
it 'should be in sync if domain is the same' do
expect(@domain.insync?('foo-domain')).to be true
describe 'name::domain' do
include_examples 'parse title correctly',
:name => 'name',
:domain => 'domain',
:calling_default => 1
end
describe 'name' do
include_examples 'parse title correctly',
:name => 'name', :domain => 'Default', :calling_default => 2
end
describe 'name::domain::extra' do
include_examples 'croak on the title'
end
describe '#autorequire' do
let(:domain_good) do
Puppet::Type.type(:keystone_domain).new(:title => 'domain_project')
end
let(:domain_bad) do
Puppet::Type.type(:keystone_domain).new(:title => 'another_domain')
end
context 'domain autorequire from title' do
let(:project) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_tenant).new(:title => 'tenant::domain_project')
end
describe 'should require the correct domain' do
let(:resources) { [project, domain_good, domain_bad] }
include_examples 'autorequire the correct resources'
end
end
context 'domain autorequire from parameter' do
let(:project) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_tenant).new(:title => 'tenant',
:domain => 'domain_project')
end
describe 'should require the correct domain' do
let(:resources) { [project, domain_good, domain_bad] }
include_examples 'autorequire the correct resources'
end
end
end
end

View File

@ -5,8 +5,9 @@ require 'puppet/type/keystone_user_role'
describe Puppet::Type.type(:keystone_user_role) do
before :each do
Puppet::Provider::Keystone.expects(:default_domain).times(4).returns('Default')
@user_roles = Puppet::Type.type(:keystone_user_role).new(
:name => 'foo@bar',
:title => 'foo@bar',
:roles => ['a', 'b']
)
@ -25,4 +26,192 @@ describe Puppet::Type.type(:keystone_user_role) do
expect(@roles.insync?(['b', 'a'])).to be true
end
['user', 'user@REALM', 'us:er'].each do |user|
describe "#{user}::user_domain@project::project_domain" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'user_domain',
:project => 'project',
:project_domain => 'project_domain',
:domain => PuppetX::Keystone::CompositeNamevar::Unset,
:calling_default => 2
end
describe "#{user}::user_domain@::domain" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'user_domain',
:project => PuppetX::Keystone::CompositeNamevar::Unset,
:project_domain => PuppetX::Keystone::CompositeNamevar::Unset,
:domain => 'domain',
:calling_default => 2
end
describe "#{user}::user_domain@project" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'user_domain',
:project => 'project',
:project_domain => 'Default',
:domain => PuppetX::Keystone::CompositeNamevar::Unset,
:calling_default => 3
end
describe "#{user}@project::project_domain" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'Default',
:project => 'project',
:project_domain => 'project_domain',
:domain => PuppetX::Keystone::CompositeNamevar::Unset,
:calling_default => 3
end
describe "#{user}@::domain" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'Default',
:project => PuppetX::Keystone::CompositeNamevar::Unset,
:project_domain => PuppetX::Keystone::CompositeNamevar::Unset,
:domain => 'domain',
:calling_default => 3
end
describe "#{user}@project" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'Default',
:project => 'project',
:project_domain => 'Default',
:domain => PuppetX::Keystone::CompositeNamevar::Unset,
:calling_default => 4
end
describe "#{user}@proj:ect" do
include_examples 'parse title correctly',
:user => user,
:user_domain => 'Default',
:project => 'proj:ect',
:project_domain => 'Default',
:domain => PuppetX::Keystone::CompositeNamevar::Unset,
:calling_default => 4
end
end
describe 'name::domain::foo@project' do
include_examples 'croak on the title'
end
describe 'name::dom@ain@project' do
include_examples 'croak on the title'
end
describe 'name::domain@' do
include_examples 'croak on the title'
end
describe 'name::domain@project::' do
include_examples 'croak on the title'
end
describe '@project:project_domain' do
include_examples 'croak on the title'
end
describe '#autorequire' do
let(:project_good) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
Puppet::Type.type(:keystone_tenant).new(:title => 'bar')
end
let(:project_good_ml) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
Puppet::Type.type(:keystone_tenant).new(:title => 'blah',
:name => 'bar')
end
let(:project_good_fq) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_tenant).new(:title => 'bar::Default')
end
let(:project_bad) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_tenant).new(:title => 'bar::other_domain')
end
let(:user_good) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
Puppet::Type.type(:keystone_user).new(:title => 'foo')
end
let(:user_good_ml) do
Puppet::Provider::Keystone.expects(:default_domain).twice.returns('Default')
Puppet::Type.type(:keystone_user).new(:title => 'blah',
:name => 'foo')
end
let(:user_good_fq) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_user).new(:title => 'foo::Default')
end
let(:user_bad) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_user).new(:title => 'foo::other_domain')
end
let(:domain) do
Puppet::Type.type(:keystone_domain).new(:title => 'bar')
end
context 'tenant' do
describe 'normal tenant title' do
let(:resources) { [@user_roles, project_good, project_bad] }
include_examples 'autorequire the correct resources',
:default_domain => 2
end
describe 'meaningless tenant title' do
let(:resources) { [@user_roles, project_good_ml, project_bad] }
include_examples 'autorequire the correct resources',
:default_domain => 1
end
describe 'meaningless tenant title' do
let(:resources) { [@user_roles, project_good_fq, project_bad] }
include_examples 'autorequire the correct resources',
:default_domain => 1
end
end
context 'domain' do
it 'should not autorequire any tenant' do
catalog.add_resource @user_roles, domain
dependency = @user_roles.autorequire
expect(dependency.size).to eq(0)
end
let(:resources) { [@user_roles, project_good, project_bad] }
include_examples 'autorequire the correct resources'
end
context 'user' do
describe 'normal user title' do
let(:resources) { [@user_roles, user_good, user_bad] }
include_examples 'autorequire the correct resources'
end
describe 'meaningless user title' do
let(:resources) { [@user_roles, user_good_ml, user_bad] }
include_examples 'autorequire the correct resources'
end
describe 'fq user title' do
let(:resources) { [@user_roles, user_good_fq, user_bad] }
include_examples 'autorequire the correct resources'
end
end
end
describe 'parameter conflict' do
let(:user_roles) do
Puppet::Provider::Keystone.expects(:default_domain).at_least(0).returns('Default')
Puppet::Type.type(:keystone_user_role).new(
:title => 'user@::domain',
:project => 'project',
:roles => %w(a b)
)
end
let(:domain) { user_roles.parameter('domain') }
it 'should not allow domain and project at the same time' do
expect { domain.validate }.to raise_error(Puppet::ResourceError, /Cannot define both project and domain/)
end
end
end

View File

@ -4,22 +4,49 @@ 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',
describe 'name::domain' do
include_examples 'parse title correctly',
:name => 'name', :domain => 'domain', :calling_default => 1
end
describe 'name' do
include_examples 'parse title correctly',
:name => 'name', :domain => 'Default', :calling_default => 2
end
describe 'name::domain::foo' do
include_examples 'croak on the title'
end
describe '#autorequire' do
let(:domain_good) do
Puppet::Type.type(:keystone_domain).new(:title => 'domain_user')
end
let(:domain_bad) do
Puppet::Type.type(:keystone_domain).new(:title => 'another_domain')
end
context 'domain autorequire from title' do
let(:user) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_user).new(:title => 'foo::domain_user')
end
describe 'should require the correct domain' do
let(:resources) { [user, domain_good, domain_bad] }
include_examples 'autorequire the correct resources'
end
end
context 'domain autorequire from parameter' do
let(:user) do
Puppet::Provider::Keystone.expects(:default_domain).returns('Default')
Puppet::Type.type(:keystone_user).new(
:title => 'foo',
:domain => 'domain_user'
)
@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/)
describe 'should require the correct domain' do
let(:resources) { [user, domain_good, domain_bad] }
include_examples 'autorequire the correct resources'
end
end
it 'should be in sync if domain is the same' do
expect(@domain.insync?('foo-domain')).to be true
end
end