desc "Provider to manage keystone users."
- @credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
+ @credentials = Puppet::Provider::Openstack::CredentialsV3.new
def initialize(value={})
super(value)
end
def create
- properties = [resource[:name]]
+ # see if resource[:domain], or user specified as user::domain
+ user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
+ properties = [user_name]
if resource[:enabled] == :true
properties << '--enable'
elsif resource[:enabled] == :false
if resource[:password]
properties << '--password' << resource[:password]
end
- if resource[:tenant]
- properties << '--project' << resource[:tenant]
- end
if resource[:email]
properties << '--email' << resource[:email]
end
- self.class.request('user', 'create', properties)
+ if user_domain
+ properties << '--domain'
+ properties << user_domain
+ end
+ @property_hash = self.class.request('user', 'create', properties)
+ @property_hash[:domain] = user_domain
+ if resource[:tenant]
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{resource[:tenant]}")[:id]
+ set_project(resource[:tenant], project_id)
+ end
@property_hash[:ensure] = :present
end
def destroy
- self.class.request('user', 'delete', @property_hash[:id])
+ self.class.request('user', 'delete', id)
@property_hash.clear
end
res = resource[:password]
else
# Password validation
- credentials = Puppet::Provider::Openstack::CredentialsV2_0.new
- credentials.auth_url = self.class.get_endpoint
- credentials.password = resource[:password]
- credentials.project_name = resource[:tenant]
- credentials.username = resource[:name]
+ credentials = Puppet::Provider::Openstack::CredentialsV3.new
+ credentials.auth_url = self.class.get_endpoint
+ credentials.password = resource[:password]
+ credentials.user_id = id
+ # NOTE: The only reason we use username is so that the openstack provider
+ # will know we are doing v3password auth - otherwise, it is not used. The
+ # user_id uniquely identifies the user including domain.
+ credentials.username, unused = self.class.name_and_domain(resource[:name], domain)
+ # Need to specify a project id to get a project scoped token. List
+ # all of the projects for the user, and use the id from the first one.
+ projects = self.class.request('project', 'list', ['--user', id, '--long'])
+ if projects && projects[0] && projects[0][:id]
+ credentials.project_id = projects[0][:id]
+ else
+ # last chance - try a domain scoped token
+ credentials.domain_name = domain
+ end
begin
token = Puppet::Provider::Openstack.request('token', 'issue', ['--format', 'value'], credentials)
rescue Puppet::Error::OpenstackUnauthorizedError
@property_flush[:replace_password] = value
end
+ def find_project_for_user(projname, project_id = nil)
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
+ project_name, project_domain = self.class.name_and_domain(projname, nil, user_domain)
+ self.class.request('project', 'list', ['--user', id, '--long']).each do |project|
+ if (project_id == project[:id]) ||
+ ((projname == project_name) && (project_domain == self.class.domain_name_from_id(project[:domain_id])))
+ return project[:name]
+ end
+ end
+ return nil
+ end
+
+ def set_project(newproject, project_id = nil)
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ unless project_id
+ project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{newproject}")[:id]
+ end
+ # Currently the only way to assign a user to a tenant not using user-create
+ # is to use role-add - this means we also need a role - there is usual
+ # a default role called _member_ which can be used for this purpose. What
+ # usually happens in a puppet module is that immediately after calling
+ # keystone_user, the module will then assign a role to that user. It is
+ # ok for a user to have the _member_ role and another role.
+ default_role = "_member_"
+ begin
+ self.class.request('role', 'show', default_role)
+ rescue
+ self.class.request('role', 'create', default_role)
+ end
+ # finally, assign the user to the project with the role
+ self.class.request('role', 'add', [default_role, '--project', project_id, '--user', id])
+ newproject
+ end
+
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
+ def tenant=(value)
+ @property_hash[:tenant] = set_project(value)
+ end
+
+ # DEPRECATED - To be removed in next release (Liberty)
+ # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
def tenant
return resource[:tenant] if sym_to_bool(resource[:ignore_default_tenant])
# use the one returned from instances
if tenant_name.nil? or tenant_name.empty?
return nil # nothing found, nothing given
end
- # If the user list command doesn't report the project, it might still be there
- # We don't need to know exactly what it is, we just need to know whether it's
- # the one we're trying to set.
- roles = self.class.request('user role', 'list', [resource[:name], '--project', tenant_name])
- if roles.empty?
- return nil
- else
- return tenant_name
- end
+ project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{tenant_name}")[:id]
+ find_project_for_user(tenant_name, project_id)
end
- def tenant=(value)
- self.class.request('user', 'set', [resource[:name], '--project', value])
- rescue Puppet::ExecutionFailure => e
- if e.message =~ /You are not authorized to perform the requested action: LDAP user update/
- # read-only LDAP identity backend - just fall through
- else
- raise e
- end
- # note: read-write ldap will silently fail, not raise an exception
- else
- @property_hash[:tenant] = self.class.set_project(value, resource[:name])
+ def domain
+ @property_hash[:domain]
+ end
+
+ def domain_id
+ @property_hash[:domain_id]
end
def self.instances
- list = request('user', 'list', '--long')
- list.collect do |user|
+ instance_hash = {}
+ request('user', 'list', ['--long']).each do |user|
+ # The field says "domain" but it is really the domain_id
+ domname = domain_name_from_id(user[:domain])
+ if instance_hash.include?(user[:name]) # not unique
+ curdomid = instance_hash[user[:name]][:domain]
+ if curdomid != default_domain_id
+ # Move the user from the short name slot to the long name slot
+ # because it is not in the default domain.
+ curdomname = domain_name_from_id(curdomid)
+ instance_hash["#{user[:name]}::#{curdomname}"] = instance_hash[user[:name]]
+ # Use the short name slot for the new user
+ instance_hash[user[:name]] = user
+ else
+ # Use the long name for the new user
+ instance_hash["#{user[:name]}::#{domname}"] = user
+ end
+ else
+ # Unique (for now) - store in short name slot
+ instance_hash[user[:name]] = user
+ end
+ end
+ instance_hash.keys.collect do |user_name|
+ user = instance_hash[user_name]
new(
- :name => user[:name],
+ :name => user_name,
:ensure => :present,
:enabled => user[:enabled].downcase.chomp == 'true' ? true : false,
:password => user[:password],
- :project => user[:project],
:email => user[:email],
+ :description => user[:description],
+ :domain => domain_name_from_id(user[:domain]),
+ :domain_id => user[:domain],
:id => user[:id]
)
end
def self.prefetch(resources)
users = instances
- resources.keys.each do |name|
- if provider = users.find{ |user| user.name == name }
- resources[name].provider = provider
+ resources.each do |resname, resource|
+ # resname may be specified as just "name" or "name::domain"
+ name, resdomain = name_and_domain(resname, resource[:domain])
+ provider = users.find do |user|
+ # have a match if the full instance name matches the full resource name, OR
+ # the base resource name matches the base instance name, and the
+ # resource domain matches the instance domain
+ username, user_domain = name_and_domain(user.name, user.domain)
+ (user.name == resname) ||
+ ((username == name) && (user_domain == resdomain))
end
+ resource.provider = provider if provider
end
end
- def self.set_project(newproject, name)
- # some backends do not store the project/tenant in the user object, so we have to
- # to modify the project/tenant instead
- # First, see if the project actually needs to change
- roles = request('user role', 'list', [name, '--project', newproject])
- unless roles.empty?
- return # if already set, just skip
- end
- # Currently the only way to assign a user to a tenant not using user-create
- # is to use user-role-add - this means we also need a role - there is usual
- # a default role called _member_ which can be used for this purpose. What
- # usually happens in a puppet module is that immediately after calling
- # keystone_user, the module will then assign a role to that user. It is
- # ok for a user to have the _member_ role and another role.
- default_role = "_member_"
- begin
- request('role', 'show', [default_role])
- rescue
- debug("Keystone role #{default_role} does not exist - creating")
- request('role', 'create', [default_role])
- end
- request('role', 'add', [default_role, '--project', newproject, '--user', name])
- end
end