blob: 9290847673df4326e22b8b0546a3189b5fcf3411 [file] [log] [blame]
Sapan Bhatia16a7f652015-01-31 05:29:49 +00001#!/usr/bin/python
2# -*- coding: utf-8 -*-
3
4# Based on Jimmy Tang's implementation
5
6DOCUMENTATION = '''
7---
8module: keystone_user
9version_added: "1.2"
10short_description: Manage OpenStack Identity (keystone) users, tenants and roles
11description:
12 - Manage users,tenants, roles from OpenStack.
13options:
14 login_user:
15 description:
16 - login username to authenticate to keystone
17 required: false
18 default: admin
19 login_password:
20 description:
21 - Password of login user
22 required: false
23 default: 'yes'
24 login_tenant_name:
25 description:
26 - The tenant login_user belongs to
27 required: false
28 default: None
29 version_added: "1.3"
30 token:
31 description:
32 - The token to be uses in case the password is not specified
33 required: false
34 default: None
35 endpoint:
36 description:
37 - The keystone url for authentication
38 required: false
39 default: 'http://127.0.0.1:35357/v2.0/'
40 user:
41 description:
42 - The name of the user that has to added/removed from OpenStack
43 required: false
44 default: None
45 password:
46 description:
47 - The password to be assigned to the user
48 required: false
49 default: None
50 tenant:
51 description:
52 - The tenant name that has be added/removed
53 required: false
54 default: None
55 tenant_description:
56 description:
57 - A description for the tenant
58 required: false
59 default: None
60 email:
61 description:
62 - An email address for the user
63 required: false
64 default: None
65 role:
66 description:
67 - The name of the role to be assigned or created
68 required: false
69 default: None
70 state:
71 description:
72 - Indicate desired state of the resource
73 choices: ['present', 'absent']
74 default: present
75requirements: [ python-keystoneclient ]
76author: Lorin Hochstein
77'''
78
79EXAMPLES = '''
80# Create a tenant
81- keystone_user: tenant=demo tenant_description="Default Tenant"
82
83# Create a user
84- keystone_user: user=john tenant=demo password=secrete
85
86# Apply the admin role to the john user in the demo tenant
87- keystone_user: role=admin user=john tenant=demo
88'''
89
90try:
91 from keystoneclient.v2_0 import client
92except ImportError:
93 keystoneclient_found = False
94else:
95 keystoneclient_found = True
96
97
98def authenticate(endpoint, token, login_user, login_password, login_tenant_name):
99 """Return a keystone client object"""
100
101 if token:
102 return client.Client(endpoint=endpoint, token=token)
103 else:
104 return client.Client(auth_url=endpoint, username=login_user,
105 password=login_password, tenant_name=login_tenant_name)
106
107
108def tenant_exists(keystone, tenant):
109 """ Return True if tenant already exists"""
110 return tenant in [x.name for x in keystone.tenants.list()]
111
112
113def user_exists(keystone, user):
114 """" Return True if user already exists"""
115 return user in [x.name for x in keystone.users.list()]
116
117
118def get_tenant(keystone, name):
119 """ Retrieve a tenant by name"""
120 tenants = [x for x in keystone.tenants.list() if x.name == name]
121 count = len(tenants)
122 if count == 0:
123 raise KeyError("No keystone tenants with name %s" % name)
124 elif count > 1:
125 raise ValueError("%d tenants with name %s" % (count, name))
126 else:
127 return tenants[0]
128
129
130def get_user(keystone, name):
131 """ Retrieve a user by name"""
132 users = [x for x in keystone.users.list() if x.name == name]
133 count = len(users)
134 if count == 0:
135 raise KeyError("No keystone users with name %s" % name)
136 elif count > 1:
137 raise ValueError("%d users with name %s" % (count, name))
138 else:
139 return users[0]
140
141
142def get_role(keystone, name):
143 """ Retrieve a role by name"""
144 roles = [x for x in keystone.roles.list() if x.name == name]
145 count = len(roles)
146 if count == 0:
147 raise KeyError("No keystone roles with name %s" % name)
148 elif count > 1:
149 raise ValueError("%d roles with name %s" % (count, name))
150 else:
151 return roles[0]
152
153
154def get_tenant_id(keystone, name):
155 return get_tenant(keystone, name).id
156
157
158def get_user_id(keystone, name):
159 return get_user(keystone, name).id
160
161
162def ensure_tenant_exists(keystone, tenant_name, tenant_description,
163 check_mode):
164 """ Ensure that a tenant exists.
165
166 Return (True, id) if a new tenant was created, (False, None) if it
167 already existed.
168 """
169
170 # Check if tenant already exists
171 try:
172 tenant = get_tenant(keystone, tenant_name)
173 except KeyError:
174 # Tenant doesn't exist yet
175 pass
176 else:
177 if tenant.description == tenant_description:
178 return (False, tenant.id)
179 else:
180 # We need to update the tenant description
181 if check_mode:
182 return (True, tenant.id)
183 else:
184 tenant.update(description=tenant_description)
185 return (True, tenant.id)
186
187 # We now know we will have to create a new tenant
188 if check_mode:
189 return (True, None)
190
191 ks_tenant = keystone.tenants.create(tenant_name=tenant_name,
192 description=tenant_description,
193 enabled=True)
194 return (True, ks_tenant.id)
195
196
197def ensure_tenant_absent(keystone, tenant, check_mode):
198 """ Ensure that a tenant does not exist
199
200 Return True if the tenant was removed, False if it didn't exist
201 in the first place
202 """
203 if not tenant_exists(keystone, tenant):
204 return False
205
206 # We now know we will have to delete the tenant
207 if check_mode:
208 return True
209
210def ensure_user_exists_and_is_current(keystone, endpoint, user_name, password, email, tenant_name,
211 check_mode):
212 """ Check if user exists and has the same email and password
213
214 Return (True, id) if a new user was created or one was updated, (False, id) if the user is
215 up to date
216 """
217
218 # Check if tenant already exists
219 try:
220 user = get_user(keystone, user_name)
221 except KeyError:
222 # Tenant doesn't exist yet
Sapan Bhatiafd67c542015-02-03 23:53:39 +0000223 user = None
Sapan Bhatia16a7f652015-01-31 05:29:49 +0000224 pass
225 else:
226 # User does exist, check if it's current
227 try:
228 authenticate(endpoint, None, user_name, password, tenant_name)
229 except:
230 pass
231 else:
232 # It's current, we're done
233 return (False, user.id)
234
235 # We now know we will have to create a new user
236 if check_mode:
237 return (True, None)
238
239 tenant = get_tenant(keystone, tenant_name)
240
241 if (not user):
242 user = keystone.users.create(name=user_name, password=password,
243 email=email, tenant_id=tenant.id)
244 else:
245 user = keystone.users.update_password(user.id, password)
246
247 return (True, user.id)
248
249
250def ensure_role_exists(keystone, user_name, tenant_name, role_name,
251 check_mode):
252 """ Check if role exists
253
254 Return (True, id) if a new role was created or if the role was newly
255 assigned to the user for the tenant. (False, id) if the role already
256 exists and was already assigned to the user ofr the tenant.
257
258 """
259 # Check if the user has the role in the tenant
260 user = get_user(keystone, user_name)
261 tenant = get_tenant(keystone, tenant_name)
262 roles = [x for x in keystone.roles.roles_for_user(user, tenant)
263 if x.name == role_name]
264 count = len(roles)
265
266 if count == 1:
267 # If the role is in there, we are done
268 role = roles[0]
269 return (False, role.id)
270 elif count > 1:
271 # Too many roles with the same name, throw an error
272 raise ValueError("%d roles with name %s" % (count, role_name))
273
274 # At this point, we know we will need to make changes
275 if check_mode:
276 return (True, None)
277
278 # Get the role if it exists
279 try:
280 role = get_role(keystone, role_name)
281 except KeyError:
282 # Role doesn't exist yet
283 role = keystone.roles.create(role_name)
284
285 # Associate the role with the user in the admin
286 keystone.roles.add_user_role(user, role, tenant)
287 return (True, role.id)
288
289
290def ensure_user_absent(keystone, user, check_mode):
291 raise NotImplementedError("Not yet implemented")
292
293
294def ensure_role_absent(keystone, uesr, tenant, role, check_mode):
295 raise NotImplementedError("Not yet implemented")
296
297
298def main():
299
300 argument_spec = openstack_argument_spec()
301 argument_spec.update(dict(
302 tenant_description=dict(required=False),
303 email=dict(required=False),
304 user=dict(required=False),
305 tenant=dict(required=False),
306 password=dict(required=False),
307 role=dict(required=False),
308 state=dict(default='present', choices=['present', 'absent']),
309 endpoint=dict(required=False,
310 default="http://127.0.0.1:35357/v2.0"),
311 token=dict(required=False),
312 login_user=dict(required=False),
313 login_password=dict(required=False),
314 login_tenant_name=dict(required=False)
315 ))
316 # keystone operations themselves take an endpoint, not a keystone auth_url
317 del(argument_spec['auth_url'])
318 module = AnsibleModule(
319 argument_spec=argument_spec,
320 supports_check_mode=True,
321 mutually_exclusive=[['token', 'login_user'],
322 ['token', 'login_password'],
323 ['token', 'login_tenant_name']]
324 )
325
326 if not keystoneclient_found:
327 module.fail_json(msg="the python-keystoneclient module is required")
328
329 user = module.params['user']
330 password = module.params['password']
331 tenant = module.params['tenant']
332 tenant_description = module.params['tenant_description']
333 email = module.params['email']
334 role = module.params['role']
335 state = module.params['state']
336 endpoint = module.params['endpoint']
337 token = module.params['token']
338 login_user = module.params['login_user']
339 login_password = module.params['login_password']
340 login_tenant_name = module.params['login_tenant_name']
341
342 keystone = authenticate(endpoint, token, login_user, login_password, login_tenant_name)
343
344 check_mode = module.check_mode
345
346 try:
347 d = dispatch(keystone, user, password, tenant, tenant_description,
348 email, role, state, endpoint, token, login_user,
349 login_password, check_mode)
350 except Exception, e:
351 if check_mode:
352 # If we have a failure in check mode
353 module.exit_json(changed=True,
354 msg="exception: %s" % e)
355 else:
356 module.fail_json(msg="exception: %s" % e)
357 else:
358 module.exit_json(**d)
359
360
361def dispatch(keystone, user=None, password=None, tenant=None,
362 tenant_description=None, email=None, role=None,
363 state="present", endpoint=None, token=None, login_user=None,
364 login_password=None, check_mode=False):
365 """ Dispatch to the appropriate method.
366
367 Returns a dict that will be passed to exit_json
368
369 tenant user role state
370 ------ ---- ---- --------
371 X present ensure_tenant_exists
372 X absent ensure_tenant_absent
373 X X present ensure_user_exists
374 X X absent ensure_user_absent
375 X X X present ensure_role_exists
376 X X X absent ensure_role_absent
377
378
379 """
380 changed = False
381 id = None
382 if tenant and not user and not role and state == "present":
383 changed, id = ensure_tenant_exists(keystone, tenant,
384 tenant_description, check_mode)
385 elif tenant and not user and not role and state == "absent":
386 changed = ensure_tenant_absent(keystone, tenant, check_mode)
387 elif tenant and user and not role and state == "present":
388 changed, id = ensure_user_exists_and_is_current(keystone, endpoint, user, password,
389 email, tenant, check_mode)
390 elif tenant and user and not role and state == "absent":
391 changed = ensure_user_absent(keystone, user, check_mode)
392 elif tenant and user and role and state == "present":
393 changed, id = ensure_role_exists(keystone, user, tenant, role,
394 check_mode)
395 elif tenant and user and role and state == "absent":
396 changed = ensure_role_absent(keystone, user, tenant, role, check_mode)
397 else:
398 # Should never reach here
399 raise ValueError("Code should never reach here")
400
401 return dict(changed=changed, id=id)
402
403# import module snippets
404from ansible.module_utils.basic import *
405from ansible.module_utils.openstack import *
406if __name__ == '__main__':
407 main()