From 869e14c31827c6ba869abe908a1940e61bb0265b Mon Sep 17 00:00:00 2001 From: Lorin Hochstein Date: Thu, 23 May 2013 10:35:27 -0400 Subject: [PATCH] OpenStack Identity (keystone) user module Module for managing users, tenants, and roles within OpenStack. --- cloud/keystone_user | 320 ++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 320 insertions(+) create mode 100644 cloud/keystone_user diff --git a/cloud/keystone_user b/cloud/keystone_user new file mode 100644 index 00000000000..c69cf92e977 --- /dev/null +++ b/cloud/keystone_user @@ -0,0 +1,320 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Based on Jimmy Tang's implementation + +DOCUMENTATION = ''' +--- +module: keystone_user +short_description: Manage OpenStack Identity (keystone) users, tenants and roles +requirements: [ python-keystoneclient ] +examples: + - code: 'keystone_user: tenant=demo tenant_description="Default Tenant"' + description: Create a tenant + - code: 'keystone_user: user=john tenant=demo password=secrete' + description: Create a user + - code: 'keystone_user: role=admin user=john tenant=demo' + description: Apply the admin role to the john user in the demo tenant +author: Lorin Hochstein +''' + +try: + from keystoneclient.v2_0 import client +except ImportError: + keystoneclient_found = False +else: + keystoneclient_found = True + + +def authenticate(endpoint, token, login_user, login_password): + """Return a keystone client object""" + + if token: + return client.Client(endpoint=endpoint, token=token) + else: + return client.Client(endpoint=endpoint, username=login_user, + password=login_password) + + +def tenant_exists(keystone, tenant): + """ Return True if tenant already exists""" + return tenant in [x.name for x in keystone.tenants.list()] + + +def user_exists(keystone, user): + """" Return True if user already exists""" + return user in [x.name for x in keystone.users.list()] + + +def get_tenant(keystone, name): + """ Retrieve a tenant by name""" + tenants = [x for x in keystone.tenants.list() if x.name == name] + count = len(tenants) + if count == 0: + raise KeyError("No keystone tenants with name %s" % name) + elif count > 1: + raise ValueError("%d tenants with name %s" % (count, name)) + else: + return tenants[0] + + +def get_user(keystone, name): + """ Retrieve a user by name""" + users = [x for x in keystone.users.list() if x.name == name] + count = len(users) + if count == 0: + raise KeyError("No keystone users with name %s" % name) + elif count > 1: + raise ValueError("%d users with name %s" % (count, name)) + else: + return users[0] + + +def get_role(keystone, name): + """ Retrieve a role by name""" + roles = [x for x in keystone.roles.list() if x.name == name] + count = len(roles) + if count == 0: + raise KeyError("No keystone roles with name %s" % name) + elif count > 1: + raise ValueError("%d roles with name %s" % (count, name)) + else: + return roles[0] + + +def get_tenant_id(keystone, name): + return get_tenant(keystone, name).id + + +def get_user_id(keystone, name): + return get_user(keystone, name).id + + +def ensure_tenant_exists(keystone, tenant_name, tenant_description, + check_mode): + """ Ensure that a tenant exists. + + Return (True, id) if a new tenant was created, (False, None) if it + already existed. + """ + + # Check if tenant already exists + try: + tenant = get_tenant(keystone, tenant_name) + except KeyError: + # Tenant doesn't exist yet + pass + else: + if tenant.description == tenant_description: + return (False, tenant.id) + else: + # We need to update the tenant description + if check_mode: + return (True, tenant.id) + else: + tenant.update(description=tenant_description) + return (True, tenant.id) + + # We now know we will have to create a new tenant + if check_mode: + return (True, None) + + ks_tenant = keystone.tenants.create(tenant_name=tenant_name, + description=tenant_description, + enabled=True) + return (True, ks_tenant.id) + + +def ensure_tenant_absent(keystone, tenant, check_mode): + """ Ensure that a tenant does not exist + + Return True if the tenant was removed, False if it didn't exist + in the first place + """ + if not tenant_exists(keystone, tenant): + return False + + # We now know we will have to delete the tenant + if check_mode: + return True + + +def ensure_user_exists(keystone, user_name, password, email, tenant_name, + check_mode): + """ Check if user exists + + Return (True, id) if a new user was created, (False, id) user alrady + exists + """ + + # Check if tenant already exists + try: + user = get_user(keystone, user_name) + except KeyError: + # Tenant doesn't exist yet + pass + else: + # User does exist, we're done + return (False, user.id) + + # We now know we will have to create a new user + if check_mode: + return (True, None) + + tenant = get_tenant(keystone, tenant_name) + + user = keystone.users.create(name=user_name, password=password, + email=email, tenant_id=tenant.id) + return (True, user.id) + + +def ensure_role_exists(keystone, user_name, tenant_name, role_name, + check_mode): + """ Check if role exists + + Return (True, id) if a new role was created or if the role was newly + assigned to the user for the tenant. (False, id) if the role already + exists and was already assigned to the user ofr the tenant. + + """ + # Check if the user has the role in the tenant + user = get_user(keystone, user_name) + tenant = get_tenant(keystone, tenant_name) + roles = [x for x in keystone.roles.roles_for_user(user, tenant) + if x.name == role_name] + count = len(roles) + + if count == 1: + # If the role is in there, we are done + role = roles[0] + return (False, role.id) + elif count > 1: + # Too many roles with the same name, throw an error + raise ValueError("%d roles with name %s" % (count, role_name)) + + # At this point, we know we will need to make changes + if check_mode: + return (True, None) + + # Get the role if it exists + try: + role = get_role(keystone, role_name) + except KeyError: + # Role doesn't exist yet + role = keystone.roles.create(role_name) + + # Associate the role with the user in the admin + keystone.roles.add_user_role(user, role, tenant) + return (True, role.id) + + +def ensure_user_absent(keystone, user, check_mode): + raise NotImplementedError("Not yet implemented") + + +def ensure_role_absent(keystone, uesr, tenant, role, check_mode): + raise NotImplementedError("Not yet implemented") + + +def main(): + + module = AnsibleModule( + argument_spec=dict( + user=dict(required=False), + password=dict(required=False), + tenant=dict(required=False), + tenant_description=dict(required=False), + email=dict(required=False), + role=dict(required=False), + state=dict(default='present', choices=['present', 'absent']), + endpoint=dict(required=False, + default="http://127.0.0.1:35357/v2.0"), + token=dict(required=False), + login_user=dict(required=False), + login_password=dict(required=False) + ), + supports_check_mode=True, + mutually_exclusive=[['token', 'login_user'], + ['token', 'login_password']] + ) + + if not keystoneclient_found: + module.fail_json(msg="the python-keystoneclient module is required") + + user = module.params['user'] + password = module.params['password'] + tenant = module.params['tenant'] + tenant_description = module.params['tenant_description'] + email = module.params['email'] + role = module.params['role'] + state = module.params['state'] + endpoint = module.params['endpoint'] + token = module.params['token'] + login_user = module.params['login_user'] + login_password = module.params['login_password'] + + keystone = authenticate(endpoint, token, login_user, login_password) + + check_mode = module.check_mode + + try: + d = dispatch(keystone, user, password, tenant, tenant_description, + email, role, state, endpoint, token, login_user, + login_password, check_mode) + except Exception as e: + if check_mode: + # If we have a failure in check mode + module.exit_json(changed=True, + msg="exception: %s" % e.message) + else: + module.fail_json(msg=e.message) + else: + module.exit_json(**d) + + +def dispatch(keystone, user=None, password=None, tenant=None, + tenant_description=None, email=None, role=None, + state="present", endpoint=None, token=None, login_user=None, + login_password=None, check_mode=False): + """ Dispatch to the appropriate method. + + Returns a dict that will be passed to exit_json + + tenant user role state + ------ ---- ---- -------- + X present ensure_tenant_exists + X absent ensure_tenant_absent + X X present ensure_user_exists + X X absent ensure_user_absent + X X X present ensure_role_exists + X X X absent ensure_role_absent + + + """ + changed = False + id = None + if tenant and not user and not role and state == "present": + changed, id = ensure_tenant_exists(keystone, tenant, + tenant_description, check_mode) + elif tenant and not user and not role and state == "absent": + changed = ensure_tenant_absent(keystone, tenant, check_mode) + elif tenant and user and not role and state == "present": + changed, id = ensure_user_exists(keystone, user, password, + email, tenant, check_mode) + elif tenant and user and not role and state == "absent": + changed = ensure_user_absent(keystone, user, check_mode) + elif tenant and user and role and state == "present": + changed, id = ensure_role_exists(keystone, user, tenant, role, + check_mode) + elif tenant and user and role and state == "absent": + changed = ensure_role_absent(keystone, user, tenant, role, check_mode) + else: + # Should never reach here + raise ValueError("Code should never reach here") + + return dict(changed=changed, id=id) + +# this is magic, see lib/ansible/module_common.py +#<> +if __name__ == '__main__': + main()