From cd14bbc6296891fccc8c4ee72f0be8b17e4a7374 Mon Sep 17 00:00:00 2001 From: Bertrand Lanson Date: Thu, 12 Sep 2024 23:17:36 +0200 Subject: [PATCH] feat: add public_cloud_config_info module --- galaxy.yml | 9 +- .../public_cloud_config_info/converge.yml | 14 +++ .../public_cloud_config_info/molecule.yml | 37 ++++++ molecule/public_cloud_config_info/prepare.yml | 9 ++ .../public_cloud_config_info/requirements.yml | 4 + plugins/README.md | 31 +++++ plugins/module_utils/infomaniak_api_client.py | 52 +++++++++ plugins/modules/public_cloud_config_info.py | 110 ++++++++++++++++++ 8 files changed, 261 insertions(+), 5 deletions(-) create mode 100644 molecule/public_cloud_config_info/converge.yml create mode 100644 molecule/public_cloud_config_info/molecule.yml create mode 100644 molecule/public_cloud_config_info/prepare.yml create mode 100644 molecule/public_cloud_config_info/requirements.yml create mode 100644 plugins/README.md create mode 100644 plugins/module_utils/infomaniak_api_client.py create mode 100644 plugins/modules/public_cloud_config_info.py diff --git a/galaxy.yml b/galaxy.yml index 47e2822..0310ea4 100644 --- a/galaxy.yml +++ b/galaxy.yml @@ -1,7 +1,7 @@ --- namespace: ednz_cloud -name: hashistack -version: 0.7.0 +name: infomaniak +version: 0.0.0 readme: README.md authors: - Bertrand Lanson @@ -11,9 +11,9 @@ license_file: "LICENSE" # A list of tags you want to associate with the collection for indexing/searching. A tag name has the same character # requirements as 'namespace' and 'name' -tags: ["tools"] +tags: ["infomaniak", "cloud"] dependencies: {} -repository: https://git.ednz.fr/ansible-collections/hashistack +repository: https://git.ednz.fr/ansible-collections/infomaniak documentation: http://docs.example.com homepage: http://example.com issues: http://example.com/issue/tracker @@ -22,7 +22,6 @@ issues: http://example.com/issue/tracker # uses 'fnmatch' to match the files or directories. Some directories and files like 'galaxy.yml', '*.pyc', '*.retry', # and '.git' are always filtered. Mutually exclusive with 'manifest' build_ignore: - - assets** - .gitea** # A dict controlling use of manifest directives used in building the collection artifact. The key 'directives' is a # list of MANIFEST.in style diff --git a/molecule/public_cloud_config_info/converge.yml b/molecule/public_cloud_config_info/converge.yml new file mode 100644 index 0000000..23ebaae --- /dev/null +++ b/molecule/public_cloud_config_info/converge.yml @@ -0,0 +1,14 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Run module" + ednz_cloud.infomaniak.public_cloud_config_info: + api_token: "{{ lookup('ansible.builtin.env', 'IK_API_TOKEN') }}" + account_id: "{{ lookup('ansible.builtin.env', 'IK_ACCOUNT_ID') }}" + register: _result + + - name: "Print result" + ansible.builtin.debug: + msg: "{{ _result }}" diff --git a/molecule/public_cloud_config_info/molecule.yml b/molecule/public_cloud_config_info/molecule.yml new file mode 100644 index 0000000..23fa83b --- /dev/null +++ b/molecule/public_cloud_config_info/molecule.yml @@ -0,0 +1,37 @@ +--- +dependency: + name: galaxy + options: + requirements-file: ./requirements.yml +driver: + name: docker +platforms: + - name: instance + image: geerlingguy/docker-${MOLECULE_TEST_OS}-ansible + command: "" + volumes: + - /sys/fs/cgroup:/sys/fs/cgroup + cgroupns_mode: host + privileged: true + pre_build_image: true +provisioner: + name: ansible + config_options: + defaults: + remote_tmp: /tmp/.ansible +verifier: + name: ansible +scenario: + name: public_cloud_config_info + test_sequence: + - dependency + - cleanup + - destroy + - syntax + - create + - prepare + - converge + - idempotence + - verify + - cleanup + - destroy diff --git a/molecule/public_cloud_config_info/prepare.yml b/molecule/public_cloud_config_info/prepare.yml new file mode 100644 index 0000000..f917aaa --- /dev/null +++ b/molecule/public_cloud_config_info/prepare.yml @@ -0,0 +1,9 @@ +--- +- name: Converge + hosts: all + become: true + tasks: + - name: "Install python requirements" + ansible.builtin.pip: + name: requests + executable: pip3 diff --git a/molecule/public_cloud_config_info/requirements.yml b/molecule/public_cloud_config_info/requirements.yml new file mode 100644 index 0000000..9180507 --- /dev/null +++ b/molecule/public_cloud_config_info/requirements.yml @@ -0,0 +1,4 @@ +--- +# requirements file for molecule +collections: + - name: ednz_cloud.infomaniak diff --git a/plugins/README.md b/plugins/README.md new file mode 100644 index 0000000..6260634 --- /dev/null +++ b/plugins/README.md @@ -0,0 +1,31 @@ +# Collections Plugins Directory + +This directory can be used to ship various plugins inside an Ansible collection. Each plugin is placed in a folder that +is named after the type of plugin it is in. It can also include the `module_utils` and `modules` directory that +would contain module utils and modules respectively. + +Here is an example directory of the majority of plugins currently supported by Ansible: + +``` +└── plugins + ├── action + ├── become + ├── cache + ├── callback + ├── cliconf + ├── connection + ├── filter + ├── httpapi + ├── inventory + ├── lookup + ├── module_utils + ├── modules + ├── netconf + ├── shell + ├── strategy + ├── terminal + ├── test + └── vars +``` + +A full list of plugin types can be found at [Working With Plugins](https://docs.ansible.com/ansible-core/2.15/plugins/plugins.html). diff --git a/plugins/module_utils/infomaniak_api_client.py b/plugins/module_utils/infomaniak_api_client.py new file mode 100644 index 0000000..d7e442a --- /dev/null +++ b/plugins/module_utils/infomaniak_api_client.py @@ -0,0 +1,52 @@ +import requests +import json + +INFOMANIAK_API_URL = "https://api.infomaniak.com" + + +class InfomaniakAPIClient: + def __init__(self, api_version, api_token): + self.base_url = f"{INFOMANIAK_API_URL}/{api_version}" + self.headers = { + "Authorization": f"Bearer {api_token}", + "Content-Type": "application/json", + "Accept": "application/json", + } + + def _request(self, method, endpoint, data=None, params=None): + url = f"{self.base_url}{endpoint}" + try: + response = requests.request( + method=method, + url=url, + headers=self.headers, + json=data, + params=params, + timeout=10, + ) + response.raise_for_status() + try: + return response.json() + except json.JSONDecodeError: + raise Exception(f"Invalid JSON response: {response.text}") + except requests.exceptions.HTTPError as http_err: + error_content = response.content if response else "No response" + raise Exception( + f"HTTP error occurred: {http_err}, Response content: {error_content}" + ) + except requests.exceptions.RequestException as req_err: + raise Exception(f"Request error occurred: {req_err}") + except Exception as err: + raise Exception(f"An error occurred: {err}") + + def get(self, endpoint, params=None): + return self._request("GET", endpoint, params=params) + + def post(self, endpoint, data=None): + return self._request("POST", endpoint, data=data) + + def put(self, endpoint, data=None): + return self._request("PUT", endpoint, data=data) + + def delete(self, endpoint): + return self._request("DELETE", endpoint) diff --git a/plugins/modules/public_cloud_config_info.py b/plugins/modules/public_cloud_config_info.py new file mode 100644 index 0000000..2fd77f4 --- /dev/null +++ b/plugins/modules/public_cloud_config_info.py @@ -0,0 +1,110 @@ +#!/usr/bin/python + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +DOCUMENTATION = r""" +--- +module: ednz_cloud.infomaniak.public_cloud_config_info + +short_description: Retrieves the public cloud configuration from the Infomaniak API. + +version_added: "0.1.0" + +description: + - This module queries the Infomaniak API to retrieve the public cloud configuration for a given account. + - It uses the provided API token for authentication and the account ID as a query parameter. + +requirements: + - C(requests) + +options: + api_token: + description: The API token used to authenticate with the Infomaniak API. + required: true + type: str + account_id: + description: The account ID for which to query the public cloud configuration. + required: true + type: str + +author: + - Bertrand Lanson (@ednxzu) +""" + +EXAMPLES = r""" +# Example: Retrieve public cloud configuration for account ID 965060 +- name: Get public cloud configuration + public_cloud_config_info: + api_token: "your_api_token" + account_id: "123456" +""" + +RETURN = r""" +config: + description: + - The public cloud configuration for the specified account. + type: dict + returned: always + sample: + free_tier: 300 + free_tier_used: 24.34 + account_resource_level: 2 + valid_from: 1707584356 + valid_to: 1717192799 + project_count: 2 +""" + +from ansible.module_utils.basic import AnsibleModule +from ansible_collections.ednz_cloud.infomaniak.plugins.module_utils.infomaniak_api_client import ( + InfomaniakAPIClient, +) + + +def get_public_cloud_config(client: InfomaniakAPIClient, account_id: str): + endpoint = "/public_clouds/config" + params = {"account_id": account_id} + response = client.get(endpoint, params=params) + + if response.get("result") != "success": + raise Exception(f"API request failed with result: {response.get('result')}") + + return response.get("data", {}) + + +def run_module(): + module_args = dict( + api_token=dict(type="str", required=True, no_log=True), + account_id=dict(type="str", required=True), + ) + + result = dict(changed=False, public_cloud_config={}) + + module = AnsibleModule(argument_spec=module_args, supports_check_mode=True) + + api_token = module.params["api_token"] + account_id = module.params["account_id"] + + if module.check_mode: + module.exit_json( + changed=False, + msg="Check mode: No changes made, would retrieve public cloud config.", + ) + + client = InfomaniakAPIClient(api_version="1", api_token=api_token) + + try: + config_data = get_public_cloud_config(client, account_id) + result["config"] = config_data + module.exit_json(**result) + except Exception as e: + module.fail_json(msg=str(e)) + + +def main(): + run_module() + + +if __name__ == "__main__": + main()