1 require 'puppet/provider/keystone'
3 Puppet::Type.type(:keystone_user).provide(
5 :parent => Puppet::Provider::Keystone
8 desc "Provider to manage keystone users."
10 @credentials = Puppet::Provider::Openstack::CredentialsV3.new
12 def initialize(value={})
18 # see if resource[:domain], or user specified as user::domain
19 user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
20 properties = [user_name]
21 if resource[:enabled] == :true
22 properties << '--enable'
23 elsif resource[:enabled] == :false
24 properties << '--disable'
26 if resource[:password]
27 properties << '--password' << resource[:password]
30 properties << '--email' << resource[:email]
33 properties << '--domain'
34 properties << user_domain
36 @property_hash = self.class.request('user', 'create', properties)
37 @property_hash[:domain] = user_domain
39 # DEPRECATED - To be removed in next release (Liberty)
40 # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
41 project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{resource[:tenant]}")[:id]
42 set_project(resource[:tenant], project_id)
44 @property_hash[:ensure] = :present
48 self.class.request('user', 'delete', id)
54 if @property_flush && !@property_flush.empty?
55 options << '--enable' if @property_flush[:enabled] == :true
56 options << '--disable' if @property_flush[:enabled] == :false
57 # There is a --description flag for the set command, but it does not work if the value is empty
58 options << '--password' << resource[:password] if @property_flush[:password]
59 options << '--email' << resource[:email] if @property_flush[:email]
60 # project handled in tenant= separately
62 options << @property_hash[:id]
63 self.class.request('user', 'set', options)
70 @property_hash[:ensure] == :present
75 bool_to_sym(@property_hash[:enabled])
79 @property_flush[:enabled] = value
83 @property_hash[:email]
87 @property_flush[:email] = value
96 return res if resource[:password] == nil
97 if resource[:enabled] == :false || resource[:replace_password] == :false
99 res = resource[:password]
101 # Password validation
102 credentials = Puppet::Provider::Openstack::CredentialsV3.new
103 credentials.auth_url = self.class.get_endpoint
104 credentials.password = resource[:password]
105 credentials.user_id = id
106 # NOTE: The only reason we use username is so that the openstack provider
107 # will know we are doing v3password auth - otherwise, it is not used. The
108 # user_id uniquely identifies the user including domain.
109 credentials.username, unused = self.class.name_and_domain(resource[:name], domain)
110 # Need to specify a project id to get a project scoped token. List
111 # all of the projects for the user, and use the id from the first one.
112 projects = self.class.request('project', 'list', ['--user', id, '--long'])
113 if projects && projects[0] && projects[0][:id]
114 credentials.project_id = projects[0][:id]
116 # last chance - try a domain scoped token
117 credentials.domain_name = domain
120 token = Puppet::Provider::Openstack.request('token', 'issue', ['--format', 'value'], credentials)
121 rescue Puppet::Error::OpenstackUnauthorizedError
122 # password is invalid
124 res = resource[:password] unless token.empty?
131 @property_flush[:password] = value
135 @property_hash[:replace_password]
138 def replace_password=(value)
139 @property_flush[:replace_password] = value
142 def find_project_for_user(projname, project_id = nil)
143 # DEPRECATED - To be removed in next release (Liberty)
144 # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
145 user_name, user_domain = self.class.name_and_domain(resource[:name], resource[:domain])
146 project_name, project_domain = self.class.name_and_domain(projname, nil, user_domain)
147 self.class.request('project', 'list', ['--user', id, '--long']).each do |project|
148 if (project_id == project[:id]) ||
149 ((projname == project_name) && (project_domain == self.class.domain_name_from_id(project[:domain_id])))
150 return project[:name]
156 def set_project(newproject, project_id = nil)
157 # DEPRECATED - To be removed in next release (Liberty)
158 # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
160 project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{newproject}")[:id]
162 # Currently the only way to assign a user to a tenant not using user-create
163 # is to use role-add - this means we also need a role - there is usual
164 # a default role called _member_ which can be used for this purpose. What
165 # usually happens in a puppet module is that immediately after calling
166 # keystone_user, the module will then assign a role to that user. It is
167 # ok for a user to have the _member_ role and another role.
168 default_role = "_member_"
170 self.class.request('role', 'show', default_role)
172 self.class.request('role', 'create', default_role)
174 # finally, assign the user to the project with the role
175 self.class.request('role', 'add', [default_role, '--project', project_id, '--user', id])
179 # DEPRECATED - To be removed in next release (Liberty)
180 # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
182 @property_hash[:tenant] = set_project(value)
185 # DEPRECATED - To be removed in next release (Liberty)
186 # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
188 return resource[:tenant] if sym_to_bool(resource[:ignore_default_tenant])
189 # use the one returned from instances
190 tenant_name = @property_hash[:project]
191 if tenant_name.nil? or tenant_name.empty?
192 # if none (i.e. ldap backend) use the given one
193 tenant_name = resource[:tenant]
197 if tenant_name.nil? or tenant_name.empty?
198 return nil # nothing found, nothing given
200 project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{tenant_name}")[:id]
201 find_project_for_user(tenant_name, project_id)
205 @property_hash[:domain]
209 @property_hash[:domain_id]
214 request('user', 'list', ['--long']).each do |user|
215 # The field says "domain" but it is really the domain_id
216 domname = domain_name_from_id(user[:domain])
217 if instance_hash.include?(user[:name]) # not unique
218 curdomid = instance_hash[user[:name]][:domain]
219 if curdomid != default_domain_id
220 # Move the user from the short name slot to the long name slot
221 # because it is not in the default domain.
222 curdomname = domain_name_from_id(curdomid)
223 instance_hash["#{user[:name]}::#{curdomname}"] = instance_hash[user[:name]]
224 # Use the short name slot for the new user
225 instance_hash[user[:name]] = user
227 # Use the long name for the new user
228 instance_hash["#{user[:name]}::#{domname}"] = user
231 # Unique (for now) - store in short name slot
232 instance_hash[user[:name]] = user
235 instance_hash.keys.collect do |user_name|
236 user = instance_hash[user_name]
240 :enabled => user[:enabled].downcase.chomp == 'true' ? true : false,
241 :password => user[:password],
242 :email => user[:email],
243 :description => user[:description],
244 :domain => domain_name_from_id(user[:domain]),
245 :domain_id => user[:domain],
251 def self.prefetch(resources)
253 resources.each do |resname, resource|
254 # resname may be specified as just "name" or "name::domain"
255 name, resdomain = name_and_domain(resname, resource[:domain])
256 provider = users.find do |user|
257 # have a match if the full instance name matches the full resource name, OR
258 # the base resource name matches the base instance name, and the
259 # resource domain matches the instance domain
260 username, user_domain = name_and_domain(user.name, user.domain)
261 (user.name == resname) ||
262 ((username == name) && (user_domain == resdomain))
264 resource.provider = provider if provider