From 8a5bde798a055abc952aabc77874ae3c2b9c5396 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20K=C3=A4mmerling?= <4281581+LKaemmerling@users.noreply.github.com> Date: Mon, 5 Aug 2019 14:06:22 +0200 Subject: [PATCH] hcloud: Add hcloud_server_network module (#60061) --- .../cloud/hcloud/hcloud_server_network.py | 229 ++++++++++++++++++ .../targets/hcloud_server_network/aliases | 2 + .../hcloud_server_network/defaults/main.yml | 6 + .../hcloud_server_network/tasks/main.yml | 187 ++++++++++++++ 4 files changed, 424 insertions(+) create mode 100644 lib/ansible/modules/cloud/hcloud/hcloud_server_network.py create mode 100644 test/integration/targets/hcloud_server_network/aliases create mode 100644 test/integration/targets/hcloud_server_network/defaults/main.yml create mode 100644 test/integration/targets/hcloud_server_network/tasks/main.yml diff --git a/lib/ansible/modules/cloud/hcloud/hcloud_server_network.py b/lib/ansible/modules/cloud/hcloud/hcloud_server_network.py new file mode 100644 index 00000000000..74cfae95257 --- /dev/null +++ b/lib/ansible/modules/cloud/hcloud/hcloud_server_network.py @@ -0,0 +1,229 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# Copyright: (c) 2019, Hetzner Cloud GmbH +# 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_network + +short_description: Manage the relationship between Hetzner Cloud Networks and servers + +version_added: "2.9" + +description: + - Create and delete the relationship Hetzner Cloud Networks and servers + +author: + - Lukas Kaemmerling (@lkaemmerling) + +options: + network: + description: + - The name of the Hetzner Cloud Networks. + + type: str + required: true + server: + description: + - The name of the Hetzner Cloud server. + type: str + required: true + ip: + description: + - The IP the server should have. + type: str + alias_ips: + description: + - Alias IPs the server has. + type: list + state: + description: + - State of the server_network. + default: present + choices: [ absent, present ] + type: str + +requirements: + - hcloud-python >= 1.3.0 + +extends_documentation_fragment: hcloud +""" + +EXAMPLES = """ +- name: Create a basic server network + hcloud_server_network: + network: my-network + server: my-server + state: present + +- name: Create a server network and specify the ip address + hcloud_server_network: + network: my-network + server: my-server + ip: 10.0.0.1 + state: present + +- name: Create a server network and add alias ips + hcloud_server_network: + network: my-network + server: my-server + ip: 10.0.0.1 + alias_ips: + - 10.1.0.1 + - 10.2.0.1 + state: present + +- name: Ensure the server network is absent (remove if needed) + hcloud_server_network: + network: my-network + server: my-server + state: absent +""" + +RETURN = """ +hcloud_server_network: + description: The relationship between a server and a network + returned: always + type: complex + contains: + network: + description: Name of the Network + type: str + returned: always + sample: my-network + server: + description: Name of the server + type: str + returned: always + sample: my-server + ip: + description: IP of the server within the Network ip range + type: str + returned: always + sample: 10.0.0.8 + alias_ips: + description: Alias IPs of the server within the Network ip range + type: str + returned: always + sample: [10.1.0.1, ...] +""" + +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 import APIException +except ImportError: + APIException = None + NetworkSubnet = None + + +class AnsibleHcloudServerNetwork(Hcloud): + def __init__(self, module): + super(AnsibleHcloudServerNetwork, self).__init__(module, "hcloud_server_network") + self.hcloud_network = None + self.hcloud_server = None + self.hcloud_server_network = None + + def _prepare_result(self): + return { + "network": to_native(self.hcloud_network.name), + "server": to_native(self.hcloud_server.name), + "ip": to_native(self.hcloud_server_network.ip), + "alias_ips": self.hcloud_server_network.alias_ips, + } + + def _get_server_and_network(self): + try: + self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network")) + self.hcloud_server = self.client.servers.get_by_name(self.module.params.get("server")) + self.hcloud_server_network = None + except APIException as e: + self.module.fail_json(msg=e.message) + + def _get_server_network(self): + for privateNet in self.hcloud_server.private_net: + if privateNet.network.id == self.hcloud_network.id: + self.hcloud_server_network = privateNet + + def _create_server_network(self): + params = { + "network": self.hcloud_network + } + + if self.module.params.get("ip") is not None: + params["ip"] = self.module.params.get("ip") + if self.module.params.get("alias_ips") is not None: + params["alias_ips"] = self.module.params.get("alias_ips") + + if not self.module.check_mode: + try: + self.hcloud_server.attach_to_network(**params).wait_until_finished() + except APIException as e: + self.module.fail_json(msg=e.message) + + self._mark_as_changed() + self._get_server_and_network() + self._get_server_network() + + def present_server_network(self): + self._get_server_and_network() + self._get_server_network() + if self.hcloud_server_network is None: + self._create_server_network() + + def delete_server_network(self): + self._get_server_and_network() + self._get_server_network() + if self.hcloud_server_network is not None and self.hcloud_server is not None: + if not self.module.check_mode: + self.hcloud_server.detach_from_network(self.hcloud_server_network.network).wait_until_finished() + self._mark_as_changed() + self.hcloud_server_network = None + + @staticmethod + def define_module(): + return AnsibleModule( + argument_spec=dict( + network={"type": "str", "required": True}, + server={"type": "str", "required": True}, + ip={"type": "str"}, + alias_ips={"type": "list"}, + state={ + "choices": ["absent", "present"], + "default": "present", + }, + **Hcloud.base_module_arguments() + ), + supports_check_mode=True, + ) + + +def main(): + module = AnsibleHcloudServerNetwork.define_module() + + hcloud = AnsibleHcloudServerNetwork(module) + state = module.params["state"] + if state == "absent": + hcloud.delete_server_network() + elif state == "present": + hcloud.present_server_network() + + module.exit_json(**hcloud.get_result()) + + +if __name__ == "__main__": + main() diff --git a/test/integration/targets/hcloud_server_network/aliases b/test/integration/targets/hcloud_server_network/aliases new file mode 100644 index 00000000000..18dc30b6c31 --- /dev/null +++ b/test/integration/targets/hcloud_server_network/aliases @@ -0,0 +1,2 @@ +cloud/hcloud +shippable/hcloud/group1 diff --git a/test/integration/targets/hcloud_server_network/defaults/main.yml b/test/integration/targets/hcloud_server_network/defaults/main.yml new file mode 100644 index 00000000000..1d62e5dc2d2 --- /dev/null +++ b/test/integration/targets/hcloud_server_network/defaults/main.yml @@ -0,0 +1,6 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +hcloud_prefix: "tests" +hcloud_network_name: "{{hcloud_prefix}}-server-network" +hcloud_server_name: "{{hcloud_prefix}}-server-network" diff --git a/test/integration/targets/hcloud_server_network/tasks/main.yml b/test/integration/targets/hcloud_server_network/tasks/main.yml new file mode 100644 index 00000000000..43235d73ee0 --- /dev/null +++ b/test/integration/targets/hcloud_server_network/tasks/main.yml @@ -0,0 +1,187 @@ +# Copyright: (c) 2019, Hetzner Cloud GmbH +# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) +--- +- name: setup network + hcloud_network: + name: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/8" + state: present + register: network +- name: verify setup network + assert: + that: + - network is success + +- name: setup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "server" + network_zone: "eu-central" + state: present + register: subnetwork +- name: verify subnetwork + assert: + that: + - subnetwork is success + +- name: setup server + hcloud_server: + name: "{{hcloud_server_name}}" + server_type: cx11 + image: ubuntu-18.04 + state: started + location: "fsn1" + register: server +- name: verify setup server + assert: + that: + - server is success + +- name: test missing required parameters on create server network + hcloud_server_network: + state: present + register: result + ignore_errors: yes +- name: verify fail test missing required parameters on create server network + assert: + that: + - result is failed + - 'result.msg == "missing required arguments: network, server"' + +- name: test create server network with checkmode + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: present + register: result + check_mode: yes +- name: verify test create server network with checkmode + assert: + that: + - result is changed + +- name: test create server network + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: present + register: serverNetwork +- name: verify create server network + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + +- name: test create server network idempotency + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: present + register: serverNetwork +- name: verify create server network idempotency + assert: + that: + - serverNetwork is not changed + +- name: test absent server network + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify test absent server network + assert: + that: + - result is changed + +- name: test create server network with specified ip + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + ip: "10.0.0.2" + state: present + register: serverNetwork +- name: verify create server network with specified ip + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + - serverNetwork.hcloud_server_network.ip == "10.0.0.2" + +- name: cleanup create server network with specified ip + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify cleanup create server network with specified ip + assert: + that: + - result is changed + +- name: test create server network with alias ips + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + ip: "10.0.0.2" + alias_ips: + - "10.0.1.2" + - "10.0.2.3" + state: present + register: serverNetwork +- name: verify create server network with alias ips + assert: + that: + - serverNetwork is changed + - serverNetwork.hcloud_server_network.network == hcloud_network_name + - serverNetwork.hcloud_server_network.server == hcloud_server_name + - serverNetwork.hcloud_server_network.ip == "10.0.0.2" + - 'serverNetwork.hcloud_server_network.alias_ips[0] == "10.0.2.3"' + - 'serverNetwork.hcloud_server_network.alias_ips[1] == "10.0.1.2"' + +- name: cleanup create server network with alias ips + hcloud_server_network: + network: "{{ hcloud_network_name }}" + server: "{{hcloud_server_name}}" + state: absent + register: result +- name: verify cleanup create server network with alias ips + assert: + that: + - result is changed + +- name: cleanup server + hcloud_server: + name: "{{ hcloud_server_name }}" + state: absent + register: result +- name: verify cleanup server + assert: + that: + - result is success + +- name: cleanup subnetwork + hcloud_subnetwork: + network: "{{ hcloud_network_name }}" + ip_range: "10.0.0.0/16" + type: "server" + network_zone: "eu-central" + state: absent + register: result +- name: verify cleanup subnetwork + assert: + that: + - result is changed + +- name: cleanup + hcloud_network: + name: "{{hcloud_network_name}}" + state: absent + register: result +- name: verify cleanup + assert: + that: + - result is success