]> git.donarmstrong.com Git - dsa-puppet.git/blob - 3rdparty/modules/keystone/lib/puppet/provider/keystone_user/openstack.rb
Update to Kilo
[dsa-puppet.git] / 3rdparty / modules / keystone / lib / puppet / provider / keystone_user / openstack.rb
1 require 'puppet/provider/keystone'
2
3 Puppet::Type.type(:keystone_user).provide(
4   :openstack,
5   :parent => Puppet::Provider::Keystone
6 ) do
7
8   desc "Provider to manage keystone users."
9
10   @credentials = Puppet::Provider::Openstack::CredentialsV3.new
11
12   def initialize(value={})
13     super(value)
14     @property_flush = {}
15   end
16
17   def create
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'
25     end
26     if resource[:password]
27       properties << '--password' << resource[:password]
28     end
29     if resource[:email]
30       properties << '--email' << resource[:email]
31     end
32     if user_domain
33       properties << '--domain'
34       properties << user_domain
35     end
36     @property_hash = self.class.request('user', 'create', properties)
37     @property_hash[:domain] = user_domain
38     if resource[:tenant]
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)
43     end
44     @property_hash[:ensure] = :present
45   end
46
47   def destroy
48     self.class.request('user', 'delete', id)
49     @property_hash.clear
50   end
51
52   def flush
53     options = []
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
61       unless options.empty?
62         options << @property_hash[:id]
63         self.class.request('user', 'set', options)
64       end
65       @property_flush.clear
66     end
67   end
68
69   def exists?
70     @property_hash[:ensure] == :present
71   end
72
73   # Types properties
74   def enabled
75     bool_to_sym(@property_hash[:enabled])
76   end
77
78   def enabled=(value)
79     @property_flush[:enabled] = value
80   end
81
82   def email
83     @property_hash[:email]
84   end
85
86   def email=(value)
87     @property_flush[:email] = value
88   end
89
90   def id
91     @property_hash[:id]
92   end
93
94   def password
95     res = nil
96     return res if resource[:password] == nil
97     if resource[:enabled] == :false || resource[:replace_password] == :false
98       # Unchanged password
99       res = resource[:password]
100     else
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]
115       else
116         # last chance - try a domain scoped token
117         credentials.domain_name = domain
118       end
119       begin
120         token = Puppet::Provider::Openstack.request('token', 'issue', ['--format', 'value'], credentials)
121       rescue Puppet::Error::OpenstackUnauthorizedError
122         # password is invalid
123       else
124         res = resource[:password] unless token.empty?
125       end
126     end
127     return res
128   end
129
130   def password=(value)
131     @property_flush[:password] = value
132   end
133
134   def replace_password
135     @property_hash[:replace_password]
136   end
137
138   def replace_password=(value)
139     @property_flush[:replace_password] = value
140   end
141
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]
151       end
152     end
153     return nil
154   end
155
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
159     unless project_id
160       project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{newproject}")[:id]
161     end
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_"
169     begin
170       self.class.request('role', 'show', default_role)
171     rescue
172       self.class.request('role', 'create', default_role)
173     end
174     # finally, assign the user to the project with the role
175     self.class.request('role', 'add', [default_role, '--project', project_id, '--user', id])
176     newproject
177   end
178
179   # DEPRECATED - To be removed in next release (Liberty)
180   # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
181   def tenant=(value)
182     @property_hash[:tenant] = set_project(value)
183   end
184
185   # DEPRECATED - To be removed in next release (Liberty)
186   # https://bugs.launchpad.net/puppet-keystone/+bug/1472437
187   def tenant
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]
194     else
195       return tenant_name
196     end
197     if tenant_name.nil? or tenant_name.empty?
198       return nil # nothing found, nothing given
199     end
200     project_id = Puppet::Resource.indirection.find("Keystone_tenant/#{tenant_name}")[:id]
201     find_project_for_user(tenant_name, project_id)
202   end
203
204   def domain
205     @property_hash[:domain]
206   end
207
208   def domain_id
209     @property_hash[:domain_id]
210   end
211
212   def self.instances
213     instance_hash = {}
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
226         else
227           # Use the long name for the new user
228           instance_hash["#{user[:name]}::#{domname}"] = user
229         end
230       else
231         # Unique (for now) - store in short name slot
232         instance_hash[user[:name]] = user
233       end
234     end
235     instance_hash.keys.collect do |user_name|
236       user = instance_hash[user_name]
237       new(
238         :name        => user_name,
239         :ensure      => :present,
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],
246         :id          => user[:id]
247       )
248     end
249   end
250
251   def self.prefetch(resources)
252     users = instances
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))
263       end
264       resource.provider = provider if provider
265     end
266   end
267
268 end