Mini Shell
"""
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__
"""
if "keystone.auth" in __salt__:
return "keystone"
return (False, "keystone module could not be loaded")
_OS_IDENTITY_API_VERSION = 2
_TENANT_ID = "tenant_id"
def _api_version(profile=None, **connection_args):
"""
Sets global variables _OS_IDENTITY_API_VERSION and _TENANT_ID
depending on API version.
"""
global _TENANT_ID
global _OS_IDENTITY_API_VERSION
try:
if (
float(
__salt__["keystone.api_version"](
profile=profile, **connection_args
).strip("v")
)
>= 3
):
_TENANT_ID = "project_id"
_OS_IDENTITY_API_VERSION = 3
except KeyError:
pass
def user_present(
name,
password,
email,
tenant=None,
enabled=True,
roles=None,
profile=None,
password_reset=True,
project=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.
.. note::
If the user already exists and a different password was set for
the user than the one specified here, the password for the user
will be updated. Please set the ``password_reset`` option to
``False`` if this is not the desired behavior.
password_reset
Whether or not to reset password after initial set. Defaults to
``True``.
email
The email address for this user
tenant
The tenant (name) for this user
project
The project (name) for this user (overrides tenant in api v3)
enabled
Availability state for this user
roles
The roles the user should have under given tenants.
Passed as a dictionary mapping tenant names to a list
of roles in this tenant, i.e.::
roles:
admin: # tenant
- admin # role
service:
- admin
- Member
"""
ret = {
"name": name,
"changes": {},
"result": True,
"comment": f'User "{name}" will be updated',
}
_api_version(profile=profile, **connection_args)
if project and not tenant:
tenant = project
# 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"] = f'Tenant / project "{tenant}" does not exist'
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:
change_email = False
change_enabled = False
change_tenant = False
change_password = False
if user[name].get("email", None) != email:
change_email = True
if user[name].get("enabled", None) != enabled:
change_enabled = True
if tenant and (
_TENANT_ID not in user[name]
or user[name].get(_TENANT_ID, None) != tenant_id
):
change_tenant = True
if password_reset is True and not __salt__["keystone.user_verify_password"](
name=name, password=password, profile=profile, **connection_args
):
change_password = True
if __opts__.get("test") and (
change_email or change_enabled or change_tenant or change_password
):
ret["result"] = None
ret["comment"] = f'User "{name}" will be updated'
if change_email is True:
ret["changes"]["Email"] = "Will be updated"
if change_enabled is True:
ret["changes"]["Enabled"] = "Will be True"
if change_tenant is True:
ret["changes"]["Tenant"] = f'Will be added to "{tenant}" tenant'
if change_password is True:
ret["changes"]["Password"] = "Will be updated"
return ret
ret["comment"] = f'User "{name}" is already present'
if change_email:
__salt__["keystone.user_update"](
name=name, email=email, profile=profile, **connection_args
)
ret["comment"] = f'User "{name}" has been updated'
ret["changes"]["Email"] = "Updated"
if change_enabled:
__salt__["keystone.user_update"](
name=name, enabled=enabled, profile=profile, **connection_args
)
ret["comment"] = f'User "{name}" has been updated'
ret["changes"]["Enabled"] = f"Now {enabled}"
if change_tenant:
__salt__["keystone.user_update"](
name=name, tenant=tenant, profile=profile, **connection_args
)
ret["comment"] = f'User "{name}" has been updated'
ret["changes"]["Tenant"] = f'Added to "{tenant}" tenant'
if change_password:
__salt__["keystone.user_password_update"](
name=name, password=password, profile=profile, **connection_args
)
ret["comment"] = f'User "{name}" has been updated'
ret["changes"]["Password"] = "Updated"
if roles:
for tenant in roles:
args = dict(
{"user_name": name, "tenant_name": tenant, "profile": profile},
**connection_args,
)
tenant_roles = __salt__["keystone.user_role_list"](**args)
for role in roles[tenant]:
if role not in tenant_roles:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = 'User roles "{}" will been updated'.format(
name
)
return ret
addargs = dict(
{
"user": name,
"role": role,
"tenant": tenant,
"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]
roles_to_remove = list(set(tenant_roles) - set(roles[tenant]))
for role in roles_to_remove:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = 'User roles "{}" will been updated'.format(
name
)
return ret
addargs = dict(
{
"user": name,
"role": role,
"tenant": tenant,
"profile": profile,
},
**connection_args,
)
oldrole = __salt__["keystone.user_role_remove"](**addargs)
if "roles" in ret["changes"]:
ret["changes"]["roles"].append(oldrole)
else:
ret["changes"]["roles"] = [oldrole]
else:
# Create that user!
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Keystone user "{name}" will be added'
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 in roles:
for role in roles[tenant]:
__salt__["keystone.user_role_add"](
user=name,
role=role,
tenant=tenant,
profile=profile,
**connection_args,
)
ret["comment"] = f"Keystone user {name} has been added"
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": f'User "{name}" is already absent',
}
# Check if user is present
user = __salt__["keystone.user_get"](name=name, profile=profile, **connection_args)
if "Error" not in user:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'User "{name}" will be deleted'
return ret
# Delete that user!
__salt__["keystone.user_delete"](name=name, profile=profile, **connection_args)
ret["comment"] = f'User "{name}" has been deleted'
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": f'Tenant / project "{name}" already exists',
}
_api_version(profile=profile, **connection_args)
# 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].get("description", None) != description:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Tenant / project "{name}" will be updated'
ret["changes"]["Description"] = "Will be updated"
return ret
__salt__["keystone.tenant_update"](
name=name,
description=description,
enabled=enabled,
profile=profile,
**connection_args,
)
ret["comment"] = f'Tenant / project "{name}" has been updated'
ret["changes"]["Description"] = "Updated"
if tenant[name].get("enabled", None) != enabled:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Tenant / project "{name}" will be updated'
ret["changes"]["Enabled"] = f"Will be {enabled}"
return ret
__salt__["keystone.tenant_update"](
name=name,
description=description,
enabled=enabled,
profile=profile,
**connection_args,
)
ret["comment"] = f'Tenant / project "{name}" has been updated'
ret["changes"]["Enabled"] = f"Now {enabled}"
else:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Tenant / project "{name}" will be added'
ret["changes"]["Tenant"] = "Will be created"
return ret
# Create tenant
if _OS_IDENTITY_API_VERSION > 2:
created = __salt__["keystone.project_create"](
name=name,
domain="default",
description=description,
enabled=enabled,
profile=profile,
**connection_args,
)
else:
created = __salt__["keystone.tenant_create"](
name=name,
description=description,
enabled=enabled,
profile=profile,
**connection_args,
)
ret["changes"]["Tenant"] = "Created" if created is True else "Failed"
ret["result"] = created
ret["comment"] = f'Tenant / project "{name}" has been added'
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": f'Tenant / project "{name}" is already absent',
}
# Check if tenant is present
tenant = __salt__["keystone.tenant_get"](
name=name, profile=profile, **connection_args
)
if "Error" not in tenant:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Tenant / project "{name}" will be deleted'
return ret
# Delete tenant
__salt__["keystone.tenant_delete"](
name=name, profile=profile, **connection_args
)
ret["comment"] = f'Tenant / project "{name}" has been deleted'
ret["changes"]["Tenant/Project"] = "Deleted"
return ret
def project_present(
name, description=None, enabled=True, profile=None, **connection_args
):
"""
Ensures that the keystone project exists
Alias for tenant_present from V2 API to fulfill
V3 API naming convention.
.. versionadded:: 2016.11.0
name
The name of the project to manage
description
The description to use for this project
enabled
Availability state for this project
.. code-block:: yaml
nova:
keystone.project_present:
- enabled: True
- description: 'Nova Compute Service'
"""
return tenant_present(
name,
description=description,
enabled=enabled,
profile=profile,
**connection_args,
)
def project_absent(name, profile=None, **connection_args):
"""
Ensure that the keystone project is absent.
Alias for tenant_absent from V2 API to fulfill
V3 API naming convention.
.. versionadded:: 2016.11.0
name
The name of the project that should not exist
.. code-block:: yaml
delete_nova:
keystone.project_absent:
- name: nova
"""
return tenant_absent(name, profile=profile, **connection_args)
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": f'Role "{name}" already exists',
}
# 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__.get("test"):
ret["result"] = None
ret["comment"] = f'Role "{name}" will be added'
return ret
# Create role
__salt__["keystone.role_create"](name, profile=profile, **connection_args)
ret["comment"] = f'Role "{name}" has been added'
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": f'Role "{name}" is already absent',
}
# Check if role is present
role = __salt__["keystone.role_get"](name=name, profile=profile, **connection_args)
if "Error" not in role:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Role "{name}" will be deleted'
return ret
# Delete role
__salt__["keystone.role_delete"](name=name, profile=profile, **connection_args)
ret["comment"] = f'Role "{name}" has been deleted'
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": f'Service "{name}" already exists',
}
# 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__.get("test"):
ret["result"] = None
ret["comment"] = f'Service "{name}" will be added'
return ret
# Create service
__salt__["keystone.service_create"](
name, service_type, description, profile=profile, **connection_args
)
ret["comment"] = f'Service "{name}" has been added'
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": f'Service "{name}" is already absent',
}
# Check if service is present
role = __salt__["keystone.service_get"](
name=name, profile=profile, **connection_args
)
if "Error" not in role:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Service "{name}" will be deleted'
return ret
# Delete service
__salt__["keystone.service_delete"](
name=name, profile=profile, **connection_args
)
ret["comment"] = f'Service "{name}" has been deleted'
ret["changes"]["Service"] = "Deleted"
return ret
def endpoint_present(
name,
publicurl=None,
internalurl=None,
adminurl=None,
region=None,
profile=None,
url=None,
interface=None,
**connection_args,
):
"""
Ensure the specified endpoints exists for service
name
The Service name
publicurl
The public url of service endpoint (for V2 API)
internalurl
The internal url of service endpoint (for V2 API)
adminurl
The admin url of the service endpoint (for V2 API)
region
The region of the endpoint
url
The endpoint URL (for V3 API)
interface
The interface type, which describes the visibility
of the endpoint. (for V3 API)
"""
ret = {"name": name, "changes": {}, "result": True, "comment": ""}
_api_version(profile=profile, **connection_args)
endpoint = __salt__["keystone.endpoint_get"](
name, region, profile=profile, interface=interface, **connection_args
)
def _changes(desc):
return ret.get("comment", "") + desc + "\n"
def _create_endpoint():
if _OS_IDENTITY_API_VERSION > 2:
ret["changes"] = __salt__["keystone.endpoint_create"](
name,
region=region,
url=url,
interface=interface,
profile=profile,
**connection_args,
)
else:
ret["changes"] = __salt__["keystone.endpoint_create"](
name,
region=region,
publicurl=publicurl,
adminurl=adminurl,
internalurl=internalurl,
profile=profile,
**connection_args,
)
if endpoint and "Error" not in endpoint and endpoint.get("region") == region:
if _OS_IDENTITY_API_VERSION > 2:
change_url = False
change_interface = False
if endpoint.get("url", None) != url:
ret["comment"] = _changes(
'URL changes from "{}" to "{}"'.format(
endpoint.get("url", None), url
)
)
change_url = True
if endpoint.get("interface", None) != interface:
ret["comment"] = _changes(
'Interface changes from "{}" to "{}"'.format(
endpoint.get("interface", None), interface
)
)
change_interface = True
if __opts__.get("test") and (change_url or change_interface):
ret["result"] = None
ret["changes"]["Endpoint"] = "Will be updated"
ret["comment"] += 'Endpoint for service "{}" will be updated'.format(
name
)
return ret
if change_url:
ret["changes"]["url"] = url
if change_interface:
ret["changes"]["interface"] = interface
else:
change_publicurl = False
change_adminurl = False
change_internalurl = False
if endpoint.get("publicurl", None) != publicurl:
change_publicurl = True
ret["comment"] = _changes(
'Public URL changes from "{}" to "{}"'.format(
endpoint.get("publicurl", None), publicurl
)
)
if endpoint.get("adminurl", None) != adminurl:
change_adminurl = True
ret["comment"] = _changes(
'Admin URL changes from "{}" to "{}"'.format(
endpoint.get("adminurl", None), adminurl
)
)
if endpoint.get("internalurl", None) != internalurl:
change_internalurl = True
ret["comment"] = _changes(
'Internal URL changes from "{}" to "{}"'.format(
endpoint.get("internalurl", None), internalurl
)
)
if __opts__.get("test") and (
change_publicurl or change_adminurl or change_internalurl
):
ret["result"] = None
ret["comment"] += 'Endpoint for service "{}" will be updated'.format(
name
)
ret["changes"]["Endpoint"] = "Will be updated"
return ret
if change_publicurl:
ret["changes"]["publicurl"] = publicurl
if change_adminurl:
ret["changes"]["adminurl"] = adminurl
if change_internalurl:
ret["changes"]["internalurl"] = internalurl
if ret["comment"]: # changed
__salt__["keystone.endpoint_delete"](
name, region, profile=profile, interface=interface, **connection_args
)
_create_endpoint()
ret["comment"] += f'Endpoint for service "{name}" has been updated'
else:
# Add new endpoint
if __opts__.get("test"):
ret["result"] = None
ret["changes"]["Endpoint"] = "Will be created"
ret["comment"] = f'Endpoint for service "{name}" will be added'
return ret
_create_endpoint()
ret["comment"] = f'Endpoint for service "{name}" has been added'
if ret["comment"] == "": # => no changes
ret["comment"] = f'Endpoint for service "{name}" already exists'
return ret
def endpoint_absent(name, region=None, profile=None, interface=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
region (optional)
The region of the endpoint. Defaults to ``RegionOne``.
interface
The interface type, which describes the visibility
of the endpoint. (for V3 API)
"""
ret = {
"name": name,
"changes": {},
"result": True,
"comment": 'Endpoint for service "{}"{} is already absent'.format(
name,
f', interface "{interface}",' if interface is not None else "",
),
}
# Check if service is present
endpoint = __salt__["keystone.endpoint_get"](
name, region, profile=profile, interface=interface, **connection_args
)
if not endpoint:
return ret
else:
if __opts__.get("test"):
ret["result"] = None
ret["comment"] = f'Endpoint for service "{name}" will be deleted'
return ret
# Delete service
__salt__["keystone.endpoint_delete"](
name, region, profile=profile, interface=interface, **connection_args
)
ret["comment"] = 'Endpoint for service "{}"{} has been deleted'.format(
name,
f', interface "{interface}",' if interface is not None else "",
)
ret["changes"]["endpoint"] = "Deleted"
return ret
Zerion Mini Shell 1.0