From 8900c2354f7c37c2e385baa4bf0c253d66f6e219 Mon Sep 17 00:00:00 2001 From: Jeffrey Zhang Date: Wed, 15 Oct 2014 11:16:04 +0800 Subject: [PATCH] Sync keystone module and state with the salt master keystone module and the state is out of date and lack a lot of method. Sync the module and state with the salt master. (copy from salt fc28d000565d40f0f7ab4c01f58464bcd19a84c3) Change-Id: I67e75d423775d56e7eaf00c9903ba9503da0c0b2 --- _modules/keystone.py | 1021 ++++++++++++++++++++++++++++++++++++++++++ _states/keystone.py | 588 ++++++++++++++++++++++++ 2 files changed, 1609 insertions(+) create mode 100644 _modules/keystone.py create mode 100644 _states/keystone.py diff --git a/_modules/keystone.py b/_modules/keystone.py new file mode 100644 index 0000000..1a671a5 --- /dev/null +++ b/_modules/keystone.py @@ -0,0 +1,1021 @@ +# -*- coding: utf-8 -*- +''' +Module for handling openstack keystone calls. + +:optdepends: - keystoneclient Python adapter +:configuration: This module is not usable until the following are specified + either in a pillar or in the minion's config file: + + .. code-block:: yaml + + keystone.user: admin + keystone.password: verybadpass + keystone.tenant: admin + keystone.tenant_id: f80919baedab48ec8931f200c65a50df + keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' + + OR (for token based authentication) + + .. code-block:: yaml + + keystone.token: 'ADMIN' + keystone.endpoint: 'http://127.0.0.1:35357/v2.0' + + If configuration for multiple openstack accounts is required, they can be + set up as different configuration profiles. For example: + + .. code-block:: yaml + + openstack1: + keystone.user: admin + keystone.password: verybadpass + keystone.tenant: admin + keystone.tenant_id: f80919baedab48ec8931f200c65a50df + keystone.auth_url: 'http://127.0.0.1:5000/v2.0/' + + openstack2: + keystone.user: admin + keystone.password: verybadpass + keystone.tenant: admin + keystone.tenant_id: f80919baedab48ec8931f200c65a50df + keystone.auth_url: 'http://127.0.0.2:5000/v2.0/' + + With this configuration in place, any of the keystone functions can make use + of a configuration profile by declaring it explicitly. + For example: + + .. code-block:: bash + + salt '*' keystone.tenant_list profile=openstack1 +''' + +# Import third party libs +HAS_KEYSTONE = False +try: + from keystoneclient.v2_0 import client + import keystoneclient.exceptions + HAS_KEYSTONE = True +except ImportError: + pass + + +def __virtual__(): + ''' + Only load this module if keystone + is installed on this minion. + ''' + if HAS_KEYSTONE: + return 'keystone' + return False + +__opts__ = {} + + +def auth(profile=None, **connection_args): + ''' + Set up keystone credentials + + Only intended to be used within Keystone-enabled modules + ''' + + if profile: + prefix = profile + ":keystone." + else: + prefix = "keystone." + + # look in connection_args first, then default to config file + def get(key, default=None): + return connection_args.get('connection_' + key, + __salt__['config.get'](prefix + key, default)) + + user = get('user', 'admin') + password = get('password', 'ADMIN') + tenant = get('tenant', 'admin') + tenant_id = get('tenant_id') + auth_url = get('auth_url', 'http://127.0.0.1:35357/v2.0/') + insecure = get('insecure', False) + token = get('token') + endpoint = get('endpoint', 'http://127.0.0.1:35357/v2.0') + + if token: + kwargs = {'token': token, + 'endpoint': endpoint} + else: + kwargs = {'username': user, + 'password': password, + 'tenant_name': tenant, + 'tenant_id': tenant_id, + 'auth_url': auth_url} + # 'insecure' keyword not supported by all v2.0 keystone clients + # this ensures it's only passed in when defined + if insecure: + kwargs['insecure'] = True + + return client.Client(**kwargs) + + +def ec2_credentials_create(user_id=None, name=None, + tenant_id=None, tenant=None, + profile=None, **connection_args): + ''' + Create EC2-compatible credentials for user per tenant + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.ec2_credentials_create name=admin tenant=admin + salt '*' keystone.ec2_credentials_create \ +user_id=c965f79c4f864eaaa9c3b41904e67082 \ +tenant_id=722787eb540849158668370dc627ec5f + ''' + kstone = auth(profile, **connection_args) + + if name: + user_id = user_get(name=name, profile=profile, + **connection_args)[name]['id'] + if not user_id: + return {'Error': 'Could not resolve User ID'} + + if tenant: + tenant_id = tenant_get(name=tenant, profile=profile, + **connection_args)[tenant]['id'] + if not tenant_id: + return {'Error': 'Could not resolve Tenant ID'} + + newec2 = kstone.ec2.create(user_id, tenant_id) + return {'access': newec2.access, + 'secret': newec2.secret, + 'tenant_id': newec2.tenant_id, + 'user_id': newec2.user_id} + + +def ec2_credentials_delete(user_id=None, name=None, access_key=None, + profile=None, **connection_args): + ''' + Delete EC2-compatible credentials + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.ec2_credentials_delete \ +860f8c2c38ca4fab989f9bc56a061a64 access_key=5f66d2f24f604b8bb9cd28886106f442 + salt '*' keystone.ec2_credentials_delete name=admin \ +access_key=5f66d2f24f604b8bb9cd28886106f442 + ''' + kstone = auth(profile, **connection_args) + + if name: + user_id = user_get(name=name, profile=None, **connection_args)[name]['id'] + if not user_id: + return {'Error': 'Could not resolve User ID'} + kstone.ec2.delete(user_id, access_key) + return 'ec2 key "{0}" deleted under user id "{1}"'.format(access_key, + user_id) + + +def ec2_credentials_get(user_id=None, name=None, access=None, + profile=None, **connection_args): + ''' + Return ec2_credentials for a user (keystone ec2-credentials-get) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.ec2_credentials_get c965f79c4f864eaaa9c3b41904e67082 access=722787eb540849158668370dc627ec5f + salt '*' keystone.ec2_credentials_get user_id=c965f79c4f864eaaa9c3b41904e67082 access=722787eb540849158668370dc627ec5f + salt '*' keystone.ec2_credentials_get name=nova access=722787eb540849158668370dc627ec5f + ''' + kstone = auth(profile, **connection_args) + ret = {} + if name: + for user in kstone.users.list(): + if user.name == name: + user_id = user.id + break + if not user_id: + return {'Error': 'Unable to resolve user id'} + if not access: + return {'Error': 'Access key is required'} + ec2_credentials = kstone.ec2.get(user_id=user_id, access=access, + profile=profile, **connection_args) + ret[ec2_credentials.user_id] = {'user_id': ec2_credentials.user_id, + 'tenant': ec2_credentials.tenant_id, + 'access': ec2_credentials.access, + 'secret': ec2_credentials.secret} + return ret + + +def ec2_credentials_list(user_id=None, name=None, profile=None, + **connection_args): + ''' + Return a list of ec2_credentials for a specific user (keystone ec2-credentials-list) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.ec2_credentials_list 298ce377245c4ec9b70e1c639c89e654 + salt '*' keystone.ec2_credentials_list user_id=298ce377245c4ec9b70e1c639c89e654 + salt '*' keystone.ec2_credentials_list name=jack + ''' + kstone = auth(profile, **connection_args) + ret = {} + if name: + for user in kstone.users.list(): + if user.name == name: + user_id = user.id + break + if not user_id: + return {'Error': 'Unable to resolve user id'} + for ec2_credential in kstone.ec2.list(user_id): + ret[ec2_credential.user_id] = {'user_id': ec2_credential.user_id, + 'tenant_id': ec2_credential.tenant_id, + 'access': ec2_credential.access, + 'secret': ec2_credential.secret} + return ret + + +def endpoint_get(service, profile=None, **connection_args): + ''' + Return a specific endpoint (keystone endpoint-get) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.endpoint_get nova + ''' + kstone = auth(profile, **connection_args) + services = service_list(profile, **connection_args) + if service not in services: + return {'Error': 'Could not find the specified service'} + service_id = services[service]['id'] + endpoints = endpoint_list(profile, **connection_args) + for endpoint in endpoints: + if endpoints[endpoint]['service_id'] == service_id: + return endpoints[endpoint] + return {'Error': 'Could not find endpoint for the specified service'} + + +def endpoint_list(profile=None, **connection_args): + ''' + Return a list of available endpoints (keystone endpoints-list) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.endpoint_list + ''' + kstone = auth(profile, **connection_args) + ret = {} + for endpoint in kstone.endpoints.list(): + ret[endpoint.id] = {'id': endpoint.id, + 'region': endpoint.region, + 'adminurl': endpoint.adminurl, + 'internalurl': endpoint.internalurl, + 'publicurl': endpoint.publicurl, + 'service_id': endpoint.service_id} + return ret + + +def endpoint_create(service, publicurl=None, internalurl=None, adminurl=None, + region=None, profile=None, **connection_args): + ''' + Create an endpoint for an Openstack service + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.endpoint_create nova 'http://public/url' + 'http://internal/url' 'http://adminurl/url' region + ''' + kstone = auth(profile, **connection_args) + keystone_service = service_get(name=service, **connection_args) + if not keystone_service or 'Error' in keystone_service: + return {'Error': 'Could not find the specified service'} + kstone.endpoints.create(region=region, + service_id=keystone_service[service]['id'], + publicurl=publicurl, + adminurl=adminurl, + internalurl=internalurl) + return endpoint_get(service, **connection_args) + + +def endpoint_delete(service, profile=None, **connection_args): + ''' + Delete endpoints of an Openstack service + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.endpoint_delete nova + ''' + kstone = auth(profile, **connection_args) + endpoint = endpoint_get(service, profile, **connection_args) + if not endpoint or 'Error' in endpoint: + return {'Error': 'Could not find any endpoints for the service'} + kstone.endpoints.delete(endpoint['id']) + endpoint = endpoint_get(service, profile, **connection_args) + if not endpoint or 'Error' in endpoint: + return True + + +def role_create(name, profile=None, **connection_args): + ''' + Create named role + + .. code-block:: bash + + salt '*' keystone.role_create admin + ''' + + kstone = auth(profile, **connection_args) + if 'Error' not in role_get(name=name, profile=profile, **connection_args): + return {'Error': 'Role "{0}" already exists'.format(name)} + role = kstone.roles.create(name) + return role_get(name=name, profile=profile, **connection_args) + + +def role_delete(role_id=None, name=None, profile=None, + **connection_args): + ''' + Delete a role (keystone role-delete) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.role_delete c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.role_delete role_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.role_delete name=admin + ''' + kstone = auth(profile, **connection_args) + + if name: + for role in kstone.roles.list(): + if role.name == name: + role_id = role.id + break + if not role_id: + return {'Error': 'Unable to resolve role id'} + role = role_get(role_id, profile=profile, **connection_args) + kstone.roles.delete(role) + ret = 'Role ID {0} deleted'.format(role_id) + if name: + ret += ' ({0})'.format(name) + return ret + + +def role_get(role_id=None, name=None, profile=None, **connection_args): + ''' + Return a specific roles (keystone role-get) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.role_get c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.role_get role_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.role_get name=nova + ''' + kstone = auth(profile, **connection_args) + ret = {} + if name: + for role in kstone.roles.list(): + if role.name == name: + role_id = role.id + break + if not role_id: + return {'Error': 'Unable to resolve role id'} + role = kstone.roles.get(role_id) + ret[role.name] = {'id': role.id, + 'name': role.name} + return ret + + +def role_list(profile=None, **connection_args): + ''' + Return a list of available roles (keystone role-list) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.role_list + ''' + kstone = auth(profile, **connection_args) + ret = {} + for role in kstone.roles.list(): + ret[role.name] = {'id': role.id, + 'name': role.name} + return ret + + +def service_create(name, service_type, description=None, profile=None, + **connection_args): + ''' + Add service to Keystone service catalog + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.service_create nova compute \ +'OpenStack Compute Service' + ''' + kstone = auth(profile, **connection_args) + service = kstone.services.create(name, service_type, description) + return service_get(service.id, profile=profile, **connection_args) + + +def service_delete(service_id=None, name=None, profile=None, **connection_args): + ''' + Delete a service from Keystone service catalog + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.service_delete c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.service_delete name=nova + ''' + kstone = auth(profile, **connection_args) + if name: + service_id = service_get(name=name, profile=profile, + **connection_args)[name]['id'] + service = kstone.services.delete(service_id) + return 'Keystone service ID "{0}" deleted'.format(service_id) + + +def service_get(service_id=None, name=None, profile=None, **connection_args): + ''' + Return a specific services (keystone service-get) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.service_get c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.service_get service_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.service_get name=nova + ''' + kstone = auth(profile, **connection_args) + ret = {} + if name: + for service in kstone.services.list(): + if service.name == name: + service_id = service.id + break + if not service_id: + return {'Error': 'Unable to resolve service id'} + service = kstone.services.get(service_id) + ret[service.name] = {'id': service.id, + 'name': service.name, + 'type': service.type, + 'description': service.description} + return ret + + +def service_list(profile=None, **connection_args): + ''' + Return a list of available services (keystone services-list) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.service_list + ''' + kstone = auth(profile, **connection_args) + ret = {} + for service in kstone.services.list(): + ret[service.name] = {'id': service.id, + 'name': service.name, + 'description': service.description, + 'type': service.type} + return ret + + +def tenant_create(name, description=None, enabled=True, profile=None, + **connection_args): + ''' + Create a keystone tenant + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.tenant_create nova description='nova tenant' + salt '*' keystone.tenant_create test enabled=False + ''' + kstone = auth(profile, **connection_args) + new = kstone.tenants.create(name, description, enabled) + return tenant_get(new.id, profile=profile, **connection_args) + + +def tenant_delete(tenant_id=None, name=None, profile=None, **connection_args): + ''' + Delete a tenant (keystone tenant-delete) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.tenant_delete c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.tenant_delete tenant_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.tenant_delete name=demo + ''' + kstone = auth(profile, **connection_args) + if name: + for tenant in kstone.tenants.list(): + if tenant.name == name: + tenant_id = tenant.id + break + if not tenant_id: + return {'Error': 'Unable to resolve tenant id'} + kstone.tenants.delete(tenant_id) + ret = 'Tenant ID {0} deleted'.format(tenant_id) + if name: + + ret += ' ({0})'.format(name) + return ret + + +def tenant_get(tenant_id=None, name=None, profile=None, + **connection_args): + ''' + Return a specific tenants (keystone tenant-get) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.tenant_get c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.tenant_get tenant_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.tenant_get name=nova + ''' + kstone = auth(profile, **connection_args) + ret = {} + if name: + for tenant in kstone.tenants.list(): + if tenant.name == name: + tenant_id = tenant.id + break + if not tenant_id: + return {'Error': 'Unable to resolve tenant id'} + tenant = kstone.tenants.get(tenant_id) + ret[tenant.name] = {'id': tenant.id, + 'name': tenant.name, + 'description': tenant.description, + 'enabled': tenant.enabled} + return ret + + +def tenant_list(profile=None, **connection_args): + ''' + Return a list of available tenants (keystone tenants-list) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.tenant_list + ''' + kstone = auth(profile, **connection_args) + ret = {} + for tenant in kstone.tenants.list(): + ret[tenant.name] = {'id': tenant.id, + 'name': tenant.name, + 'description': tenant.description, + 'enabled': tenant.enabled} + return ret + + +def tenant_update(tenant_id=None, name=None, description=None, + enabled=None, profile=None, **connection_args): + ''' + Update a tenant's information (keystone tenant-update) + The following fields may be updated: name, email, enabled. + Can only update name if targeting by ID + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.tenant_update name=admin enabled=True + salt '*' keystone.tenant_update c965f79c4f864eaaa9c3b41904e67082 name=admin email=admin@domain.com + ''' + kstone = auth(profile, **connection_args) + if not tenant_id: + for tenant in kstone.tenants.list(): + if tenant.name == name: + tenant_id = tenant.id + break + if not tenant_id: + return {'Error': 'Unable to resolve tenant id'} + + tenant = kstone.tenants.get(tenant_id) + if not name: + name = tenant.name + if not description: + description = tenant.description + if enabled is None: + enabled = tenant.enabled + kstone.tenants.update(tenant_id, name, description, enabled) + + +def token_get(profile=None, **connection_args): + ''' + Return the configured tokens (keystone token-get) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.token_get c965f79c4f864eaaa9c3b41904e67082 + ''' + kstone = auth(profile, **connection_args) + token = kstone.service_catalog.get_token() + return {'id': token['id'], + 'expires': token['expires'], + 'user_id': token['user_id'], + 'tenant_id': token['tenant_id']} + + +def user_list(profile=None, **connection_args): + ''' + Return a list of available users (keystone user-list) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.user_list + ''' + kstone = auth(profile, **connection_args) + ret = {} + for user in kstone.users.list(): + ret[user.name] = {'id': user.id, + 'name': user.name, + 'email': user.email, + 'enabled': user.enabled} + tenant_id = getattr(user, 'tenantId', None) + if tenant_id: + ret[user.name]['tenant_id'] = tenant_id + return ret + + +def user_get(user_id=None, name=None, profile=None, **connection_args): + ''' + Return a specific users (keystone user-get) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_get c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.user_get user_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.user_get name=nova + ''' + kstone = auth(profile, **connection_args) + ret = {} + if name: + for user in kstone.users.list(): + if user.name == name: + user_id = user.id + break + if not user_id: + return {'Error': 'Unable to resolve user id'} + user = kstone.users.get(user_id) + ret[user.name] = {'id': user.id, + 'name': user.name, + 'email': user.email, + 'enabled': user.enabled} + tenant_id = getattr(user, 'tenantId', None) + if tenant_id: + ret[user.name]['tenant_id'] = tenant_id + return ret + + +def user_create(name, password, email, tenant_id=None, + enabled=True, profile=None, **connection_args): + ''' + Create a user (keystone user-create) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_create name=jack password=zero email=jack@halloweentown.org tenant_id=a28a7b5a999a455f84b1f5210264375e enabled=True + ''' + kstone = auth(profile, **connection_args) + item = kstone.users.create(name=name, + password=password, + email=email, + tenant_id=tenant_id, + enabled=enabled) + return user_get(item.id, profile=profile, **connection_args) + + +def user_delete(user_id=None, name=None, profile=None, **connection_args): + ''' + Delete a user (keystone user-delete) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_delete c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.user_delete user_id=c965f79c4f864eaaa9c3b41904e67082 + salt '*' keystone.user_delete name=nova + ''' + kstone = auth(profile, **connection_args) + if name: + for user in kstone.users.list(): + if user.name == name: + user_id = user.id + break + if not user_id: + return {'Error': 'Unable to resolve user id'} + kstone.users.delete(user_id) + ret = 'User ID {0} deleted'.format(user_id) + if name: + + ret += ' ({0})'.format(name) + return ret + + +def user_update(user_id=None, name=None, email=None, enabled=None, + tenant=None, profile=None, **connection_args): + ''' + Update a user's information (keystone user-update) + The following fields may be updated: name, email, enabled, tenant. + Because the name is one of the fields, a valid user id is required. + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_update user_id=c965f79c4f864eaaa9c3b41904e67082 name=newname + salt '*' keystone.user_update c965f79c4f864eaaa9c3b41904e67082 name=newname email=newemail@domain.com + ''' + kstone = auth(profile, **connection_args) + if not user_id: + for user in kstone.users.list(): + if user.name == name: + user_id = user.id + break + if not user_id: + return {'Error': 'Unable to resolve user id'} + user = kstone.users.get(user_id) + # Keep previous settings if not updating them + if not name: + name = user.name + if not email: + email = user.email + if enabled is None: + enabled = user.enabled + kstone.users.update(user=user_id, name=name, email=email, enabled=enabled) + if tenant: + for t in kstone.tenants.list(): + if t.name == tenant: + tenant_id = t.id + break + kstone.users.update_tenant(user_id, tenant_id) + ret = 'Info updated for user ID {0}'.format(user_id) + return ret + + +def user_verify_password(user_id=None, name=None, password=None, + profile=None, **connection_args): + ''' + Verify a user's password + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_verify_password name=test password=foobar + salt '*' keystone.user_verify_password user_id=c965f79c4f864eaaa9c3b41904e67082 password=foobar + ''' + kstone = auth(profile, **connection_args) + if 'connection_endpoint' in connection_args: + auth_url = connection_args.get('connection_endpoint') + else: + auth_url = __salt__['config.option']('keystone.endpoint', + 'http://127.0.0.1:35357/v2.0') + + if user_id: + for user in kstone.users.list(): + if user.id == user_id: + name = user.name + break + if not name: + return {'Error': 'Unable to resolve user name'} + kwargs = {'username': name, + 'password': password, + 'auth_url': auth_url} + try: + userauth = client.Client(**kwargs) + except keystoneclient.exceptions.Unauthorized: + return False + return True + + +def user_password_update(user_id=None, name=None, password=None, + profile=None, **connection_args): + ''' + Update a user's password (keystone user-password-update) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_password_update c965f79c4f864eaaa9c3b41904e67082 password=12345 + salt '*' keystone.user_password_update user_id=c965f79c4f864eaaa9c3b41904e67082 password=12345 + salt '*' keystone.user_password_update name=nova password=12345 + ''' + kstone = auth(profile, **connection_args) + if name: + for user in kstone.users.list(): + if user.name == name: + user_id = user.id + break + if not user_id: + return {'Error': 'Unable to resolve user id'} + kstone.users.update_password(user=user_id, password=password) + ret = 'Password updated for user ID {0}'.format(user_id) + if name: + ret += ' ({0})'.format(name) + return ret + + +def user_role_add(user_id=None, user=None, tenant_id=None, + tenant=None, role_id=None, role=None, profile=None, + **connection_args): + ''' + Add role for user in tenant (keystone user-role-add) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_role_add \ +user_id=298ce377245c4ec9b70e1c639c89e654 \ +tenant_id=7167a092ece84bae8cead4bf9d15bb3b \ +role_id=ce377245c4ec9b70e1c639c89e8cead4 + salt '*' keystone.user_role_add user=admin tenant=admin role=admin + ''' + kstone = auth(profile, **connection_args) + if user: + user_id = user_get(name=user, profile=profile, + **connection_args)[user]['id'] + else: + user = user_get(user_id, profile=profile, + **connection_args).keys()[0]['name'] + if not user_id: + return {'Error': 'Unable to resolve user id'} + + if tenant: + tenant_id = tenant_get(name=tenant, profile=profile, + **connection_args)[tenant]['id'] + else: + tenant = tenant_get(tenant_id, profile=profile, + **connection_args).keys()[0]['name'] + if not tenant_id: + return {'Error': 'Unable to resolve tenant id'} + + if role: + role_id = role_get(name=role, profile=profile, + **connection_args)[role]['id'] + else: + role = role_get(role_id, profile=profile, + **connection_args).keys()[0]['name'] + if not role_id: + return {'Error': 'Unable to resolve role id'} + + kstone.roles.add_user_role(user_id, role_id, tenant_id) + ret_msg = '"{0}" role added for user "{1}" for "{2}" tenant' + return ret_msg.format(role, user, tenant) + + +def user_role_remove(user_id=None, user=None, tenant_id=None, + tenant=None, role_id=None, role=None, + profile=None, **connection_args): + ''' + Remove role for user in tenant (keystone user-role-remove) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_role_remove \ +user_id=298ce377245c4ec9b70e1c639c89e654 \ +tenant_id=7167a092ece84bae8cead4bf9d15bb3b \ +role_id=ce377245c4ec9b70e1c639c89e8cead4 + salt '*' keystone.user_role_remove user=admin tenant=admin role=admin + ''' + kstone = auth(profile, **connection_args) + if user: + user_id = user_get(name=user, profile=profile, + **connection_args)[user]['id'] + else: + user = user_get(user_id, profile=profile, + **connection_args).keys()[0]['name'] + if not user_id: + return {'Error': 'Unable to resolve user id'} + + if tenant: + tenant_id = tenant_get(name=tenant, profile=profile, + **connection_args)[tenant]['id'] + else: + tenant = tenant_get(tenant_id, profile=profile, + **connection_args).keys()[0]['name'] + if not tenant_id: + return {'Error': 'Unable to resolve tenant id'} + + if role: + role_id = role_get(name=role, profile=profile, + **connection_args)[role]['id'] + else: + role = role_get(role_id).keys()[0]['name'] + if not role_id: + return {'Error': 'Unable to resolve role id'} + + kstone.roles.remove_user_role(user_id, role_id, tenant_id) + ret_msg = '"{0}" role removed for user "{1}" under "{2}" tenant' + return ret_msg.format(role, user, tenant) + + +def user_role_list(user_id=None, tenant_id=None, user_name=None, + tenant_name=None, profile=None, **connection_args): + ''' + Return a list of available user_roles (keystone user-roles-list) + + CLI Examples: + + .. code-block:: bash + + salt '*' keystone.user_role_list \ +user_id=298ce377245c4ec9b70e1c639c89e654 \ +tenant_id=7167a092ece84bae8cead4bf9d15bb3b + salt '*' keystone.user_role_list user_name=admin tenant_name=admin + ''' + kstone = auth(profile, **connection_args) + ret = {} + if user_name: + for user in kstone.users.list(): + if user.name == user_name: + user_id = user.id + break + if tenant_name: + for tenant in kstone.tenants.list(): + if tenant.name == tenant_name: + tenant_id = tenant.id + break + if not user_id or not tenant_id: + return {'Error': 'Unable to resolve user or tenant id'} + for role in kstone.roles.roles_for_user(user=user_id, tenant=tenant_id): + ret[role.name] = {'id': role.id, + 'name': role.name, + 'user_id': user_id, + 'tenant_id': tenant_id} + return ret + + +def _item_list(profile=None, **connection_args): + ''' + Template for writing list functions + Return a list of available items (keystone items-list) + + CLI Example: + + .. code-block:: bash + + salt '*' keystone.item_list + ''' + kstone = auth(profile, **connection_args) + ret = [] + for item in kstone.items.list(): + ret.append(item.__dict__) + #ret[item.name] = { + # 'id': item.id, + # 'name': item.name, + # } + return ret + + #The following is a list of functions that need to be incorporated in the + #keystone module. This list should be updated as functions are added. + # + #endpoint-create Create a new endpoint associated with a service + #endpoint-delete Delete a service endpoint + #discover Discover Keystone servers and show authentication + # protocols and + #bootstrap Grants a new role to a new user on a new tenant, after + # creating each. diff --git a/_states/keystone.py b/_states/keystone.py new file mode 100644 index 0000000..16b4be5 --- /dev/null +++ b/_states/keystone.py @@ -0,0 +1,588 @@ +# -*- coding: utf-8 -*- +''' +Management of Keystone users +============================ + +:depends: - keystoneclient Python module +:configuration: See :py:mod:`salt.modules.keystone` for setup instructions. + +.. code-block:: yaml + + Keystone tenants: + keystone.tenant_present: + - names: + - admin + - demo + - service + + Keystone roles: + keystone.role_present: + - names: + - admin + - Member + + admin: + keystone.user_present: + - password: R00T_4CC3SS + - email: admin@domain.com + - roles: + - admin: # tenants + - admin # roles + - service: + - admin + - Member + - require: + - keystone: Keystone tenants + - keystone: Keystone roles + + nova: + keystone.user_present: + - password: '$up3rn0v4' + - email: nova@domain.com + - tenant: service + - roles: + - service: + - admin + - require: + - keystone: Keystone tenants + - keystone: Keystone roles + + demo: + keystone.user_present: + - password: 'd3m0n$trati0n' + - email: demo@domain.com + - tenant: demo + - roles: + - demo: + - Member + - require: + - keystone: Keystone tenants + - keystone: Keystone roles + + nova service: + keystone.service_present: + - name: nova + - service_type: compute + - description: OpenStack Compute Service + +''' + + +def __virtual__(): + ''' + Only load if the keystone module is in __salt__ + ''' + return 'keystone' if 'keystone.auth' in __salt__ else False + + +def user_present(name, + password, + email, + tenant=None, + enabled=True, + roles=None, + profile=None, + **connection_args): + ''' + Ensure that the keystone user is present with the specified properties. + + name + The name of the user to manage + + password + The password to use for this user + + email + The email address for this user + + tenant + The tenant for this user + + enabled + Availability state for this user + + roles + The roles the user should have under tenants + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'User "{0}" will be updated'.format(name)} + + # Validate tenant if set + if tenant is not None: + tenantdata = __salt__['keystone.tenant_get'](name=tenant, + profile=profile, + **connection_args) + if 'Error' in tenantdata: + ret['result'] = False + ret['comment'] = 'Tenant "{0}" does not exist'.format(tenant) + return ret + tenant_id = tenantdata[tenant]['id'] + else: + tenant_id = None + + # Check if user is already present + user = __salt__['keystone.user_get'](name=name, profile=profile, + **connection_args) + if 'Error' not in user: + ret['comment'] = 'User "{0}" is already present'.format(name) + if user[name]['email'] != email: + if __opts__['test']: + ret['result'] = None + ret['changes']['Email'] = 'Will be updated' + return ret + __salt__['keystone.user_update'](name=name, email=email, + profile=profile, **connection_args) + ret['comment'] = 'User "{0}" has been updated'.format(name) + ret['changes']['Email'] = 'Updated' + if user[name]['enabled'] != enabled: + if __opts__['test']: + ret['result'] = None + ret['changes']['Enabled'] = 'Will be {0}'.format(enabled) + return ret + __salt__['keystone.user_update'](name=name, + enabled=enabled, + profile=profile, + **connection_args) + ret['comment'] = 'User "{0}" has been updated'.format(name) + ret['changes']['Enabled'] = 'Now {0}'.format(enabled) + if tenant and ('tenant_id' not in user[name] or + user[name]['tenant_id'] != tenant_id): + if __opts__['test']: + ret['result'] = None + ret['changes']['Tenant'] = 'Will be added to "{0}" tenant'.format(tenant) + return ret + __salt__['keystone.user_update'](name=name, tenant=tenant, + profile=profile, + **connection_args) + ret['comment'] = 'User "{0}" has been updated'.format(name) + ret['changes']['Tenant'] = 'Added to "{0}" tenant'.format(tenant) + if not __salt__['keystone.user_verify_password'](name=name, + password=password, + profile=profile, + **connection_args): + if __opts__['test']: + ret['result'] = None + ret['changes']['Password'] = 'Will be updated' + return ret + __salt__['keystone.user_password_update'](name=name, + password=password, + profile=profile, + **connection_args) + ret['comment'] = 'User "{0}" has been updated'.format(name) + ret['changes']['Password'] = 'Updated' + if roles: + for tenant_role in roles[0].keys(): + args = dict({'user_name': name, 'tenant_name': + tenant_role, 'profile': profile}, **connection_args) + tenant_roles = __salt__['keystone.user_role_list'](**args) + for role in roles[0][tenant_role]: + if role not in tenant_roles: + if __opts__['test']: + ret['result'] = None + if 'roles' in ret['changes']: + ret['changes']['roles'].append(role) + else: + ret['changes']['roles'] = [role] + return ret + addargs = dict({'user': name, 'role': role, + 'tenant': tenant_role, + 'profile': profile}, + **connection_args) + newrole = __salt__['keystone.user_role_add'](**addargs) + if 'roles' in ret['changes']: + ret['changes']['roles'].append(newrole) + else: + ret['changes']['roles'] = [newrole] + else: + # Create that user! + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Keystone user "{0}" will be added'.format(name) + ret['changes']['User'] = 'Will be created' + return ret + __salt__['keystone.user_create'](name=name, + password=password, + email=email, + tenant_id=tenant_id, + enabled=enabled, + profile=profile, + **connection_args) + if roles: + for tenant_role in roles[0].keys(): + for role in roles[0][tenant_role]: + __salt__['keystone.user_role_add'](user=name, + role=role, + tenant=tenant_role, + profile=profile, + **connection_args) + ret['comment'] = 'Keystone user {0} has been added'.format(name) + ret['changes']['User'] = 'Created' + + return ret + + +def user_absent(name, profile=None, **connection_args): + ''' + Ensure that the keystone user is absent. + + name + The name of the user that should not exist + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'User "{0}" is already absent'.format(name)} + + # Check if user is present + user = __salt__['keystone.user_get'](name=name, profile=profile, + **connection_args) + if 'Error' not in user: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'User "{0}" will be deleted'.format(name) + ret['changes']['User'] = 'Will be deleted' + return ret + # Delete that user! + __salt__['keystone.user_delete'](name=name, profile=profile, + **connection_args) + ret['comment'] = 'User "{0}" has been deleted'.format(name) + ret['changes']['User'] = 'Deleted' + + return ret + + +def tenant_present(name, description=None, enabled=True, profile=None, + **connection_args): + ''' + Ensures that the keystone tenant exists + + name + The name of the tenant to manage + + description + The description to use for this tenant + + enabled + Availability state for this tenant + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Tenant "{0}" already exists'.format(name)} + + # Check if tenant is already present + tenant = __salt__['keystone.tenant_get'](name=name, + profile=profile, + **connection_args) + + if 'Error' not in tenant: + if tenant[name]['description'] != description: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Tenant "{0}" will be updated'.format(name) + ret['changes']['Description'] = 'Will be updated' + return ret + __salt__['keystone.tenant_update'](name=name, + description=description, + enabled=enabled, + profile=profile, + **connection_args) + ret['comment'] = 'Tenant "{0}" has been updated'.format(name) + ret['changes']['Description'] = 'Updated' + if tenant[name]['enabled'] != enabled: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Tenant "{0}" will be updated'.format(name) + ret['changes']['Enabled'] = 'Will be {0}'.format(enabled) + return ret + __salt__['keystone.tenant_update'](name=name, + description=description, + enabled=enabled, + profile=profile, + **connection_args) + ret['comment'] = 'Tenant "{0}" has been updated'.format(name) + ret['changes']['Enabled'] = 'Now {0}'.format(enabled) + else: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Tenant "{0}" will be added'.format(name) + ret['changes']['Tenant'] = 'Will be created' + return ret + # Create tenant + __salt__['keystone.tenant_create'](name, description, enabled, + profile=profile, + **connection_args) + ret['comment'] = 'Tenant "{0}" has been added'.format(name) + ret['changes']['Tenant'] = 'Created' + return ret + + +def tenant_absent(name, profile=None, **connection_args): + ''' + Ensure that the keystone tenant is absent. + + name + The name of the tenant that should not exist + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Tenant "{0}" is already absent'.format(name)} + + # Check if tenant is present + tenant = __salt__['keystone.tenant_get'](name=name, + profile=profile, + **connection_args) + if 'Error' not in tenant: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Tenant "{0}" will be deleted'.format(name) + ret['changes']['Tenant'] = 'Will be deleted' + return ret + # Delete tenant + __salt__['keystone.tenant_delete'](name=name, profile=profile, + **connection_args) + ret['comment'] = 'Tenant "{0}" has been deleted'.format(name) + ret['changes']['Tenant'] = 'Deleted' + + return ret + + +def role_present(name, profile=None, **connection_args): + '''' + Ensures that the keystone role exists + + name + The name of the role that should be present + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Role "{0}" already exists'.format(name)} + + # Check if role is already present + role = __salt__['keystone.role_get'](name=name, profile=profile, + **connection_args) + + if 'Error' not in role: + return ret + else: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Role "{0}" will be added'.format(name) + ret['changes']['Role'] = 'Will be created' + return ret + # Create role + __salt__['keystone.role_create'](name, profile=profile, + **connection_args) + ret['comment'] = 'Role "{0}" has been added'.format(name) + ret['changes']['Role'] = 'Created' + return ret + + +def role_absent(name, profile=None, **connection_args): + ''' + Ensure that the keystone role is absent. + + name + The name of the role that should not exist + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Role "{0}" is already absent'.format(name)} + + # Check if role is present + role = __salt__['keystone.role_get'](name=name, profile=profile, + **connection_args) + if 'Error' not in role: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Role "{0}" will be deleted'.format(name) + ret['changes']['Role'] = 'Will be deleted' + return ret + # Delete role + __salt__['keystone.role_delete'](name=name, profile=profile, + **connection_args) + ret['comment'] = 'Role "{0}" has been deleted'.format(name) + ret['changes']['Role'] = 'Deleted' + + return ret + + +def service_present(name, service_type, description=None, + profile=None, **connection_args): + ''' + Ensure service present in Keystone catalog + + name + The name of the service + + service_type + The type of Openstack Service + + description (optional) + Description of the service + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Service "{0}" already exists'.format(name)} + + # Check if service is already present + role = __salt__['keystone.service_get'](name=name, + profile=profile, + **connection_args) + + if 'Error' not in role: + return ret + else: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Service "{0}" will be added'.format(name) + ret['changes']['Service'] = 'Will be created' + return ret + # Create service + __salt__['keystone.service_create'](name, service_type, + description, + profile=profile, + **connection_args) + ret['comment'] = 'Service "{0}" has been added'.format(name) + ret['changes']['Service'] = 'Created' + return ret + + +def service_absent(name, profile=None, **connection_args): + ''' + Ensure that the service doesn't exist in Keystone catalog + + name + The name of the service that should not exist + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'Service "{0}" is already absent'.format(name)} + + # Check if service is present + role = __salt__['keystone.service_get'](name=name, + profile=profile, + **connection_args) + if 'Error' not in role: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Service "{0}" will be deleted'.format(name) + ret['changes']['Service'] = 'Will be deleted' + return ret + # Delete service + __salt__['keystone.service_delete'](name=name, + profile=profile, + **connection_args) + ret['comment'] = 'Service "{0}" has been deleted'.format(name) + ret['changes']['Service'] = 'Deleted' + + return ret + + +def endpoint_present(name, + publicurl=None, + internalurl=None, + adminurl=None, + region='RegionOne', profile=None, **connection_args): + ''' + Ensure the specified endpoints exists for service + + name + The Service name + + public url + The public url of service endpoint + + internal url + The internal url of service endpoint + + admin url + The admin url of the service endpoint + + region + The region of the endpoint + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'endpoint for service "{0}" already exists'.format(name)} + endpoint = __salt__['keystone.endpoint_get'](name, + profile=profile, + **connection_args) + cur_endpoint = dict(region=region, + publicurl=publicurl, + adminurl=adminurl, + internalurl=internalurl) + if endpoint and 'Error' not in endpoint: + endpoint.pop('id') + endpoint.pop('service_id') + if endpoint == cur_endpoint: + return ret + else: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Endpoint for service "{0}" will be updated'.format(name) + ret['changes']['endpoint'] = 'Will be updated' + return ret + __salt__['keystone.endpoint_delete'](name, + profile=profile, + **connection_args) + ret['comment'] = 'Endpoint for service "{0}" has been updated'.format(name) + else: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Endpoint for service "{0}" will be added'.format(name) + ret['changes']['endpoint'] = 'Will be created' + return ret + ret['comment'] = 'Endpoint for service "{0}" has been added'.format(name) + + if not __opts__['test']: + ret['changes'] = __salt__['keystone.endpoint_create']( + name, + region=region, + publicurl=publicurl, + adminurl=adminurl, + internalurl=internalurl, + profile=profile, + **connection_args) + return ret + + +def endpoint_absent(name, profile=None, **connection_args): + ''' + Ensure that the endpoint for a service doesn't exist in Keystone catalog + + name + The name of the service whose endpoints should not exist + ''' + ret = {'name': name, + 'changes': {}, + 'result': True, + 'comment': 'endpoint for service "{0}" is already absent'.format(name)} + + # Check if service is present + endpoint = __salt__['keystone.endpoint_get'](name, + profile=profile, + **connection_args) + if not endpoint: + return ret + else: + if __opts__['test']: + ret['result'] = None + ret['comment'] = 'Endpoint for service "{0}" will be deleted'.format(name) + ret['changes']['endpoint'] = 'Will be deleted' + return ret + # Delete service + __salt__['keystone.endpoint_delete'](name, + profile=profile, + **connection_args) + ret['comment'] = 'Endpoint for service "{0}" has been deleted'.format(name) + ret['changes']['endpoint'] = 'Deleted' + return ret