3 require 'puppet/provider/keystone'
4 Puppet::Type.type(:keystone_user).provide(
6 :parent => Puppet::Provider::Keystone
9 desc "Provider to manage keystone users."
11 def initialize(value={})
18 if resource[:enabled] == :true
19 properties << '--enable'
20 elsif resource[:enabled] == :false
21 properties << '--disable'
23 if resource[:password]
24 properties << '--password'
25 properties << resource[:password]
28 properties << '--project'
29 properties << resource[:tenant]
32 properties << '--email'
33 properties << resource[:email]
35 @instance = request('user', 'create', resource[:name], resource[:auth], properties)
39 ! instance(resource[:name]).empty?
43 request('user', 'delete', resource[:name], resource[:auth])
48 @property_flush[:enabled] = value
52 bool_to_sym(instance(resource[:name])[:enabled])
57 @property_flush[:password] = value
61 # if we don't know a password we can't test it
62 return nil if resource[:password] == nil
63 # if the user is disabled then the password can't be changed
64 return resource[:password] if resource[:enabled] == :false
65 # if replacing password is disabled, then don't change it
66 return resource[:password] if resource[:replace_password] == :false
67 # we can't get the value of the password but we can test to see if the one we know
68 # about works, if it doesn't then return nil, causing it to be reset
70 if password_credentials_set?(resource[:auth]) || service_credentials_set?(resource[:auth])
71 endpoint = (resource[:auth])['auth_url']
72 elsif openrc_set?(resource[:auth])
73 endpoint = get_credentials_from_openrc(resource[:auth])['auth_url']
75 endpoint = ENV['OS_AUTH_URL']
77 # try to get endpoint from keystone.conf
78 endpoint = get_admin_endpoint
81 raise(Puppet::Error::OpenstackAuthInputError, 'Could not find auth url to check user password.')
84 'username' => resource[:name],
85 'password' => resource[:password],
86 'tenant_name' => resource[:tenant],
87 'auth_url' => endpoint,
90 # Ideally this would be checked with the `openstack token issue` command,
91 # but that command is not available with version 0.3.0 of openstackclient
92 # which is what ships on Ubuntu during Juno.
93 # Instead we'll check whether the user can authenticate with curl.
96 :passwordCredentials => {
97 :username => auth_params['username'],
98 :password => auth_params['password'],
102 url = URI.parse(endpoint)
103 # There is issue with ipv6 where address has to be in brackets, this causes the
104 # underlying ruby TCPSocket to fail. Net::HTTP.new will fail without brackets on
105 # joining the ipv6 address with :port or passing brackets to TCPSocket. It was
106 # found that if we use Net::HTTP.start with url.hostname the incriminated code
108 use_ssl = url.scheme == "https" ? true : false
109 http = Net::HTTP.start(url.hostname, url.port, {:use_ssl => use_ssl})
110 request = Net::HTTP::Post.new('/v2.0/tokens')
111 request.body = creds_hash.to_json
112 request.content_type = 'application/json'
113 response = http.request(request)
114 if response.code.to_i == 401 || response.code.to_i == 403 # 401 => unauthorized, 403 => userDisabled
116 elsif ! (response.code == 200 || response.code == 203)
117 return resource[:password]
119 raise(Puppet::Error, "Received bad response while trying to authenticate user: #{response.body}")
126 request('user', 'set', resource[:name], resource[:auth], '--project', value)
127 rescue Puppet::ExecutionFailure => e
128 if e.message =~ /You are not authorized to perform the requested action: LDAP user update/
129 # read-only LDAP identity backend - just fall through
133 # note: read-write ldap will silently fail, not raise an exception
139 return resource[:tenant] if sym_to_bool(resource[:ignore_default_tenant])
140 # use the one returned from instances
141 tenant_name = instance(resource[:name])[:project]
142 if tenant_name.nil? or tenant_name.empty?
143 # if none (i.e. ldap backend) use the given one
144 tenant_name = resource[:tenant]
148 if tenant_name.nil? or tenant_name.empty?
149 return nil # nothing found, nothing given
151 # If the user list command doesn't report the project, it might still be there
152 # We don't need to know exactly what it is, we just need to know whether it's
153 # the one we're trying to set.
154 roles = request('user role', 'list', resource[:name], resource[:auth], ['--project', tenant_name])
163 instance(resource[:name])[:replace_password]
166 def replace_password=(value)
167 @property_flush[:replace_password] = value
171 @property_flush[:email] = value
175 instance(resource[:name])[:email]
179 instance(resource[:name])[:id]
183 list = request('user', 'list', nil, nil, '--long')
184 list.collect do |user|
186 :name => user[:name],
188 :enabled => user[:enabled].downcase.chomp == 'true' ? true : false,
189 :password => user[:password],
190 :tenant => user[:project],
191 :email => user[:email],
198 instances = request('user', 'list', nil, resource[:auth], '--long')
199 instances.collect do |user|
201 :name => user[:name],
202 :enabled => user[:enabled].downcase.chomp == 'true' ? true : false,
203 :password => user[:password],
204 :project => user[:project],
205 :email => user[:email],
212 @instance ||= instances.select { |instance| instance[:name] == name }.first || {}
215 def set_project(newproject)
216 # some backends do not store the project/tenant in the user object, so we have to
217 # to modify the project/tenant instead
218 # First, see if the project actually needs to change
219 roles = request('user role', 'list', resource[:name], resource[:auth], ['--project', newproject])
221 return # if already set, just skip
223 # Currently the only way to assign a user to a tenant not using user-create
224 # is to use user-role-add - this means we also need a role - there is usual
225 # a default role called _member_ which can be used for this purpose. What
226 # usually happens in a puppet module is that immediately after calling
227 # keystone_user, the module will then assign a role to that user. It is
228 # ok for a user to have the _member_ role and another role.
229 default_role = "_member_"
231 request('role', 'show', default_role, resource[:auth])
233 debug("Keystone role #{default_role} does not exist - creating")
234 request('role', 'create', default_role, resource[:auth])
236 request('role', 'add', default_role, resource[:auth],
237 '--project', newproject, '--user', resource[:name])
243 (options << '--enable') if @property_flush[:enabled] == :true
244 (options << '--disable') if @property_flush[:enabled] == :false
245 # There is a --description flag for the set command, but it does not work if the value is empty
246 (options << '--password' << resource[:password]) if @property_flush[:password]
247 (options << '--email' << resource[:email]) if @property_flush[:email]
248 # project handled in tenant= separately
249 request('user', 'set', resource[:name], resource[:auth], options) unless options.empty?