mirror of
https://github.com/ansible-collections/community.general.git
synced 2026-02-04 07:51:50 +00:00
523 lines
17 KiB
Python
523 lines
17 KiB
Python
#!/usr/bin/python
|
|
# Copyright (c) 2018, Evert Mulder (base on manageiq_user.py by Daniel Korn <korndaniel1@gmail.com>)
|
|
# GNU General Public License v3.0+ (see LICENSES/GPL-3.0-or-later.txt or https://www.gnu.org/licenses/gpl-3.0.txt)
|
|
# SPDX-License-Identifier: GPL-3.0-or-later
|
|
|
|
from __future__ import annotations
|
|
|
|
DOCUMENTATION = r"""
|
|
module: manageiq_tenant
|
|
|
|
short_description: Management of tenants in ManageIQ
|
|
extends_documentation_fragment:
|
|
- community.general.manageiq
|
|
- community.general.attributes
|
|
|
|
author: Evert Mulder (@evertmulder)
|
|
description:
|
|
- The manageiq_tenant module supports adding, updating and deleting tenants in ManageIQ.
|
|
requirements:
|
|
- manageiq-client
|
|
attributes:
|
|
check_mode:
|
|
support: none
|
|
diff_mode:
|
|
support: none
|
|
options:
|
|
state:
|
|
type: str
|
|
description:
|
|
- V(absent) - tenant should not exist,
|
|
- V(present) - tenant should be.
|
|
choices: ['absent', 'present']
|
|
default: 'present'
|
|
name:
|
|
type: str
|
|
description:
|
|
- The tenant name.
|
|
required: true
|
|
default:
|
|
description:
|
|
type: str
|
|
description:
|
|
- The tenant description.
|
|
required: true
|
|
default:
|
|
parent_id:
|
|
type: int
|
|
description:
|
|
- The ID of the parent tenant. If not supplied the root tenant is used.
|
|
- The O(parent_id) takes president over O(parent) when supplied.
|
|
default:
|
|
parent:
|
|
type: str
|
|
description:
|
|
- The name of the parent tenant. If not supplied and no O(parent_id) is supplied the root tenant is used.
|
|
default:
|
|
quotas:
|
|
type: dict
|
|
description:
|
|
- The tenant quotas.
|
|
- All parameters case sensitive.
|
|
- 'Valid attributes are:'
|
|
- '- V(cpu_allocated) (int): use null to remove the quota.'
|
|
- '- V(mem_allocated) (GB): use null to remove the quota.'
|
|
- '- V(storage_allocated) (GB): use null to remove the quota.'
|
|
- '- V(vms_allocated) (int): use null to remove the quota.'
|
|
- '- V(templates_allocated) (int): use null to remove the quota.'
|
|
default: {}
|
|
"""
|
|
|
|
EXAMPLES = r"""
|
|
- name: Update the root tenant in ManageIQ
|
|
community.general.manageiq_tenant:
|
|
name: 'My Company'
|
|
description: 'My company name'
|
|
manageiq_connection:
|
|
url: 'http://127.0.0.1:3000'
|
|
username: 'admin'
|
|
password: 'smartvm'
|
|
validate_certs: false # only do this when you trust the network!
|
|
|
|
- name: Create a tenant in ManageIQ
|
|
community.general.manageiq_tenant:
|
|
name: 'Dep1'
|
|
description: 'Manufacturing department'
|
|
parent_id: 1
|
|
manageiq_connection:
|
|
url: 'http://127.0.0.1:3000'
|
|
username: 'admin'
|
|
password: 'smartvm'
|
|
validate_certs: false # only do this when you trust the network!
|
|
|
|
- name: Delete a tenant in ManageIQ
|
|
community.general.manageiq_tenant:
|
|
state: 'absent'
|
|
name: 'Dep1'
|
|
parent_id: 1
|
|
manageiq_connection:
|
|
url: 'http://127.0.0.1:3000'
|
|
username: 'admin'
|
|
password: 'smartvm'
|
|
validate_certs: false # only do this when you trust the network!
|
|
|
|
- name: Set tenant quota for cpu_allocated, mem_allocated, remove quota for vms_allocated
|
|
community.general.manageiq_tenant:
|
|
name: 'Dep1'
|
|
parent_id: 1
|
|
quotas:
|
|
- cpu_allocated: 100
|
|
- mem_allocated: 50
|
|
- vms_allocated:
|
|
manageiq_connection:
|
|
url: 'http://127.0.0.1:3000'
|
|
username: 'admin'
|
|
password: 'smartvm'
|
|
validate_certs: false # only do this when you trust the network!
|
|
|
|
|
|
- name: Delete a tenant in ManageIQ using a token
|
|
community.general.manageiq_tenant:
|
|
state: 'absent'
|
|
name: 'Dep1'
|
|
parent_id: 1
|
|
manageiq_connection:
|
|
url: 'http://127.0.0.1:3000'
|
|
token: 'sometoken'
|
|
validate_certs: false # only do this when you trust the network!
|
|
"""
|
|
|
|
RETURN = r"""
|
|
tenant:
|
|
description: The tenant.
|
|
returned: success
|
|
type: complex
|
|
contains:
|
|
id:
|
|
description: The tenant ID.
|
|
returned: success
|
|
type: int
|
|
name:
|
|
description: The tenant name.
|
|
returned: success
|
|
type: str
|
|
description:
|
|
description: The tenant description.
|
|
returned: success
|
|
type: str
|
|
parent_id:
|
|
description: The ID of the parent tenant.
|
|
returned: success
|
|
type: int
|
|
quotas:
|
|
description: List of tenant quotas.
|
|
returned: success
|
|
type: list
|
|
sample:
|
|
cpu_allocated: 100
|
|
mem_allocated: 50
|
|
"""
|
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
from ansible_collections.community.general.plugins.module_utils.manageiq import ManageIQ, manageiq_argument_spec
|
|
|
|
|
|
class ManageIQTenant:
|
|
"""
|
|
Object to execute tenant management operations in manageiq.
|
|
"""
|
|
|
|
def __init__(self, manageiq):
|
|
self.manageiq = manageiq
|
|
|
|
self.module = self.manageiq.module
|
|
self.api_url = self.manageiq.api_url
|
|
self.client = self.manageiq.client
|
|
|
|
def tenant(self, name, parent_id, parent):
|
|
"""Search for tenant object by name and parent_id or parent
|
|
or the root tenant if no parent or parent_id is supplied.
|
|
Returns:
|
|
the parent tenant, None for the root tenant
|
|
the tenant or None if tenant was not found.
|
|
"""
|
|
|
|
if parent_id:
|
|
parent_tenant_res = self.client.collections.tenants.find_by(id=parent_id)
|
|
if not parent_tenant_res:
|
|
self.module.fail_json(msg=f"Parent tenant with id '{parent_id}' not found in manageiq")
|
|
parent_tenant = parent_tenant_res[0]
|
|
tenants = self.client.collections.tenants.find_by(name=name)
|
|
|
|
for tenant in tenants:
|
|
try:
|
|
ancestry = tenant["ancestry"]
|
|
except AttributeError:
|
|
ancestry = None
|
|
|
|
if ancestry:
|
|
tenant_parent_id = int(ancestry.split("/")[-1])
|
|
if int(tenant_parent_id) == parent_id:
|
|
return parent_tenant, tenant
|
|
|
|
return parent_tenant, None
|
|
else:
|
|
if parent:
|
|
parent_tenant_res = self.client.collections.tenants.find_by(name=parent)
|
|
if not parent_tenant_res:
|
|
self.module.fail_json(msg=f"Parent tenant '{parent}' not found in manageiq")
|
|
|
|
if len(parent_tenant_res) > 1:
|
|
self.module.fail_json(msg=f"Multiple parent tenants not found in manageiq with name '{parent}'")
|
|
|
|
parent_tenant = parent_tenant_res[0]
|
|
parent_id = int(parent_tenant["id"])
|
|
tenants = self.client.collections.tenants.find_by(name=name)
|
|
|
|
for tenant in tenants:
|
|
try:
|
|
ancestry = tenant["ancestry"]
|
|
except AttributeError:
|
|
ancestry = None
|
|
|
|
if ancestry:
|
|
tenant_parent_id = int(ancestry.split("/")[-1])
|
|
if tenant_parent_id == parent_id:
|
|
return parent_tenant, tenant
|
|
|
|
return parent_tenant, None
|
|
else:
|
|
# No parent or parent id supplied we select the root tenant
|
|
return None, self.client.collections.tenants.find_by(ancestry=None)[0]
|
|
|
|
def compare_tenant(self, tenant, name, description):
|
|
"""Compare tenant fields with new field values.
|
|
|
|
Returns:
|
|
false if tenant fields have some difference from new fields, true o/w.
|
|
"""
|
|
found_difference = (name and tenant["name"] != name) or (description and tenant["description"] != description)
|
|
|
|
return not found_difference
|
|
|
|
def delete_tenant(self, tenant):
|
|
"""Deletes a tenant from manageiq.
|
|
|
|
Returns:
|
|
dict with `msg` and `changed`
|
|
"""
|
|
try:
|
|
url = f"{self.api_url}/tenants/{tenant['id']}"
|
|
result = self.client.post(url, action="delete")
|
|
except Exception as e:
|
|
self.module.fail_json(msg=f"failed to delete tenant {tenant['name']}: {e}")
|
|
|
|
if result["success"] is False:
|
|
self.module.fail_json(msg=result["message"])
|
|
|
|
return dict(changed=True, msg=result["message"])
|
|
|
|
def edit_tenant(self, tenant, name, description):
|
|
"""Edit a manageiq tenant.
|
|
|
|
Returns:
|
|
dict with `msg` and `changed`
|
|
"""
|
|
resource = dict(name=name, description=description, use_config_for_attributes=False)
|
|
|
|
# check if we need to update ( compare_tenant is true is no difference found )
|
|
if self.compare_tenant(tenant, name, description):
|
|
return dict(changed=False, msg=f"tenant {tenant['name']} is not changed.", tenant=tenant["_data"])
|
|
|
|
# try to update tenant
|
|
try:
|
|
self.client.post(tenant["href"], action="edit", resource=resource)
|
|
except Exception as e:
|
|
self.module.fail_json(msg=f"failed to update tenant {tenant['name']}: {e}")
|
|
|
|
return dict(changed=True, msg=f"successfully updated the tenant with id {tenant['id']}")
|
|
|
|
def create_tenant(self, name, description, parent_tenant):
|
|
"""Creates the tenant in manageiq.
|
|
|
|
Returns:
|
|
dict with `msg`, `changed` and `tenant_id`
|
|
"""
|
|
parent_id = parent_tenant["id"]
|
|
# check for required arguments
|
|
for key, value in dict(name=name, description=description, parent_id=parent_id).items():
|
|
if value in (None, ""):
|
|
self.module.fail_json(msg=f"missing required argument: {key}")
|
|
|
|
url = f"{self.api_url}/tenants"
|
|
|
|
resource = {"name": name, "description": description, "parent": {"id": parent_id}}
|
|
|
|
try:
|
|
result = self.client.post(url, action="create", resource=resource)
|
|
tenant_id = result["results"][0]["id"]
|
|
except Exception as e:
|
|
self.module.fail_json(msg=f"failed to create tenant {name}: {e}")
|
|
|
|
return dict(
|
|
changed=True, msg=f"successfully created tenant '{name}' with id '{tenant_id}'", tenant_id=tenant_id
|
|
)
|
|
|
|
def tenant_quota(self, tenant, quota_key):
|
|
"""Search for tenant quota object by tenant and quota_key.
|
|
Returns:
|
|
the quota for the tenant, or None if the tenant quota was not found.
|
|
"""
|
|
|
|
tenant_quotas = self.client.get(f"{tenant['href']}/quotas?expand=resources&filter[]=name={quota_key}")
|
|
|
|
return tenant_quotas["resources"]
|
|
|
|
def tenant_quotas(self, tenant):
|
|
"""Search for tenant quotas object by tenant.
|
|
Returns:
|
|
the quotas for the tenant, or None if no tenant quotas were not found.
|
|
"""
|
|
|
|
tenant_quotas = self.client.get(f"{tenant['href']}/quotas?expand=resources")
|
|
|
|
return tenant_quotas["resources"]
|
|
|
|
def update_tenant_quotas(self, tenant, quotas):
|
|
"""Creates the tenant quotas in manageiq.
|
|
|
|
Returns:
|
|
dict with `msg` and `changed`
|
|
"""
|
|
|
|
changed = False
|
|
messages = []
|
|
for quota_key, quota_value in quotas.items():
|
|
current_quota_filtered = self.tenant_quota(tenant, quota_key)
|
|
if current_quota_filtered:
|
|
current_quota = current_quota_filtered[0]
|
|
else:
|
|
current_quota = None
|
|
|
|
if quota_value:
|
|
# Change the byte values to GB
|
|
if quota_key in ["storage_allocated", "mem_allocated"]:
|
|
quota_value_int = int(quota_value) * 1024 * 1024 * 1024
|
|
else:
|
|
quota_value_int = int(quota_value)
|
|
if current_quota:
|
|
res = self.edit_tenant_quota(tenant, current_quota, quota_key, quota_value_int)
|
|
else:
|
|
res = self.create_tenant_quota(tenant, quota_key, quota_value_int)
|
|
else:
|
|
if current_quota:
|
|
res = self.delete_tenant_quota(tenant, current_quota)
|
|
else:
|
|
res = dict(changed=False, msg=f"tenant quota '{quota_key}' does not exist")
|
|
|
|
if res["changed"]:
|
|
changed = True
|
|
|
|
messages.append(res["msg"])
|
|
|
|
return dict(changed=changed, msg=", ".join(messages))
|
|
|
|
def edit_tenant_quota(self, tenant, current_quota, quota_key, quota_value):
|
|
"""Update the tenant quotas in manageiq.
|
|
|
|
Returns:
|
|
result
|
|
"""
|
|
|
|
if current_quota["value"] == quota_value:
|
|
return dict(changed=False, msg=f"tenant quota {quota_key} already has value {quota_value}")
|
|
else:
|
|
url = f"{tenant['href']}/quotas/{current_quota['id']}"
|
|
resource = {"value": quota_value}
|
|
try:
|
|
self.client.post(url, action="edit", resource=resource)
|
|
except Exception as e:
|
|
self.module.fail_json(msg=f"failed to update tenant quota {quota_key}: {e}")
|
|
|
|
return dict(changed=True, msg=f"successfully updated tenant quota {quota_key}")
|
|
|
|
def create_tenant_quota(self, tenant, quota_key, quota_value):
|
|
"""Creates the tenant quotas in manageiq.
|
|
|
|
Returns:
|
|
result
|
|
"""
|
|
url = f"{tenant['href']}/quotas"
|
|
resource = {"name": quota_key, "value": quota_value}
|
|
try:
|
|
self.client.post(url, action="create", resource=resource)
|
|
except Exception as e:
|
|
self.module.fail_json(msg=f"failed to create tenant quota {quota_key}: {e}")
|
|
|
|
return dict(changed=True, msg=f"successfully created tenant quota {quota_key}")
|
|
|
|
def delete_tenant_quota(self, tenant, quota):
|
|
"""deletes the tenant quotas in manageiq.
|
|
|
|
Returns:
|
|
result
|
|
"""
|
|
try:
|
|
result = self.client.post(quota["href"], action="delete")
|
|
except Exception as e:
|
|
self.module.fail_json(msg=f"failed to delete tenant quota '{quota['name']}': {e}")
|
|
|
|
return dict(changed=True, msg=result["message"])
|
|
|
|
def create_tenant_response(self, tenant, parent_tenant):
|
|
"""Creates the ansible result object from a manageiq tenant entity
|
|
|
|
Returns:
|
|
a dict with the tenant id, name, description, parent id,
|
|
quota's
|
|
"""
|
|
tenant_quotas = self.create_tenant_quotas_response(tenant["tenant_quotas"])
|
|
|
|
try:
|
|
ancestry = tenant["ancestry"]
|
|
tenant_parent_id = ancestry.split("/")[-1]
|
|
except AttributeError:
|
|
# The root tenant does not return the ancestry attribute
|
|
tenant_parent_id = None
|
|
|
|
return dict(
|
|
id=tenant["id"],
|
|
name=tenant["name"],
|
|
description=tenant["description"],
|
|
parent_id=tenant_parent_id,
|
|
quotas=tenant_quotas,
|
|
)
|
|
|
|
@staticmethod
|
|
def create_tenant_quotas_response(tenant_quotas):
|
|
"""Creates the ansible result object from a manageiq tenant_quotas entity
|
|
|
|
Returns:
|
|
a dict with the applied quotas, name and value
|
|
"""
|
|
|
|
if not tenant_quotas:
|
|
return {}
|
|
|
|
result = {}
|
|
for quota in tenant_quotas:
|
|
if quota["unit"] == "bytes":
|
|
value = float(quota["value"]) / (1024 * 1024 * 1024)
|
|
else:
|
|
value = quota["value"]
|
|
result[quota["name"]] = value
|
|
return result
|
|
|
|
|
|
def main():
|
|
argument_spec = dict(
|
|
name=dict(required=True, type="str"),
|
|
description=dict(required=True, type="str"),
|
|
parent_id=dict(type="int"),
|
|
parent=dict(type="str"),
|
|
state=dict(choices=["absent", "present"], default="present"),
|
|
quotas=dict(type="dict", default={}),
|
|
)
|
|
# add the manageiq connection arguments to the arguments
|
|
argument_spec.update(manageiq_argument_spec())
|
|
|
|
module = AnsibleModule(argument_spec=argument_spec)
|
|
|
|
name = module.params["name"]
|
|
description = module.params["description"]
|
|
parent_id = module.params["parent_id"]
|
|
parent = module.params["parent"]
|
|
state = module.params["state"]
|
|
quotas = module.params["quotas"]
|
|
|
|
manageiq = ManageIQ(module)
|
|
manageiq_tenant = ManageIQTenant(manageiq)
|
|
|
|
parent_tenant, tenant = manageiq_tenant.tenant(name, parent_id, parent)
|
|
|
|
# tenant should not exist
|
|
if state == "absent":
|
|
# if we have a tenant, delete it
|
|
if tenant:
|
|
res_args = manageiq_tenant.delete_tenant(tenant)
|
|
# if we do not have a tenant, nothing to do
|
|
else:
|
|
if parent_id:
|
|
msg = f"tenant '{name}' with parent_id {int(parent_id)} does not exist in manageiq"
|
|
else:
|
|
msg = f"tenant '{name}' with parent '{parent}' does not exist in manageiq"
|
|
|
|
res_args = dict(changed=False, msg=msg)
|
|
|
|
# tenant should exist
|
|
if state == "present":
|
|
# if we have a tenant, edit it
|
|
if tenant:
|
|
res_args = manageiq_tenant.edit_tenant(tenant, name, description)
|
|
|
|
# if we do not have a tenant, create it
|
|
else:
|
|
res_args = manageiq_tenant.create_tenant(name, description, parent_tenant)
|
|
tenant = manageiq.client.get_entity("tenants", res_args["tenant_id"])
|
|
|
|
# quotas as supplied and we have a tenant
|
|
if quotas:
|
|
tenant_quotas_res = manageiq_tenant.update_tenant_quotas(tenant, quotas)
|
|
if tenant_quotas_res["changed"]:
|
|
res_args["changed"] = True
|
|
res_args["tenant_quotas_msg"] = tenant_quotas_res["msg"]
|
|
|
|
tenant.reload(expand="resources", attributes=["tenant_quotas"])
|
|
res_args["tenant"] = manageiq_tenant.create_tenant_response(tenant, parent_tenant)
|
|
|
|
module.exit_json(**res_args)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|