summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorLukas Kämmerling <4281581+LKaemmerling@users.noreply.github.com>2019-03-05 07:30:24 +0100
committerRené Moser <mail@renemoser.net>2019-03-05 07:30:24 +0100
commit66beeaf032a6098dfe991480450158ae862efd0f (patch)
treee50c7a020472c58718147071828d63852ec580bb
parentcleaning up class names in azure_rm_mysql* (#53260) (diff)
downloadansible-66beeaf032a6098dfe991480450158ae862efd0f.tar.xz
ansible-66beeaf032a6098dfe991480450158ae862efd0f.zip
Add hcloud server module (#53062)
-rw-r--r--lib/ansible/module_utils/hcloud.py63
-rw-r--r--lib/ansible/modules/cloud/hcloud/__init__.py0
-rw-r--r--lib/ansible/modules/cloud/hcloud/hcloud_server.py380
-rw-r--r--lib/ansible/plugins/doc_fragments/hcloud.py26
-rw-r--r--test/integration/cloud-config-hcloud.ini.template12
-rw-r--r--test/integration/targets/hcloud_server/aliases2
-rw-r--r--test/integration/targets/hcloud_server/defaults/main.yml5
-rw-r--r--test/integration/targets/hcloud_server/tasks/main.yml275
-rw-r--r--test/runner/lib/cloud/hcloud.py69
9 files changed, 832 insertions, 0 deletions
diff --git a/lib/ansible/module_utils/hcloud.py b/lib/ansible/module_utils/hcloud.py
new file mode 100644
index 0000000000..932b0c5294
--- /dev/null
+++ b/lib/ansible/module_utils/hcloud.py
@@ -0,0 +1,63 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+
+# Simplified BSD License (see licenses/simplified_bsd.txt or https://opensource.org/licenses/BSD-2-Clause)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+from ansible.module_utils.ansible_release import __version__
+from ansible.module_utils.basic import env_fallback, missing_required_lib
+
+try:
+ import hcloud
+
+ HAS_HCLOUD = True
+except ImportError:
+ HAS_HCLOUD = False
+
+
+class Hcloud(object):
+ def __init__(self, module, represent):
+ self.module = module
+ self.represent = represent
+ self.result = {"changed": False, self.represent: None}
+ if not HAS_HCLOUD:
+ module.fail_json(msg=missing_required_lib("hcloud-python"))
+ self._build_client()
+
+ def _build_client(self):
+ self.client = hcloud.Client(
+ token=self.module.params["api_token"],
+ api_endpoint=self.module.params["endpoint"],
+ application_name="ansible-module",
+ application_version=__version__,
+ )
+
+ def _mark_as_changed(self):
+ self.result["changed"] = True
+
+ @staticmethod
+ def base_module_arguments():
+ return {
+ "api_token": {
+ "type": "str",
+ "required": True,
+ "fallback": (env_fallback, ["HCLOUD_TOKEN"]),
+ "no_log": True,
+ },
+ "endpoint": {"type": "str", "default": "https://api.hetzner.cloud/v1"},
+ }
+
+ def _prepare_result(self):
+ """Prepare the result for every module
+
+ :return: dict
+ """
+ return {}
+
+ def get_result(self):
+ if getattr(self, self.represent) is not None:
+ self.result[self.represent] = self._prepare_result()
+ return self.result
diff --git a/lib/ansible/modules/cloud/hcloud/__init__.py b/lib/ansible/modules/cloud/hcloud/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/lib/ansible/modules/cloud/hcloud/__init__.py
diff --git a/lib/ansible/modules/cloud/hcloud/hcloud_server.py b/lib/ansible/modules/cloud/hcloud/hcloud_server.py
new file mode 100644
index 0000000000..bd3a0d743e
--- /dev/null
+++ b/lib/ansible/modules/cloud/hcloud/hcloud_server.py
@@ -0,0 +1,380 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# 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
+
+ANSIBLE_METADATA = {
+ "metadata_version": "1.1",
+ "status": ["preview"],
+ "supported_by": "community",
+}
+
+DOCUMENTATION = """
+---
+module: hcloud_server
+
+short_description: Create and manage cloud servers on the Hetzner Cloud.
+
+version_added: "2.8"
+
+description:
+ - Create, update and manage cloud servers on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud server to manage.
+ - Only required if no server I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud server to manage.
+ - Only required if no server I(id) is given or a server does not exists.
+ type: str
+ server_type:
+ description:
+ - The Server Type of the Hetzner Cloud server to manage.
+ - Required if server does not exists.
+ type: str
+ ssh_keys:
+ description:
+ - List of SSH Keys Names
+ type: list
+ volumes:
+ description:
+ - List of Volumes IDs that should be attached to the server on server creation.
+ type: list
+ image:
+ description:
+ - Image the server should be created from.
+ - Required if server does not exists.
+ type: str
+ location:
+ description:
+ - Location of Server.
+ - Required if no I(datacenter) is given and server does not exists.
+ type: str
+ datacenter:
+ description:
+ - Datacenter of Server.
+ - Required of no I(location) is given and server does not exists.
+ type: str
+ backups:
+ description:
+ - Enable or disable Backups for the given Server.
+ type: bool
+ default: no
+ upgrade_disk:
+ description:
+ - Resize the disk size, when resizing a server.
+ - If you want to downgrade the server later, this value should be False.
+ type: bool
+ default: no
+ force_upgrade:
+ description:
+ - Force the upgrade of the server.
+ - Power off the server if it is running on upgrade.
+ type: bool
+ default: no
+ user_data:
+ description:
+ - User Data to be passed to the server on creation.
+ - Only used if server does not exists.
+ type: str
+ state:
+ description:
+ - State of the server.
+ default: present
+ choices: [ absent, present, restarted, started, stopped ]
+ type: str
+extends_documentation_fragment: hcloud
+"""
+
+EXAMPLES = """
+- name: Create a basic server
+ hcloud_server:
+ name: my-server
+ server_type: cx11
+ image: ubuntu-18.04
+ state: present
+
+- name: Create a basic server with ssh key
+ hcloud_server:
+ name: my-server
+ server_type: cx11
+ image: ubuntu-18.04
+ location: fsn1
+ ssh-key: my-ssh-key
+ state: present
+
+- name: Resize an existing server
+ hcloud_server:
+ name: my-server
+ server_type: cx21
+ keep_disk: yes
+ state: present
+
+- name: Ensure the server is absent (remove if needed)
+ hcloud_server:
+ name: my-server
+ state: absent
+
+- name: Ensure the server is started
+ hcloud_server:
+ name: my-server
+ state: started
+
+- name: Ensure the server is stopped
+ hcloud_server:
+ name: my-server
+ state: stopped
+
+- name: Ensure the server is restarted
+ hcloud_server:
+ name: my-server
+ state: restarted
+"""
+
+RETURN = """
+hcloud_server:
+ description: The server instance
+ returned: Always
+ type: dict
+ sample: {
+ "backup_window": null,
+ "datacenter": "nbg1-dc3",
+ "id": 1937415,
+ "image": "ubuntu-18.04",
+ "ipv4_address": "116.203.104.109",
+ "ipv6": "2a01:4f8:1c1c:c140::/64",
+ "labels": {},
+ "location": "nbg1",
+ "name": "mein-server-2",
+ "rescue_enabled": false,
+ "server_type": "cx11",
+ "status": "running"
+ }
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible.module_utils.hcloud import Hcloud
+
+try:
+ from hcloud.volumes.domain import Volume
+ from hcloud.ssh_keys.domain import SSHKey
+ from hcloud.servers.domain import Server
+ from hcloud import APIException
+except ImportError:
+ pass
+
+
+class AnsibleHcloudServer(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_server")
+ self.hcloud_server = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_server.id),
+ "name": to_native(self.hcloud_server.name),
+ "ipv4_address": to_native(self.hcloud_server.public_net.ipv4.ip),
+ "ipv6": to_native(self.hcloud_server.public_net.ipv6.ip),
+ "image": to_native(self.hcloud_server.image.name),
+ "server_type": to_native(self.hcloud_server.server_type.name),
+ "datacenter": to_native(self.hcloud_server.datacenter.name),
+ "location": to_native(self.hcloud_server.datacenter.location.name),
+ "rescue_enabled": self.hcloud_server.rescue_enabled,
+ "backup_window": to_native(self.hcloud_server.backup_window),
+ "labels": self.hcloud_server.labels,
+ "status": to_native(self.hcloud_server.status),
+ }
+
+ def _get_server(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_server = self.client.servers.get_by_id(
+ self.module.params.get("id")
+ )
+ else:
+ self.hcloud_server = self.client.servers.get_by_name(
+ self.module.params.get("name")
+ )
+ except APIException as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_server(self):
+
+ self.module.fail_on_missing_params(
+ required_params=["name", "server_type", "image"]
+ )
+ params = {
+ "name": self.module.params.get("name"),
+ "server_type": self.client.server_types.get_by_name(
+ self.module.params.get("server_type")
+ ),
+ "image": self.client.images.get_by_name(self.module.params.get("image")),
+ "user_data": self.module.params.get("user_data"),
+ }
+
+ if self.module.params.get("ssh_keys") is not None:
+ params["ssh_keys"] = [
+ SSHKey(name=ssh_key_name)
+ for ssh_key_name in self.module.params.get("ssh_keys")
+ ]
+
+ if self.module.params.get("volumes") is not None:
+ params["volumes"] = [
+ Volume(id=volume_id) for volume_id in self.module.params.get("volumes")
+ ]
+
+ if self.module.params.get("location") is None and self.module.params.get("datacenter") is None:
+ # When not given, the API will choose the location.
+ params["location"] = None
+ params["datacenter"] = None
+ elif self.module.params.get("location") is not None and self.module.params.get("datacenter") is None:
+ params["location"] = self.client.locations.get_by_name(
+ self.module.params.get("location")
+ )
+ elif self.module.params.get("location") is None and self.module.params.get("datacenter") is not None:
+ params["datacenter"] = self.client.datacenters.get_by_name(
+ self.module.params.get("datacenter")
+ )
+
+ if not self.module.check_mode:
+ resp = self.client.servers.create(**params)
+ self.result["root_password"] = resp.root_password
+ resp.action.wait_until_finished()
+ [action.wait_until_finished() for action in resp.next_actions]
+ self._mark_as_changed()
+ self._get_server()
+
+ def _update_server(self):
+ if self.module.params.get("backups") and self.hcloud_server.backup_window is None:
+ if not self.module.check_mode:
+ self.hcloud_server.enable_backup().wait_until_finished()
+ self._mark_as_changed()
+ elif not self.module.params.get("backups") and self.hcloud_server.backup_window is not None:
+ if not self.module.check_mode:
+ self.hcloud_server.disable_backup().wait_until_finished()
+ self._mark_as_changed()
+
+ server_type = self.module.params.get("server_type")
+ if server_type is not None and self.hcloud_server.server_type.name != server_type:
+ previous_server_status = self.hcloud_server.status
+ state = self.module.params.get("state")
+ if previous_server_status == Server.STATUS_RUNNING:
+ if not self.module.check_mode:
+ if self.module.params.get("force_upgrade") or state == "stopped":
+ self.stop_server() # Only stopped server can be upgraded
+ else:
+ self.module.warn(
+ "You can not upgrade a running instance %s. You need to stop the instance or use force_upgrade=yes."
+ % self.hcloud_server.name
+ )
+ timeout = 100
+ if self.module.params.get("upgrade_disk"):
+ timeout = (
+ 500
+ ) # When we upgrade the disk too the resize progress takes some more time.
+ if not self.module.check_mode:
+ self.hcloud_server.change_type(
+ server_type=self.client.server_types.get_by_name(server_type),
+ upgrade_disk=self.module.params.get("upgrade_disk"),
+ ).wait_until_finished(timeout)
+ if state == "present" and previous_server_status == Server.STATUS_RUNNING or state == "started":
+ self.start_server()
+
+ self._mark_as_changed()
+ self._get_server()
+
+ def start_server(self):
+ if self.hcloud_server.status != Server.STATUS_RUNNING:
+ if not self.module.check_mode:
+ self.client.servers.power_on(self.hcloud_server).wait_until_finished()
+ self._mark_as_changed()
+ self._get_server()
+
+ def stop_server(self):
+ if self.hcloud_server.status != Server.STATUS_OFF:
+ if not self.module.check_mode:
+ self.client.servers.power_off(self.hcloud_server).wait_until_finished()
+ self._mark_as_changed()
+ self._get_server()
+
+ def present_server(self):
+ self._get_server()
+ if self.hcloud_server is None:
+ self._create_server()
+ else:
+ self._update_server()
+
+ def delete_server(self):
+ self._get_server()
+ if self.hcloud_server is not None:
+ if not self.module.check_mode:
+ self.client.servers.delete(self.hcloud_server).wait_until_finished()
+ self._mark_as_changed()
+ self.hcloud_server = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ image={"type": "str"},
+ server_type={"type": "str"},
+ location={"type": "str"},
+ datacenter={"type": "str"},
+ user_data={"type": "str"},
+ ssh_keys={"type": "list"},
+ volumes={"type": "list"},
+ backups={"type": "bool", "default": False},
+ upgrade_disk={"type": "bool", "default": False},
+ force_upgrade={"type": "bool", "default": False},
+ state={
+ "choices": ["absent", "present", "restarted", "started", "stopped"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ mutually_exclusive=[["location", "datacenter"]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudServer.define_module()
+
+ hcloud = AnsibleHcloudServer(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_server()
+ elif state == "present":
+ hcloud.present_server()
+ elif state == "started":
+ hcloud.present_server()
+ hcloud.start_server()
+ elif state == "stopped":
+ hcloud.present_server()
+ hcloud.stop_server()
+ elif state == "restarted":
+ hcloud.present_server()
+ hcloud.stop_server()
+ hcloud.start_server()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/lib/ansible/plugins/doc_fragments/hcloud.py b/lib/ansible/plugins/doc_fragments/hcloud.py
new file mode 100644
index 0000000000..a92b30268e
--- /dev/null
+++ b/lib/ansible/plugins/doc_fragments/hcloud.py
@@ -0,0 +1,26 @@
+# -*- coding: utf-8 -*-
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+
+class ModuleDocFragment(object):
+
+ DOCUMENTATION = '''
+options:
+ api_token:
+ description:
+ - This is the API Token for the Hetzner Cloud.
+ required: True
+ type: str
+ endpoint:
+ description:
+ - This is the API Endpoint for the Hetzner Cloud.
+ default: https://api.hetzner.cloud/v1
+ type: str
+requirements:
+ - hcloud-python >= 1.0.0
+seealso:
+- name: Documentation for Hetzner Cloud API
+ description: Complete reference for the Hetzner Cloud API.
+ link: https://docs.hetzner.cloud/
+'''
diff --git a/test/integration/cloud-config-hcloud.ini.template b/test/integration/cloud-config-hcloud.ini.template
new file mode 100644
index 0000000000..ad93e3e123
--- /dev/null
+++ b/test/integration/cloud-config-hcloud.ini.template
@@ -0,0 +1,12 @@
+# This is the configuration template for ansible-test Hetzner Cloud integration tests.
+#
+# You do not need this template if you are:
+#
+# 1) Running integration tests without using ansible-test.
+#
+# If you want to test against the Hetzner Cloud public API,
+# fill in the values below and save this file without the .template extension.
+# This will cause ansible-test to use the given configuration.
+
+[default]
+hcloud_api_token= @TOKEN
diff --git a/test/integration/targets/hcloud_server/aliases b/test/integration/targets/hcloud_server/aliases
new file mode 100644
index 0000000000..51742ee23f
--- /dev/null
+++ b/test/integration/targets/hcloud_server/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+unsupported
diff --git a/test/integration/targets/hcloud_server/defaults/main.yml b/test/integration/targets/hcloud_server/defaults/main.yml
new file mode 100644
index 0000000000..b9a9a8df7b
--- /dev/null
+++ b/test/integration/targets/hcloud_server/defaults/main.yml
@@ -0,0 +1,5 @@
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+hcloud_prefix: "tests"
+hcloud_server_name: "{{hcloud_prefix}}-integration"
diff --git a/test/integration/targets/hcloud_server/tasks/main.yml b/test/integration/targets/hcloud_server/tasks/main.yml
new file mode 100644
index 0000000000..c53c0035d3
--- /dev/null
+++ b/test/integration/targets/hcloud_server/tasks/main.yml
@@ -0,0 +1,275 @@
+# Copyright: (c) 2019, Hetzner Cloud GmbH <info@hetzner-cloud.de>
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+---
+- name: setup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify setup
+ assert:
+ that:
+ - result is success
+- name: test missing required parameters on create server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create server
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: server_type, image"'
+
+- name: test create server with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: ubuntu-18.04
+ state: present
+ register: result
+ check_mode: yes
+- name: test create server server
+ assert:
+ that:
+ - result is changed
+
+- name: test create server
+ hcloud_server:
+ name: "{{ hcloud_server_name}}"
+ server_type: cx11
+ image: ubuntu-18.04
+ state: started
+ register: main_server
+- name: verify create server
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.name == "{{ hcloud_server_name }}"
+ - main_server.hcloud_server.server_type == "cx11"
+ - main_server.hcloud_server.status == "running"
+ - main_server.root_password != ""
+
+- name: test create server idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: started
+ register: result
+- name: verify create server idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test stop server with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: stopped
+ register: result
+ check_mode: yes
+- name: verify stop server with check mode
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.status == "running"
+
+- name: test stop server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: stopped
+ register: result
+- name: verify stop server
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.status == "off"
+
+- name: test start server with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: started
+ register: result
+ check_mode: true
+- name: verify start server with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test start server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: started
+ register: result
+- name: verify start server
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.status == "running"
+
+- name: test start server idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: started
+ register: result
+- name: verify start server idempotence
+ assert:
+ that:
+ - result is not changed
+ - result.hcloud_server.status == "running"
+
+- name: test stop server by its id
+ hcloud_server:
+ id: "{{ main_server.hcloud_server.id }}"
+ state: stopped
+ register: result
+- name: verify stop server by its id
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.status == "off"
+
+- name: test resize server running without force
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: "cx21"
+ state: present
+ register: result
+ check_mode: true
+- name: verify test resize server running without force
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.server_type == "cx11"
+
+- name: test resize server with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: "cx21"
+ state: stopped
+ register: result
+ check_mode: true
+- name: verify resize server with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test resize server without disk
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: "cx21"
+ state: stopped
+ register: result
+- name: verify resize server without disk
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.server_type == "cx21"
+
+- name: test resize server idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: "cx21"
+ state: stopped
+ register: result
+- name: verify resize server idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test resize server to smaller plan
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: "cx11"
+ state: stopped
+ register: result
+- name: verify resize server to smaller plan
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.server_type == "cx11"
+
+- name: test resize server with disk
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: "cx21"
+ upgrade_disk: true
+ state: stopped
+ register: result
+- name: verify resize server with disk
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.server_type == "cx21"
+
+- name: test enable backups with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ backups: true
+ state: stopped
+ register: result
+ check_mode: true
+- name: verify enable backups with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test enable backups
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ backups: true
+ state: stopped
+ register: result
+- name: verify enable backups
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.backup_window != ""
+
+- name: test enable backups idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ backups: true
+ state: stopped
+ register: result
+- name: verify enable backups idempotence
+ assert:
+ that:
+ - result is not changed
+ - result.hcloud_server.backup_window != ""
+
+- name: absent server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify absent server
+ assert:
+ that:
+ - result is success
+
+- name: test create server with ssh key
+ hcloud_server:
+ name: "{{ hcloud_server_name}}"
+ server_type: cx11
+ image: "ubuntu-18.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: started
+ register: main_server
+- name: verify create server
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.name == "{{ hcloud_server_name }}"
+ - main_server.hcloud_server.server_type == "cx11"
+ - main_server.hcloud_server.status == "running"
+ - main_server.root_password != ""
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/test/runner/lib/cloud/hcloud.py b/test/runner/lib/cloud/hcloud.py
new file mode 100644
index 0000000000..7c985d4b9a
--- /dev/null
+++ b/test/runner/lib/cloud/hcloud.py
@@ -0,0 +1,69 @@
+"""Hetzner Cloud plugin for integration tests."""
+from __future__ import absolute_import, print_function
+
+from os.path import isfile
+
+from lib.cloud import (
+ CloudProvider,
+ CloudEnvironment,
+ CloudEnvironmentConfig,
+)
+
+from lib.util import ConfigParser, display
+
+
+class HcloudCloudProvider(CloudProvider):
+ """Hetzner Cloud provider plugin. Sets up cloud resources before
+ delegation.
+ """
+
+ def __init__(self, args):
+ """
+ :type args: TestConfig
+ """
+ super(HcloudCloudProvider, self).__init__(args)
+
+ def filter(self, targets, exclude):
+ """Filter out the cloud tests when the necessary config and resources are not available.
+ :type targets: tuple[TestTarget]
+ :type exclude: list[str]
+ """
+ if isfile(self.config_static_path):
+ return
+
+ super(HcloudCloudProvider, self).filter(targets, exclude)
+
+ def setup(self):
+ """Setup the cloud resource before delegation and register a cleanup callback."""
+ super(HcloudCloudProvider, self).setup()
+
+ if isfile(self.config_static_path):
+ self.config_path = self.config_static_path
+ return True
+
+ return False
+
+
+class HcloudCloudEnvironment(CloudEnvironment):
+ """Hetzner Cloud cloud environment plugin. Updates integration test environment
+ after delegation.
+ """
+
+ def get_environment_config(self):
+ parser = ConfigParser()
+ parser.read(self.config_path)
+
+ env_vars = dict(
+ HCLOUD_TOKEN=parser.get('default', 'hcloud_api_token'),
+ )
+
+ ansible_vars = dict(
+ hcloud_prefix=self.resource_prefix,
+ )
+
+ ansible_vars.update(dict((key.lower(), value) for key, value in env_vars.items()))
+
+ return CloudEnvironmentConfig(
+ env_vars=env_vars,
+ ansible_vars=ansible_vars,
+ )