dd4a6e86bd
There are a completely new set of modules that do all of the things like keystone v3 and auth_plugins and the like correctly. Structurally upgrading these would have been massively disruptive and there is no real good way to do so without breaking people. These modules should be kept around for several releases - they still work for people - and they should get bug fixes. But they should not take new features. New features should go to the os_ modules.
397 lines
12 KiB
Python
397 lines
12 KiB
Python
#!/usr/bin/python
|
|
# -*- coding: utf-8 -*-
|
|
|
|
# Based on Jimmy Tang's implementation
|
|
|
|
DOCUMENTATION = '''
|
|
---
|
|
module: keystone_user
|
|
version_added: "1.2"
|
|
short_description: Manage OpenStack Identity (keystone) users, tenants and roles
|
|
description:
|
|
- Manage users,tenants, roles from OpenStack.
|
|
options:
|
|
login_user:
|
|
description:
|
|
- login username to authenticate to keystone
|
|
required: false
|
|
default: admin
|
|
login_password:
|
|
description:
|
|
- Password of login user
|
|
required: false
|
|
default: 'yes'
|
|
login_tenant_name:
|
|
description:
|
|
- The tenant login_user belongs to
|
|
required: false
|
|
default: None
|
|
version_added: "1.3"
|
|
token:
|
|
description:
|
|
- The token to be uses in case the password is not specified
|
|
required: false
|
|
default: None
|
|
endpoint:
|
|
description:
|
|
- The keystone url for authentication
|
|
required: false
|
|
default: 'http://127.0.0.1:35357/v2.0/'
|
|
user:
|
|
description:
|
|
- The name of the user that has to added/removed from OpenStack
|
|
required: false
|
|
default: None
|
|
password:
|
|
description:
|
|
- The password to be assigned to the user
|
|
required: false
|
|
default: None
|
|
tenant:
|
|
description:
|
|
- The tenant name that has be added/removed
|
|
required: false
|
|
default: None
|
|
tenant_description:
|
|
description:
|
|
- A description for the tenant
|
|
required: false
|
|
default: None
|
|
email:
|
|
description:
|
|
- An email address for the user
|
|
required: false
|
|
default: None
|
|
role:
|
|
description:
|
|
- The name of the role to be assigned or created
|
|
required: false
|
|
default: None
|
|
state:
|
|
description:
|
|
- Indicate desired state of the resource
|
|
choices: ['present', 'absent']
|
|
default: present
|
|
requirements: [ python-keystoneclient ]
|
|
author: Lorin Hochstein
|
|
'''
|
|
|
|
EXAMPLES = '''
|
|
# Create a tenant
|
|
- keystone_user: tenant=demo tenant_description="Default Tenant"
|
|
|
|
# Create a user
|
|
- keystone_user: user=john tenant=demo password=secrete
|
|
|
|
# Apply the admin role to the john user in the demo tenant
|
|
- keystone_user: role=admin user=john tenant=demo
|
|
'''
|
|
|
|
try:
|
|
from keystoneclient.v2_0 import client
|
|
except ImportError:
|
|
keystoneclient_found = False
|
|
else:
|
|
keystoneclient_found = True
|
|
|
|
|
|
def authenticate(endpoint, token, login_user, login_password, login_tenant_name):
|
|
"""Return a keystone client object"""
|
|
|
|
if token:
|
|
return client.Client(endpoint=endpoint, token=token)
|
|
else:
|
|
return client.Client(auth_url=endpoint, username=login_user,
|
|
password=login_password, tenant_name=login_tenant_name)
|
|
|
|
|
|
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():
|
|
|
|
argument_spec = openstack_argument_spec()
|
|
argument_spec.update(dict(
|
|
tenant_description=dict(required=False),
|
|
email=dict(required=False),
|
|
user=dict(required=False),
|
|
tenant=dict(required=False),
|
|
password=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),
|
|
login_tenant_name=dict(required=False)
|
|
))
|
|
# keystone operations themselves take an endpoint, not a keystone auth_url
|
|
del(argument_spec['auth_url'])
|
|
module = AnsibleModule(
|
|
argument_spec=argument_spec,
|
|
supports_check_mode=True,
|
|
mutually_exclusive=[['token', 'login_user'],
|
|
['token', 'login_password'],
|
|
['token', 'login_tenant_name']]
|
|
)
|
|
|
|
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']
|
|
login_tenant_name = module.params['login_tenant_name']
|
|
|
|
keystone = authenticate(endpoint, token, login_user, login_password, login_tenant_name)
|
|
|
|
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, e:
|
|
if check_mode:
|
|
# If we have a failure in check mode
|
|
module.exit_json(changed=True,
|
|
msg="exception: %s" % e)
|
|
else:
|
|
module.fail_json(msg="exception: %s" % e)
|
|
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)
|
|
|
|
# import module snippets
|
|
from ansible.module_utils.basic import *
|
|
from ansible.module_utils.openstack import *
|
|
if __name__ == '__main__':
|
|
main()
|