From 6c4c20baa62df636a16aa4a090b4947b594052f1 Mon Sep 17 00:00:00 2001 From: Martin Reinhardt Date: Fri, 28 May 2021 15:30:17 +0200 Subject: [PATCH] Add possibility to create networks --- .drone.yml | 2 +- README.md | 56 +++++++++++++++++-- integration/molecule/default/molecule.yml | 11 ++++ .../{{cookiecutter.scenario_name}}/create.yml | 45 ++++++++++++++- .../destroy.yml | 19 +++++++ .../molecule.yml | 13 +++++ molecule_hetznercloud/playbooks/create.yml | 43 ++++++++++++++ molecule_hetznercloud/playbooks/destroy.yml | 19 +++++++ .../get_hetznercloud_networks.py | 54 ++++++++++++++++++ .../playbooks/hetznercloud/create.yml | 45 ++++++++++++++- .../playbooks/hetznercloud/destroy.yml | 19 +++++++ 11 files changed, 318 insertions(+), 8 deletions(-) create mode 100644 molecule_hetznercloud/playbooks/filter_plugins/get_hetznercloud_networks.py diff --git a/.drone.yml b/.drone.yml index 5d9ff01..e64973f 100644 --- a/.drone.yml +++ b/.drone.yml @@ -81,7 +81,7 @@ steps: from_secret: HCLOUD_TOKEN commands: - pip install -e . - - pip install "ansible>=2.10, <2.11" + - pip install "ansible>=2.10, <2.11" netaddr - export INSTANCE_UUID=$(openssl rand -hex 5) - cd integration && molecule test depends_on: diff --git a/README.md b/README.md index bfed6ef..df2b365 100644 --- a/README.md +++ b/README.md @@ -96,11 +96,15 @@ see [#24](https://github.com/ansible-community/molecule-hetznercloud/issues/24) for more). ```yaml -volumes: - - name: "molecule-hetznercloud-volume-1-${INSTANCE_UUID}" - location: /foo/bar - - name: "molecule-hetznercloud-volume-2-${INSTANCE_UUID}" - size: 20 +platforms: + - name: instance + server_type: cx11 + image: debian-10 + volumes: + - name: "molecule-hetznercloud-volume-1-${INSTANCE_UUID}" + location: /foo/bar + - name: "molecule-hetznercloud-volume-2-${INSTANCE_UUID}" + size: 20 ``` Supported keys are: @@ -109,6 +113,48 @@ Supported keys are: - **size** (optional, default: `10GB`): size of volume - **location** (optional, default: `omitted`): path for volume +## Network Creation + +This Driver is able to generate networks and subnetworks during the test run. +This can be useful for cluster tests. You can create networks with the +following snippet: + +```yaml +platforms: + - name: instance1 + server_type: cx11 + image: debian-10 + networks: + test-network: + ip_range: 10.10.0.0/16 + subnet: + ip: 10.10.10.1/24 + type: cloud + network_zone: eu-central + test-network-2: + ip_range: 10.20.0.0/16 + subnet: + ip: 10.20.10.1/24 + - name: instance2 + server_type: cx11 + image: debian-10 + networks: + test-network: + subnet: + ip: 10.10.10.2/24 +``` + +The networks **ip_range** is only important for creating. If you have multiple +hosts, it is okay to only define **ip_range** once. The supported keys are: + +- **networks** + - **ip_range** (required): ip range of network (usually `/16`) + +- **subnet** + - **ip** (required): ip that should be assigned to host (also generates subnetwork) - prefix mandatory + - **type** (optional, default: `cloud`): type of subnetwork + - **network_zone** (optional, default: `eu-central`): network zone of subnetwork + ## Only use `molecule.yml` for configuration It is being worked on that it is possible to remove all the files except the diff --git a/integration/molecule/default/molecule.yml b/integration/molecule/default/molecule.yml index 18234ad..b4d0de6 100644 --- a/integration/molecule/default/molecule.yml +++ b/integration/molecule/default/molecule.yml @@ -10,6 +10,17 @@ platforms: volumes: - name: "molecule-hetznercloud-volume-1-${INSTANCE_UUID}" # - name: "molecule-hetznercloud-volume-2-${INSTANCE_UUID}" + networks: + molecule-hetznercloud-network-1: + ip_range: 10.10.0.0/16 + subnet: + ip: 10.10.10.1/24 + type: cloud + network_zone: eu-central + molecule-hetznercloud-network-2: + ip_range: 10.20.0.0/16 + subnet: + ip: 10.20.10.1/24 provisioner: name: ansible verifier: diff --git a/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/create.yml b/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/create.yml index 71034b8..1da9aed 100644 --- a/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/create.yml +++ b/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/create.yml @@ -77,6 +77,48 @@ when: volumes.changed with_items: "{{ volumes.results }}" + - name: Create private network(s) + hcloud_network: + name: "{{ item.name }}" + ip_range: "{{ item.ip_range | default(omit) }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}" + register: networks + async: 7200 + poll: 0 + + - name: Wait for network(s) creation to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: hetzner_networks + until: hetzner_networks.finished + retries: 300 + when: + - networks is defined + - networks.changed + with_items: "{{ networks.results }}" + + - name: Create private subnetwork(s) + hcloud_subnetwork: + network: "{{ item.network_name }}" + ip_range: "{{ item.ip|ipaddr('network/prefix') }}" + network_zone: "{{ item.network_zone | default('eu-central') }}" + type: "{{ item.type | default('cloud') }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" + register: subnetworks + + - name: Attach Server to Subnetwork(s) + hcloud_server_network: + network: "{{ item.network_name }}" + server: "{{ item.server_name }}" + ip: "{{ item.ip|ipaddr('address') }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" + - name: Populate instance config dict set_fact: instance_conf_dict: { @@ -86,7 +128,8 @@ 'user': "{{ ssh_user }}", 'port': "{{ ssh_port }}", 'identity_file': "{{ ssh_path }}", - 'volumes': "{{ item.item.item.volumes | default({}) }}", } + 'volumes': "{{ item.item.item.volumes | default({}) }}", + 'networks': "{{ item.item.item.networks | default({}) | dict2items(key_name='name', value_name='data') }}", } with_items: "{{ hetzner_jobs.results }}" register: instance_config_dict when: server.changed | bool diff --git a/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/destroy.yml b/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/destroy.yml index fc93d52..86ebd46 100644 --- a/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/destroy.yml +++ b/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/destroy.yml @@ -60,6 +60,25 @@ when: volumes.changed with_items: "{{ volumes.results }}" + - name: Destroy network(s) + hcloud_network: + name: "{{ item.1.name }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: absent + register: networks + loop: "{{ instance_conf|subelements('networks', skip_missing=True) }}" + async: 7200 + poll: 0 + + - name: Wait for network(s) deletion to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: hetzner_networks + until: hetzner_networks.finished + retries: 300 + when: networks.changed + with_items: "{{ networks.results }}" + - name: Remove registered SSH key hcloud_ssh_key: name: "{{ instance_conf[0].ssh_key_name }}" diff --git a/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/molecule.yml b/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/molecule.yml index 427525d..a61025e 100644 --- a/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/molecule.yml +++ b/molecule_hetznercloud/cookiecutter/scenario/driver/hetznercloud/{{cookiecutter.molecule_directory}}/{{cookiecutter.scenario_name}}/molecule.yml @@ -7,6 +7,19 @@ platforms: - name: "{{ cookiecutter.role_name }}" server_type: cx11 image: debian-10 + volumes: + - name: "molecule-hetznercloud-volume-1" + networks: + molecule-hetznercloud-network-1: + ip_range: 10.10.0.0/16 + subnet: + ip: 10.10.10.1/24 + type: cloud + network_zone: eu-central + molecule-hetznercloud-network-2: + ip_range: 10.20.0.0/16 + subnet: + ip: 10.20.10.1/24 provisioner: name: ansible lint: | diff --git a/molecule_hetznercloud/playbooks/create.yml b/molecule_hetznercloud/playbooks/create.yml index b5fa86e..3e19a31 100644 --- a/molecule_hetznercloud/playbooks/create.yml +++ b/molecule_hetznercloud/playbooks/create.yml @@ -75,6 +75,48 @@ - volumes.changed with_items: "{{ volumes.results }}" + - name: Create private network(s) + hcloud_network: + name: "{{ item.name }}" + ip_range: "{{ item.ip_range | default(omit) }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}" + register: networks + async: 7200 + poll: 0 + + - name: Wait for network(s) creation to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: hetzner_networks + until: hetzner_networks.finished + retries: 300 + when: + - networks is defined + - networks.changed + with_items: "{{ networks.results }}" + + - name: Create private subnetwork(s) + hcloud_subnetwork: + network: "{{ item.network_name }}" + ip_range: "{{ item.ip|ipaddr('network/prefix') }}" + network_zone: "{{ item.network_zone | default('eu-central') }}" + type: "{{ item.type | default('cloud') }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" + register: subnetworks + + - name: Attach Server to Subnetwork(s) + hcloud_server_network: + network: "{{ item.network_name }}" + server: "{{ item.server_name }}" + ip: "{{ item.ip|ipaddr('address') }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" + - name: Populate instance config dict set_fact: instance_conf_dict: @@ -86,6 +128,7 @@ "port": "{{ ssh_port }}", "identity_file": "{{ ssh_path }}", "volumes": "{{ item.item.item.volumes | default({}) }}", + "networks": "{{ item.item.item.networks | default({}) | dict2items(key_name='name', value_name='data') }}", } with_items: "{{ hetzner_jobs.results }}" register: instance_config_dict diff --git a/molecule_hetznercloud/playbooks/destroy.yml b/molecule_hetznercloud/playbooks/destroy.yml index b9fed48..1c8b1ac 100644 --- a/molecule_hetznercloud/playbooks/destroy.yml +++ b/molecule_hetznercloud/playbooks/destroy.yml @@ -56,6 +56,25 @@ when: volumes.changed with_items: "{{ volumes.results }}" + - name: Destroy network(s) + hcloud_network: + name: "{{ item.1.name }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: absent + register: networks + loop: "{{ instance_conf|subelements('networks', skip_missing=True) }}" + async: 7200 + poll: 0 + + - name: Wait for network(s) deletion to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: hetzner_networks + until: hetzner_networks.finished + retries: 300 + when: networks.changed + with_items: "{{ networks.results }}" + - name: Remove registered SSH key hcloud_ssh_key: name: "{{ instance_conf[0].ssh_key_name }}" diff --git a/molecule_hetznercloud/playbooks/filter_plugins/get_hetznercloud_networks.py b/molecule_hetznercloud/playbooks/filter_plugins/get_hetznercloud_networks.py new file mode 100644 index 0000000..2ebb99f --- /dev/null +++ b/molecule_hetznercloud/playbooks/filter_plugins/get_hetznercloud_networks.py @@ -0,0 +1,54 @@ +#!/usr/bin/env python +"""Usage:""" +""" loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}" """ # noqa +""" loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" """ # noqa + + +def merge_two_dicts(x, y): + z = x.copy() + z.update(y) + return z + + +def get_hetznercloud_networks(data, request): + network_list = {} + subnetwork_list = [] + + if request == "networks": + + for platform in data: + if "networks" in platform: + for network_name, values in platform["networks"].items(): + del values["subnet"] + values["name"] = network_name + if network_name in network_list: + network_list[network_name] = merge_two_dicts( + network_list[network_name], values + ) + else: + network_list[network_name] = values + + return [x for x in network_list.values()] + + elif request == "subnetworks": + + for platform in data: + name = platform["name"] + if "networks" in platform: + for network_name, values in platform["networks"].items(): + values["name"] = network_name + if "subnet" in values: + values["subnet"]["server_name"] = name + values["subnet"]["network_name"] = network_name + subnetwork_list.append(values["subnet"]) + + return subnetwork_list + + +class FilterModule(object): + """Core Molecule filter plugins.""" + + def filters(self): + return { + "molecule_get_hetznercloud_networks": get_hetznercloud_networks, + } diff --git a/molecule_hetznercloud/test/resources/playbooks/hetznercloud/create.yml b/molecule_hetznercloud/test/resources/playbooks/hetznercloud/create.yml index 23ee263..a6bd63a 100644 --- a/molecule_hetznercloud/test/resources/playbooks/hetznercloud/create.yml +++ b/molecule_hetznercloud/test/resources/playbooks/hetznercloud/create.yml @@ -76,6 +76,48 @@ when: volumes.changed with_items: "{{ volumes.results }}" + - name: Create private network(s) + hcloud_network: + name: "{{ item.name }}" + ip_range: "{{ item.ip_range | default(omit) }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('networks') }}" + register: networks + async: 7200 + poll: 0 + + - name: Wait for network(s) creation to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: hetzner_networks + until: hetzner_networks.finished + retries: 300 + when: + - networks is defined + - networks.changed + with_items: "{{ networks.results }}" + + - name: Create private subnetwork(s) + hcloud_subnetwork: + network: "{{ item.network_name }}" + ip_range: "{{ item.ip|ipaddr('network/prefix') }}" + network_zone: "{{ item.network_zone | default('eu-central') }}" + type: "{{ item.type | default('cloud') }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" + register: subnetworks + + - name: Attach Server to Subnetwork(s) + hcloud_server_network: + network: "{{ item.network_name }}" + server: "{{ item.server_name }}" + ip: "{{ item.ip|ipaddr('address') }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: "present" + loop: "{{ molecule_yml.platforms|molecule_get_hetznercloud_networks('subnetworks') }}" + - name: Populate instance config dict set_fact: instance_conf_dict: { @@ -85,7 +127,8 @@ 'user': "{{ ssh_user }}", 'port': "{{ ssh_port }}", 'identity_file': "{{ ssh_path }}", - 'volumes': "{{ item.item.item.volumes | default({}) }}", } + 'volumes': "{{ item.item.item.volumes | default({}) }}", + 'networks': "{{ item.item.item.networks | default({}) | dict2items(key_name='name', value_name='data') }}", } with_items: "{{ hetzner_jobs.results }}" register: instance_config_dict when: server.changed | bool diff --git a/molecule_hetznercloud/test/resources/playbooks/hetznercloud/destroy.yml b/molecule_hetznercloud/test/resources/playbooks/hetznercloud/destroy.yml index 16eb0e5..2a80dda 100644 --- a/molecule_hetznercloud/test/resources/playbooks/hetznercloud/destroy.yml +++ b/molecule_hetznercloud/test/resources/playbooks/hetznercloud/destroy.yml @@ -59,6 +59,25 @@ when: volumes.changed with_items: "{{ volumes.results }}" + - name: Destroy network(s) + hcloud_network: + name: "{{ item.1.name }}" + api_token: "{{ lookup('env', 'HCLOUD_TOKEN') }}" + state: absent + register: networks + loop: "{{ instance_conf|subelements('networks', skip_missing=True) }}" + async: 7200 + poll: 0 + + - name: Wait for network(s) deletion to complete + async_status: + jid: "{{ item.ansible_job_id }}" + register: hetzner_networks + until: hetzner_networks.finished + retries: 300 + when: networks.changed + with_items: "{{ networks.results }}" + - name: Remove registered SSH key hcloud_ssh_key: name: "{{ instance_conf[0].ssh_key_name }}"