#!/usr/bin/python # Copyright (c) 2017 Ansible Project # 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: linode_v4 short_description: Manage instances on the Linode cloud description: Manage instances on the Linode cloud. requirements: - linode_api4 >= 2.0.0 author: - Luke Murphy (@decentral1se) notes: - No Linode resizing is currently implemented. This module aims to replace the current Linode module which uses deprecated API bindings on the Linode side. extends_documentation_fragment: - community.general.attributes attributes: check_mode: support: none diff_mode: support: none options: region: description: - The region of the instance. This is a required parameter only when creating Linode instances. See U(https://www.linode.com/docs/api/regions/). type: str image: description: - The image of the instance. This is a required parameter only when creating Linode instances. - See U(https://www.linode.com/docs/api/images/). type: str type: description: - The type of the instance. This is a required parameter only when creating Linode instances. - See U(https://www.linode.com/docs/api/linode-types/). type: str label: description: - The instance label. This label is used as the main determiner for idempotency for the module and is therefore mandatory. type: str required: true group: description: - The group that the instance should be marked under. Please note, that group labelling is deprecated but still supported. The encouraged method for marking instances is to use tags. type: str private_ip: description: - If V(true), the created Linode instance has private networking enabled and assigned a private IPv4 address. type: bool default: false version_added: 3.0.0 tags: description: - The tags that the instance should be marked under. - See U(https://www.linode.com/docs/api/tags/). type: list elements: str root_pass: description: - The password for the root user. If not specified, it generates a new one. This generated password is available in the task success JSON. type: str authorized_keys: description: - A list of SSH public key parts to deploy for the root user. type: list elements: str state: description: - The desired instance state. type: str choices: - present - absent required: true access_token: description: - The Linode API v4 access token. It may also be specified by exposing the E(LINODE_ACCESS_TOKEN) environment variable. - See U(https://www.linode.com/docs/api#access-and-authentication). required: true type: str stackscript_id: description: - The numeric ID of the StackScript to use when creating the instance. - See U(https://www.linode.com/docs/api/stackscripts/). type: int version_added: 1.3.0 stackscript_data: description: - An object containing arguments to any User Defined Fields present in the StackScript used when creating the instance. Only valid when a O(stackscript_id) is provided. - See U(https://www.linode.com/docs/api/stackscripts/). type: dict version_added: 1.3.0 """ EXAMPLES = r""" - name: Create a new Linode. community.general.linode_v4: label: new-linode type: g6-nanode-1 region: eu-west image: linode/debian9 root_pass: passw0rd authorized_keys: - "ssh-rsa ..." stackscript_id: 1337 stackscript_data: variable: value state: present - name: Delete that new Linode. community.general.linode_v4: label: new-linode state: absent """ RETURN = r""" instance: description: The instance description in JSON serialized form. returned: Always. type: dict sample: { "root_pass": "foobar", # if auto-generated "alerts": { "cpu": 90, "io": 10000, "network_in": 10, "network_out": 10, "transfer_quota": 80 }, "backups": { "enabled": false, "schedule": { "day": null, "window": null } }, "created": "2018-09-26T08:12:33", "group": "Foobar Group", "hypervisor": "kvm", "id": 10480444, "image": "linode/centos7", "ipv4": [ "130.132.285.233" ], "ipv6": "2a82:7e00::h03c:46ff:fe04:5cd2/64", "label": "lin-foo", "region": "eu-west", "specs": { "disk": 25600, "memory": 1024, "transfer": 1000, "vcpus": 1 }, "status": "running", "tags": [], "type": "g6-nanode-1", "updated": "2018-09-26T10:10:14", "watchdog_enabled": true } """ import traceback from ansible.module_utils.basic import AnsibleModule, env_fallback, missing_required_lib from ansible_collections.community.general.plugins.module_utils.linode import get_user_agent LINODE_IMP_ERR = None try: from linode_api4 import Instance, LinodeClient HAS_LINODE_DEPENDENCY = True except ImportError: LINODE_IMP_ERR = traceback.format_exc() HAS_LINODE_DEPENDENCY = False def create_linode(module, client, **kwargs): """Creates a Linode instance and handles return format.""" if kwargs["root_pass"] is None: kwargs.pop("root_pass") try: response = client.linode.instance_create(**kwargs) except Exception as exception: module.fail_json(msg=f"Unable to query the Linode API. Saw: {exception}") try: if isinstance(response, tuple): instance, root_pass = response instance_json = instance._raw_json instance_json.update({"root_pass": root_pass}) return instance_json else: return response._raw_json except TypeError: module.fail_json( msg="Unable to parse Linode instance creation response. Please raise a bug against this" " module on https://github.com/ansible-collections/community.general/issues" ) def maybe_instance_from_label(module, client): """Try to retrieve an instance based on a label.""" try: label = module.params["label"] result = client.linode.instances(Instance.label == label) return result[0] except IndexError: return None except Exception as exception: module.fail_json(msg=f"Unable to query the Linode API. Saw: {exception}") def initialise_module(): """Initialise the module parameter specification.""" return AnsibleModule( argument_spec=dict( label=dict(type="str", required=True), state=dict(type="str", required=True, choices=["present", "absent"]), access_token=dict( type="str", required=True, no_log=True, fallback=(env_fallback, ["LINODE_ACCESS_TOKEN"]), ), authorized_keys=dict(type="list", elements="str", no_log=False), group=dict(type="str"), image=dict(type="str"), private_ip=dict(type="bool", default=False), region=dict(type="str"), root_pass=dict(type="str", no_log=True), tags=dict(type="list", elements="str"), type=dict(type="str"), stackscript_id=dict(type="int"), stackscript_data=dict(type="dict"), ), supports_check_mode=False, required_one_of=(["state", "label"],), required_together=(["region", "image", "type"],), ) def build_client(module): """Build a LinodeClient.""" return LinodeClient(module.params["access_token"], user_agent=get_user_agent("linode_v4_module")) def main(): """Module entrypoint.""" module = initialise_module() if not HAS_LINODE_DEPENDENCY: module.fail_json(msg=missing_required_lib("linode-api4"), exception=LINODE_IMP_ERR) client = build_client(module) instance = maybe_instance_from_label(module, client) if module.params["state"] == "present" and instance is not None: module.exit_json(changed=False, instance=instance._raw_json) elif module.params["state"] == "present" and instance is None: instance_json = create_linode( module, client, authorized_keys=module.params["authorized_keys"], group=module.params["group"], image=module.params["image"], label=module.params["label"], private_ip=module.params["private_ip"], region=module.params["region"], root_pass=module.params["root_pass"], tags=module.params["tags"], ltype=module.params["type"], stackscript=module.params["stackscript_id"], stackscript_data=module.params["stackscript_data"], ) module.exit_json(changed=True, instance=instance_json) elif module.params["state"] == "absent" and instance is not None: instance.delete() module.exit_json(changed=True, instance=instance._raw_json) elif module.params["state"] == "absent" and instance is None: module.exit_json(changed=False, instance={}) if __name__ == "__main__": main()