diff --git a/plugins/inventory/hcloud.py b/plugins/inventory/hcloud.py index 147ebc6..3bcddfb 100644 --- a/plugins/inventory/hcloud.py +++ b/plugins/inventory/hcloud.py @@ -144,7 +144,7 @@ from ansible.module_utils.common.text.converters import to_native from ansible.plugins.inventory import BaseInventoryPlugin, Cacheable, Constructable from ansible.utils.display import Display -from ..module_utils.hcloud import HAS_DATEUTIL, HAS_REQUESTS +from ..module_utils.client import HAS_DATEUTIL, HAS_REQUESTS from ..module_utils.vendor import hcloud from ..module_utils.vendor.hcloud.servers import Server from ..module_utils.version import version diff --git a/plugins/module_utils/client.py b/plugins/module_utils/client.py new file mode 100644 index 0000000..d2178d2 --- /dev/null +++ b/plugins/module_utils/client.py @@ -0,0 +1,63 @@ +# Copyright: (c) 2023, Hetzner Cloud GmbH + +from __future__ import annotations + +from ansible.module_utils.basic import missing_required_lib + +from .vendor.hcloud import APIException, Client + +HAS_REQUESTS = True +HAS_DATEUTIL = True + +try: + import requests # pylint: disable=unused-import +except ImportError: + HAS_REQUESTS = False + +try: + import dateutil # pylint: disable=unused-import +except ImportError: + HAS_DATEUTIL = False + + +class ClientException(Exception): + """An error related to the client occurred.""" + + +def client_check_required_lib(): + if not HAS_REQUESTS: + raise ClientException(missing_required_lib("requests")) + if not HAS_DATEUTIL: + raise ClientException(missing_required_lib("python-dateutil")) + + +def _client_resource_not_found(resource: str, param: str | int): + return ClientException(f"resource ({resource.rstrip('s')}) does not exist: {param}") + + +def client_get_by_name_or_id(client: Client, resource: str, param: str | int): + """ + Get a resource by name, and if not found by its ID. + + :param client: Client to use to make the call + :param resource: Name of the resource client that implements both `get_by_name` and `get_by_id` methods + :param param: Name or ID of the resource to query + """ + resource_client = getattr(client, resource) + + result = resource_client.get_by_name(param) + if result is not None: + return result + + # If the param is not a valid ID, prevent an unnecessary call to the API. + try: + int(param) + except ValueError as exception: + raise _client_resource_not_found(resource, param) from exception + + try: + return resource_client.get_by_id(param) + except APIException as exception: + if exception.code == "not_found": + raise _client_resource_not_found(resource, param) from exception + raise exception diff --git a/plugins/module_utils/hcloud.py b/plugins/module_utils/hcloud.py index c83a6c6..3dc4f0a 100644 --- a/plugins/module_utils/hcloud.py +++ b/plugins/module_utils/hcloud.py @@ -8,30 +8,14 @@ from __future__ import annotations import traceback from typing import Any -from ansible.module_utils.basic import ( - AnsibleModule as AnsibleModuleBase, - env_fallback, - missing_required_lib, -) +from ansible.module_utils.basic import AnsibleModule as AnsibleModuleBase, env_fallback from ansible.module_utils.common.text.converters import to_native -from ..module_utils.vendor.hcloud import APIException, Client, HCloudException -from ..module_utils.vendor.hcloud.actions import ActionException +from .client import ClientException, client_check_required_lib, client_get_by_name_or_id +from .vendor.hcloud import APIException, Client, HCloudException +from .vendor.hcloud.actions import ActionException from .version import version -HAS_REQUESTS = True -HAS_DATEUTIL = True - -try: - import requests # pylint: disable=unused-import -except ImportError: - HAS_REQUESTS = False - -try: - import dateutil # pylint: disable=unused-import -except ImportError: - HAS_DATEUTIL = False - # Provide typing definitions to the AnsibleModule class class AnsibleModule(AnsibleModuleBase): @@ -49,10 +33,12 @@ class AnsibleHCloud: self.module = module self.result = {"changed": False, self.represent: None} - if not HAS_REQUESTS: - module.fail_json(msg=missing_required_lib("requests")) - if not HAS_DATEUTIL: - module.fail_json(msg=missing_required_lib("python-dateutil")) + + try: + client_check_required_lib() + except ClientException as exception: + module.fail_json(msg=to_native(exception)) + self._build_client() def fail_json_hcloud( @@ -100,19 +86,10 @@ class AnsibleHCloud: :param resource: Name of the resource client that implements both `get_by_name` and `get_by_id` methods :param param: Name or ID of the resource to query """ - resource_client = getattr(self.client, resource) - - result = resource_client.get_by_name(param) - if result is not None: - return result - - # If the param is not a valid ID, prevent an unnecessary call to the API. try: - int(param) - except ValueError: - self.module.fail_json(msg=f"resource ({resource.rstrip('s')}) does not exist: {param}") - - return resource_client.get_by_id(param) + return client_get_by_name_or_id(self.client, resource, param) + except ClientException as exception: + self.module.fail_json(msg=to_native(exception)) def _mark_as_changed(self) -> None: self.result["changed"] = True