summaryrefslogtreecommitdiffstats
path: root/ansible_collections/vultr/cloud/plugins/modules/instance.py
diff options
context:
space:
mode:
Diffstat (limited to 'ansible_collections/vultr/cloud/plugins/modules/instance.py')
-rw-r--r--ansible_collections/vultr/cloud/plugins/modules/instance.py739
1 files changed, 739 insertions, 0 deletions
diff --git a/ansible_collections/vultr/cloud/plugins/modules/instance.py b/ansible_collections/vultr/cloud/plugins/modules/instance.py
new file mode 100644
index 000000000..73099164d
--- /dev/null
+++ b/ansible_collections/vultr/cloud/plugins/modules/instance.py
@@ -0,0 +1,739 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+#
+# Copyright (c) 2022, René Moser <mail@renemoser.net>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+
+DOCUMENTATION = """
+---
+module: instance
+short_description: Manages server instances on Vultr.
+description:
+ - Manage server instances on Vultr.
+version_added: "1.1.0"
+author:
+ - "René Moser (@resmo)"
+options:
+ label:
+ description:
+ - Name of the instance.
+ required: true
+ aliases: [ name ]
+ type: str
+ hostname:
+ description:
+ - The hostname to assign to this instance.
+ type: str
+ os:
+ description:
+ - The operating system name.
+ - Mutually exclusive with I(image) and I(app).
+ type: str
+ app:
+ description:
+ - The app deploy name of Vultr OneClick apps.
+ - Mutually exclusive with I(image) and I(os).
+ type: str
+ image:
+ description:
+ - The image deploy name of Vultr Marketplace apps.
+ - Mutually exclusive with I(os) and I(app).
+ type: str
+ firewall_group:
+ description:
+ - The firewall group description to assign this instance to.
+ type: str
+ plan:
+ description:
+ - The plan name to use for the instance.
+ - Required if the instance does not yet exist.
+ type: str
+ activation_email:
+ description:
+ - Whether to send an activation email when the instance is ready or not.
+ - Only considered on creation.
+ type: bool
+ default: false
+ backups:
+ description:
+ - Whether to enable automatic backups or not.
+ type: bool
+ ddos_protection:
+ description:
+ - Whether to enable ddos_protection or not.
+ type: bool
+ enable_ipv6:
+ description:
+ - Whether to enable IPv6 or not.
+ type: bool
+ tags:
+ description:
+ - Tags for the instance.
+ type: list
+ elements: str
+ user_data:
+ description:
+ - User data to be passed to the instance.
+ type: str
+ startup_script:
+ description:
+ - Name or ID of the startup script to execute on boot.
+ - Only considered while creating the instance.
+ type: str
+ ssh_keys:
+ description:
+ - List of SSH key names passed to the instance on creation.
+ type: list
+ elements: str
+ snapshot:
+ description:
+ - Description or ID of the snapshot.
+ - Only considered while creating the instance.
+ type: str
+ version_added: "1.7.0"
+ reserved_ipv4:
+ description:
+ - IP address of the floating IP to use as the main IP of this instance.
+ - Only considered on creation.
+ type: str
+ region:
+ description:
+ - Region the instance is deployed into.
+ type: str
+ required: true
+ vpcs:
+ description:
+ - A list of VPCs identified by their description to be assigned to the instance.
+ type: list
+ elements: str
+ version_added: "1.5.0"
+ state:
+ description:
+ - State of the instance.
+ - The state I(reinstalled) was added in version 1.8.0.
+ default: present
+ choices: [ present, absent, started, stopped, restarted, reinstalled ]
+ type: str
+extends_documentation_fragment:
+ - vultr.cloud.vultr_v2
+"""
+
+EXAMPLES = """
+---
+- name: Create an instance using OS
+ vultr.cloud.instance:
+ label: my web server
+ hostname: my-hostname
+ user_data: |
+ #cloud-config
+ packages:
+ - nginx
+ firewall_group: my firewall group
+ plan: vc2-1c-2gb
+ ddos_protection: true
+ backups: true
+ enable_ipv6: true
+ ssh_keys:
+ - my ssh key
+ vpcs:
+ - my vpc description
+ tags:
+ - web
+ - project-genesis
+ region: ams
+ os: Debian 11 x64 (bullseye)
+
+- name: Deploy an instance of a marketplace app
+ vultr.cloud.instance:
+ label: git-server
+ hostname: git
+ firewall_group: my firewall group
+ plan: vc2-1c-2gb
+ ddos_protection: true
+ backups: true
+ enable_ipv6: true
+ region: ams
+ image: Gitea on Ubuntu 20.04
+
+- name: Stop an existing instance
+ vultr.cloud.instance:
+ label: my web server
+ region: ams
+ state: stopped
+
+- name: Start an existing instance
+ vultr.cloud.instance:
+ label: my web server
+ region: ams
+ state: started
+
+- name: Reinstall an instance
+ vultr.cloud.instance:
+ label: my web server
+ region: ams
+ state: reinstalled
+
+- name: Delete an instance
+ vultr.cloud.instance:
+ label: my web server
+ region: ams
+ state: absent
+"""
+
+RETURN = """
+---
+vultr_api:
+ description: Response from Vultr API with a few additions/modification.
+ returned: success
+ type: dict
+ contains:
+ api_timeout:
+ description: Timeout used for the API requests.
+ returned: success
+ type: int
+ sample: 60
+ api_retries:
+ description: Amount of max retries for the API requests.
+ returned: success
+ type: int
+ sample: 5
+ api_retry_max_delay:
+ description: Exponential backoff delay in seconds between retries up to this max delay value.
+ returned: success
+ type: int
+ sample: 12
+ api_endpoint:
+ description: Endpoint used for the API requests.
+ returned: success
+ type: str
+ sample: "https://api.vultr.com/v2"
+vultr_instance:
+ description: Response from Vultr API.
+ returned: success
+ type: dict
+ contains:
+ id:
+ description: ID of the instance.
+ returned: success
+ type: str
+ sample: cb676a46-66fd-4dfb-b839-443f2e6c0b60
+ v6_main_ip:
+ description: IPv6 of the instance.
+ returned: success
+ type: str
+ sample: ""
+ v6_network:
+ description: IPv6 network of the instance.
+ returned: success
+ type: str
+ sample: ""
+ v6_network_size:
+ description: IPv6 network size of the instance.
+ returned: success
+ type: int
+ sample: 0
+ main_ip:
+ description: IPv4 of the instance.
+ returned: success
+ type: str
+ sample: 95.179.189.95
+ netmask_v4:
+ description: Netmask IPv4 of the instance.
+ returned: success
+ type: str
+ sample: 255.255.254.0
+ hostname:
+ description: Hostname of the instance.
+ returned: success
+ type: str
+ sample: vultr.guest
+ internal_ip:
+ description: Internal IP of the instance.
+ returned: success
+ type: str
+ sample: ""
+ gateway_v4:
+ description: Gateway IPv4.
+ returned: success
+ type: str
+ sample: 95.179.188.1
+ kvm:
+ description: KVM of the instance.
+ returned: success
+ type: str
+ sample: "https://my.vultr.com/subs/vps/novnc/api.php?data=..."
+ disk:
+ description: Disk size of the instance.
+ returned: success
+ type: int
+ sample: 25
+ allowed_bandwidth:
+ description: Allowed bandwidth of the instance.
+ returned: success
+ type: int
+ sample: 1000
+ vcpu_count:
+ description: vCPUs of the instance.
+ returned: success
+ type: int
+ sample: 1
+ firewall_group_id:
+ description: Firewall group ID of the instance.
+ returned: success
+ type: str
+ sample: ""
+ plan:
+ description: Plan of the instance.
+ returned: success
+ type: str
+ sample: vc2-1c-1gb
+ image_id:
+ description: Image ID of the instance.
+ returned: success
+ type: str
+ sample: ""
+ os_id:
+ description: OS ID of the instance.
+ returned: success
+ type: int
+ sample: 186
+ app_id:
+ description: App ID of the instance.
+ returned: success
+ type: int
+ sample: 37
+ date_created:
+ description: Date when the instance was created.
+ returned: success
+ type: str
+ sample: "2020-10-10T01:56:20+00:00"
+ label:
+ description: Label of the instance.
+ returned: success
+ type: str
+ sample: my instance
+ region:
+ description: Region the instance was deployed into.
+ returned: success
+ type: str
+ sample: ews
+ status:
+ description: Status about the deployment of the instance.
+ returned: success
+ type: str
+ sample: active
+ server_status:
+ description: Server status of the instance.
+ returned: success
+ type: str
+ sample: installingbooting
+ power_status:
+ description: Power status of the instance.
+ returned: success
+ type: str
+ sample: running
+ ram:
+ description: RAM in MB of the instance.
+ returned: success
+ type: int
+ sample: 1024
+ os:
+ description: OS of the instance.
+ returned: success
+ type: str
+ sample: Application
+ tags:
+ description: Tags of the instance.
+ returned: success
+ type: list
+ sample: [ my-tag ]
+ features:
+ description: Features of the instance.
+ returned: success
+ type: list
+ sample: [ ddos_protection, ipv6, auto_backups ]
+ user_data:
+ description: Base64 encoded user data (cloud init) of the instance.
+ returned: success
+ type: str
+ sample: I2Nsb3VkLWNvbmZpZwpwYWNrYWdlczoKICAtIGh0b3AK
+ backups:
+ description: Whether backups are enabled or disabled.
+ returned: success
+ type: str
+ sample: enabled
+ version_added: "1.3.0"
+ ddos_protection:
+ description: Whether DDOS protections is enabled or not.
+ returned: success
+ type: bool
+ sample: true
+ version_added: "1.3.0"
+ enable_ipv6:
+ description: Whether IPv6 is enabled or not.
+ returned: success
+ type: bool
+ sample: true
+ version_added: "1.3.0"
+ vpcs:
+ description: List of VPCs attached.
+ returned: success
+ type: list
+ version_added: "1.5.0"
+ contains:
+ id:
+ description: ID of the VPC.
+ returned: success
+ type: str
+ sample: 5536d2a4-66fd-4dfb-b839-7672fd5bc116
+ description:
+ description: Description of the VPC.
+ returned: success
+ type: str
+ sample: my vpc
+ ip_address:
+ description: IP assigned from the VPC.
+ returned: success
+ type: str
+ sample: "192.168.23.3"
+ mac_address:
+ description: MAC address of the network interface.
+ returned: success
+ type: str
+ sample: "5a:01:04:3d:5e:72"
+"""
+
+import base64
+
+from ansible.module_utils.basic import AnsibleModule
+
+from ..module_utils.vultr_v2 import AnsibleVultr, vultr_argument_spec
+
+
+class AnsibleVultrInstance(AnsibleVultr):
+ def get_ssh_key_ids(self):
+ ssh_key_names = list(self.module.params["ssh_keys"])
+ ssh_keys = self.query_list(path="/ssh-keys", result_key="ssh_keys")
+
+ ssh_key_ids = list()
+ for ssh_key in ssh_keys:
+ if ssh_key["name"] in ssh_key_names:
+ ssh_key_ids.append(ssh_key["id"])
+ ssh_key_names.remove(ssh_key["name"])
+
+ if ssh_key_names:
+ self.module.fail_json(msg="SSH key names not found: %s" % ", ".join(ssh_key_names))
+
+ return ssh_key_ids
+
+ def get_vpc_ids(self):
+ vpc_names = list(self.module.params["vpcs"])
+ vpcs = self.query_list(path="/vpcs", result_key="vpcs")
+
+ vpc_ids = list()
+ for vpc in vpcs:
+ if vpc["description"] in vpc_names:
+ vpc_ids.append(vpc["id"])
+ vpc_names.remove(vpc["description"])
+
+ if vpc_names:
+ self.module.fail_json(msg="VPCs not found: %s" % ", ".join(vpc_names))
+
+ return vpc_ids
+
+ def get_instance_vpcs(self, resource):
+ path = "/instances/%s/vpcs" % resource["id"]
+ vpcs = self.query_list(path=path, result_key="vpcs")
+
+ # Workaround to get the description field into the list
+ result = list()
+ for vpc in vpcs:
+ vpc_detail = self.query_by_id(resource_id=vpc["id"], path="/vpcs", result_key="vpc")
+ vpc["description"] = vpc_detail["description"]
+ result.append(vpc)
+ return result
+
+ def get_firewall_group(self):
+ return self.query_filter_list_by_name(
+ key_name="description",
+ param_key="firewall_group",
+ path="/firewalls",
+ result_key="firewall_groups",
+ fail_not_found=True,
+ )
+
+ def get_snapshot(self):
+ return self.query_filter_list_by_name(
+ key_name="description",
+ param_key="snapshot",
+ path="/snapshots",
+ result_key="snapshots",
+ fail_not_found=True,
+ )
+
+ def get_startup_script(self):
+ return self.query_filter_list_by_name(
+ key_name="name",
+ param_key="startup_script",
+ path="/startup-scripts",
+ result_key="startup_scripts",
+ fail_not_found=True,
+ )
+
+ def get_os(self):
+ return self.query_filter_list_by_name(
+ key_name="name",
+ param_key="os",
+ path="/os",
+ result_key="os",
+ fail_not_found=True,
+ )
+
+ def get_app(self):
+ return self.query_filter_list_by_name(
+ key_name="deploy_name",
+ param_key="app",
+ path="/applications",
+ result_key="applications",
+ fail_not_found=True,
+ query_params={"type": "one-click"},
+ )
+
+ def get_image(self):
+ return self.query_filter_list_by_name(
+ key_name="deploy_name",
+ param_key="image",
+ path="/applications",
+ result_key="applications",
+ fail_not_found=True,
+ query_params={"type": "marketplace"},
+ )
+
+ def get_user_data(self, resource):
+ res = self.api_query(
+ path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], "user-data"),
+ )
+ if res:
+ return str(res.get("user_data", dict()).get("data"))
+ return ""
+
+ def transform_resource(self, resource):
+ if not resource:
+ return resource
+
+ features = resource.get("features", list())
+ resource["backups"] = "enabled" if "auto_backups" in features else "disabled"
+ resource["enable_ipv6"] = "ipv6" in features
+ resource["ddos_protection"] = "ddos_protection" in features
+ resource["vpcs"] = self.get_instance_vpcs(resource=resource)
+
+ return resource
+
+ def get_detach_vpcs_ids(self, resource):
+ detach_vpc_ids = []
+ for vpc in resource.get("vpcs", list()):
+ if vpc["id"] not in list(self.module.params["attach_vpc"]):
+ detach_vpc_ids.append(vpc["id"])
+ return detach_vpc_ids
+
+ def configure(self):
+ if self.module.params["state"] != "absent":
+ if self.module.params["startup_script"] is not None:
+ self.module.params["script_id"] = self.get_startup_script()["id"]
+
+ if self.module.params["snapshot"] is not None:
+ self.module.params["snapshot_id"] = self.get_snapshot()["id"]
+
+ if self.module.params["firewall_group"] is not None:
+ self.module.params["firewall_group_id"] = self.get_firewall_group()["id"]
+
+ if self.module.params["os"] is not None:
+ self.module.params["os_id"] = self.get_os()["id"]
+
+ if self.module.params["app"] is not None:
+ self.module.params["app_id"] = self.get_app()["id"]
+
+ if self.module.params["image"] is not None:
+ self.module.params["image_id"] = self.get_image()["image_id"]
+
+ if self.module.params["user_data"] is not None:
+ self.module.params["user_data"] = base64.b64encode(self.module.params["user_data"].encode())
+
+ if self.module.params["ssh_keys"] is not None:
+ # sshkey_id ist a list of ids
+ self.module.params["sshkey_id"] = self.get_ssh_key_ids()
+
+ if self.module.params["backups"] is not None:
+ self.module.params["backups"] = "enabled" if self.module.params["backups"] else "disabled"
+
+ if self.module.params["vpcs"] is not None:
+ # attach_vpc is a list of ids used while creating
+ self.module.params["attach_vpc"] = self.get_vpc_ids()
+
+ def handle_power_status(self, resource, state, action, power_status, force=False, wait_for_state=True):
+ if state == self.module.params["state"] and (resource["power_status"] != power_status or force):
+ self.result["changed"] = True
+ if not self.module.check_mode:
+ resource = self.wait_for_state(resource=resource, key="server_status", states=["none", "locked"], cmp="!=")
+ self.api_query(
+ path="%s/%s/%s" % (self.resource_path, resource[self.resource_key_id], action),
+ method="POST",
+ )
+ if wait_for_state:
+ resource = self.wait_for_state(resource=resource, key="power_status", states=[power_status])
+ return resource
+
+ def create(self):
+ param_keys = ("os", "image", "app", "snapshot")
+ if not any(self.module.params.get(x) is not None for x in param_keys):
+ self.module.fail_json(msg="missing required arguements, one of the following required: %s" % ", ".join(param_keys))
+ return super(AnsibleVultrInstance, self).create()
+
+ def update(self, resource):
+ user_data = self.get_user_data(resource=resource)
+ resource["user_data"] = user_data.encode()
+
+ if self.module.params["vpcs"] is not None:
+ resource["attach_vpc"] = list()
+ for vpc in list(resource["vpcs"]):
+ resource["attach_vpc"].append(vpc["id"])
+
+ # detach_vpc is a list of ids to be detached
+ resource["detach_vpc"] = list()
+ self.module.params["detach_vpc"] = self.get_detach_vpcs_ids(resource=resource)
+
+ return super(AnsibleVultrInstance, self).update(resource=resource)
+
+ def create_or_update(self):
+ resource = super(AnsibleVultrInstance, self).create_or_update()
+ if resource:
+ resource = self.wait_for_state(resource=resource, key="status", states=["active"])
+ resource = self.wait_for_state(resource=resource, key="server_status", states=["none", "locked"], cmp="!=")
+
+ # Hanlde power status
+ resource = self.handle_power_status(resource=resource, state="stopped", action="halt", power_status="stopped")
+ resource = self.handle_power_status(resource=resource, state="started", action="start", power_status="running")
+ resource = self.handle_power_status(resource=resource, state="restarted", action="reboot", power_status="running", force=True)
+ resource = self.handle_power_status(
+ resource=resource,
+ state="reinstalled",
+ action="reinstall",
+ power_status="running",
+ force=True,
+ wait_for_state=False,
+ )
+
+ return resource
+
+ def transform_result(self, resource):
+ if resource:
+ resource["user_data"] = self.get_user_data(resource=resource)
+ return resource
+
+ def absent(self):
+ resource = self.query()
+ if resource and not self.module.check_mode:
+ resource = self.wait_for_state(resource=resource, key="server_status", states=["none", "locked"], cmp="!=")
+
+ return super(AnsibleVultrInstance, self).absent(resource=resource)
+
+
+def main():
+ argument_spec = vultr_argument_spec()
+ argument_spec.update(
+ dict(
+ label=dict(type="str", required=True, aliases=["name"]),
+ hostname=dict(type="str"),
+ app=dict(type="str"),
+ image=dict(type="str"),
+ snapshot=dict(type="str"),
+ os=dict(type="str"),
+ plan=dict(type="str"),
+ activation_email=dict(type="bool", default=False),
+ ddos_protection=dict(type="bool"),
+ backups=dict(type="bool"),
+ enable_ipv6=dict(type="bool"),
+ tags=dict(type="list", elements="str"),
+ vpcs=dict(type="list", elements="str"),
+ reserved_ipv4=dict(type="str"),
+ firewall_group=dict(type="str"),
+ startup_script=dict(type="str"),
+ user_data=dict(type="str"),
+ ssh_keys=dict(type="list", elements="str", no_log=False),
+ region=dict(type="str", required=True),
+ state=dict(
+ choices=[
+ "present",
+ "absent",
+ "started",
+ "stopped",
+ "restarted",
+ "reinstalled",
+ ],
+ default="present",
+ ),
+ ) # type: ignore
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ required_if=(("state", "present", ("plan",)),),
+ mutually_exclusive=(("os", "app", "image", "snapshot"),),
+ supports_check_mode=True,
+ )
+
+ vultr = AnsibleVultrInstance(
+ module=module,
+ namespace="vultr_instance",
+ resource_path="/instances",
+ ressource_result_key_singular="instance",
+ resource_create_param_keys=[
+ "label",
+ "hostname",
+ "plan",
+ "app_id",
+ "os_id",
+ "iso_id",
+ "image_id",
+ "snapshot_id",
+ "script_id",
+ "region",
+ "enable_ipv6",
+ "reserved_ipv4",
+ "firewall_group_id",
+ "user_data",
+ "tags",
+ "activation_email",
+ "ddos_protection",
+ "sshkey_id",
+ "backups",
+ "attach_vpc",
+ ],
+ resource_update_param_keys=[
+ "plan",
+ "tags",
+ "firewall_group_id",
+ "enable_ipv6",
+ "ddos_protection",
+ "backups",
+ "user_data",
+ "attach_vpc",
+ "detach_vpc",
+ ],
+ resource_key_name="label",
+ )
+
+ state = module.params.get("state") # type: ignore
+ if state == "absent":
+ vultr.absent()
+ else:
+ vultr.present()
+
+
+if __name__ == "__main__":
+ main()