blob: 0139ad423a00a8889e6a8ec93e53e49fcf9f7a5d [file] [log] [blame]
Sapan Bhatiaf0801972015-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
223 pass
224 else:
225 # User does exist, check if it's current
226 try:
227 authenticate(endpoint, None, user_name, password, tenant_name)
228 except:
229 pass
230 else:
231 # It's current, we're done
232 return (False, user.id)
233
234 # We now know we will have to create a new user
235 if check_mode:
236 return (True, None)
237
238 tenant = get_tenant(keystone, tenant_name)
239
240 if (not user):
241 user = keystone.users.create(name=user_name, password=password,
242 email=email, tenant_id=tenant.id)
243 else:
244 user = keystone.users.update_password(user.id, password)
245
246 return (True, user.id)
247
248
249def ensure_role_exists(keystone, user_name, tenant_name, role_name,
250 check_mode):
251 """ Check if role exists
252
253 Return (True, id) if a new role was created or if the role was newly
254 assigned to the user for the tenant. (False, id) if the role already
255 exists and was already assigned to the user ofr the tenant.
256
257 """
258 # Check if the user has the role in the tenant
259 user = get_user(keystone, user_name)
260 tenant = get_tenant(keystone, tenant_name)
261 roles = [x for x in keystone.roles.roles_for_user(user, tenant)
262 if x.name == role_name]
263 count = len(roles)
264
265 if count == 1:
266 # If the role is in there, we are done
267 role = roles[0]
268 return (False, role.id)
269 elif count > 1:
270 # Too many roles with the same name, throw an error
271 raise ValueError("%d roles with name %s" % (count, role_name))
272
273 # At this point, we know we will need to make changes
274 if check_mode:
275 return (True, None)
276
277 # Get the role if it exists
278 try:
279 role = get_role(keystone, role_name)
280 except KeyError:
281 # Role doesn't exist yet
282 role = keystone.roles.create(role_name)
283
284 # Associate the role with the user in the admin
285 keystone.roles.add_user_role(user, role, tenant)
286 return (True, role.id)
287
288
289def ensure_user_absent(keystone, user, check_mode):
290 raise NotImplementedError("Not yet implemented")
291
292
293def ensure_role_absent(keystone, uesr, tenant, role, check_mode):
294 raise NotImplementedError("Not yet implemented")
295
296
297def main():
298
299 argument_spec = openstack_argument_spec()
300 argument_spec.update(dict(
301 tenant_description=dict(required=False),
302 email=dict(required=False),
303 user=dict(required=False),
304 tenant=dict(required=False),
305 password=dict(required=False),
306 role=dict(required=False),
307 state=dict(default='present', choices=['present', 'absent']),
308 endpoint=dict(required=False,
309 default="http://127.0.0.1:35357/v2.0"),
310 token=dict(required=False),
311 login_user=dict(required=False),
312 login_password=dict(required=False),
313 login_tenant_name=dict(required=False)
314 ))
315 # keystone operations themselves take an endpoint, not a keystone auth_url
316 del(argument_spec['auth_url'])
317 module = AnsibleModule(
318 argument_spec=argument_spec,
319 supports_check_mode=True,
320 mutually_exclusive=[['token', 'login_user'],
321 ['token', 'login_password'],
322 ['token', 'login_tenant_name']]
323 )
324
325 if not keystoneclient_found:
326 module.fail_json(msg="the python-keystoneclient module is required")
327
328 user = module.params['user']
329 password = module.params['password']
330 tenant = module.params['tenant']
331 tenant_description = module.params['tenant_description']
332 email = module.params['email']
333 role = module.params['role']
334 state = module.params['state']
335 endpoint = module.params['endpoint']
336 token = module.params['token']
337 login_user = module.params['login_user']
338 login_password = module.params['login_password']
339 login_tenant_name = module.params['login_tenant_name']
340
341 keystone = authenticate(endpoint, token, login_user, login_password, login_tenant_name)
342
343 check_mode = module.check_mode
344
345 try:
346 d = dispatch(keystone, user, password, tenant, tenant_description,
347 email, role, state, endpoint, token, login_user,
348 login_password, check_mode)
349 except Exception, e:
350 if check_mode:
351 # If we have a failure in check mode
352 module.exit_json(changed=True,
353 msg="exception: %s" % e)
354 else:
355 module.fail_json(msg="exception: %s" % e)
356 else:
357 module.exit_json(**d)
358
359
360def dispatch(keystone, user=None, password=None, tenant=None,
361 tenant_description=None, email=None, role=None,
362 state="present", endpoint=None, token=None, login_user=None,
363 login_password=None, check_mode=False):
364 """ Dispatch to the appropriate method.
365
366 Returns a dict that will be passed to exit_json
367
368 tenant user role state
369 ------ ---- ---- --------
370 X present ensure_tenant_exists
371 X absent ensure_tenant_absent
372 X X present ensure_user_exists
373 X X absent ensure_user_absent
374 X X X present ensure_role_exists
375 X X X absent ensure_role_absent
376
377
378 """
379 changed = False
380 id = None
381 if tenant and not user and not role and state == "present":
382 changed, id = ensure_tenant_exists(keystone, tenant,
383 tenant_description, check_mode)
384 elif tenant and not user and not role and state == "absent":
385 changed = ensure_tenant_absent(keystone, tenant, check_mode)
386 elif tenant and user and not role and state == "present":
387 changed, id = ensure_user_exists_and_is_current(keystone, endpoint, user, password,
388 email, tenant, check_mode)
389 elif tenant and user and not role and state == "absent":
390 changed = ensure_user_absent(keystone, user, check_mode)
391 elif tenant and user and role and state == "present":
392 changed, id = ensure_role_exists(keystone, user, tenant, role,
393 check_mode)
394 elif tenant and user and role and state == "absent":
395 changed = ensure_role_absent(keystone, user, tenant, role, check_mode)
396 else:
397 # Should never reach here
398 raise ValueError("Code should never reach here")
399
400 return dict(changed=changed, id=id)
401
402# import module snippets
403from ansible.module_utils.basic import *
404from ansible.module_utils.openstack import *
405if __name__ == '__main__':
406 main()