summaryrefslogtreecommitdiffstats
path: root/ansible_collections/hetzner
diff options
context:
space:
mode:
authorDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
committerDaniel Baumann <daniel.baumann@progress-linux.org>2024-04-13 12:04:41 +0000
commit975f66f2eebe9dadba04f275774d4ab83f74cf25 (patch)
tree89bd26a93aaae6a25749145b7e4bca4a1e75b2be /ansible_collections/hetzner
parentInitial commit. (diff)
downloadansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.tar.xz
ansible-975f66f2eebe9dadba04f275774d4ab83f74cf25.zip
Adding upstream version 7.7.0+dfsg.upstream/7.7.0+dfsg
Signed-off-by: Daniel Baumann <daniel.baumann@progress-linux.org>
Diffstat (limited to '')
-rw-r--r--ansible_collections/hetzner/hcloud/.azure-pipelines/README.md3
-rw-r--r--ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml147
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh20
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py60
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh24
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh27
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh15
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh34
-rwxr-xr-xansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py25
-rw-r--r--ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml39
-rw-r--r--ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml55
-rw-r--r--ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml45
-rw-r--r--ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml20
-rw-r--r--ansible_collections/hetzner/hcloud/.gitignore389
-rw-r--r--ansible_collections/hetzner/hcloud/.gitlab-ci.yml34
-rw-r--r--ansible_collections/hetzner/hcloud/CHANGELOG.rst289
-rw-r--r--ansible_collections/hetzner/hcloud/COPYING675
-rw-r--r--ansible_collections/hetzner/hcloud/FILES.json2441
-rw-r--r--ansible_collections/hetzner/hcloud/MANIFEST.json36
-rw-r--r--ansible_collections/hetzner/hcloud/README.md62
-rw-r--r--ansible_collections/hetzner/hcloud/changelogs/.gitignore1
-rw-r--r--ansible_collections/hetzner/hcloud/changelogs/changelog.yaml275
-rw-r--r--ansible_collections/hetzner/hcloud/changelogs/config.yaml28
-rw-r--r--ansible_collections/hetzner/hcloud/changelogs/fragments/.keep0
-rw-r--r--ansible_collections/hetzner/hcloud/meta/runtime.yml35
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py0
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py29
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py0
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py351
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py0
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/module_utils/hcloud.py63
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/__init__.py0
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py291
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py162
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py160
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py160
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py359
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py355
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py185
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py185
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py219
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py219
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py318
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py398
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py209
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py620
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py321
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py158
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py159
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py159
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py243
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py293
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py230
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py271
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py360
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py196
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py928
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py244
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py244
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py246
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py183
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py183
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py243
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py169
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py169
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py247
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py340
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py186
-rw-r--r--ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py186
-rw-r--r--ansible_collections/hetzner/hcloud/tests/.gitignore1
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/constraints.txt1
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/requirements.txt2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml155
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml4
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml66
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml39
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml210
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml470
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml87
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml7
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml93
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml247
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml128
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml181
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml126
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml7
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml154
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml38
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml57
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml215
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml117
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml169
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml243
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml8
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml224
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml99
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml8
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml615
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml105
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml8
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml82
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml135
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml51
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml128
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml222
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml38
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml11
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml156
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml68
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml125
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml6
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml289
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/defaults/main.yml5
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml101
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml27
-rw-r--r--ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml55
-rw-r--r--ansible_collections/hetzner/hcloud/tests/requirements.yml3
-rw-r--r--ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt2
-rw-r--r--ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt2
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh99
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh20
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh47
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py120
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh34
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh27
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh213
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py16
-rwxr-xr-xansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh5
209 files changed, 21264 insertions, 0 deletions
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/README.md b/ansible_collections/hetzner/hcloud/.azure-pipelines/README.md
new file mode 100644
index 000000000..385e70bac
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/README.md
@@ -0,0 +1,3 @@
+## Azure Pipelines Configuration
+
+Please see the [Documentation](https://github.com/ansible/community/wiki/Testing:-Azure-Pipelines) for more information.
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml
new file mode 100644
index 000000000..d55524f27
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/azure-pipelines.yml
@@ -0,0 +1,147 @@
+trigger:
+ batch: true
+ branches:
+ include:
+ - main
+
+pr:
+ autoCancel: true
+ branches:
+ include:
+ - main
+
+schedules:
+ - cron: 0 9 * * *
+ displayName: Nightly
+ always: true
+ branches:
+ include:
+ - main
+
+variables:
+ - name: checkoutPath
+ value: ansible_collections/hetzner/hcloud
+ - name: coverageBranches
+ value: main
+ - name: pipelinesCoverage
+ value: coverage
+ - name: entryPoint
+ value: tests/utils/shippable/shippable.sh
+ - name: fetchDepth
+ value: 0
+
+resources:
+ containers:
+ - container: default
+ image: quay.io/ansible/azure-pipelines-test-container:3.0.0
+
+pool: Standard
+
+stages:
+### Sanity
+ - stage: Ansible_devel
+ displayName: Sanity devel
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ targets:
+ - name: Sanity
+ test: 'devel/sanity/1'
+
+ - stage: Ansible_2_14
+ displayName: Sanity 2.14
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ targets:
+ - name: Sanity
+ test: '2.14/sanity/1'
+ - stage: Ansible_2_13
+ displayName: Sanity 2.13
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ targets:
+ - name: Sanity
+ test: '2.13/sanity/1'
+ - stage: Ansible_2_12
+ displayName: Sanity 2.12
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ targets:
+ - name: Sanity
+ test: '2.12/sanity/1'
+
+
+## Integration tests (remote)
+ - stage: Hetzner_devel
+ displayName: Hetzner devel
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ groups:
+ - 1
+ - 2
+ targets:
+ - name: hcloud
+ test: 'devel/hcloud/3.9'
+
+ - stage: Hetzner_2_14
+ displayName: Hetzner 2.14
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ groups:
+ - 1
+ - 2
+ targets:
+ - name: hcloud
+ test: '2.14/hcloud/3.9'
+
+ - stage: Hetzner_2_13
+ displayName: Hetzner 2.13
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ groups:
+ - 1
+ - 2
+ targets:
+ - name: hcloud
+ test: '2.13/hcloud/3.9'
+
+ - stage: Hetzner_2_12
+ displayName: Hetzner 2.12
+ dependsOn: []
+ jobs:
+ - template: templates/matrix.yml
+ parameters:
+ groups:
+ - 1
+ - 2
+ targets:
+ - name: hcloud
+ test: '2.12/hcloud/3.9'
+
+### Finally
+ - stage: Summary
+ condition: succeededOrFailed()
+ dependsOn:
+ - Ansible_devel
+ - Ansible_2_14
+ - Ansible_2_13
+ - Ansible_2_12
+ - Hetzner_devel
+ - Hetzner_2_14
+ - Hetzner_2_13
+ - Hetzner_2_12
+ jobs:
+ - template: templates/coverage.yml
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh
new file mode 100755
index 000000000..f3113dd0a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/aggregate-coverage.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+# Aggregate code coverage results for later processing.
+
+set -o pipefail -eu
+
+agent_temp_directory="$1"
+
+PATH="${PWD}/bin:${PATH}"
+
+mkdir "${agent_temp_directory}/coverage/"
+
+options=(--venv --venv-system-site-packages --color -v)
+
+ansible-test coverage combine --export "${agent_temp_directory}/coverage/" "${options[@]}"
+
+if ansible-test coverage analyze targets generate --help >/dev/null 2>&1; then
+ # Only analyze coverage if the installed version of ansible-test supports it.
+ # Doing so allows this script to work unmodified for multiple Ansible versions.
+ ansible-test coverage analyze targets generate "${agent_temp_directory}/coverage/coverage-analyze-targets.json" "${options[@]}"
+fi
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py
new file mode 100755
index 000000000..506ade646
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/combine-coverage.py
@@ -0,0 +1,60 @@
+#!/usr/bin/env python
+"""
+Combine coverage data from multiple jobs, keeping the data only from the most recent attempt from each job.
+Coverage artifacts must be named using the format: "Coverage $(System.JobAttempt) {StableUniqueNameForEachJob}"
+The recommended coverage artifact name format is: Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)
+Keep in mind that Azure Pipelines does not enforce unique job display names (only names).
+It is up to pipeline authors to avoid name collisions when deviating from the recommended format.
+"""
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import os
+import re
+import shutil
+import sys
+
+
+def main():
+ """Main program entry point."""
+ source_directory = sys.argv[1]
+
+ if '/ansible_collections/' in os.getcwd():
+ output_path = "tests/output"
+ else:
+ output_path = "test/results"
+
+ destination_directory = os.path.join(output_path, 'coverage')
+
+ if not os.path.exists(destination_directory):
+ os.makedirs(destination_directory)
+
+ jobs = {}
+ count = 0
+
+ for name in os.listdir(source_directory):
+ match = re.search('^Coverage (?P<attempt>[0-9]+) (?P<label>.+)$', name)
+ label = match.group('label')
+ attempt = int(match.group('attempt'))
+ jobs[label] = max(attempt, jobs.get(label, 0))
+
+ for label, attempt in jobs.items():
+ name = 'Coverage {attempt} {label}'.format(label=label, attempt=attempt)
+ source = os.path.join(source_directory, name)
+ source_files = os.listdir(source)
+
+ for source_file in source_files:
+ source_path = os.path.join(source, source_file)
+ destination_path = os.path.join(destination_directory, source_file + '.' + label)
+ print('"%s" -> "%s"' % (source_path, destination_path))
+ shutil.copyfile(source_path, destination_path)
+ count += 1
+
+ print('Coverage file count: %d' % count)
+ print('##vso[task.setVariable variable=coverageFileCount]%d' % count)
+ print('##vso[task.setVariable variable=outputPath]%s' % output_path)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh
new file mode 100755
index 000000000..f3f1d1bae
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/process-results.sh
@@ -0,0 +1,24 @@
+#!/usr/bin/env bash
+# Check the test results and set variables for use in later steps.
+
+set -o pipefail -eu
+
+if [[ "$PWD" =~ /ansible_collections/ ]]; then
+ output_path="tests/output"
+else
+ output_path="test/results"
+fi
+
+echo "##vso[task.setVariable variable=outputPath]${output_path}"
+
+if compgen -G "${output_path}"'/junit/*.xml' > /dev/null; then
+ echo "##vso[task.setVariable variable=haveTestResults]true"
+fi
+
+if compgen -G "${output_path}"'/bot/ansible-test-*' > /dev/null; then
+ echo "##vso[task.setVariable variable=haveBotResults]true"
+fi
+
+if compgen -G "${output_path}"'/coverage/*' > /dev/null; then
+ echo "##vso[task.setVariable variable=haveCoverageData]true"
+fi
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh
new file mode 100755
index 000000000..6d184f0b8
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/publish-codecov.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+# Upload code coverage reports to codecov.io.
+# Multiple coverage files from multiple languages are accepted and aggregated after upload.
+# Python coverage, as well as PowerShell and Python stubs can all be uploaded.
+
+set -o pipefail -eu
+
+output_path="$1"
+
+curl --silent --show-error https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh > codecov.sh
+
+for file in "${output_path}"/reports/coverage*.xml; do
+ name="${file}"
+ name="${name##*/}" # remove path
+ name="${name##coverage=}" # remove 'coverage=' prefix if present
+ name="${name%.xml}" # remove '.xml' suffix
+
+ bash codecov.sh \
+ -f "${file}" \
+ -n "${name}" \
+ -X coveragepy \
+ -X gcov \
+ -X fix \
+ -X search \
+ -X xcode \
+ || echo "Failed to upload code coverage report to codecov.io: ${file}"
+done
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh
new file mode 100755
index 000000000..1bd91bdc9
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/report-coverage.sh
@@ -0,0 +1,15 @@
+#!/usr/bin/env bash
+# Generate code coverage reports for uploading to Azure Pipelines and codecov.io.
+
+set -o pipefail -eu
+
+PATH="${PWD}/bin:${PATH}"
+
+if ! ansible-test --help >/dev/null 2>&1; then
+ # Install the devel version of ansible-test for generating code coverage reports.
+ # This is only used by Ansible Collections, which are typically tested against multiple Ansible versions (in separate jobs).
+ # Since a version of ansible-test is required that can work the output from multiple older releases, the devel version is used.
+ pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
+fi
+
+ansible-test coverage xml --stub --venv --venv-system-site-packages --color -v
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh
new file mode 100755
index 000000000..a947fdf01
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/run-tests.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+# Configure the test environment and run the tests.
+
+set -o pipefail -eu
+
+entry_point="$1"
+test="$2"
+read -r -a coverage_branches <<< "$3" # space separated list of branches to run code coverage on for scheduled builds
+
+export COMMIT_MESSAGE
+export COMPLETE
+export COVERAGE
+export IS_PULL_REQUEST
+
+if [ "${SYSTEM_PULLREQUEST_TARGETBRANCH:-}" ]; then
+ IS_PULL_REQUEST=true
+ COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD^2)
+else
+ IS_PULL_REQUEST=
+ COMMIT_MESSAGE=$(git log --format=%B -n 1 HEAD)
+fi
+
+COMPLETE=
+COVERAGE=
+
+if [ "${BUILD_REASON}" = "Schedule" ]; then
+ COMPLETE=yes
+
+ if printf '%s\n' "${coverage_branches[@]}" | grep -q "^${BUILD_SOURCEBRANCHNAME}$"; then
+ COVERAGE=yes
+ fi
+fi
+
+"${entry_point}" "${test}" 2>&1 | "$(dirname "$0")/time-command.py"
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py
new file mode 100755
index 000000000..5e8eb8d4c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/scripts/time-command.py
@@ -0,0 +1,25 @@
+#!/usr/bin/env python
+"""Prepends a relative timestamp to each input line from stdin and writes it to stdout."""
+
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+import time
+
+
+def main():
+ """Main program entry point."""
+ start = time.time()
+
+ sys.stdin.reconfigure(errors='surrogateescape')
+ sys.stdout.reconfigure(errors='surrogateescape')
+
+ for line in sys.stdin:
+ seconds = time.time() - start
+ sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line))
+ sys.stdout.flush()
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml
new file mode 100644
index 000000000..1864e4441
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/coverage.yml
@@ -0,0 +1,39 @@
+# This template adds a job for processing code coverage data.
+# It will upload results to Azure Pipelines and codecov.io.
+# Use it from a job stage that completes after all other jobs have completed.
+# This can be done by placing it in a separate summary stage that runs after the test stage(s) have completed.
+
+jobs:
+ - job: Coverage
+ displayName: Code Coverage
+ container: default
+ workspace:
+ clean: all
+ steps:
+ - checkout: self
+ fetchDepth: $(fetchDepth)
+ path: $(checkoutPath)
+ - task: DownloadPipelineArtifact@2
+ displayName: Download Coverage Data
+ inputs:
+ path: coverage/
+ patterns: "Coverage */*=coverage.combined"
+ - bash: .azure-pipelines/scripts/combine-coverage.py coverage/
+ displayName: Combine Coverage Data
+ - bash: .azure-pipelines/scripts/report-coverage.sh
+ displayName: Generate Coverage Report
+ condition: gt(variables.coverageFileCount, 0)
+ - task: PublishCodeCoverageResults@1
+ inputs:
+ codeCoverageTool: Cobertura
+ # Azure Pipelines only accepts a single coverage data file.
+ # That means only Python or PowerShell coverage can be uploaded, but not both.
+ # Set the "pipelinesCoverage" variable to determine which type is uploaded.
+ # Use "coverage" for Python and "coverage-powershell" for PowerShell.
+ summaryFileLocation: "$(outputPath)/reports/$(pipelinesCoverage).xml"
+ displayName: Publish to Azure Pipelines
+ condition: gt(variables.coverageFileCount, 0)
+ - bash: .azure-pipelines/scripts/publish-codecov.sh "$(outputPath)"
+ displayName: Publish to codecov.io
+ condition: gt(variables.coverageFileCount, 0)
+ continueOnError: true
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml
new file mode 100644
index 000000000..4e9555dd3
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/matrix.yml
@@ -0,0 +1,55 @@
+# This template uses the provided targets and optional groups to generate a matrix which is then passed to the test template.
+# If this matrix template does not provide the required functionality, consider using the test template directly instead.
+
+parameters:
+ # A required list of dictionaries, one per test target.
+ # Each item in the list must contain a "test" or "name" key.
+ # Both may be provided. If one is omitted, the other will be used.
+ - name: targets
+ type: object
+
+ # An optional list of values which will be used to multiply the targets list into a matrix.
+ # Values can be strings or numbers.
+ - name: groups
+ type: object
+ default: []
+
+ # An optional format string used to generate the job name.
+ # - {0} is the name of an item in the targets list.
+ - name: nameFormat
+ type: string
+ default: "{0}"
+
+ # An optional format string used to generate the test name.
+ # - {0} is the name of an item in the targets list.
+ - name: testFormat
+ type: string
+ default: "{0}"
+
+ # An optional format string used to add the group to the job name.
+ # {0} is the formatted name of an item in the targets list.
+ # {{1}} is the group -- be sure to include the double "{{" and "}}".
+ - name: nameGroupFormat
+ type: string
+ default: "{0} - {{1}}"
+
+ # An optional format string used to add the group to the test name.
+ # {0} is the formatted test of an item in the targets list.
+ # {{1}} is the group -- be sure to include the double "{{" and "}}".
+ - name: testGroupFormat
+ type: string
+ default: "{0}/{{1}}"
+
+jobs:
+ - template: test.yml
+ parameters:
+ jobs:
+ - ${{ if eq(length(parameters.groups), 0) }}:
+ - ${{ each target in parameters.targets }}:
+ - name: ${{ format(parameters.nameFormat, coalesce(target.name, target.test)) }}
+ test: ${{ format(parameters.testFormat, coalesce(target.test, target.name)) }}
+ - ${{ if not(eq(length(parameters.groups), 0)) }}:
+ - ${{ each group in parameters.groups }}:
+ - ${{ each target in parameters.targets }}:
+ - name: ${{ format(format(parameters.nameGroupFormat, parameters.nameFormat), coalesce(target.name, target.test), group) }}
+ test: ${{ format(format(parameters.testGroupFormat, parameters.testFormat), coalesce(target.test, target.name), group) }}
diff --git a/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml
new file mode 100644
index 000000000..5250ed802
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.azure-pipelines/templates/test.yml
@@ -0,0 +1,45 @@
+# This template uses the provided list of jobs to create test one or more test jobs.
+# It can be used directly if needed, or through the matrix template.
+
+parameters:
+ # A required list of dictionaries, one per test job.
+ # Each item in the list must contain a "job" and "name" key.
+ - name: jobs
+ type: object
+
+jobs:
+ - ${{ each job in parameters.jobs }}:
+ - job: test_${{ replace(replace(replace(job.test, '/', '_'), '.', '_'), '-', '_') }}
+ displayName: ${{ job.name }}
+ container: default
+ workspace:
+ clean: all
+ steps:
+ - checkout: self
+ fetchDepth: $(fetchDepth)
+ path: $(checkoutPath)
+ - bash: .azure-pipelines/scripts/run-tests.sh "$(entryPoint)" "${{ job.test }}" "$(coverageBranches)"
+ displayName: Run Tests
+ - bash: .azure-pipelines/scripts/process-results.sh
+ condition: succeededOrFailed()
+ displayName: Process Results
+ - bash: .azure-pipelines/scripts/aggregate-coverage.sh "$(Agent.TempDirectory)"
+ condition: eq(variables.haveCoverageData, 'true')
+ displayName: Aggregate Coverage Data
+ - task: PublishTestResults@2
+ condition: eq(variables.haveTestResults, 'true')
+ inputs:
+ testResultsFiles: "$(outputPath)/junit/*.xml"
+ displayName: Publish Test Results
+ - task: PublishPipelineArtifact@1
+ condition: eq(variables.haveBotResults, 'true')
+ displayName: Publish Bot Results
+ inputs:
+ targetPath: "$(outputPath)/bot/"
+ artifactName: "Bot $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)"
+ - task: PublishPipelineArtifact@1
+ condition: eq(variables.haveCoverageData, 'true')
+ displayName: Publish Coverage Data
+ inputs:
+ targetPath: "$(Agent.TempDirectory)/coverage/"
+ artifactName: "Coverage $(System.JobAttempt) $(System.StageDisplayName) $(System.JobDisplayName)"
diff --git a/ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml b/ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml
new file mode 100644
index 000000000..037ff7ec0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.github/workflows/check-changelog-fragments.yml
@@ -0,0 +1,20 @@
+name: antsibull-changelog tests
+
+on: [push, pull_request]
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ name: antsibull-changelog-lint
+ steps:
+ - uses: actions/checkout@v2
+ - name: Setup python
+ uses: actions/setup-python@v3
+ with:
+ python-version: "3.10"
+ - name: Install dependencies
+ run: |
+ python -m pip install --upgrade pip
+ pip install antsibull-changelog
+ - name: test with lint
+ run: antsibull-changelog lint
diff --git a/ansible_collections/hetzner/hcloud/.gitignore b/ansible_collections/hetzner/hcloud/.gitignore
new file mode 100644
index 000000000..d6a37e5c5
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.gitignore
@@ -0,0 +1,389 @@
+
+# Created by https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
+# Edit at https://www.gitignore.io/?templates=git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
+
+### dotenv ###
+.env
+
+### Emacs ###
+# -*- mode: gitignore; -*-
+*~
+\#*\#
+/.emacs.desktop
+/.emacs.desktop.lock
+*.elc
+auto-save-list
+tramp
+.\#*
+
+# Org-mode
+.org-id-locations
+*_archive
+
+# flymake-mode
+*_flymake.*
+
+# eshell files
+/eshell/history
+/eshell/lastdir
+
+# elpa packages
+/elpa/
+
+# reftex files
+*.rel
+
+# AUCTeX auto folder
+/auto/
+
+# cask packages
+.cask/
+dist/
+
+# Flycheck
+flycheck_*.el
+
+# server auth directory
+/server/
+
+# projectiles files
+.projectile
+
+# directory configuration
+.dir-locals.el
+
+# network security
+/network-security.data
+
+
+### Git ###
+# Created by git for backups. To disable backups in Git:
+# $ git config --global mergetool.keepBackup false
+*.orig
+
+# Created by git when using merge tools for conflicts
+*.BACKUP.*
+*.BASE.*
+*.LOCAL.*
+*.REMOTE.*
+*_BACKUP_*.txt
+*_BASE_*.txt
+*_LOCAL_*.txt
+*_REMOTE_*.txt
+
+#!! ERROR: jupyternotebook is undefined. Use list command to see defined gitignore types !!#
+
+### Linux ###
+
+# temporary files which can be created if a process still has a handle open of a deleted file
+.fuse_hidden*
+
+# KDE directory preferences
+.directory
+
+# Linux trash folder which might appear on any partition or disk
+.Trash-*
+
+# .nfs files are created when an open file is removed but is still being accessed
+.nfs*
+
+### PyCharm+all ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### PyCharm+all Patch ###
+# Ignores the whole .idea folder and all .iml files
+# See https://github.com/joeblau/gitignore.io/issues/186 and https://github.com/joeblau/gitignore.io/issues/360
+
+.idea/
+
+# Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-249601023
+
+*.iml
+modules.xml
+.idea/misc.xml
+*.ipr
+
+# Sonarlint plugin
+.idea/sonarlint
+
+### pydev ###
+.pydevproject
+
+### Python ###
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+pip-wheel-metadata/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+.hypothesis/
+.pytest_cache/
+
+# Translations
+*.mo
+*.pot
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+target/
+
+# pyenv
+.python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# celery beat schedule file
+celerybeat-schedule
+
+# SageMath parsed files
+*.sage.py
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# Mr Developer
+.mr.developer.cfg
+.project
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+### Vim ###
+# Swap
+[._]*.s[a-v][a-z]
+[._]*.sw[a-p]
+[._]s[a-rt-v][a-z]
+[._]ss[a-gi-z]
+[._]sw[a-p]
+
+# Session
+Session.vim
+Sessionx.vim
+
+# Temporary
+.netrwhist
+# Auto-generated tag files
+tags
+# Persistent undo
+[._]*.un~
+
+### WebStorm ###
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+
+# Generated files
+
+# Sensitive or high-churn files
+
+# Gradle
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+
+# Mongo Explorer plugin
+
+# File-based project format
+
+# IntelliJ
+
+# mpeltonen/sbt-idea plugin
+
+# JIRA plugin
+
+# Cursive Clojure plugin
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+
+# Editor-based Rest Client
+
+# Android studio 3.1+ serialized cache file
+
+### WebStorm Patch ###
+# Comment Reason: https://github.com/joeblau/gitignore.io/issues/186#issuecomment-215987721
+
+# *.iml
+# modules.xml
+# .idea/misc.xml
+# *.ipr
+
+# Sonarlint plugin
+.idea/**/sonarlint/
+
+# SonarQube Plugin
+.idea/**/sonarIssues.xml
+
+# Markdown Navigator plugin
+.idea/**/markdown-navigator.xml
+.idea/**/markdown-navigator/
+
+### Windows ###
+# Windows thumbnail cache files
+Thumbs.db
+Thumbs.db:encryptable
+ehthumbs.db
+ehthumbs_vista.db
+
+# Dump file
+*.stackdump
+
+# Folder config file
+[Dd]esktop.ini
+
+# Recycle Bin used on file shares
+$RECYCLE.BIN/
+
+# Windows Installer files
+*.cab
+*.msi
+*.msix
+*.msm
+*.msp
+
+# Windows shortcuts
+*.lnk
+
+# End of https://www.gitignore.io/api/git,linux,pydev,python,windows,pycharm+all,jupyternotebook,vim,webstorm,emacs,dotenv
+
+cloud-config-hcloud.ini
diff --git a/ansible_collections/hetzner/hcloud/.gitlab-ci.yml b/ansible_collections/hetzner/hcloud/.gitlab-ci.yml
new file mode 100644
index 000000000..7748f7237
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/.gitlab-ci.yml
@@ -0,0 +1,34 @@
+stages:
+ - sanity
+ - integration
+
+variables:
+ PYTHON_VERSION: "3.9"
+
+default:
+ image: python:$PYTHON_VERSION
+
+sanity:
+ stage: sanity
+ allow_failure: true
+ except:
+ - tags
+ parallel:
+ matrix:
+ - ANSIBLE_VERSION: ["devel", "2.12", "2.13", "2.14"]
+ GROUP: [1]
+ script:
+ - bash tests/utils/gitlab/gitlab.sh ${ANSIBLE_VERSION}/sanity/${GROUP}
+
+integration:
+ stage: integration
+ except:
+ - tags
+ parallel:
+ matrix:
+ - ANSIBLE_VERSION: ["devel"]
+ GROUP: [1, 2, 3]
+ script:
+ - echo "$HCLOUD_TOKEN" >> "$(pwd)/hcloud_token.txt"
+ - echo "$CI_JOB_ID" >> "$(pwd)/prefix.txt"
+ - bash tests/utils/gitlab/gitlab.sh ${ANSIBLE_VERSION}/hcloud/${PYTHON_VERSION}/${GROUP}
diff --git a/ansible_collections/hetzner/hcloud/CHANGELOG.rst b/ansible_collections/hetzner/hcloud/CHANGELOG.rst
new file mode 100644
index 000000000..e9eff3cad
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/CHANGELOG.rst
@@ -0,0 +1,289 @@
+==============================================
+Hetzner Cloud Ansible Collection Release Notes
+==============================================
+
+.. contents:: Topics
+
+
+v1.11.0
+=======
+
+Minor Changes
+-------------
+
+- hcloud_image_info - Add cpu architecture field to return value.
+- hcloud_image_info - Allow filtering images by cpu architecture.
+- hcloud_server - Select matching image for the cpu architecture of the server type on create & rebuild.
+- hcloud_server_type_info - Add cpu architecture field to return value.
+- inventory plugin - Add cpu architecture to server variables.
+
+v1.10.1
+=======
+
+Bugfixes
+--------
+
+- hcloud_server - Prevent backups from being disabled when undefined
+- hcloud_server - Server locked after attaching to placement group
+
+v1.10.0
+=======
+
+Minor Changes
+-------------
+
+- hcloud_server - add private_networks_info containing name and private ip in responses
+- hcloud_server_info - add private_networks_info containing name and private ip in responses
+- inventory plugin - Add list of all private networks to server variables.
+- inventory plugin - Add new connect_with setting public_ipv6 to connect to discovered servers via public IPv6 address.
+- inventory plugin - Add public IPv6 address to server variables.
+- inventory plugin - Log warning instead of crashing when some servers do not work with global connect_with setting.
+
+Breaking Changes / Porting Guide
+--------------------------------
+
+- inventory plugin - Python v3.5+ is now required.
+
+v1.9.1
+======
+
+Bugfixes
+--------
+
+- hcloud_server - externally attached networks (using hcloud_server_network) were removed when not specified in the hcloud_server resource
+
+v1.9.0
+======
+
+Minor Changes
+-------------
+
+- dynamic inventory - add support changing the name of the top level group all servers are added to
+- hcloud_firewall - add support for esp and gre protocols
+
+Bugfixes
+--------
+
+- hcloud_firewall - the deletion could fail if the firewall was referenced right before
+- hcloud_server - fix backup window was given out as "None" instead of null
+- hcloud_server_info - fix backup window was given out as "None" instead of null
+- hcloud_volume - fix server name was given out as "None" instead of null if no server was attached
+- hcloud_volume_info - fix server name was given out as "None" instead of null if no server was attached
+
+v1.8.2
+======
+
+Bugfixes
+--------
+
+- dynamic inventory - fix crash when having servers without IPs (flexible networks)
+- hcloud_server - When state stopped and server is created, do not start the server
+- hcloud_server_info - fix crash when having servers without IPs (flexible networks)
+
+v1.8.1
+======
+
+v1.8.0
+======
+
+New Modules
+-----------
+
+Hetzner
+~~~~~~~
+
+hcloud
+^^^^^^
+
+- hetzner.hcloud.hcloud_primary_ip - Create and manage cloud Primary IPs on the Hetzner Cloud.
+
+v1.7.1
+======
+
+Minor Changes
+-------------
+
+- inventory - allow filtering by server status
+
+Bugfixes
+--------
+
+- hcloud_server_network - fixes changed alias_ips by using sorted
+
+v1.7.0
+======
+
+Minor Changes
+-------------
+
+- inventory - support jinjia templating within `network`
+
+v1.6.0
+======
+
+Minor Changes
+-------------
+
+- hcloud_rdns Add support for load balancer
+
+v1.5.0
+======
+
+Major Changes
+-------------
+
+- Introduction of placement groups
+
+Minor Changes
+-------------
+
+- hcloud_firewall Add description field to firewall rules
+
+Bugfixes
+--------
+
+- hcloud_rdns improve error message on not existing server/Floating IP
+- hcloud_server backups property defaults to None now instead of False
+
+v1.4.4
+======
+
+Bugfixes
+--------
+
+- hcloud_server Improve Error Message when attaching a not existing firewall to a server
+- hcloud_volume Force detaching of volumes on servers before deletion
+
+v1.4.3
+======
+
+Bugfixes
+--------
+
+- hcloud_server Fix incompatbility with python < 3.6
+- hcloud_server Improve error handling when using not existing server types
+
+v1.4.2
+======
+
+Bugfixes
+--------
+
+- inventory fix image name was set as server type instead of the correct server type
+
+v1.4.1
+======
+
+Minor Changes
+-------------
+
+- hcloud_server - improve the handling of deprecated images
+- hcloud_server - improve the validation and error response for not existing images
+- inventory - support jinjia templating within `token`
+
+v1.4.0
+======
+
+Security Fixes
+--------------
+
+- hcloud_certificate - mark the ``private_key`` parameter as ``no_log`` to prevent potential leaking of secret values (https://github.com/ansible-collections/hetzner.hcloud/pull/70).
+
+Bugfixes
+--------
+
+- hcloud_firewall - fix idempotence related to rules comparison (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
+- hcloud_load_balancer_service - fix imported wrong HealthCheck from hcloud-python (https://github.com/ansible-collections/hetzner.hcloud/pull/73).
+- hcloud_server - fix idempotence related to firewall handling (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
+
+v1.3.1
+======
+
+Bugfixes
+--------
+
+- hcloud_server - fix a crash related to check mode if ``state=started`` or ``state=stopped`` (https://github.com/ansible-collections/hetzner.hcloud/issues/54).
+
+v1.3.0
+======
+
+Minor Changes
+-------------
+
+- Add firewalls to hcloud_server module
+
+New Modules
+-----------
+
+- hcloud_firewall - Manage Hetzner Cloud Firewalls
+
+v1.2.1
+======
+
+Bugfixes
+--------
+
+- Inventory Restore Python 2.7 compatibility
+
+v1.2.0
+======
+
+Minor Changes
+-------------
+
+- Dynamic Inventory Add option to specifiy the token_env variable which is used for identification if now token is set
+- Improve imports of API Exception
+- hcloud_server_network Allow updating alias ips
+- hcloud_subnetwork Allow creating vswitch subnetworks
+
+New Modules
+-----------
+
+- hcloud_load_balancer_info - Gather infos about your Hetzner Cloud load_balancers.
+
+v1.1.0
+======
+
+Minor Changes
+-------------
+
+- hcloud_floating_ip Allow creating Floating IP with protection
+- hcloud_load_balancer Allow creating Load Balancer with protection
+- hcloud_network Allow creating Network with protection
+- hcloud_server Allow creating server with protection
+- hcloud_volume Allow creating Volumes with protection
+
+Bugfixes
+--------
+
+- hcloud_floating_ip Fix idempotency when floating ip is assigned to server
+
+v1.0.0
+======
+
+Minor Changes
+-------------
+
+- hcloud_load_balancer Allow changing the type of a Load Balancer
+- hcloud_server Allow the creation of servers with enabled backups
+
+v0.2.0
+======
+
+Bugfixes
+--------
+
+- hcloud inventory plugin - Allow usage of hcloud.yml and hcloud.yaml - this was removed by error within the migration from build-in ansible to our collection
+
+v0.1.0
+======
+
+New Modules
+-----------
+
+- hcloud_floating_ip - Create and manage cloud Floating IPs on the Hetzner Cloud.
+- hcloud_load_balancer - Create and manage cloud Load Balancers on the Hetzner Cloud.
+- hcloud_load_balancer_network - Manage the relationship between Hetzner Cloud Networks and Load Balancers
+- hcloud_load_balancer_service - Create and manage the services of cloud Load Balancers on the Hetzner Cloud.
+- hcloud_load_balancer_target - Manage Hetzner Cloud Load Balancer targets
+- hcloud_load_balancer_type_info - Gather infos about the Hetzner Cloud Load Balancer types.
diff --git a/ansible_collections/hetzner/hcloud/COPYING b/ansible_collections/hetzner/hcloud/COPYING
new file mode 100644
index 000000000..10926e87f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/COPYING
@@ -0,0 +1,675 @@
+ GNU GENERAL PUBLIC LICENSE
+ Version 3, 29 June 2007
+
+ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
+ Everyone is permitted to copy and distribute verbatim copies
+ of this license document, but changing it is not allowed.
+
+ Preamble
+
+ The GNU General Public License is a free, copyleft license for
+software and other kinds of works.
+
+ The licenses for most software and other practical works are designed
+to take away your freedom to share and change the works. By contrast,
+the GNU General Public License is intended to guarantee your freedom to
+share and change all versions of a program--to make sure it remains free
+software for all its users. We, the Free Software Foundation, use the
+GNU General Public License for most of our software; it applies also to
+any other work released this way by its authors. You can apply it to
+your programs, too.
+
+ When we speak of free software, we are referring to freedom, not
+price. Our General Public Licenses are designed to make sure that you
+have the freedom to distribute copies of free software (and charge for
+them if you wish), that you receive source code or can get it if you
+want it, that you can change the software or use pieces of it in new
+free programs, and that you know you can do these things.
+
+ To protect your rights, we need to prevent others from denying you
+these rights or asking you to surrender the rights. Therefore, you have
+certain responsibilities if you distribute copies of the software, or if
+you modify it: responsibilities to respect the freedom of others.
+
+ For example, if you distribute copies of such a program, whether
+gratis or for a fee, you must pass on to the recipients the same
+freedoms that you received. You must make sure that they, too, receive
+or can get the source code. And you must show them these terms so they
+know their rights.
+
+ Developers that use the GNU GPL protect your rights with two steps:
+(1) assert copyright on the software, and (2) offer you this License
+giving you legal permission to copy, distribute and/or modify it.
+
+ For the developers' and authors' protection, the GPL clearly explains
+that there is no warranty for this free software. For both users' and
+authors' sake, the GPL requires that modified versions be marked as
+changed, so that their problems will not be attributed erroneously to
+authors of previous versions.
+
+ Some devices are designed to deny users access to install or run
+modified versions of the software inside them, although the manufacturer
+can do so. This is fundamentally incompatible with the aim of
+protecting users' freedom to change the software. The systematic
+pattern of such abuse occurs in the area of products for individuals to
+use, which is precisely where it is most unacceptable. Therefore, we
+have designed this version of the GPL to prohibit the practice for those
+products. If such problems arise substantially in other domains, we
+stand ready to extend this provision to those domains in future versions
+of the GPL, as needed to protect the freedom of users.
+
+ Finally, every program is threatened constantly by software patents.
+States should not allow patents to restrict development and use of
+software on general-purpose computers, but in those that do, we wish to
+avoid the special danger that patents applied to a free program could
+make it effectively proprietary. To prevent this, the GPL assures that
+patents cannot be used to render the program non-free.
+
+ The precise terms and conditions for copying, distribution and
+modification follow.
+
+ TERMS AND CONDITIONS
+
+ 0. Definitions.
+
+ "This License" refers to version 3 of the GNU General Public License.
+
+ "Copyright" also means copyright-like laws that apply to other kinds of
+works, such as semiconductor masks.
+
+ "The Program" refers to any copyrightable work licensed under this
+License. Each licensee is addressed as "you". "Licensees" and
+"recipients" may be individuals or organizations.
+
+ To "modify" a work means to copy from or adapt all or part of the work
+in a fashion requiring copyright permission, other than the making of an
+exact copy. The resulting work is called a "modified version" of the
+earlier work or a work "based on" the earlier work.
+
+ A "covered work" means either the unmodified Program or a work based
+on the Program.
+
+ To "propagate" a work means to do anything with it that, without
+permission, would make you directly or secondarily liable for
+infringement under applicable copyright law, except executing it on a
+computer or modifying a private copy. Propagation includes copying,
+distribution (with or without modification), making available to the
+public, and in some countries other activities as well.
+
+ To "convey" a work means any kind of propagation that enables other
+parties to make or receive copies. Mere interaction with a user through
+a computer network, with no transfer of a copy, is not conveying.
+
+ An interactive user interface displays "Appropriate Legal Notices"
+to the extent that it includes a convenient and prominently visible
+feature that (1) displays an appropriate copyright notice, and (2)
+tells the user that there is no warranty for the work (except to the
+extent that warranties are provided), that licensees may convey the
+work under this License, and how to view a copy of this License. If
+the interface presents a list of user commands or options, such as a
+menu, a prominent item in the list meets this criterion.
+
+ 1. Source Code.
+
+ The "source code" for a work means the preferred form of the work
+for making modifications to it. "Object code" means any non-source
+form of a work.
+
+ A "Standard Interface" means an interface that either is an official
+standard defined by a recognized standards body, or, in the case of
+interfaces specified for a particular programming language, one that
+is widely used among developers working in that language.
+
+ The "System Libraries" of an executable work include anything, other
+than the work as a whole, that (a) is included in the normal form of
+packaging a Major Component, but which is not part of that Major
+Component, and (b) serves only to enable use of the work with that
+Major Component, or to implement a Standard Interface for which an
+implementation is available to the public in source code form. A
+"Major Component", in this context, means a major essential component
+(kernel, window system, and so on) of the specific operating system
+(if any) on which the executable work runs, or a compiler used to
+produce the work, or an object code interpreter used to run it.
+
+ The "Corresponding Source" for a work in object code form means all
+the source code needed to generate, install, and (for an executable
+work) run the object code and to modify the work, including scripts to
+control those activities. However, it does not include the work's
+System Libraries, or general-purpose tools or generally available free
+programs which are used unmodified in performing those activities but
+which are not part of the work. For example, Corresponding Source
+includes interface definition files associated with source files for
+the work, and the source code for shared libraries and dynamically
+linked subprograms that the work is specifically designed to require,
+such as by intimate data communication or control flow between those
+subprograms and other parts of the work.
+
+ The Corresponding Source need not include anything that users
+can regenerate automatically from other parts of the Corresponding
+Source.
+
+ The Corresponding Source for a work in source code form is that
+same work.
+
+ 2. Basic Permissions.
+
+ All rights granted under this License are granted for the term of
+copyright on the Program, and are irrevocable provided the stated
+conditions are met. This License explicitly affirms your unlimited
+permission to run the unmodified Program. The output from running a
+covered work is covered by this License only if the output, given its
+content, constitutes a covered work. This License acknowledges your
+rights of fair use or other equivalent, as provided by copyright law.
+
+ You may make, run and propagate covered works that you do not
+convey, without conditions so long as your license otherwise remains
+in force. You may convey covered works to others for the sole purpose
+of having them make modifications exclusively for you, or provide you
+with facilities for running those works, provided that you comply with
+the terms of this License in conveying all material for which you do
+not control copyright. Those thus making or running the covered works
+for you must do so exclusively on your behalf, under your direction
+and control, on terms that prohibit them from making any copies of
+your copyrighted material outside their relationship with you.
+
+ Conveying under any other circumstances is permitted solely under
+the conditions stated below. Sublicensing is not allowed; section 10
+makes it unnecessary.
+
+ 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
+
+ No covered work shall be deemed part of an effective technological
+measure under any applicable law fulfilling obligations under article
+11 of the WIPO copyright treaty adopted on 20 December 1996, or
+similar laws prohibiting or restricting circumvention of such
+measures.
+
+ When you convey a covered work, you waive any legal power to forbid
+circumvention of technological measures to the extent such circumvention
+is effected by exercising rights under this License with respect to
+the covered work, and you disclaim any intention to limit operation or
+modification of the work as a means of enforcing, against the work's
+users, your or third parties' legal rights to forbid circumvention of
+technological measures.
+
+ 4. Conveying Verbatim Copies.
+
+ You may convey verbatim copies of the Program's source code as you
+receive it, in any medium, provided that you conspicuously and
+appropriately publish on each copy an appropriate copyright notice;
+keep intact all notices stating that this License and any
+non-permissive terms added in accord with section 7 apply to the code;
+keep intact all notices of the absence of any warranty; and give all
+recipients a copy of this License along with the Program.
+
+ You may charge any price or no price for each copy that you convey,
+and you may offer support or warranty protection for a fee.
+
+ 5. Conveying Modified Source Versions.
+
+ You may convey a work based on the Program, or the modifications to
+produce it from the Program, in the form of source code under the
+terms of section 4, provided that you also meet all of these conditions:
+
+ a) The work must carry prominent notices stating that you modified
+ it, and giving a relevant date.
+
+ b) The work must carry prominent notices stating that it is
+ released under this License and any conditions added under section
+ 7. This requirement modifies the requirement in section 4 to
+ "keep intact all notices".
+
+ c) You must license the entire work, as a whole, under this
+ License to anyone who comes into possession of a copy. This
+ License will therefore apply, along with any applicable section 7
+ additional terms, to the whole of the work, and all its parts,
+ regardless of how they are packaged. This License gives no
+ permission to license the work in any other way, but it does not
+ invalidate such permission if you have separately received it.
+
+ d) If the work has interactive user interfaces, each must display
+ Appropriate Legal Notices; however, if the Program has interactive
+ interfaces that do not display Appropriate Legal Notices, your
+ work need not make them do so.
+
+ A compilation of a covered work with other separate and independent
+works, which are not by their nature extensions of the covered work,
+and which are not combined with it such as to form a larger program,
+in or on a volume of a storage or distribution medium, is called an
+"aggregate" if the compilation and its resulting copyright are not
+used to limit the access or legal rights of the compilation's users
+beyond what the individual works permit. Inclusion of a covered work
+in an aggregate does not cause this License to apply to the other
+parts of the aggregate.
+
+ 6. Conveying Non-Source Forms.
+
+ You may convey a covered work in object code form under the terms
+of sections 4 and 5, provided that you also convey the
+machine-readable Corresponding Source under the terms of this License,
+in one of these ways:
+
+ a) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by the
+ Corresponding Source fixed on a durable physical medium
+ customarily used for software interchange.
+
+ b) Convey the object code in, or embodied in, a physical product
+ (including a physical distribution medium), accompanied by a
+ written offer, valid for at least three years and valid for as
+ long as you offer spare parts or customer support for that product
+ model, to give anyone who possesses the object code either (1) a
+ copy of the Corresponding Source for all the software in the
+ product that is covered by this License, on a durable physical
+ medium customarily used for software interchange, for a price no
+ more than your reasonable cost of physically performing this
+ conveying of source, or (2) access to copy the
+ Corresponding Source from a network server at no charge.
+
+ c) Convey individual copies of the object code with a copy of the
+ written offer to provide the Corresponding Source. This
+ alternative is allowed only occasionally and noncommercially, and
+ only if you received the object code with such an offer, in accord
+ with subsection 6b.
+
+ d) Convey the object code by offering access from a designated
+ place (gratis or for a charge), and offer equivalent access to the
+ Corresponding Source in the same way through the same place at no
+ further charge. You need not require recipients to copy the
+ Corresponding Source along with the object code. If the place to
+ copy the object code is a network server, the Corresponding Source
+ may be on a different server (operated by you or a third party)
+ that supports equivalent copying facilities, provided you maintain
+ clear directions next to the object code saying where to find the
+ Corresponding Source. Regardless of what server hosts the
+ Corresponding Source, you remain obligated to ensure that it is
+ available for as long as needed to satisfy these requirements.
+
+ e) Convey the object code using peer-to-peer transmission, provided
+ you inform other peers where the object code and Corresponding
+ Source of the work are being offered to the general public at no
+ charge under subsection 6d.
+
+ A separable portion of the object code, whose source code is excluded
+from the Corresponding Source as a System Library, need not be
+included in conveying the object code work.
+
+ A "User Product" is either (1) a "consumer product", which means any
+tangible personal property which is normally used for personal, family,
+or household purposes, or (2) anything designed or sold for incorporation
+into a dwelling. In determining whether a product is a consumer product,
+doubtful cases shall be resolved in favor of coverage. For a particular
+product received by a particular user, "normally used" refers to a
+typical or common use of that class of product, regardless of the status
+of the particular user or of the way in which the particular user
+actually uses, or expects or is expected to use, the product. A product
+is a consumer product regardless of whether the product has substantial
+commercial, industrial or non-consumer uses, unless such uses represent
+the only significant mode of use of the product.
+
+ "Installation Information" for a User Product means any methods,
+procedures, authorization keys, or other information required to install
+and execute modified versions of a covered work in that User Product from
+a modified version of its Corresponding Source. The information must
+suffice to ensure that the continued functioning of the modified object
+code is in no case prevented or interfered with solely because
+modification has been made.
+
+ If you convey an object code work under this section in, or with, or
+specifically for use in, a User Product, and the conveying occurs as
+part of a transaction in which the right of possession and use of the
+User Product is transferred to the recipient in perpetuity or for a
+fixed term (regardless of how the transaction is characterized), the
+Corresponding Source conveyed under this section must be accompanied
+by the Installation Information. But this requirement does not apply
+if neither you nor any third party retains the ability to install
+modified object code on the User Product (for example, the work has
+been installed in ROM).
+
+ The requirement to provide Installation Information does not include a
+requirement to continue to provide support service, warranty, or updates
+for a work that has been modified or installed by the recipient, or for
+the User Product in which it has been modified or installed. Access to a
+network may be denied when the modification itself materially and
+adversely affects the operation of the network or violates the rules and
+protocols for communication across the network.
+
+ Corresponding Source conveyed, and Installation Information provided,
+in accord with this section must be in a format that is publicly
+documented (and with an implementation available to the public in
+source code form), and must require no special password or key for
+unpacking, reading or copying.
+
+ 7. Additional Terms.
+
+ "Additional permissions" are terms that supplement the terms of this
+License by making exceptions from one or more of its conditions.
+Additional permissions that are applicable to the entire Program shall
+be treated as though they were included in this License, to the extent
+that they are valid under applicable law. If additional permissions
+apply only to part of the Program, that part may be used separately
+under those permissions, but the entire Program remains governed by
+this License without regard to the additional permissions.
+
+ When you convey a copy of a covered work, you may at your option
+remove any additional permissions from that copy, or from any part of
+it. (Additional permissions may be written to require their own
+removal in certain cases when you modify the work.) You may place
+additional permissions on material, added by you to a covered work,
+for which you have or can give appropriate copyright permission.
+
+ Notwithstanding any other provision of this License, for material you
+add to a covered work, you may (if authorized by the copyright holders of
+that material) supplement the terms of this License with terms:
+
+ a) Disclaiming warranty or limiting liability differently from the
+ terms of sections 15 and 16 of this License; or
+
+ b) Requiring preservation of specified reasonable legal notices or
+ author attributions in that material or in the Appropriate Legal
+ Notices displayed by works containing it; or
+
+ c) Prohibiting misrepresentation of the origin of that material, or
+ requiring that modified versions of such material be marked in
+ reasonable ways as different from the original version; or
+
+ d) Limiting the use for publicity purposes of names of licensors or
+ authors of the material; or
+
+ e) Declining to grant rights under trademark law for use of some
+ trade names, trademarks, or service marks; or
+
+ f) Requiring indemnification of licensors and authors of that
+ material by anyone who conveys the material (or modified versions of
+ it) with contractual assumptions of liability to the recipient, for
+ any liability that these contractual assumptions directly impose on
+ those licensors and authors.
+
+ All other non-permissive additional terms are considered "further
+restrictions" within the meaning of section 10. If the Program as you
+received it, or any part of it, contains a notice stating that it is
+governed by this License along with a term that is a further
+restriction, you may remove that term. If a license document contains
+a further restriction but permits relicensing or conveying under this
+License, you may add to a covered work material governed by the terms
+of that license document, provided that the further restriction does
+not survive such relicensing or conveying.
+
+ If you add terms to a covered work in accord with this section, you
+must place, in the relevant source files, a statement of the
+additional terms that apply to those files, or a notice indicating
+where to find the applicable terms.
+
+ Additional terms, permissive or non-permissive, may be stated in the
+form of a separately written license, or stated as exceptions;
+the above requirements apply either way.
+
+ 8. Termination.
+
+ You may not propagate or modify a covered work except as expressly
+provided under this License. Any attempt otherwise to propagate or
+modify it is void, and will automatically terminate your rights under
+this License (including any patent licenses granted under the third
+paragraph of section 11).
+
+ However, if you cease all violation of this License, then your
+license from a particular copyright holder is reinstated (a)
+provisionally, unless and until the copyright holder explicitly and
+finally terminates your license, and (b) permanently, if the copyright
+holder fails to notify you of the violation by some reasonable means
+prior to 60 days after the cessation.
+
+ Moreover, your license from a particular copyright holder is
+reinstated permanently if the copyright holder notifies you of the
+violation by some reasonable means, this is the first time you have
+received notice of violation of this License (for any work) from that
+copyright holder, and you cure the violation prior to 30 days after
+your receipt of the notice.
+
+ Termination of your rights under this section does not terminate the
+licenses of parties who have received copies or rights from you under
+this License. If your rights have been terminated and not permanently
+reinstated, you do not qualify to receive new licenses for the same
+material under section 10.
+
+ 9. Acceptance Not Required for Having Copies.
+
+ You are not required to accept this License in order to receive or
+run a copy of the Program. Ancillary propagation of a covered work
+occurring solely as a consequence of using peer-to-peer transmission
+to receive a copy likewise does not require acceptance. However,
+nothing other than this License grants you permission to propagate or
+modify any covered work. These actions infringe copyright if you do
+not accept this License. Therefore, by modifying or propagating a
+covered work, you indicate your acceptance of this License to do so.
+
+ 10. Automatic Licensing of Downstream Recipients.
+
+ Each time you convey a covered work, the recipient automatically
+receives a license from the original licensors, to run, modify and
+propagate that work, subject to this License. You are not responsible
+for enforcing compliance by third parties with this License.
+
+ An "entity transaction" is a transaction transferring control of an
+organization, or substantially all assets of one, or subdividing an
+organization, or merging organizations. If propagation of a covered
+work results from an entity transaction, each party to that
+transaction who receives a copy of the work also receives whatever
+licenses to the work the party's predecessor in interest had or could
+give under the previous paragraph, plus a right to possession of the
+Corresponding Source of the work from the predecessor in interest, if
+the predecessor has it or can get it with reasonable efforts.
+
+ You may not impose any further restrictions on the exercise of the
+rights granted or affirmed under this License. For example, you may
+not impose a license fee, royalty, or other charge for exercise of
+rights granted under this License, and you may not initiate litigation
+(including a cross-claim or counterclaim in a lawsuit) alleging that
+any patent claim is infringed by making, using, selling, offering for
+sale, or importing the Program or any portion of it.
+
+ 11. Patents.
+
+ A "contributor" is a copyright holder who authorizes use under this
+License of the Program or a work on which the Program is based. The
+work thus licensed is called the contributor's "contributor version".
+
+ A contributor's "essential patent claims" are all patent claims
+owned or controlled by the contributor, whether already acquired or
+hereafter acquired, that would be infringed by some manner, permitted
+by this License, of making, using, or selling its contributor version,
+but do not include claims that would be infringed only as a
+consequence of further modification of the contributor version. For
+purposes of this definition, "control" includes the right to grant
+patent sublicenses in a manner consistent with the requirements of
+this License.
+
+ Each contributor grants you a non-exclusive, worldwide, royalty-free
+patent license under the contributor's essential patent claims, to
+make, use, sell, offer for sale, import and otherwise run, modify and
+propagate the contents of its contributor version.
+
+ In the following three paragraphs, a "patent license" is any express
+agreement or commitment, however denominated, not to enforce a patent
+(such as an express permission to practice a patent or covenant not to
+sue for patent infringement). To "grant" such a patent license to a
+party means to make such an agreement or commitment not to enforce a
+patent against the party.
+
+ If you convey a covered work, knowingly relying on a patent license,
+and the Corresponding Source of the work is not available for anyone
+to copy, free of charge and under the terms of this License, through a
+publicly available network server or other readily accessible means,
+then you must either (1) cause the Corresponding Source to be so
+available, or (2) arrange to deprive yourself of the benefit of the
+patent license for this particular work, or (3) arrange, in a manner
+consistent with the requirements of this License, to extend the patent
+license to downstream recipients. "Knowingly relying" means you have
+actual knowledge that, but for the patent license, your conveying the
+covered work in a country, or your recipient's use of the covered work
+in a country, would infringe one or more identifiable patents in that
+country that you have reason to believe are valid.
+
+ If, pursuant to or in connection with a single transaction or
+arrangement, you convey, or propagate by procuring conveyance of, a
+covered work, and grant a patent license to some of the parties
+receiving the covered work authorizing them to use, propagate, modify
+or convey a specific copy of the covered work, then the patent license
+you grant is automatically extended to all recipients of the covered
+work and works based on it.
+
+ A patent license is "discriminatory" if it does not include within
+the scope of its coverage, prohibits the exercise of, or is
+conditioned on the non-exercise of one or more of the rights that are
+specifically granted under this License. You may not convey a covered
+work if you are a party to an arrangement with a third party that is
+in the business of distributing software, under which you make payment
+to the third party based on the extent of your activity of conveying
+the work, and under which the third party grants, to any of the
+parties who would receive the covered work from you, a discriminatory
+patent license (a) in connection with copies of the covered work
+conveyed by you (or copies made from those copies), or (b) primarily
+for and in connection with specific products or compilations that
+contain the covered work, unless you entered into that arrangement,
+or that patent license was granted, prior to 28 March 2007.
+
+ Nothing in this License shall be construed as excluding or limiting
+any implied license or other defenses to infringement that may
+otherwise be available to you under applicable patent law.
+
+ 12. No Surrender of Others' Freedom.
+
+ If conditions are imposed on you (whether by court order, agreement or
+otherwise) that contradict the conditions of this License, they do not
+excuse you from the conditions of this License. If you cannot convey a
+covered work so as to satisfy simultaneously your obligations under this
+License and any other pertinent obligations, then as a consequence you may
+not convey it at all. For example, if you agree to terms that obligate you
+to collect a royalty for further conveying from those to whom you convey
+the Program, the only way you could satisfy both those terms and this
+License would be to refrain entirely from conveying the Program.
+
+ 13. Use with the GNU Affero General Public License.
+
+ Notwithstanding any other provision of this License, you have
+permission to link or combine any covered work with a work licensed
+under version 3 of the GNU Affero General Public License into a single
+combined work, and to convey the resulting work. The terms of this
+License will continue to apply to the part which is the covered work,
+but the special requirements of the GNU Affero General Public License,
+section 13, concerning interaction through a network will apply to the
+combination as such.
+
+ 14. Revised Versions of this License.
+
+ The Free Software Foundation may publish revised and/or new versions of
+the GNU General Public License from time to time. Such new versions will
+be similar in spirit to the present version, but may differ in detail to
+address new problems or concerns.
+
+ Each version is given a distinguishing version number. If the
+Program specifies that a certain numbered version of the GNU General
+Public License "or any later version" applies to it, you have the
+option of following the terms and conditions either of that numbered
+version or of any later version published by the Free Software
+Foundation. If the Program does not specify a version number of the
+GNU General Public License, you may choose any version ever published
+by the Free Software Foundation.
+
+ If the Program specifies that a proxy can decide which future
+versions of the GNU General Public License can be used, that proxy's
+public statement of acceptance of a version permanently authorizes you
+to choose that version for the Program.
+
+ Later license versions may give you additional or different
+permissions. However, no additional obligations are imposed on any
+author or copyright holder as a result of your choosing to follow a
+later version.
+
+ 15. Disclaimer of Warranty.
+
+ THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
+APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
+HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
+OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
+THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
+PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
+IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
+ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
+
+ 16. Limitation of Liability.
+
+ IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
+WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
+THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
+GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
+USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
+DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
+PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
+EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
+SUCH DAMAGES.
+
+ 17. Interpretation of Sections 15 and 16.
+
+ If the disclaimer of warranty and limitation of liability provided
+above cannot be given local legal effect according to their terms,
+reviewing courts shall apply local law that most closely approximates
+an absolute waiver of all civil liability in connection with the
+Program, unless a warranty or assumption of liability accompanies a
+copy of the Program in return for a fee.
+
+ END OF TERMS AND CONDITIONS
+
+ How to Apply These Terms to Your New Programs
+
+ If you develop a new program, and you want it to be of the greatest
+possible use to the public, the best way to achieve this is to make it
+free software which everyone can redistribute and change under these terms.
+
+ To do so, attach the following notices to the program. It is safest
+to attach them to the start of each source file to most effectively
+state the exclusion of warranty; and each file should have at least
+the "copyright" line and a pointer to where the full notice is found.
+
+ <one line to give the program's name and a brief idea of what it does.>
+ Copyright (C) <year> <name of author>
+
+ This program is free software: you can redistribute it and/or modify
+ it under the terms of the GNU General Public License as published by
+ the Free Software Foundation, either version 3 of the License, or
+ (at your option) any later version.
+
+ This program is distributed in the hope that it will be useful,
+ but WITHOUT ANY WARRANTY; without even the implied warranty of
+ MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ GNU General Public License for more details.
+
+ You should have received a copy of the GNU General Public License
+ along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+Also add information on how to contact you by electronic and paper mail.
+
+ If the program does terminal interaction, make it output a short
+notice like this when it starts in an interactive mode:
+
+ <program> Copyright (C) <year> <name of author>
+ This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'.
+ This is free software, and you are welcome to redistribute it
+ under certain conditions; type `show c' for details.
+
+The hypothetical commands `show w' and `show c' should show the appropriate
+parts of the General Public License. Of course, your program's commands
+might be different; for a GUI interface, you would use an "about box".
+
+ You should also get your employer (if you work as a programmer) or school,
+if any, to sign a "copyright disclaimer" for the program, if necessary.
+For more information on this, and how to apply and follow the GNU GPL, see
+<http://www.gnu.org/licenses/>.
+
+ The GNU General Public License does not permit incorporating your program
+into proprietary programs. If your program is a subroutine library, you
+may consider it more useful to permit linking proprietary applications with
+the library. If this is what you want to do, use the GNU Lesser General
+Public License instead of this License. But first, please read
+<http://www.gnu.org/philosophy/why-not-lgpl.html>.
+
diff --git a/ansible_collections/hetzner/hcloud/FILES.json b/ansible_collections/hetzner/hcloud/FILES.json
new file mode 100644
index 000000000..5d393e7b2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/FILES.json
@@ -0,0 +1,2441 @@
+{
+ "files": [
+ {
+ "name": ".",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/aggregate-coverage.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "820353ffde6fd3ad655118772547549d84ccf0a7ba951e8fb1325f912ef640a0",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/combine-coverage.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e34d4e863a65b9f53c4ca8ae37655858969898a949e050e9cb3cb0d5f02342d0",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/process-results.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c03d7273fe58882a439b6723e92ab89f1e127772b5ce35aa67c546dd62659741",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/publish-codecov.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "70c795c8dbca2534b7909b17911630b7afaa693bbd7154e63a51340bc8b28dad",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/report-coverage.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f6a373322759ccc2736fb25d25d8c402dfe16b5d9a57cfccb1ca8cb136e09663",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/run-tests.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cb08a3ec5715b00d476ae6d63ca22e11a9ad8887239439937d2a7ea342e5a623",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/scripts/time-command.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0232f415efeb583ddff907c058986963b775441eaf129d7162aee0acb0d36834",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/templates",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/templates/coverage.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "daf1930264760d47b54588f05c6339fd69ca2d239c77c44bc4cee3c4e9f76447",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/templates/matrix.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4fb0d3ffb2125d5806c7597e4f9d4b2af69cf8c337e9d57803081eddd4a6b081",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/templates/test.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2cfa1271f94c71f05ffa0b1f763d8946394b5636e14579cda8ee14bb38bbcf1c",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "61f20decd3c8fb34ac2cc6ff79f598fc5136e642130a7ba065ccc5aa37960cd2",
+ "format": 1
+ },
+ {
+ "name": ".azure-pipelines/azure-pipelines.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3c838b484ce8f956163c6fd6ae930f4b792075d5c1b6ddd1130196a48b9333d6",
+ "format": 1
+ },
+ {
+ "name": ".github",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/workflows",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": ".github/workflows/check-changelog-fragments.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c0c78648b73d122ec9cbc96b6b1640ef97fbf31cc8cbb847667ed4b68637f26d",
+ "format": 1
+ },
+ {
+ "name": "changelogs",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "changelogs/fragments/.keep",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "changelogs/.gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "919ef00776e7d2ff349950ac4b806132aa9faf006e214d5285de54533e443b33",
+ "format": 1
+ },
+ {
+ "name": "changelogs/changelog.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3310467c01307e817d9efba1a431034cd39df741f4c758842fce4dd899822d6b",
+ "format": 1
+ },
+ {
+ "name": "changelogs/config.yaml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cfa06a718f8b3c226c944d5f33fd3ba5a07415293b07146631503539bea5d1ea",
+ "format": 1
+ },
+ {
+ "name": "meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "meta/runtime.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3758e45dc3de5da271325c52c3f1a95a8bb518de67721dc634a85e67e93ecc69",
+ "format": 1
+ },
+ {
+ "name": "plugins",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/doc_fragments/hcloud.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "40624302c695078a48d9883e47133f0b29809c188584af3a314005eb6c495c2d",
+ "format": 1
+ },
+ {
+ "name": "plugins/inventory",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/inventory/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/inventory/hcloud.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2a457686e85db10e3bde0e48a603988fbb64a121ba11372a869f66c988ea5338",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/module_utils/hcloud.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8246c7b016b767dbf0d5bffe0e27d5f2992b83cce4393255f664e41663e2e7dd",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_datacenter_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e35fd6cad5a6e867260be027a13e985623e1e147ec6b25bce848b2cf1d0d1e5f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_floating_ip_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6f6b9bd26b6f508d71824832ece8b1463e7ff8bc2e9225d1f8694006793b7c16",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_image_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2383873b050200488ca7c90e6506a7c11539bbc60948fe914eafe1c606047c48",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_location_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7e4c87b1fac74d9b15dd76c462aab1cdb219ddd14009c392162e0e7ce18aecca",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_server_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "69f52a3daa13373f32f14074dc1ef14d586fb5d87d653438dae06d78ef12975f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_server_type_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8716f5f31f6fc0c868ea9526812b99b0d67e6645f6e993983967a2a04544e5b6",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_ssh_key_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6b33f5766ad194010c3bf1b872d27ae6e8bf2fdb5ed7f00fcd998451b7b86528",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_volume_facts.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c2e4545af69cd1ef6dc10ffbf5901b4ba1cae2cb693f5ab4bbde6603e25e6aec",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/__init__.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_certificate.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "73e1556df459f2da780aa9aacfd95884cec28e1e14e688aee12df4d521b04825",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_certificate_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "181d2abb4c961c065a4661173835d48795e04ed569b2ac91788c5db2b0e9faeb",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_datacenter_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e35fd6cad5a6e867260be027a13e985623e1e147ec6b25bce848b2cf1d0d1e5f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_firewall.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a2541dd2758819ab99904da21c29dcb7f973069d9d228ae16f77ea4bb312b4b7",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_floating_ip.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6668354c432db61272b71c8424a9a2ad38fc9179bb7e5b9c88d049012cf79259",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_floating_ip_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6f6b9bd26b6f508d71824832ece8b1463e7ff8bc2e9225d1f8694006793b7c16",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_image_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2383873b050200488ca7c90e6506a7c11539bbc60948fe914eafe1c606047c48",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_load_balancer.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9040e4d20e62211a45f5e87886d0d4274be88fa5642f7ca838bad6e9e4da85a9",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_load_balancer_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "19c0f2673a29c02a85280f75f3cad34ac070ee611eb87f6b12a6ec5d15066349",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_load_balancer_network.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1add8639020a29332c474f5d590b523b16c214cfc312aeb9c2fb476e6d6043ba",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_load_balancer_service.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c36342c4f33995aed50a5c31de45a14c6636d4c3188edee4fd85ae1d0a4041b1",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_load_balancer_target.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f228d4c8c9e3cbabb11c003db01ba4399195d5881a9e001b24c2e83cec4aed2f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_load_balancer_type_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "44b9672195eb0ba06bacd81c871595ca31455ca343887b964d3d6e473509c41a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_location_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7e4c87b1fac74d9b15dd76c462aab1cdb219ddd14009c392162e0e7ce18aecca",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_network.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9e18800b2b9d77a3e14d02460263809ca5859288f4a5db9ac3f5487d590edab2",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_network_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c672dbebee7fce20c3ace097e427e52e1aa240c3ac32b256a79edee38c7d89c2",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_placement_group.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ac5dcbe68d6775b17a3731be4eae060ab191d649dc4d93ef5bd608d1795c3855",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_primary_ip.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b9c4bb5f7c6cb8daae694c649910d2191aeb5577b04e5032e9fd4e1cb54acfde",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_rdns.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f1ff9bb22dd97a9188cfdfda654bedefb0f261b7ec6b469642e93b0c7ac588db",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_route.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3b94be64a305628502f5798b6125f6f8f6ae7ab2f249d30c3773c03bf4cab2a",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_server.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "60ce8badfaa9266dded57290497619e234904a555b6f61d18cfac7f0d581a780",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_server_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "69f52a3daa13373f32f14074dc1ef14d586fb5d87d653438dae06d78ef12975f",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_server_network.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8e6d0b574c0c7704b61d89e06b7ea7cae10d3f61f71807e4694f6cfdcf8abb7c",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_server_type_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8716f5f31f6fc0c868ea9526812b99b0d67e6645f6e993983967a2a04544e5b6",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_ssh_key.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "753f50cadb0aff1240c92302ba83bfe36601d80c08b131f2b975781bf2e0f2f6",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_ssh_key_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6b33f5766ad194010c3bf1b872d27ae6e8bf2fdb5ed7f00fcd998451b7b86528",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_subnetwork.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3100c0f9fe4f184386b57ff90462437649eaab8513631e7ed26db3f4c9514220",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_volume.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "7b80037e2b1ab8e38dfeb86dcaef129711436ae8314e82288cec7c3be358a94c",
+ "format": 1
+ },
+ {
+ "name": "plugins/modules/hcloud_volume_info.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c2e4545af69cd1ef6dc10ffbf5901b4ba1cae2cb693f5ab4bbde6603e25e6aec",
+ "format": 1
+ },
+ {
+ "name": "tests",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8c7f6588b06981201aaac29d9812da66019603bb613dd38f415355e6ea3f2f17",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a613e3027e9e2dd78cda228949082fa43284dae03c297b946e974ee3c0e76129",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8fb8130a3dfba9804a0d0cdb26aea93c41ae4fc555c6e0cd1cef303a6556150e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "54abe03862bd73dde2caed2d60073ae9a9f43aa16642f877891afde936dbadd1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "55efb3bbb7018cf5810a5c93d39d3647f245e2e46db1fda2a4a45332e951767b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a63d83d5da4d2fbea8fd4849cadab7dadb4ade729f6b9fcc6fe5c789e766d725",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_certificate_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "40e03f6534c2f7203b2b3f2aaa720b36d1ef10bfb6e6ab7f46e34bed8c976ec0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d340f77bcf4217306d8913fed14a3fbfcc1c07960e79c9967e194386778bb2c1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_datacenter_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "61d34ce86e49d5276fafb8777f7bae73bbfa4a19634bd3b20acb7a0478518c62",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "123c9c4f92f86644b19fcb25c3be36c460b4c70354f0fbfef84de7ad580954ce",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_firewall/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "fbd1af75fee59098d3aad4eb8d3c33da014855310457f3b28309ea0f6d601782",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0276c136a340f6ab58b9d3b70f4c977a65fb71c097afda3085bddfcb388de6b9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8da453a6164fb45ef21bc3c098f838b2fc8d12d218b9a5181033f9d9fe1f10bc",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6046d0d6217a122060d5475f76a7cf78577850caca478faa95e8483cbb53d753",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_floating_ip_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8c980497c38e44bc1a09ec895a592835525728c6b43efd9e5ae77242fd0e17d3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "54275a2526a90d1dd179f685f0a80970f34a6b714bd3ff988a6faa0613ff639d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_image_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ffd88985ada5f3e746640b7767ad9fd1b2de04da44dad808cb4c44005be83136",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "37823961c8a8ad27ade27df4d929423373e9ad5d8037240f6f137747e39f3c72",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b8e931c775c02f0df653a7f9b077d0baa6ff8cba2651e7e7e37b2770259b1590",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c595101d0d1838a92198660b95c472514fc903757989378c3905cb1d644e5fcd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5f84c924f92341fe78976aac4e941b2b12b351b19db1e2a1ad111d7c75143844",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5969da2934af118e3359860f30d323073a266f82adc0c1cf85335fc1c387d306",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_network/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "04105693afa5180466c36776a37b716b03bf6a359f433d9a52f4e91845f8ba8a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3107b8864c2eaeaa97d3568f7a52122b64bea694f9903151e073924b1b45574e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_service/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ecbfca90616286afa88ca58f5bd84522a9adb959fb673893e6e4298c6e07ab37",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "3338b80159baa36a184a00750e6943d5cf7770febdc2db91d047f803c9410f6c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_target/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e5641db8ac5de1ea6954d78949fbf6ac9829f86f9135ae9361859aa8220d1892",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "56fc91b4ed15be279423b2859d700b70589bb1e10bee32a829fa263253917fa2",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_load_balancer_type_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "49468959c5285a8c2c8bccefa9c1d9dca5f0239158eb292bb8f4b768af0fc387",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c56ca0af25e0ff72b7f1eb84a71924b3ba7b65a659f47e2c2ee75e797dfd3ad7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_location_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c7c1cb312be5bbd4d22ec450029303bd989ec1701b9776f03787f96539fcdd1c",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "93baf9ebc898fb93f961dad90033a1a50e0aa9aa4c9cc197aa78587738e42616",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "67633416742aa9e9480b796190982c057f5a9b7cff7c57625029c3958b07bfb4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c35ec449e5b6556c7c7205e598fdc98b7dff12a9b4a6be290b3fffd936422dc4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cd53b64efc25ba17d0b8dd5db034e63ddd2bc30bd912766fd8e6f4bed585510a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_network_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "17ff18cf59a1d19ba682a9ec20ea00af243721108fb8ae635d26a4f7c8159bdb",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "aeb396dd105fc91adda4785d275eef4a421b2a4d8512ae0ad74532452253162f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_placement_group/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e6bd2109171a870ae7429ace6bf18a6c0280f188c0030623a1eb7ec5b8c2f72e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f757913b19fad8b1256fc52046dc8f7bc286e135832837452d54e1744bf6236b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_primary_ip/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8eb41c71669a809fdc595161f96c374cd0514a0fe232db2af105c5b97c86f7d3",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cf9085335e8c310c57255c35c9a7f9ed61437277b0eb23af810565ed3a1b63d9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "90b0be18a534d3700c7fefb669b463786e8c3321377b14b909135ba59629f46a",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_rdns/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "73f973bef8b6a33b117aaac954c339df4a6f8c5d482db92f4cc1e95e30153001",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cf9085335e8c310c57255c35c9a7f9ed61437277b0eb23af810565ed3a1b63d9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "754a7fee0573b3a5c27cba4c53e7f66bb36ca485e153305efbd3b332d52c36d7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_route/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "96ffda2413e62b64c60f03e5861ebeb6bf0f2837775a412cd2f6c166329ce40b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks/basic.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "19feb42f8f21128cb8cf3fc0efe562d4d8b2a5d0126bde222ff1fa674204be67",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks/firewalls.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b43203d049ab787df703e53808f6bcaed4917283ee0a6c5108fab3bc7a0533e9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6be3a83aea87ad5a7d89ebf84718d1881d9106e95b55d0895fc58e8aaad4bb6e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks/primary_ips.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "67a5ed8f27f4c55daa46c128212ccffda58b15dd9e1adea5fd75d027820d45ee",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks/private_network_only.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "6fe7897ff35cd82e4bef8d5a1fa44a6901a16e75b84f605984cd8881120e41fc",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/tasks/validation.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4ca7c4b3b4460686b05dc9878c6365c99dd2f0e290f6b79563312ac9e48a45d7",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "899ec496fc016f5e221bdf39f63bcf73374fe136f199b14cac9dd23375c989e4",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "86d40e0b83f231ac4c5d3d94657d4fb529311261efbba7c0db0093959d82e3a8",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "9b22f51084b84013a65066696e8e389643dc7bbf35a64545072bf5c6767b71c1",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0d2464557ed4ade951ffc241834816068b4869198c79cb12d58c3d297f3f9d62",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4aaf9b7f5c70031287488817d492fb19e9b23cb8e2f886bcf9c7f2b6bb647bf0",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_network/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "864269fd363e663625df637c1cca2ad3a47ca364bbbad59da58cb75c6002a827",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1be0206ddc23c1059392c89a7d0e321ceccf2f3f04a0a33f2d72ff209fb51b62",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "cbc4ccf4a22f6d7cf98a9c1c5710a93611f4992817dd6ffd8260e7c503274852",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_server_type_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e9edea10b3c410594ee50a56d4ee65e460170c493ac0af8b0cd64e515af2bcde",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2267e95d9e1412bfa94ca2ecb76ecf5ea027753e4b50f87cf59c9450f9090b00",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f70d83fc7b47354a328da3c929ef9c7c0eda4d9b98707e5c591399963c15c262",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "622b6f801a80c1b2c9e0f1743cee078a93689245b8b05744a6797e52cd152272",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "2267e95d9e1412bfa94ca2ecb76ecf5ea027753e4b50f87cf59c9450f9090b00",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f2ac15fb16f8e251a10741c938b839bbe4786accfa7ac34a52624f1b03c4d1a9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_ssh_key_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "48ec8ba489041f8a7f69a1bdca4f5e2a4fea038317fb8cafd870b9b2dc2aaea9",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a1b71650fde2c710274a8e017cca68e43832ed6360f5aa83eb5909f8f8dc00fd",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_subnetwork/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "a48fdbe183742154c38aa6c1658c907b2fac5a90170aca3f813c65234a33845e",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4be0ed3631b304c66f46a7443dcc0f05b5d7eb99c8d55dc98cf3a4ec7a513a44",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b41df455c2d9c29dad9308de40a386808471e6c386ca104bb9f3fcd0f7b86555",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/defaults",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/defaults/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "59d614a7e95cd4313b26fb93a5bc68fc1fd8591c08a103af883e885858232d0f",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/meta",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/meta/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eb96249de09a0a3fdd399aca62aa600ee7bb7b66670ffb65e000fdbe3419d986",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "85ad9de1a55e08bfa43a2fc065a515eda84b9c4cc411fee92084c7f47ad7485b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/hcloud_volume_info/aliases",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b02a426443d5b1f10b9edc49bad4cdaf57c66f422bdaf60f1b4b904529431fb5",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_selfsigned_certificate",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_selfsigned_certificate/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "e4d4d2cd1069f55d65c9e6c8017b934023575ecb0540445ac65714d85d422323",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_sshkey",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_sshkey/tasks",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/integration/targets/setup_sshkey/tasks/main.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "016d4c789eb18f7d6cd03c5cd32300a5e1ccec5bb6695aa35b60105c0a5d5d2d",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/constraints.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "67495540cfc7f8c60daeb40ff4607f95f44163cd1ee5d29726042ae7e6c7f45b",
+ "format": 1
+ },
+ {
+ "name": "tests/integration/requirements.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f8c3336e9a6315d376c59719374e5aced8a1be5d81934e0b39bc9483fdb49dc6",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.12.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.13.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.14.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8",
+ "format": 1
+ },
+ {
+ "name": "tests/sanity/ignore-2.15.txt",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "8b8815dcc93330716312b23095ad47d984bd7a039d20a5baa1127ce6d73ca6e8",
+ "format": 1
+ },
+ {
+ "name": "tests/utils",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/utils/gitlab",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/utils/gitlab/gitlab.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "4649d4c2fc11e85bf786a6a5894aebdb114f32866006f18d3878058b04318501",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/gitlab/integration.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "944d27147a31dfcee01b9a94003b06d1f0ba0c7a396b8fe53f606045ad80d186",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/gitlab/sanity.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "d3de6463986d6023f096a2cd43900cecbe697e7b46b4014a6d1c22a7403a7017",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable",
+ "ftype": "dir",
+ "chksum_type": null,
+ "chksum_sha256": null,
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable/check_matrix.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "eca2864515b1854d55dd78b481962969ac030fa8d10a4fd4edb9833797ee85fb",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable/hcloud.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "99db6946e47cf9e49ab2fccbe0aca8ffc9aaa0918fdc9e3ef543601c55a98713",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable/sanity.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5815f3e7f2275a6a218e94c6529b0bff8dd6c162b2fece0dacca65cf734c0b22",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable/shippable.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "76af8f1e1f106ddd5128405aed79a4cfc15f26ee090b1f20c0b82281efecaf51",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable/timing.py",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "ebb7d3553349747ad41d80899ed353e13cf32fcbecbb6566cf36e9d2bc33703e",
+ "format": 1
+ },
+ {
+ "name": "tests/utils/shippable/timing.sh",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "f3f3cc03a997cdba719b0542fe668fc612451841cbe840ab36865f30aa54a1bd",
+ "format": 1
+ },
+ {
+ "name": "tests/.gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "b5726d3ec9335a09c124469eca039523847a6b0f08a083efaefd002b83326600",
+ "format": 1
+ },
+ {
+ "name": "tests/requirements.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "bdf5cf6c8dad0da0a4e726e341c0ba44124759ce387da8c1ab158a6662cb2bb6",
+ "format": 1
+ },
+ {
+ "name": ".gitignore",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c30262d952e58b561e2f59039f3dee6b5b16ff266c5e948fa7ea36049a19acfb",
+ "format": 1
+ },
+ {
+ "name": ".gitlab-ci.yml",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "1fcd5b6555665ac7b3c1a8ec7c0183fc3c9e35f40d8e549faf02a86b948338bc",
+ "format": 1
+ },
+ {
+ "name": "CHANGELOG.rst",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "5c0249d66834ac8b3f72c326bf8666fc3c46ca8d8f023b9363745a0b7c51e358",
+ "format": 1
+ },
+ {
+ "name": "COPYING",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "0ae0485a5bd37a63e63603596417e4eb0e653334fa6c7f932ca3a0e85d4af227",
+ "format": 1
+ },
+ {
+ "name": "README.md",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "036ac8c2ec4dcbbdea4b3b5e8bb5ad1e0576ffa4795ad7dc6245da27f8f7998e",
+ "format": 1
+ }
+ ],
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/hetzner/hcloud/MANIFEST.json b/ansible_collections/hetzner/hcloud/MANIFEST.json
new file mode 100644
index 000000000..3a02e99d8
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/MANIFEST.json
@@ -0,0 +1,36 @@
+{
+ "collection_info": {
+ "namespace": "hetzner",
+ "name": "hcloud",
+ "version": "1.11.0",
+ "authors": [
+ "Hetzner Cloud (github.com/hetznercloud)"
+ ],
+ "readme": "README.md",
+ "tags": [
+ "hetzner",
+ "cloud",
+ "hcloud"
+ ],
+ "description": "A Collection for managing Hetzner Cloud resources",
+ "license": [
+ "GPL-3.0-or-later"
+ ],
+ "license_file": null,
+ "dependencies": {
+ "ansible.netcommon": ">=0.0.1"
+ },
+ "repository": "https://github.com/ansible-collections/hetzner.hcloud",
+ "documentation": "https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud",
+ "homepage": "https://github.com/ansible-collections/hetzner.hcloud",
+ "issues": "https://github.com/ansible-collections/hetzner.hcloud/issues"
+ },
+ "file_manifest_file": {
+ "name": "FILES.json",
+ "ftype": "file",
+ "chksum_type": "sha256",
+ "chksum_sha256": "c08aa88abd3883a1ce05b7b35a56516e099b1d2b8acbc37dc495d39e2874c31a",
+ "format": 1
+ },
+ "format": 1
+} \ No newline at end of file
diff --git a/ansible_collections/hetzner/hcloud/README.md b/ansible_collections/hetzner/hcloud/README.md
new file mode 100644
index 000000000..f57ae7be3
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/README.md
@@ -0,0 +1,62 @@
+[![Build Status](https://dev.azure.com/ansible/hetzner.hcloud/_apis/build/status/CI?branchName=master)](https://dev.azure.com/ansible/hetzner.hcloud/_build?definitionId=35)
+[![Codecov](https://img.shields.io/codecov/c/github/ansible-collections/hetzner.hcloud)](https://codecov.io/gh/ansible-collections/hetzner.hcloud)
+
+Ansible Collection: hetzner.hcloud
+=================================================
+
+Ansible Hetzner Cloud Collection for controlling your Hetzner Cloud Resources.
+
+## Release notes
+
+See [here](https://github.com/ansible-collections/hetzner.hcloud/tree/master/CHANGELOG.rst).
+
+## Documentation
+
+The documentation for all modules are available through `ansible-doc`.
+
+Sample: `ansible-doc hetzner.hcloud.hcloud_server` shows the documentation for the `hcloud_server` module.
+
+For all modules that were part of Ansible directly (before Ansible 2.11) we also have the documentation published in the
+Ansible documentation: https://docs.ansible.com/ansible/latest/collections/hetzner/hcloud/
+
+# Development
+
+## Requirements
+
+You should place the collection (clone the repository) into the Ansible collection path. Normally this
+is `~/.ansible/collections/ansible_collections/<namespace>/<collection`, so for our collection it would
+be: `~/.ansible/collections/ansible_collections/hetzner/hcloud`.
+
+```
+git clone git@github.com:ansible-collections/hetzner.hcloud.git ~/.ansible/collections/ansible_collections/hetzner/hcloud
+```
+
+After this you just need `ansible` installed.
+
+## Testing
+
+Testing is done via `ansible-test`. Make sure to have a `cloud-config-hcloud.ini` file in `tests/integration` which
+contains the hcloud API token:
+
+```
+[default]
+hcloud_api_token=<token>
+```
+
+After this you should be able to use `ansible-test integration` to perform the integration tests for a specific module.
+Sample:
+
+```
+ansible-test integration --color --local -vvv hcloud_server // Executed all integration tests for hcloud_server module
+```
+
+## Releasing a new version
+
+### Generating changelog from fragments
+
+1. Check if the changelog fragments are available (there should be files in `changelogs/fragments`)
+2. Run `antsibull-changelog release --version <version>`, it should remove all fragments and change
+ the `changelogs/changlog.yaml` and `CHANGELOG.rst`
+3. Push the changes to the main branch
+4. Tag the release through the Github UI, after this the Github Actions will run and publish the collection to Ansible
+ Galaxy
diff --git a/ansible_collections/hetzner/hcloud/changelogs/.gitignore b/ansible_collections/hetzner/hcloud/changelogs/.gitignore
new file mode 100644
index 000000000..6be6b5331
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/changelogs/.gitignore
@@ -0,0 +1 @@
+/.plugin-cache.yaml
diff --git a/ansible_collections/hetzner/hcloud/changelogs/changelog.yaml b/ansible_collections/hetzner/hcloud/changelogs/changelog.yaml
new file mode 100644
index 000000000..0a4f123d0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/changelogs/changelog.yaml
@@ -0,0 +1,275 @@
+ancestor: null
+releases:
+ 0.1.0:
+ modules:
+ - description: Create and manage cloud Floating IPs on the Hetzner Cloud.
+ name: hcloud_floating_ip
+ namespace: ''
+ - description: Create and manage cloud Load Balancers on the Hetzner Cloud.
+ name: hcloud_load_balancer
+ namespace: ''
+ - description: Manage the relationship between Hetzner Cloud Networks and Load
+ Balancers
+ name: hcloud_load_balancer_network
+ namespace: ''
+ - description: Create and manage the services of cloud Load Balancers on the Hetzner
+ Cloud.
+ name: hcloud_load_balancer_service
+ namespace: ''
+ - description: Manage Hetzner Cloud Load Balancer targets
+ name: hcloud_load_balancer_target
+ namespace: ''
+ - description: Gather infos about the Hetzner Cloud Load Balancer types.
+ name: hcloud_load_balancer_type_info
+ namespace: ''
+ release_date: '2020-06-29'
+ 0.2.0:
+ changes:
+ bugfixes:
+ - hcloud inventory plugin - Allow usage of hcloud.yml and hcloud.yaml - this
+ was removed by error within the migration from build-in ansible to our collection
+ fragments:
+ - inventory-allow-usage-of-pre-migration-configuration-fuiles.yml
+ release_date: '2020-06-30'
+ 1.0.0:
+ changes:
+ minor_changes:
+ - hcloud_load_balancer Allow changing the type of a Load Balancer
+ - hcloud_server Allow the creation of servers with enabled backups
+ fragments:
+ - gh7-allow-enabling-of-backups-on-server-creation.yml
+ - lb-allow-change-type.yml
+ release_date: '2020-08-11'
+ 1.1.0:
+ changes:
+ bugfixes:
+ - hcloud_floating_ip Fix idempotency when floating ip is assigned to server
+ minor_changes:
+ - hcloud_floating_ip Allow creating Floating IP with protection
+ - hcloud_load_balancer Allow creating Load Balancer with protection
+ - hcloud_network Allow creating Network with protection
+ - hcloud_server Allow creating server with protection
+ - hcloud_volume Allow creating Volumes with protection
+ fragments:
+ - fix-idempotency-floating-ip.yml
+ - gh-28-allow-setting-of-protection-on-creation.yml
+ release_date: '2020-10-05'
+ 1.10.0:
+ changes:
+ breaking_changes:
+ - inventory plugin - Python v3.5+ is now required.
+ minor_changes:
+ - hcloud_server - add private_networks_info containing name and private ip in
+ responses
+ - hcloud_server_info - add private_networks_info containing name and private
+ ip in responses
+ - inventory plugin - Add list of all private networks to server variables.
+ - inventory plugin - Add new connect_with setting public_ipv6 to connect to
+ discovered servers via public IPv6 address.
+ - inventory plugin - Add public IPv6 address to server variables.
+ - inventory plugin - Log warning instead of crashing when some servers do not
+ work with global connect_with setting.
+ fragments:
+ - inventory-connect-via-ipv6.yml
+ - inventory-handle-invalid-connect-with.yml
+ - inventory-ipv6-adress-variable.yml
+ - inventory-private-network-info.yml
+ - server-private-networks-info.yml
+ release_date: '2023-02-01'
+ 1.10.1:
+ changes:
+ bugfixes:
+ - hcloud_server - Prevent backups from being disabled when undefined
+ - hcloud_server - Server locked after attaching to placement group
+ fragments:
+ - server-fix-backups.yml
+ - server-race-condition-pg-attach.yml
+ release_date: '2023-04-03'
+ 1.11.0:
+ changes:
+ minor_changes:
+ - hcloud_image_info - Add cpu architecture field to return value.
+ - hcloud_image_info - Allow filtering images by cpu architecture.
+ - hcloud_server - Select matching image for the cpu architecture of the server
+ type on create & rebuild.
+ - hcloud_server_type_info - Add cpu architecture field to return value.
+ - inventory plugin - Add cpu architecture to server variables.
+ fragments:
+ - arm-features.yaml
+ release_date: '2023-04-11'
+ 1.2.0:
+ changes:
+ minor_changes:
+ - Dynamic Inventory Add option to specifiy the token_env variable which is used
+ for identification if now token is set
+ - Improve imports of API Exception
+ - hcloud_server_network Allow updating alias ips
+ - hcloud_subnetwork Allow creating vswitch subnetworks
+ modules:
+ - description: Gather infos about your Hetzner Cloud load_balancers.
+ name: hcloud_load_balancer_info
+ namespace: ''
+ release_date: '2020-12-01'
+ 1.2.1:
+ changes:
+ bugfixes:
+ - Inventory Restore Python 2.7 compatibility
+ release_date: '2020-12-16'
+ 1.3.0:
+ changes:
+ minor_changes:
+ - Add firewalls to hcloud_server module
+ modules:
+ - description: Manage Hetzner Cloud Firewalls
+ name: hcloud_firewall
+ namespace: ''
+ release_date: '2021-03-11'
+ 1.3.1:
+ changes:
+ bugfixes:
+ - hcloud_server - fix a crash related to check mode if ``state=started`` or
+ ``state=stopped`` (https://github.com/ansible-collections/hetzner.hcloud/issues/54).
+ fragments:
+ - 64-hcloud_server_fix_checkmode_state_started.yml
+ release_date: '2021-03-18'
+ 1.4.0:
+ changes:
+ bugfixes:
+ - hcloud_firewall - fix idempotence related to rules comparison (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
+ - hcloud_load_balancer_service - fix imported wrong HealthCheck from hcloud-python
+ (https://github.com/ansible-collections/hetzner.hcloud/pull/73).
+ - hcloud_server - fix idempotence related to firewall handling (https://github.com/ansible-collections/hetzner.hcloud/pull/71).
+ security_fixes:
+ - hcloud_certificate - mark the ``private_key`` parameter as ``no_log`` to prevent
+ potential leaking of secret values (https://github.com/ansible-collections/hetzner.hcloud/pull/70).
+ fragments:
+ - 70-no_log_security_fixes.yml
+ - 71-hcloud_firewall_fix_idempotence.yml
+ - 73-hcloud_load_balancer_service_fix_wrong_import.yml
+ release_date: '2021-04-06'
+ 1.4.1:
+ changes:
+ minor_changes:
+ - hcloud_server - improve the handling of deprecated images
+ - hcloud_server - improve the validation and error response for not existing
+ images
+ - inventory - support jinjia templating within `token`
+ fragments:
+ - 74-hcloud_server-improve-error-message-images.yml
+ release_date: '2021-04-07'
+ 1.4.2:
+ changes:
+ bugfixes:
+ - inventory fix image name was set as server type instead of the correct server
+ type
+ fragments:
+ - inventory-fix-server-type-wrong-value.yml
+ release_date: '2021-04-14'
+ 1.4.3:
+ changes:
+ bugfixes:
+ - hcloud_server Fix incompatbility with python < 3.6
+ - hcloud_server Improve error handling when using not existing server types
+ fragments:
+ - hcloud-server-py36.yaml
+ - hcloud-server-server-type.yaml
+ release_date: '2021-04-22'
+ 1.4.4:
+ changes:
+ bugfixes:
+ - hcloud_server Improve Error Message when attaching a not existing firewall
+ to a server
+ - hcloud_volume Force detaching of volumes on servers before deletion
+ fragments:
+ - hcloud_server-improve-error-message-on-not-existing-firewall.yml
+ - hcloud_volume-force-detach-before-deletion.yml
+ release_date: '2021-07-19'
+ 1.5.0:
+ changes:
+ bugfixes:
+ - hcloud_rdns improve error message on not existing server/Floating IP
+ - hcloud_server backups property defaults to None now instead of False
+ major_changes:
+ - Introduction of placement groups
+ minor_changes:
+ - hcloud_firewall Add description field to firewall rules
+ fragments:
+ - hcloud_firewall-add-description-field-to-rules.yml
+ - hcloud_placement_group.yml
+ - hcloud_rdns-improve-validation-of-input.yml
+ - hcloud_server_default-backups-to-none.yml
+ release_date: '2021-08-16'
+ 1.6.0:
+ changes:
+ minor_changes:
+ - hcloud_rdns Add support for load balancer
+ fragments:
+ - hcloud_rdns-add-support-for-load-balancers.yml
+ release_date: '2021-08-17'
+ 1.7.0:
+ changes:
+ minor_changes:
+ - inventory - support jinjia templating within `network`
+ fragments:
+ - inventory-network-templating.yml
+ release_date: '2022-06-09'
+ 1.7.1:
+ changes:
+ bugfixes:
+ - hcloud_server_network - fixes changed alias_ips by using sorted
+ minor_changes:
+ - inventory - allow filtering by server status
+ fragments:
+ - hcloud_server_network-alias-ips.yaml
+ - inventory-filter-by-status.yaml
+ release_date: '2022-06-13'
+ 1.8.0:
+ modules:
+ - description: Create and manage cloud Primary IPs on the Hetzner Cloud.
+ name: hcloud_primary_ip
+ namespace: hetzner.hcloud
+ release_date: '2022-06-29'
+ 1.8.1:
+ release_date: '2022-06-29'
+ 1.8.2:
+ changes:
+ bugfixes:
+ - dynamic inventory - fix crash when having servers without IPs (flexible networks)
+ - hcloud_server - When state stopped and server is created, do not start the
+ server
+ - hcloud_server_info - fix crash when having servers without IPs (flexible networks)
+ fragments:
+ - flexible-networks-hcloud-server-info.yml
+ - inventory.yml
+ release_date: '2022-09-14'
+ 1.9.0:
+ changes:
+ bugfixes:
+ - hcloud_firewall - the deletion could fail if the firewall was referenced right
+ before
+ - hcloud_server - fix backup window was given out as "None" instead of null
+ - hcloud_server_info - fix backup window was given out as "None" instead of
+ null
+ - hcloud_volume - fix server name was given out as "None" instead of null if
+ no server was attached
+ - hcloud_volume_info - fix server name was given out as "None" instead of null
+ if no server was attached
+ minor_changes:
+ - dynamic inventory - add support changing the name of the top level group all
+ servers are added to
+ - hcloud_firewall - add support for esp and gre protocols
+ fragments:
+ - hcloud_firewall-deletion.yml
+ - hcloud_firewall-esp-gre.yml
+ - hcloud_inventory.yml
+ - hcloud_server_backup_window.yml
+ - hcloud_volume_server_none.yml
+ release_date: '2022-11-10'
+ 1.9.1:
+ changes:
+ bugfixes:
+ - hcloud_server - externally attached networks (using hcloud_server_network)
+ were removed when not specified in the hcloud_server resource
+ fragments:
+ - hcloud_server-removed-networks.yml
+ release_date: '2022-12-20'
diff --git a/ansible_collections/hetzner/hcloud/changelogs/config.yaml b/ansible_collections/hetzner/hcloud/changelogs/config.yaml
new file mode 100644
index 000000000..739603a44
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/changelogs/config.yaml
@@ -0,0 +1,28 @@
+title: Hetzner Cloud Ansible Collection
+changelog_filename_template: ../CHANGELOG.rst
+changelog_filename_version_depth: 0
+changes_file: changelog.yaml
+changes_format: combined
+keep_fragments: false
+mention_ancestor: true
+new_plugins_after_name: removed_features
+notesdir: fragments
+prelude_section_name: release_summary
+prelude_section_title: Release Summary
+sections:
+- - major_changes
+ - Major Changes
+- - minor_changes
+ - Minor Changes
+- - breaking_changes
+ - Breaking Changes / Porting Guide
+- - deprecated_features
+ - Deprecated Features
+- - removed_features
+ - Removed Features (previously deprecated)
+- - security_fixes
+ - Security Fixes
+- - bugfixes
+ - Bugfixes
+- - known_issues
+ - Known Issues
diff --git a/ansible_collections/hetzner/hcloud/changelogs/fragments/.keep b/ansible_collections/hetzner/hcloud/changelogs/fragments/.keep
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/changelogs/fragments/.keep
diff --git a/ansible_collections/hetzner/hcloud/meta/runtime.yml b/ansible_collections/hetzner/hcloud/meta/runtime.yml
new file mode 100644
index 000000000..d37a1864a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/meta/runtime.yml
@@ -0,0 +1,35 @@
+requires_ansible: '>=2.12.0'
+plugin_routing:
+ modules:
+ hcloud_location_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_server_type_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_image_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_volume_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_floating_ip_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_ssh_key_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_datacenter_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
+ hcloud_server_facts:
+ deprecation:
+ removal_version: 2.0.0
+ warning_text: see plugin documentation for details
diff --git a/ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/__init__.py
diff --git a/ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py
new file mode 100644
index 000000000..d19e64616
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/doc_fragments/hcloud.py
@@ -0,0 +1,29 @@
+# -*- 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
+
+
+class ModuleDocFragment(object):
+ DOCUMENTATION = '''
+options:
+ api_token:
+ description:
+ - This is the API Token for the Hetzner Cloud.
+ - You can also set this option by using the environment variable HCLOUD_TOKEN
+ 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/ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py b/ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/inventory/__init__.py
diff --git a/ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py b/ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py
new file mode 100644
index 000000000..390dedc4c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/inventory/hcloud.py
@@ -0,0 +1,351 @@
+# 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
+
+DOCUMENTATION = r'''
+ name: hcloud
+ author:
+ - Lukas Kaemmerling (@lkaemmerling)
+ short_description: Ansible dynamic inventory plugin for the Hetzner Cloud.
+ requirements:
+ - python >= 3.5
+ - hcloud-python >= 1.0.0
+ description:
+ - Reads inventories from the Hetzner Cloud API.
+ - Uses a YAML configuration file that ends with hcloud.(yml|yaml).
+ extends_documentation_fragment:
+ - constructed
+ options:
+ plugin:
+ description: marks this as an instance of the "hcloud" plugin
+ required: true
+ choices: ["hcloud", "hetzner.hcloud.hcloud"]
+ token:
+ description: The Hetzner Cloud API Token.
+ required: false
+ group:
+ description: The group all servers are automatically added to.
+ default: hcloud
+ type: str
+ required: false
+ token_env:
+ description: Environment variable to load the Hetzner Cloud API Token from.
+ default: HCLOUD_TOKEN
+ type: str
+ required: false
+ connect_with:
+ description: |
+ Connect to the server using the value from this field. This sets the `ansible_host`
+ variable to the value indicated, if that value is available. If you need further
+ customization, like falling back to private ipv4 if the server has no public ipv4,
+ you can use `compose` top-level key.
+ default: public_ipv4
+ type: str
+ choices:
+ - public_ipv4
+ - public_ipv6
+ - hostname
+ - ipv4_dns_ptr
+ - private_ipv4
+ locations:
+ description: Populate inventory with instances in this location.
+ default: []
+ type: list
+ elements: str
+ required: false
+ types:
+ description: Populate inventory with instances with this type.
+ default: []
+ type: list
+ elements: str
+ required: false
+ images:
+ description: Populate inventory with instances with this image name, only available for system images.
+ default: []
+ type: list
+ elements: str
+ required: false
+ label_selector:
+ description: Populate inventory with instances with this label.
+ default: ""
+ type: str
+ required: false
+ network:
+ description: Populate inventory with instances which are attached to this network name or ID.
+ default: ""
+ type: str
+ required: false
+ status:
+ description: Populate inventory with instances with this status.
+ default: []
+ type: list
+ elements: str
+ required: false
+'''
+
+EXAMPLES = r"""
+# Minimal example. `HCLOUD_TOKEN` is exposed in environment.
+plugin: hcloud
+
+# Example with templated token, e.g. provided through extra vars.
+plugin: hcloud
+token: "{{ hetzner_apitoken }}"
+
+# Example with locations, types, status and token
+plugin: hcloud
+token: foobar
+locations:
+ - nbg1
+types:
+ - cx11
+status:
+ - running
+
+# Group by a location with prefix e.g. "hcloud_location_nbg1"
+# and image_os_flavor without prefix and separator e.g. "ubuntu"
+# and status with prefix e.g. "server_status_running"
+plugin: hcloud
+keyed_groups:
+ - key: location
+ prefix: hcloud_location
+ - key: image_os_flavor
+ separator: ""
+ - key: status
+ prefix: server_status
+"""
+
+import os
+from ansible.errors import AnsibleError
+from ansible.module_utils._text import to_native
+from ansible.plugins.inventory import BaseInventoryPlugin, Constructable
+from ansible.release import __version__
+from ipaddress import IPv6Network
+
+try:
+ from hcloud import hcloud
+ from hcloud import APIException
+ HAS_HCLOUD = True
+except ImportError:
+ HAS_HCLOUD = False
+
+
+class InventoryModule(BaseInventoryPlugin, Constructable):
+ NAME = 'hetzner.hcloud.hcloud'
+
+ def _configure_hcloud_client(self):
+ self.token_env = self.get_option("token_env")
+ self.templar.available_variables = self._vars
+ self.api_token = self.templar.template(self.get_option("token"), fail_on_undefined=False) or os.getenv(self.token_env)
+ if self.api_token is None:
+ raise AnsibleError(
+ "Please specify a token, via the option token, via environment variable HCLOUD_TOKEN "
+ "or via custom environment variable set by token_env option."
+ )
+
+ self.endpoint = os.getenv("HCLOUD_ENDPOINT") or "https://api.hetzner.cloud/v1"
+
+ self.client = hcloud.Client(token=self.api_token,
+ api_endpoint=self.endpoint,
+ application_name="ansible-inventory",
+ application_version=__version__)
+
+ def _test_hcloud_token(self):
+ try:
+ # We test the API Token against the location API, because this is the API with the smallest result
+ # and not controllable from the customer.
+ self.client.locations.get_all()
+ except APIException:
+ raise AnsibleError("Invalid Hetzner Cloud API Token.")
+
+ def _get_servers(self):
+ if len(self.get_option("label_selector")) > 0:
+ self.servers = self.client.servers.get_all(label_selector=self.get_option("label_selector"))
+ else:
+ self.servers = self.client.servers.get_all()
+
+ def _filter_servers(self):
+ if self.get_option("network"):
+ network = self.templar.template(self.get_option("network"), fail_on_undefined=False) or self.get_option("network")
+ try:
+ self.network = self.client.networks.get_by_name(network)
+ if self.network is None:
+ self.network = self.client.networks.get_by_id(network)
+ except APIException:
+ raise AnsibleError(
+ "The given network is not found.")
+
+ tmp = []
+ for server in self.servers:
+ for server_private_network in server.private_net:
+ if server_private_network.network.id == self.network.id:
+ tmp.append(server)
+ self.servers = tmp
+
+ if self.get_option("locations"):
+ tmp = []
+ for server in self.servers:
+ if server.datacenter.location.name in self.get_option("locations"):
+ tmp.append(server)
+ self.servers = tmp
+
+ if self.get_option("types"):
+ tmp = []
+ for server in self.servers:
+ if server.server_type.name in self.get_option("types"):
+ tmp.append(server)
+ self.servers = tmp
+
+ if self.get_option("images"):
+ tmp = []
+ for server in self.servers:
+ if server.image is not None and server.image.os_flavor in self.get_option("images"):
+ tmp.append(server)
+ self.servers = tmp
+
+ if self.get_option("status"):
+ tmp = []
+ for server in self.servers:
+ if server.status in self.get_option("status"):
+ tmp.append(server)
+ self.servers = tmp
+
+ def _set_server_attributes(self, server):
+ self.inventory.set_variable(server.name, "id", to_native(server.id))
+ self.inventory.set_variable(server.name, "name", to_native(server.name))
+ self.inventory.set_variable(server.name, "status", to_native(server.status))
+ self.inventory.set_variable(server.name, "type", to_native(server.server_type.name))
+ self.inventory.set_variable(server.name, "architecture", to_native(server.server_type.architecture))
+
+ # Network
+ if server.public_net.ipv4:
+ self.inventory.set_variable(server.name, "ipv4", to_native(server.public_net.ipv4.ip))
+
+ if server.public_net.ipv6:
+ self.inventory.set_variable(server.name, "ipv6_network", to_native(server.public_net.ipv6.network))
+ self.inventory.set_variable(server.name, "ipv6_network_mask", to_native(server.public_net.ipv6.network_mask))
+ self.inventory.set_variable(server.name, "ipv6", to_native(self._first_ipv6_address(server.public_net.ipv6.ip)))
+
+ self.inventory.set_variable(
+ server.name,
+ "private_networks",
+ [
+ {"name": n.network.name, "id": n.network.id, "ip": n.ip}
+ for n in server.private_net
+ ],
+ )
+
+ if self.get_option("network"):
+ for server_private_network in server.private_net:
+ # Set private_ipv4 if user filtered for one network
+ if server_private_network.network.id == self.network.id:
+ self.inventory.set_variable(server.name, "private_ipv4", to_native(server_private_network.ip))
+
+ try:
+ self.inventory.set_variable(server.name, "ansible_host", self._get_server_ansible_host(server))
+ except AnsibleError as e:
+ # Log warning that for this host can not be connected to, using the
+ # method specified in `connect_with`. Users might use `compose` to
+ # override the connection method, or implement custom logic, so we
+ # do not need to abort if nothing matched.
+ self.display.v("[hcloud] %s" % e, server.name)
+
+ # Server Type
+ if server.server_type is not None:
+ self.inventory.set_variable(server.name, "server_type", to_native(server.server_type.name))
+
+ # Datacenter
+ self.inventory.set_variable(server.name, "datacenter", to_native(server.datacenter.name))
+ self.inventory.set_variable(server.name, "location", to_native(server.datacenter.location.name))
+
+ # Image
+ if server.image is not None:
+ self.inventory.set_variable(server.name, "image_id", to_native(server.image.id))
+ self.inventory.set_variable(server.name, "image_os_flavor", to_native(server.image.os_flavor))
+ if server.image.name is not None:
+ self.inventory.set_variable(server.name, "image_name", to_native(server.image.name))
+ else:
+ self.inventory.set_variable(server.name, "image_name", to_native(server.image.description))
+ else:
+ self.inventory.set_variable(server.name, "image_id", to_native("No Image ID found"))
+ self.inventory.set_variable(server.name, "image_name", to_native("No Image Name found"))
+ self.inventory.set_variable(server.name, "image_os_flavor", to_native("No Image OS Flavor found"))
+
+ # Labels
+ self.inventory.set_variable(server.name, "labels", dict(server.labels))
+
+ def _get_server_ansible_host(self, server):
+ if self.get_option("connect_with") == "public_ipv4":
+ if server.public_net.ipv4:
+ return to_native(server.public_net.ipv4.ip)
+ else:
+ raise AnsibleError("Server has no public ipv4, but connect_with=public_ipv4 was specified")
+
+ if self.get_option("connect_with") == "public_ipv6":
+ if server.public_net.ipv6:
+ return to_native(self._first_ipv6_address(server.public_net.ipv6.ip))
+ else:
+ raise AnsibleError("Server has no public ipv6, but connect_with=public_ipv6 was specified")
+
+ elif self.get_option("connect_with") == "hostname":
+ # every server has a name, no need to guard this
+ return to_native(server.name)
+
+ elif self.get_option("connect_with") == "ipv4_dns_ptr":
+ if server.public_net.ipv4:
+ return to_native(server.public_net.ipv4.dns_ptr)
+ else:
+ raise AnsibleError("Server has no public ipv4, but connect_with=ipv4_dns_ptr was specified")
+
+ elif self.get_option("connect_with") == "private_ipv4":
+ if self.get_option("network"):
+ for server_private_network in server.private_net:
+ if server_private_network.network.id == self.network.id:
+ return to_native(server_private_network.ip)
+
+ else:
+ raise AnsibleError(
+ "You can only connect via private IPv4 if you specify a network")
+
+ def _first_ipv6_address(self, network):
+ return next(IPv6Network(network).hosts())
+
+ def verify_file(self, path):
+ """Return the possibly of a file being consumable by this plugin."""
+ return (
+ super(InventoryModule, self).verify_file(path) and
+ path.endswith(("hcloud.yaml", "hcloud.yml"))
+ )
+
+ def parse(self, inventory, loader, path, cache=True):
+ super(InventoryModule, self).parse(inventory, loader, path, cache)
+
+ if not HAS_HCLOUD:
+ raise AnsibleError("The Hetzner Cloud dynamic inventory plugin requires hcloud-python.")
+
+ self._read_config_data(path)
+ self._configure_hcloud_client()
+ self._test_hcloud_token()
+ self._get_servers()
+ self._filter_servers()
+
+ # Add a top group
+ self.inventory.add_group(group=self.get_option("group"))
+
+ for server in self.servers:
+ self.inventory.add_host(server.name, group=self.get_option("group"))
+ self._set_server_attributes(server)
+
+ # Use constructed if applicable
+ strict = self.get_option('strict')
+
+ # Composed variables
+ self._set_composite_vars(self.get_option('compose'), self.inventory.get_host(server.name).get_vars(), server.name, strict=strict)
+
+ # Complex groups based on jinja2 conditionals, hosts that meet the conditional are added to group
+ self._add_host_to_composed_groups(self.get_option('groups'), {}, server.name, strict=strict)
+
+ # Create groups based on variable values and add the corresponding hosts to it
+ self._add_host_to_keyed_groups(self.get_option('keyed_groups'), {}, server.name, strict=strict)
diff --git a/ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py b/ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/module_utils/__init__.py
diff --git a/ansible_collections/hetzner/hcloud/plugins/module_utils/hcloud.py b/ansible_collections/hetzner/hcloud/plugins/module_utils/hcloud.py
new file mode 100644
index 000000000..932b0c529
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/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/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py b/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py
new file mode 100644
index 000000000..e69de29bb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/__init__.py
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py
new file mode 100644
index 000000000..0f6dcf0f2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate.py
@@ -0,0 +1,291 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_certificate
+
+short_description: Create and manage certificates on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage certificates on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud certificate to manage.
+ - Only required if no certificate I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud certificate to manage.
+ - Only required if no certificate I(id) is given or a certificate does not exist.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs)
+ type: dict
+ certificate:
+ description:
+ - Certificate and chain in PEM format, in order so that each record directly certifies the one preceding.
+ - Required if certificate does not exist.
+ type: str
+ private_key:
+ description:
+ - Certificate key in PEM format.
+ - Required if certificate does not exist.
+ type: str
+ domain_names:
+ description:
+ - Certificate key in PEM format.
+ - Required if certificate does not exist.
+ type: list
+ default: [ ]
+ elements: str
+ type:
+ description:
+ - Choose between uploading a Certificate in PEM format or requesting a managed Let's Encrypt Certificate.
+ default: uploaded
+ choices: [ uploaded, managed ]
+ type: str
+ state:
+ description:
+ - State of the certificate.
+ default: present
+ choices: [ absent, present ]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic certificate
+ hcloud_certificate:
+ name: my-certificate
+ certificate: "ssh-rsa AAAjjk76kgf...Xt"
+ private_key: "ssh-rsa AAAjjk76kgf...Xt"
+ state: present
+
+- name: Create a certificate with labels
+ hcloud_certificate:
+ name: my-certificate
+ certificate: "ssh-rsa AAAjjk76kgf...Xt"
+ private_key: "ssh-rsa AAAjjk76kgf...Xt"
+ labels:
+ key: value
+ mylabel: 123
+ state: present
+
+- name: Ensure the certificate is absent (remove if needed)
+ hcloud_certificate:
+ name: my-certificate
+ state: absent
+"""
+
+RETURN = """
+hcloud_certificate:
+ description: The certificate instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the certificate
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the certificate
+ returned: always
+ type: str
+ sample: my website cert
+ fingerprint:
+ description: Fingerprint of the certificate
+ returned: always
+ type: str
+ sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f"
+ certificate:
+ description: Certificate and chain in PEM format
+ returned: always
+ type: str
+ sample: "-----BEGIN CERTIFICATE-----..."
+ domain_names:
+ description: List of Domains and Subdomains covered by the Certificate
+ returned: always
+ type: dict
+ not_valid_before:
+ description: Point in time when the Certificate becomes valid (in ISO-8601 format)
+ returned: always
+ type: str
+ not_valid_after:
+ description: Point in time when the Certificate stops being valid (in ISO-8601 format)
+ returned: always
+ type: str
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudCertificate(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_certificate")
+ self.hcloud_certificate = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_certificate.id),
+ "name": to_native(self.hcloud_certificate.name),
+ "type": to_native(self.hcloud_certificate.type),
+ "fingerprint": to_native(self.hcloud_certificate.fingerprint),
+ "certificate": to_native(self.hcloud_certificate.certificate),
+ "not_valid_before": to_native(self.hcloud_certificate.not_valid_before),
+ "not_valid_after": to_native(self.hcloud_certificate.not_valid_after),
+ "domain_names": [to_native(domain) for domain in self.hcloud_certificate.domain_names],
+ "labels": self.hcloud_certificate.labels
+ }
+
+ def _get_certificate(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_certificate = self.client.certificates.get_by_id(
+ self.module.params.get("id")
+ )
+ elif self.module.params.get("name") is not None:
+ self.hcloud_certificate = self.client.certificates.get_by_name(
+ self.module.params.get("name")
+ )
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_certificate(self):
+ self.module.fail_on_missing_params(
+ required_params=["name"]
+ )
+
+ params = {
+ "name": self.module.params.get("name"),
+ "labels": self.module.params.get("labels")
+ }
+ if self.module.params.get('type') == 'uploaded':
+ self.module.fail_on_missing_params(
+ required_params=["certificate", "private_key"]
+ )
+ params["certificate"] = self.module.params.get("certificate")
+ params["private_key"] = self.module.params.get("private_key")
+ if not self.module.check_mode:
+ try:
+ self.client.certificates.create(**params)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ else:
+ self.module.fail_on_missing_params(
+ required_params=["domain_names"]
+ )
+ params["domain_names"] = self.module.params.get("domain_names")
+ if not self.module.check_mode:
+ try:
+ resp = self.client.certificates.create_managed(**params)
+ resp.action.wait_until_finished(max_retries=1000)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ self._mark_as_changed()
+ self._get_certificate()
+
+ def _update_certificate(self):
+ try:
+ name = self.module.params.get("name")
+ if name is not None and self.hcloud_certificate.name != name:
+ self.module.fail_on_missing_params(
+ required_params=["id"]
+ )
+ if not self.module.check_mode:
+ self.hcloud_certificate.update(name=name)
+ self._mark_as_changed()
+
+ labels = self.module.params.get("labels")
+ if labels is not None and self.hcloud_certificate.labels != labels:
+ if not self.module.check_mode:
+ self.hcloud_certificate.update(labels=labels)
+ self._mark_as_changed()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._get_certificate()
+
+ def present_certificate(self):
+ self._get_certificate()
+ if self.hcloud_certificate is None:
+ self._create_certificate()
+ else:
+ self._update_certificate()
+
+ def delete_certificate(self):
+ self._get_certificate()
+ if self.hcloud_certificate is not None:
+ if not self.module.check_mode:
+ try:
+ self.client.certificates.delete(self.hcloud_certificate)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_certificate = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ type={
+ "choices": ["uploaded", "managed"],
+ "default": "uploaded",
+ },
+ domain_names={"type": "list", "elements": "str", "default": []},
+ certificate={"type": "str"},
+ private_key={"type": "str", "no_log": True},
+ labels={"type": "dict"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ required_if=[['state', 'present', ['name']]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudCertificate.define_module()
+
+ hcloud = AnsibleHcloudCertificate(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_certificate()
+ elif state == "present":
+ hcloud.present_certificate()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py
new file mode 100644
index 000000000..855706f1f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_certificate_info.py
@@ -0,0 +1,162 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_certificate_info
+short_description: Gather infos about your Hetzner Cloud certificates.
+description:
+ - Gather facts about your Hetzner Cloud certificates.
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+options:
+ id:
+ description:
+ - The ID of the certificate you want to get.
+ type: int
+ name:
+ description:
+ - The name of the certificate you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the certificate you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud certificate infos
+ hcloud_certificate_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_certificate_info
+"""
+
+RETURN = """
+hcloud_certificate_info:
+ description: The certificate instances
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the certificate
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the certificate
+ returned: always
+ type: str
+ sample: my website cert
+ fingerprint:
+ description: Fingerprint of the certificate
+ returned: always
+ type: str
+ sample: "03:c7:55:9b:2a:d1:04:17:09:f6:d0:7f:18:34:63:d4:3e:5f"
+ certificate:
+ description: Certificate and chain in PEM format
+ returned: always
+ type: str
+ sample: "-----BEGIN CERTIFICATE-----..."
+ domain_names:
+ description: List of Domains and Subdomains covered by the Certificate
+ returned: always
+ type: dict
+ not_valid_before:
+ description: Point in time when the Certificate becomes valid (in ISO-8601 format)
+ returned: always
+ type: str
+ not_valid_after:
+ description: Point in time when the Certificate stops being valid (in ISO-8601 format)
+ returned: always
+ type: str
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudCertificateInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_certificate_info")
+ self.hcloud_certificate_info = None
+
+ def _prepare_result(self):
+ certificates = []
+
+ for certificate in self.hcloud_certificate_info:
+ if certificate:
+ certificates.append({
+ "id": to_native(certificate.id),
+ "name": to_native(certificate.name),
+ "fingerprint": to_native(certificate.fingerprint),
+ "certificate": to_native(certificate.certificate),
+ "not_valid_before": to_native(certificate.not_valid_before),
+ "not_valid_after": to_native(certificate.not_valid_after),
+ "domain_names": [to_native(domain) for domain in certificate.domain_names],
+ "labels": certificate.labels
+ })
+ return certificates
+
+ def get_certificates(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_certificate_info = [self.client.certificates.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_certificate_info = [self.client.certificates.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_certificate_info = self.client.certificates.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_certificate_info = self.client.certificates.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudCertificateInfo.define_module()
+
+ hcloud = AnsibleHcloudCertificateInfo(module)
+ hcloud.get_certificates()
+ result = hcloud.get_result()
+
+ ansible_info = {
+ 'hcloud_certificate_info': result['hcloud_certificate_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py
new file mode 100644
index 000000000..8cebabf8c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_facts.py
@@ -0,0 +1,160 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_datacenter_info
+
+short_description: Gather info about the Hetzner Cloud datacenters.
+
+description:
+ - Gather info about your Hetzner Cloud datacenters.
+ - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
+ Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the datacenter you want to get.
+ type: int
+ name:
+ description:
+ - The name of the datacenter you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud datacenter info
+ hcloud_datacenter_info:
+ register: output
+- name: Print the gathered info
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_datacenter_info:
+ description:
+ - The datacenter info as list
+ - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
+ Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the datacenter
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the datacenter
+ returned: always
+ type: str
+ sample: fsn1-dc8
+ description:
+ description: Detail description of the datacenter
+ returned: always
+ type: str
+ sample: Falkenstein DC 8
+ location:
+ description: Name of the location where the datacenter resides in
+ returned: always
+ type: str
+ sample: fsn1
+ city:
+ description: City of the location
+ returned: always
+ type: str
+ sample: fsn1
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudDatacenterInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_datacenter_info")
+ self.hcloud_datacenter_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for datacenter in self.hcloud_datacenter_info:
+ if datacenter is not None:
+ tmp.append({
+ "id": to_native(datacenter.id),
+ "name": to_native(datacenter.name),
+ "description": to_native(datacenter.description),
+ "location": to_native(datacenter.location.name)
+ })
+
+ return tmp
+
+ def get_datacenters(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_datacenter_info = [self.client.datacenters.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_datacenter_info = [self.client.datacenters.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_datacenter_info = self.client.datacenters.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudDatacenterInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_datacenter_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+ hcloud = AnsibleHcloudDatacenterInfo(module)
+
+ hcloud.get_datacenters()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_datacenter_facts': result['hcloud_datacenter_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_datacenter_info': result['hcloud_datacenter_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py
new file mode 100644
index 000000000..8cebabf8c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_datacenter_info.py
@@ -0,0 +1,160 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_datacenter_info
+
+short_description: Gather info about the Hetzner Cloud datacenters.
+
+description:
+ - Gather info about your Hetzner Cloud datacenters.
+ - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
+ Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the datacenter you want to get.
+ type: int
+ name:
+ description:
+ - The name of the datacenter you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud datacenter info
+ hcloud_datacenter_info:
+ register: output
+- name: Print the gathered info
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_datacenter_info:
+ description:
+ - The datacenter info as list
+ - This module was called C(hcloud_datacenter_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_datacenter_facts).
+ Note that the M(hetzner.hcloud.hcloud_datacenter_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_datacenter_info)!
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the datacenter
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the datacenter
+ returned: always
+ type: str
+ sample: fsn1-dc8
+ description:
+ description: Detail description of the datacenter
+ returned: always
+ type: str
+ sample: Falkenstein DC 8
+ location:
+ description: Name of the location where the datacenter resides in
+ returned: always
+ type: str
+ sample: fsn1
+ city:
+ description: City of the location
+ returned: always
+ type: str
+ sample: fsn1
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudDatacenterInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_datacenter_info")
+ self.hcloud_datacenter_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for datacenter in self.hcloud_datacenter_info:
+ if datacenter is not None:
+ tmp.append({
+ "id": to_native(datacenter.id),
+ "name": to_native(datacenter.name),
+ "description": to_native(datacenter.description),
+ "location": to_native(datacenter.location.name)
+ })
+
+ return tmp
+
+ def get_datacenters(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_datacenter_info = [self.client.datacenters.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_datacenter_info = [self.client.datacenters.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_datacenter_info = self.client.datacenters.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudDatacenterInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_datacenter_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_datacenter_facts' module has been renamed to 'hcloud_datacenter_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+ hcloud = AnsibleHcloudDatacenterInfo(module)
+
+ hcloud.get_datacenters()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_datacenter_facts': result['hcloud_datacenter_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_datacenter_info': result['hcloud_datacenter_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py
new file mode 100644
index 000000000..34608977e
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_firewall.py
@@ -0,0 +1,359 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_firewall
+
+short_description: Create and manage firewalls on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage firewalls on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud firewall to manage.
+ - Only required if no firewall I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud firewall to manage.
+ - Only required if no firewall I(id) is given, or a firewall does not exist.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs)
+ type: dict
+ rules:
+ description:
+ - List of rules the firewall should contain.
+ type: list
+ elements: dict
+ suboptions:
+ direction:
+ description:
+ - The direction of the firewall rule.
+ type: str
+ choices: [ in, out ]
+ port:
+ description:
+ - The port of the firewall rule.
+ type: str
+ protocol:
+ description:
+ - The protocol of the firewall rule.
+ type: str
+ choices: [ icmp, tcp, udp, esp, gre ]
+ source_ips:
+ description:
+ - List of CIDRs that are allowed within this rule
+ type: list
+ elements: str
+ default: [ ]
+ destination_ips:
+ description:
+ - List of CIDRs that are allowed within this rule
+ type: list
+ elements: str
+ default: [ ]
+ description:
+ description:
+ - User defined description of this rule.
+ type: str
+ state:
+ description:
+ - State of the firewall.
+ default: present
+ choices: [ absent, present ]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+'''
+
+EXAMPLES = """
+- name: Create a basic firewall
+ hcloud_firewall:
+ name: my-firewall
+ state: present
+
+- name: Create a firewall with rules
+ hcloud_firewall:
+ name: my-firewall
+ rules:
+ - direction: in
+ protocol: icmp
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ description: allow icmp in
+ state: present
+
+- name: Create a firewall with labels
+ hcloud_firewall:
+ name: my-firewall
+ labels:
+ key: value
+ mylabel: 123
+ state: present
+
+- name: Ensure the firewall is absent (remove if needed)
+ hcloud_firewall:
+ name: my-firewall
+ state: absent
+"""
+
+RETURN = """
+hcloud_firewall:
+ description: The firewall instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the firewall
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the firewall
+ returned: always
+ type: str
+ sample: my firewall
+ rules:
+ description: List of Rules within this Firewall
+ returned: always
+ type: complex
+ contains:
+ direction:
+ description: Direction of the Firewall Rule
+ type: str
+ returned: always
+ sample: in
+ protocol:
+ description: Protocol of the Firewall Rule
+ type: str
+ returned: always
+ sample: icmp
+ port:
+ description: Port of the Firewall Rule, None/Null if protocol is icmp
+ type: str
+ returned: always
+ sample: in
+ source_ips:
+ description: Source IPs of the Firewall
+ type: list
+ elements: str
+ returned: always
+ destination_ips:
+ description: Source IPs of the Firewall
+ type: list
+ elements: str
+ returned: always
+ description:
+ description: User defined description of the Firewall Rule
+ type: str
+ returned: always
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+import time
+
+try:
+ from hcloud.firewalls.domain import FirewallRule
+ from hcloud import APIException
+except ImportError:
+ APIException = None
+ FirewallRule = None
+
+
+class AnsibleHcloudFirewall(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_firewall")
+ self.hcloud_firewall = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_firewall.id),
+ "name": to_native(self.hcloud_firewall.name),
+ "rules": [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules],
+ "labels": self.hcloud_firewall.labels
+ }
+
+ def _prepare_result_rule(self, rule):
+ return {
+ "direction": rule.direction,
+ "protocol": to_native(rule.protocol),
+ "port": to_native(rule.port) if rule.port is not None else None,
+ "source_ips": [to_native(cidr) for cidr in rule.source_ips],
+ "destination_ips": [to_native(cidr) for cidr in rule.destination_ips],
+ "description": to_native(rule.description) if rule.description is not None else None,
+ }
+
+ def _get_firewall(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_firewall = self.client.firewalls.get_by_id(
+ self.module.params.get("id")
+ )
+ elif self.module.params.get("name") is not None:
+ self.hcloud_firewall = self.client.firewalls.get_by_name(
+ self.module.params.get("name")
+ )
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_firewall(self):
+ self.module.fail_on_missing_params(
+ required_params=["name"]
+ )
+ params = {
+ "name": self.module.params.get("name"),
+ "labels": self.module.params.get("labels")
+ }
+ rules = self.module.params.get("rules")
+ if rules is not None:
+ params["rules"] = [
+ FirewallRule(
+ direction=rule["direction"],
+ protocol=rule["protocol"],
+ source_ips=rule["source_ips"] if rule["source_ips"] is not None else [],
+ destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [],
+ port=rule["port"],
+ description=rule["description"],
+ )
+ for rule in rules
+ ]
+ if not self.module.check_mode:
+ try:
+ self.client.firewalls.create(**params)
+ except Exception as e:
+ self.module.fail_json(msg=e.message, **params)
+ self._mark_as_changed()
+ self._get_firewall()
+
+ def _update_firewall(self):
+ name = self.module.params.get("name")
+ if name is not None and self.hcloud_firewall.name != name:
+ self.module.fail_on_missing_params(
+ required_params=["id"]
+ )
+ if not self.module.check_mode:
+ self.hcloud_firewall.update(name=name)
+ self._mark_as_changed()
+
+ labels = self.module.params.get("labels")
+ if labels is not None and self.hcloud_firewall.labels != labels:
+ if not self.module.check_mode:
+ self.hcloud_firewall.update(labels=labels)
+ self._mark_as_changed()
+
+ rules = self.module.params.get("rules")
+ if rules is not None and rules != [self._prepare_result_rule(rule) for rule in self.hcloud_firewall.rules]:
+ if not self.module.check_mode:
+ new_rules = [
+ FirewallRule(
+ direction=rule["direction"],
+ protocol=rule["protocol"],
+ source_ips=rule["source_ips"] if rule["source_ips"] is not None else [],
+ destination_ips=rule["destination_ips"] if rule["destination_ips"] is not None else [],
+ port=rule["port"],
+ description=rule["description"],
+ )
+ for rule in rules
+ ]
+ self.hcloud_firewall.set_rules(new_rules)
+ self._mark_as_changed()
+ self._get_firewall()
+
+ def present_firewall(self):
+ self._get_firewall()
+ if self.hcloud_firewall is None:
+ self._create_firewall()
+ else:
+ self._update_firewall()
+
+ def delete_firewall(self):
+ self._get_firewall()
+ if self.hcloud_firewall is not None:
+ if not self.module.check_mode:
+ retry_count = 0
+ while retry_count < 10:
+ try:
+ self.client.firewalls.delete(self.hcloud_firewall)
+ break
+ except APIException as e:
+ if "is still in use" in e.message:
+ retry_count = retry_count + 1
+ time.sleep(0.5 * retry_count)
+ else:
+ self.module.fail_json(msg=e.message)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_firewall = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ rules=dict(
+ type="list",
+ elements="dict",
+ options=dict(
+ direction={"type": "str", "choices": ["in", "out"]},
+ protocol={"type": "str", "choices": ["icmp", "udp", "tcp", "esp", "gre"]},
+ port={"type": "str"},
+ source_ips={"type": "list", "elements": "str", "default": []},
+ destination_ips={"type": "list", "elements": "str", "default": []},
+ description={"type": "str"},
+ ),
+ required_together=[["direction", "protocol"]],
+ ),
+ labels={"type": "dict"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ required_if=[['state', 'present', ['name']]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudFirewall.define_module()
+
+ hcloud = AnsibleHcloudFirewall(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_firewall()
+ elif state == "present":
+ hcloud.present_firewall()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py
new file mode 100644
index 000000000..1ee61ea13
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip.py
@@ -0,0 +1,355 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_floating_ip
+
+short_description: Create and manage cloud Floating IPs on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage cloud Floating IPs on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+version_added: 0.1.0
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud Floating IPs to manage.
+ - Only required if no Floating IP I(name) is given.
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud Floating IPs to manage.
+ - Only required if no Floating IP I(id) is given or a Floating IP does not exist.
+ type: str
+ description:
+ description:
+ - The Description of the Hetzner Cloud Floating IPs.
+ type: str
+ home_location:
+ description:
+ - Home Location of the Hetzner Cloud Floating IP.
+ - Required if no I(server) is given and Floating IP does not exist.
+ type: str
+ server:
+ description:
+ - Server Name the Floating IP should be assigned to.
+ - Required if no I(home_location) is given and Floating IP does not exist.
+ type: str
+ type:
+ description:
+ - Type of the Floating IP.
+ - Required if Floating IP does not exist
+ choices: [ ipv4, ipv6 ]
+ type: str
+ force:
+ description:
+ - Force the assignment or deletion of the Floating IP.
+ type: bool
+ delete_protection:
+ description:
+ - Protect the Floating IP for deletion.
+ type: bool
+ labels:
+ description:
+ - User-defined labels (key-value pairs).
+ type: dict
+ state:
+ description:
+ - State of the Floating IP.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.6.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic IPv4 Floating IP
+ hcloud_floating_ip:
+ name: my-floating-ip
+ home_location: fsn1
+ type: ipv4
+ state: present
+- name: Create a basic IPv6 Floating IP
+ hcloud_floating_ip:
+ name: my-floating-ip
+ home_location: fsn1
+ type: ipv6
+ state: present
+- name: Assign a Floating IP to a server
+ hcloud_floating_ip:
+ name: my-floating-ip
+ server: 1234
+ state: present
+- name: Assign a Floating IP to another server
+ hcloud_floating_ip:
+ name: my-floating-ip
+ server: 1234
+ force: yes
+ state: present
+- name: Floating IP should be absent
+ hcloud_floating_ip:
+ name: my-floating-ip
+ state: absent
+"""
+
+RETURN = """
+hcloud_floating_ip:
+ description: The Floating IP instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: ID of the Floating IP
+ type: int
+ returned: Always
+ sample: 12345
+ name:
+ description: Name of the Floating IP
+ type: str
+ returned: Always
+ sample: my-floating-ip
+ description:
+ description: Description of the Floating IP
+ type: str
+ returned: Always
+ sample: my-floating-ip
+ ip:
+ description: IP Address of the Floating IP
+ type: str
+ returned: Always
+ sample: 116.203.104.109
+ type:
+ description: Type of the Floating IP
+ type: str
+ returned: Always
+ sample: ipv4
+ home_location:
+ description: Name of the home location of the Floating IP
+ type: str
+ returned: Always
+ sample: fsn1
+ server:
+ description: Name of the server the Floating IP is assigned to.
+ type: str
+ returned: Always
+ sample: "my-server"
+ delete_protection:
+ description: True if Floating IP is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+ labels:
+ description: User-defined labels (key-value pairs)
+ type: dict
+ returned: Always
+ sample:
+ key: value
+ mylabel: 123
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudFloatingIP(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_floating_ip")
+ self.hcloud_floating_ip = None
+
+ def _prepare_result(self):
+ server = None
+
+ if self.hcloud_floating_ip.server is not None:
+ server = to_native(self.hcloud_floating_ip.server.name)
+ return {
+ "id": to_native(self.hcloud_floating_ip.id),
+ "name": to_native(self.hcloud_floating_ip.name),
+ "description": to_native(self.hcloud_floating_ip.description),
+ "ip": to_native(self.hcloud_floating_ip.ip),
+ "type": to_native(self.hcloud_floating_ip.type),
+ "home_location": to_native(self.hcloud_floating_ip.home_location.name),
+ "labels": self.hcloud_floating_ip.labels,
+ "server": server,
+ "delete_protection": self.hcloud_floating_ip.protection["delete"],
+ }
+
+ def _get_floating_ip(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_floating_ip = self.client.floating_ips.get_by_id(
+ self.module.params.get("id")
+ )
+ else:
+ self.hcloud_floating_ip = self.client.floating_ips.get_by_name(
+ self.module.params.get("name")
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_floating_ip(self):
+ self.module.fail_on_missing_params(
+ required_params=["type"]
+ )
+ try:
+ params = {
+ "description": self.module.params.get("description"),
+ "type": self.module.params.get("type"),
+ "name": self.module.params.get("name"),
+ }
+ if self.module.params.get("home_location") is not None:
+ params["home_location"] = self.client.locations.get_by_name(
+ self.module.params.get("home_location")
+ )
+ elif self.module.params.get("server") is not None:
+ params["server"] = self.client.servers.get_by_name(
+ self.module.params.get("server")
+ )
+ else:
+ self.module.fail_json(msg="one of the following is required: home_location, server")
+
+ if self.module.params.get("labels") is not None:
+ params["labels"] = self.module.params.get("labels")
+ if not self.module.check_mode:
+ resp = self.client.floating_ips.create(**params)
+ self.hcloud_floating_ip = resp.floating_ip
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None:
+ self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_floating_ip()
+
+ def _update_floating_ip(self):
+ try:
+ labels = self.module.params.get("labels")
+ if labels is not None and labels != self.hcloud_floating_ip.labels:
+ if not self.module.check_mode:
+ self.hcloud_floating_ip.update(labels=labels)
+ self._mark_as_changed()
+
+ description = self.module.params.get("description")
+ if description is not None and description != self.hcloud_floating_ip.description:
+ if not self.module.check_mode:
+ self.hcloud_floating_ip.update(description=description)
+ self._mark_as_changed()
+
+ server = self.module.params.get("server")
+ if server is not None and self.hcloud_floating_ip.server is not None:
+ if self.module.params.get("force") and server != self.hcloud_floating_ip.server.name:
+ if not self.module.check_mode:
+ self.hcloud_floating_ip.assign(
+ self.client.servers.get_by_name(server)
+ )
+ self._mark_as_changed()
+ elif server != self.hcloud_floating_ip.server.name:
+ self.module.warn(
+ "Floating IP is already assigned to another server %s. You need to unassign the Floating IP or use force=yes."
+ % self.hcloud_floating_ip.server.name
+ )
+ self._mark_as_changed()
+ elif server is not None and self.hcloud_floating_ip.server is None:
+ if not self.module.check_mode:
+ self.hcloud_floating_ip.assign(
+ self.client.servers.get_by_name(server)
+ )
+ self._mark_as_changed()
+ elif server is None and self.hcloud_floating_ip.server is not None:
+ if not self.module.check_mode:
+ self.hcloud_floating_ip.unassign()
+ self._mark_as_changed()
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None and delete_protection != self.hcloud_floating_ip.protection["delete"]:
+ if not self.module.check_mode:
+ self.hcloud_floating_ip.change_protection(delete=delete_protection).wait_until_finished()
+ self._mark_as_changed()
+
+ self._get_floating_ip()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def present_floating_ip(self):
+ self._get_floating_ip()
+ if self.hcloud_floating_ip is None:
+ self._create_floating_ip()
+ else:
+ self._update_floating_ip()
+
+ def delete_floating_ip(self):
+ try:
+ self._get_floating_ip()
+ if self.hcloud_floating_ip is not None:
+ if self.module.params.get("force") or self.hcloud_floating_ip.server is None:
+ if not self.module.check_mode:
+ self.client.floating_ips.delete(self.hcloud_floating_ip)
+ else:
+ self.module.warn(
+ "Floating IP is currently assigned to server %s. You need to unassign the Floating IP or use force=yes."
+ % self.hcloud_floating_ip.server.name
+ )
+ self._mark_as_changed()
+ self.hcloud_floating_ip = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ description={"type": "str"},
+ server={"type": "str"},
+ home_location={"type": "str"},
+ force={"type": "bool"},
+ type={"choices": ["ipv4", "ipv6"]},
+ labels={"type": "dict"},
+ delete_protection={"type": "bool"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ mutually_exclusive=[['home_location', 'server']],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudFloatingIP.define_module()
+
+ hcloud = AnsibleHcloudFloatingIP(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_floating_ip()
+ elif state == "present":
+ hcloud.present_floating_ip()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py
new file mode 100644
index 000000000..2ec359600
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_facts.py
@@ -0,0 +1,185 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_floating_ip_info
+
+short_description: Gather infos about the Hetzner Cloud Floating IPs.
+
+description:
+ - Gather facts about your Hetzner Cloud Floating IPs.
+ - This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts).
+ Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Floating IP you want to get.
+ type: int
+ label_selector:
+ description:
+ - The label selector for the Floating IP you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud Floating ip infos
+ hcloud_floating_ip_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_floating_ip_info:
+ description: The Floating ip infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Floating IP
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Floating IP
+ returned: Always
+ type: str
+ sample: my-floating-ip
+ version_added: "0.1.0"
+ description:
+ description: Description of the Floating IP
+ returned: always
+ type: str
+ sample: Falkenstein DC 8
+ ip:
+ description: IP address of the Floating IP
+ returned: always
+ type: str
+ sample: 131.232.99.1
+ type:
+ description: Type of the Floating IP
+ returned: always
+ type: str
+ sample: ipv4
+ server:
+ description: Name of the server where the Floating IP is assigned to.
+ returned: always
+ type: str
+ sample: my-server
+ home_location:
+ description: Location the Floating IP was created in
+ returned: always
+ type: str
+ sample: fsn1
+ delete_protection:
+ description: True if the Floating IP is protected for deletion
+ returned: always
+ type: bool
+ version_added: "0.1.0"
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudFloatingIPInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_floating_ip_info")
+ self.hcloud_floating_ip_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for floating_ip in self.hcloud_floating_ip_info:
+ if floating_ip is not None:
+ server_name = None
+ if floating_ip.server is not None:
+ server_name = floating_ip.server.name
+ tmp.append({
+ "id": to_native(floating_ip.id),
+ "name": to_native(floating_ip.name),
+ "description": to_native(floating_ip.description),
+ "ip": to_native(floating_ip.ip),
+ "type": to_native(floating_ip.type),
+ "server": to_native(server_name),
+ "home_location": to_native(floating_ip.home_location.name),
+ "labels": floating_ip.labels,
+ "delete_protection": floating_ip.protection["delete"],
+ })
+
+ return tmp
+
+ def get_floating_ips(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_floating_ip_info = self.client.floating_ips.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_floating_ip_info = self.client.floating_ips.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudFloatingIPInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_floating_ip_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudFloatingIPInfo(module)
+
+ hcloud.get_floating_ips()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_floating_ip_facts': result['hcloud_floating_ip_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_floating_ip_info': result['hcloud_floating_ip_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py
new file mode 100644
index 000000000..2ec359600
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_floating_ip_info.py
@@ -0,0 +1,185 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_floating_ip_info
+
+short_description: Gather infos about the Hetzner Cloud Floating IPs.
+
+description:
+ - Gather facts about your Hetzner Cloud Floating IPs.
+ - This module was called C(hcloud_floating_ip_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_floating_ip_facts).
+ Note that the M(hetzner.hcloud.hcloud_floating_ip_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_floating_ip_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Floating IP you want to get.
+ type: int
+ label_selector:
+ description:
+ - The label selector for the Floating IP you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud Floating ip infos
+ hcloud_floating_ip_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_floating_ip_info:
+ description: The Floating ip infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Floating IP
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Floating IP
+ returned: Always
+ type: str
+ sample: my-floating-ip
+ version_added: "0.1.0"
+ description:
+ description: Description of the Floating IP
+ returned: always
+ type: str
+ sample: Falkenstein DC 8
+ ip:
+ description: IP address of the Floating IP
+ returned: always
+ type: str
+ sample: 131.232.99.1
+ type:
+ description: Type of the Floating IP
+ returned: always
+ type: str
+ sample: ipv4
+ server:
+ description: Name of the server where the Floating IP is assigned to.
+ returned: always
+ type: str
+ sample: my-server
+ home_location:
+ description: Location the Floating IP was created in
+ returned: always
+ type: str
+ sample: fsn1
+ delete_protection:
+ description: True if the Floating IP is protected for deletion
+ returned: always
+ type: bool
+ version_added: "0.1.0"
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudFloatingIPInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_floating_ip_info")
+ self.hcloud_floating_ip_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for floating_ip in self.hcloud_floating_ip_info:
+ if floating_ip is not None:
+ server_name = None
+ if floating_ip.server is not None:
+ server_name = floating_ip.server.name
+ tmp.append({
+ "id": to_native(floating_ip.id),
+ "name": to_native(floating_ip.name),
+ "description": to_native(floating_ip.description),
+ "ip": to_native(floating_ip.ip),
+ "type": to_native(floating_ip.type),
+ "server": to_native(server_name),
+ "home_location": to_native(floating_ip.home_location.name),
+ "labels": floating_ip.labels,
+ "delete_protection": floating_ip.protection["delete"],
+ })
+
+ return tmp
+
+ def get_floating_ips(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_floating_ip_info = [self.client.floating_ips.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_floating_ip_info = self.client.floating_ips.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_floating_ip_info = self.client.floating_ips.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudFloatingIPInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_floating_ip_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_floating_ip_facts' module has been renamed to 'hcloud_floating_ip_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudFloatingIPInfo(module)
+
+ hcloud.get_floating_ips()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_floating_ip_facts': result['hcloud_floating_ip_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_floating_ip_info': result['hcloud_floating_ip_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py
new file mode 100644
index 000000000..8acd8846a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_facts.py
@@ -0,0 +1,219 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_image_info
+
+short_description: Gather infos about your Hetzner Cloud images.
+
+
+description:
+ - Gather infos about your Hetzner Cloud images.
+ - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
+ Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the image you want to get.
+ type: int
+ name:
+ description:
+ - The name of the image you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the images you want to get.
+ type: str
+ type:
+ description:
+ - The type for the images you want to get.
+ default: system
+ choices: [ system, snapshot, backup ]
+ type: str
+ architecture:
+ description:
+ - The architecture for the images you want to get.
+ type: str
+ choices: [ x86, arm ]
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud image infos
+ hcloud_image_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_image_info:
+ description: The image infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the image
+ returned: always
+ type: int
+ sample: 1937415
+ type:
+ description: Type of the image
+ returned: always
+ type: str
+ sample: system
+ status:
+ description: Status of the image
+ returned: always
+ type: str
+ sample: available
+ name:
+ description: Name of the image
+ returned: always
+ type: str
+ sample: ubuntu-18.04
+ description:
+ description: Detail description of the image
+ returned: always
+ type: str
+ sample: Ubuntu 18.04 Standard 64 bit
+ os_flavor:
+ description: OS flavor of the image
+ returned: always
+ type: str
+ sample: ubuntu
+ os_version:
+ description: OS version of the image
+ returned: always
+ type: str
+ sample: 18.04
+ architecture:
+ description: Image is compatible with this architecture
+ returned: always
+ type: str
+ sample: x86
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudImageInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_image_info")
+ self.hcloud_image_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for image in self.hcloud_image_info:
+ if image is not None:
+ tmp.append({
+ "id": to_native(image.id),
+ "status": to_native(image.status),
+ "type": to_native(image.type),
+ "name": to_native(image.name),
+ "description": to_native(image.description),
+ "os_flavor": to_native(image.os_flavor),
+ "os_version": to_native(image.os_version),
+ "architecture": to_native(image.architecture),
+ "labels": image.labels,
+ })
+ return tmp
+
+ def get_images(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_image_info = [self.client.images.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None:
+ self.hcloud_image_info = [self.client.images.get_by_name_and_architecture(
+ self.module.params.get("name"),
+ self.module.params.get("architecture")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_image_info = [self.client.images.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ params = {}
+ label_selector = self.module.params.get("label_selector")
+ if label_selector:
+ params["label_selector"] = label_selector
+
+ image_type = self.module.params.get("type")
+ if image_type:
+ params["type"] = image_type
+
+ architecture = self.module.params.get("architecture")
+ if architecture:
+ params["architecture"] = architecture
+
+ self.hcloud_image_info = self.client.images.get_all(**params)
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"},
+ architecture={"choices": ["x86", "arm"], "type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudImageInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_image_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudImageInfo(module)
+ hcloud.get_images()
+ result = hcloud.get_result()
+
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_imagen_facts': result['hcloud_image_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_image_info': result['hcloud_image_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py
new file mode 100644
index 000000000..8acd8846a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_image_info.py
@@ -0,0 +1,219 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_image_info
+
+short_description: Gather infos about your Hetzner Cloud images.
+
+
+description:
+ - Gather infos about your Hetzner Cloud images.
+ - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
+ Note that the M(hetzner.hcloud.hcloud_image_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_image_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the image you want to get.
+ type: int
+ name:
+ description:
+ - The name of the image you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the images you want to get.
+ type: str
+ type:
+ description:
+ - The type for the images you want to get.
+ default: system
+ choices: [ system, snapshot, backup ]
+ type: str
+ architecture:
+ description:
+ - The architecture for the images you want to get.
+ type: str
+ choices: [ x86, arm ]
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud image infos
+ hcloud_image_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_image_info:
+ description: The image infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the image
+ returned: always
+ type: int
+ sample: 1937415
+ type:
+ description: Type of the image
+ returned: always
+ type: str
+ sample: system
+ status:
+ description: Status of the image
+ returned: always
+ type: str
+ sample: available
+ name:
+ description: Name of the image
+ returned: always
+ type: str
+ sample: ubuntu-18.04
+ description:
+ description: Detail description of the image
+ returned: always
+ type: str
+ sample: Ubuntu 18.04 Standard 64 bit
+ os_flavor:
+ description: OS flavor of the image
+ returned: always
+ type: str
+ sample: ubuntu
+ os_version:
+ description: OS version of the image
+ returned: always
+ type: str
+ sample: 18.04
+ architecture:
+ description: Image is compatible with this architecture
+ returned: always
+ type: str
+ sample: x86
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudImageInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_image_info")
+ self.hcloud_image_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for image in self.hcloud_image_info:
+ if image is not None:
+ tmp.append({
+ "id": to_native(image.id),
+ "status": to_native(image.status),
+ "type": to_native(image.type),
+ "name": to_native(image.name),
+ "description": to_native(image.description),
+ "os_flavor": to_native(image.os_flavor),
+ "os_version": to_native(image.os_version),
+ "architecture": to_native(image.architecture),
+ "labels": image.labels,
+ })
+ return tmp
+
+ def get_images(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_image_info = [self.client.images.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None and self.module.params.get("architecture") is not None:
+ self.hcloud_image_info = [self.client.images.get_by_name_and_architecture(
+ self.module.params.get("name"),
+ self.module.params.get("architecture")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_image_info = [self.client.images.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ params = {}
+ label_selector = self.module.params.get("label_selector")
+ if label_selector:
+ params["label_selector"] = label_selector
+
+ image_type = self.module.params.get("type")
+ if image_type:
+ params["type"] = image_type
+
+ architecture = self.module.params.get("architecture")
+ if architecture:
+ params["architecture"] = architecture
+
+ self.hcloud_image_info = self.client.images.get_all(**params)
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ type={"choices": ["system", "snapshot", "backup"], "default": "system", "type": "str"},
+ architecture={"choices": ["x86", "arm"], "type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudImageInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_image_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_image_facts' module has been renamed to 'hcloud_image_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudImageInfo(module)
+ hcloud.get_images()
+ result = hcloud.get_result()
+
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_imagen_facts': result['hcloud_image_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_image_info': result['hcloud_image_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py
new file mode 100644
index 000000000..9c6c2bbaf
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer.py
@@ -0,0 +1,318 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_load_balancer
+
+short_description: Create and manage cloud Load Balancers on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage cloud Load Balancers on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+version_added: 0.1.0
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud Load Balancer to manage.
+ - Only required if no Load Balancer I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud Load Balancer to manage.
+ - Only required if no Load Balancer I(id) is given or a Load Balancer does not exist.
+ type: str
+ load_balancer_type:
+ description:
+ - The Load Balancer Type of the Hetzner Cloud Load Balancer to manage.
+ - Required if Load Balancer does not exist.
+ type: str
+ location:
+ description:
+ - Location of Load Balancer.
+ - Required if no I(network_zone) is given and Load Balancer does not exist.
+ type: str
+ network_zone:
+ description:
+ - Network Zone of Load Balancer.
+ - Required of no I(location) is given and Load Balancer does not exist.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs).
+ type: dict
+ disable_public_interface:
+ description:
+ - Disables the public interface.
+ type: bool
+ default: False
+ delete_protection:
+ description:
+ - Protect the Load Balancer for deletion.
+ type: bool
+ state:
+ description:
+ - State of the Load Balancer.
+ default: present
+ choices: [ absent, present ]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+requirements:
+ - hcloud-python >= 1.8.0
+'''
+
+EXAMPLES = """
+- name: Create a basic Load Balancer
+ hcloud_load_balancer:
+ name: my-Load Balancer
+ load_balancer_type: lb11
+ location: fsn1
+ state: present
+
+- name: Ensure the Load Balancer is absent (remove if needed)
+ hcloud_load_balancer:
+ name: my-Load Balancer
+ state: absent
+
+"""
+
+RETURN = """
+hcloud_load_balancer:
+ description: The Load Balancer instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Load Balancer
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Load Balancer
+ returned: always
+ type: str
+ sample: my-Load-Balancer
+ status:
+ description: Status of the Load Balancer
+ returned: always
+ type: str
+ sample: running
+ load_balancer_type:
+ description: Name of the Load Balancer type of the Load Balancer
+ returned: always
+ type: str
+ sample: cx11
+ ipv4_address:
+ description: Public IPv4 address of the Load Balancer
+ returned: always
+ type: str
+ sample: 116.203.104.109
+ ipv6_address:
+ description: Public IPv6 address of the Load Balancer
+ returned: always
+ type: str
+ sample: 2a01:4f8:1c1c:c140::1
+ location:
+ description: Name of the location of the Load Balancer
+ returned: always
+ type: str
+ sample: fsn1
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ delete_protection:
+ description: True if Load Balancer is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ disable_public_interface:
+ description: True if Load Balancer public interface is disabled
+ type: bool
+ returned: always
+ sample: false
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudLoadBalancer(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_load_balancer")
+ self.hcloud_load_balancer = None
+
+ def _prepare_result(self):
+ private_ipv4_address = None if len(self.hcloud_load_balancer.private_net) == 0 else to_native(
+ self.hcloud_load_balancer.private_net[0].ip)
+ return {
+ "id": to_native(self.hcloud_load_balancer.id),
+ "name": to_native(self.hcloud_load_balancer.name),
+ "ipv4_address": to_native(self.hcloud_load_balancer.public_net.ipv4.ip),
+ "ipv6_address": to_native(self.hcloud_load_balancer.public_net.ipv6.ip),
+ "private_ipv4_address": private_ipv4_address,
+ "load_balancer_type": to_native(self.hcloud_load_balancer.load_balancer_type.name),
+ "location": to_native(self.hcloud_load_balancer.location.name),
+ "labels": self.hcloud_load_balancer.labels,
+ "delete_protection": self.hcloud_load_balancer.protection["delete"],
+ "disable_public_interface": False if self.hcloud_load_balancer.public_net.enabled else True,
+ }
+
+ def _get_load_balancer(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_load_balancer = self.client.load_balancers.get_by_id(
+ self.module.params.get("id")
+ )
+ else:
+ self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
+ self.module.params.get("name")
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_load_balancer(self):
+
+ self.module.fail_on_missing_params(
+ required_params=["name", "load_balancer_type"]
+ )
+ try:
+ params = {
+ "name": self.module.params.get("name"),
+ "load_balancer_type": self.client.load_balancer_types.get_by_name(
+ self.module.params.get("load_balancer_type")
+ ),
+ "labels": self.module.params.get("labels"),
+ }
+
+ if self.module.params.get("location") is None and self.module.params.get("network_zone") is None:
+ self.module.fail_json(msg="one of the following is required: location, network_zone")
+ elif self.module.params.get("location") is not None and self.module.params.get("network_zone") 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("network_zone") is not None:
+ params["network_zone"] = self.module.params.get("network_zone")
+
+ if not self.module.check_mode:
+ resp = self.client.load_balancers.create(**params)
+ resp.action.wait_until_finished(max_retries=1000)
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None:
+ self._get_load_balancer()
+ self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_load_balancer()
+
+ def _update_load_balancer(self):
+ try:
+ labels = self.module.params.get("labels")
+ if labels is not None and labels != self.hcloud_load_balancer.labels:
+ if not self.module.check_mode:
+ self.hcloud_load_balancer.update(labels=labels)
+ self._mark_as_changed()
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None and delete_protection != self.hcloud_load_balancer.protection["delete"]:
+ if not self.module.check_mode:
+ self.hcloud_load_balancer.change_protection(delete=delete_protection).wait_until_finished()
+ self._mark_as_changed()
+ self._get_load_balancer()
+
+ disable_public_interface = self.module.params.get("disable_public_interface")
+ if disable_public_interface is not None and disable_public_interface != (not self.hcloud_load_balancer.public_net.enabled):
+ if not self.module.check_mode:
+ if disable_public_interface is True:
+ self.hcloud_load_balancer.disable_public_interface().wait_until_finished()
+ else:
+ self.hcloud_load_balancer.enable_public_interface().wait_until_finished()
+ self._mark_as_changed()
+
+ load_balancer_type = self.module.params.get("load_balancer_type")
+ if load_balancer_type is not None and self.hcloud_load_balancer.load_balancer_type.name != load_balancer_type:
+ new_load_balancer_type = self.client.load_balancer_types.get_by_name(load_balancer_type)
+ if not new_load_balancer_type:
+ self.module.fail_json(msg="unknown load balancer type")
+ if not self.module.check_mode:
+ self.hcloud_load_balancer.change_type(
+ load_balancer_type=new_load_balancer_type,
+ ).wait_until_finished(max_retries=1000)
+
+ self._mark_as_changed()
+ self._get_load_balancer()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def present_load_balancer(self):
+ self._get_load_balancer()
+ if self.hcloud_load_balancer is None:
+ self._create_load_balancer()
+ else:
+ self._update_load_balancer()
+
+ def delete_load_balancer(self):
+ try:
+ self._get_load_balancer()
+ if self.hcloud_load_balancer is not None:
+ if not self.module.check_mode:
+ self.client.load_balancers.delete(self.hcloud_load_balancer)
+ self._mark_as_changed()
+ self.hcloud_load_balancer = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ load_balancer_type={"type": "str"},
+ location={"type": "str"},
+ network_zone={"type": "str"},
+ labels={"type": "dict"},
+ delete_protection={"type": "bool"},
+ disable_public_interface={"type": "bool", "default": False},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ mutually_exclusive=[["location", "network_zone"]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLoadBalancer.define_module()
+
+ hcloud = AnsibleHcloudLoadBalancer(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_load_balancer()
+ elif state == "present":
+ hcloud.present_load_balancer()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py
new file mode 100644
index 000000000..159dad258
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_info.py
@@ -0,0 +1,398 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_load_balancer_info
+
+short_description: Gather infos about your Hetzner Cloud Load Balancers.
+
+
+description:
+ - Gather infos about your Hetzner Cloud Load Balancers..
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Load Balancers you want to get.
+ type: int
+ name:
+ description:
+ - The name of the Load Balancers you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the Load Balancers you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud load_balancer infos
+ hcloud_load_balancer_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_load_balancer_info:
+ description: The load_balancer infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Load Balancer
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Load Balancer
+ returned: always
+ type: str
+ sample: my-Load-Balancer
+ status:
+ description: Status of the Load Balancer
+ returned: always
+ type: str
+ sample: running
+ load_balancer_type:
+ description: Name of the Load Balancer type of the Load Balancer
+ returned: always
+ type: str
+ sample: cx11
+ ipv4_address:
+ description: Public IPv4 address of the Load Balancer
+ returned: always
+ type: str
+ sample: 116.203.104.109
+ ipv6_address:
+ description: Public IPv6 address of the Load Balancer
+ returned: always
+ type: str
+ sample: 2a01:4f8:1c1c:c140::1
+ location:
+ description: Name of the location of the Load Balancer
+ returned: always
+ type: str
+ sample: fsn1
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ delete_protection:
+ description: True if Load Balancer is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ disable_public_interface:
+ description: True if Load Balancer public interface is disabled
+ type: bool
+ returned: always
+ sample: false
+ targets:
+ description: The targets of the Load Balancer
+ returned: always
+ type: complex
+ contains:
+ type:
+ description: Type of the Load Balancer Target
+ type: str
+ returned: always
+ sample: server
+ load_balancer:
+ description: Name of the Load Balancer
+ type: str
+ returned: always
+ sample: my-LoadBalancer
+ server:
+ description: Name of the Server
+ type: str
+ returned: if I(type) is server
+ sample: my-server
+ label_selector:
+ description: Label Selector
+ type: str
+ returned: if I(type) is label_selector
+ sample: application=backend
+ ip:
+ description: IP of the dedicated server
+ type: str
+ returned: if I(type) is ip
+ sample: 127.0.0.1
+ use_private_ip:
+ description:
+ - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network.
+ - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network)
+ type: bool
+ sample: true
+ returned: always
+ services:
+ description: all services from this Load Balancer
+ returned: Always
+ type: complex
+ contains:
+ listen_port:
+ description: The port the service listens on, i.e. the port users can connect to.
+ returned: always
+ type: int
+ sample: 443
+ protocol:
+ description: Protocol of the service
+ returned: always
+ type: str
+ sample: http
+ destination_port:
+ description:
+ - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on.
+ returned: always
+ type: int
+ sample: 80
+ proxyprotocol:
+ description:
+ - Enable the PROXY protocol.
+ returned: always
+ type: bool
+ sample: false
+ http:
+ description: Configuration for HTTP and HTTPS services
+ returned: always
+ type: complex
+ contains:
+ cookie_name:
+ description: Name of the cookie which will be set when you enable sticky sessions
+ returned: always
+ type: str
+ sample: HCLBSTICKY
+ cookie_lifetime:
+ description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds
+ returned: always
+ type: int
+ sample: 3600
+ certificates:
+ description: List of Names or IDs of certificates
+ returned: always
+ type: list
+ elements: str
+ sticky_sessions:
+ description: Enable or disable sticky_sessions
+ returned: always
+ type: bool
+ sample: true
+ redirect_http:
+ description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https
+ returned: always
+ type: bool
+ sample: false
+ health_check:
+ description: Configuration for health checks
+ returned: always
+ type: complex
+ contains:
+ protocol:
+ description: Protocol the health checks will be performed over
+ returned: always
+ type: str
+ sample: http
+ port:
+ description: Port the health check will be performed on
+ returned: always
+ type: int
+ sample: 80
+ interval:
+ description: Interval of health checks, in seconds
+ returned: always
+ type: int
+ sample: 15
+ timeout:
+ description: Timeout of health checks, in seconds
+ returned: always
+ type: int
+ sample: 10
+ retries:
+ description: Number of retries until a target is marked as unhealthy
+ returned: always
+ type: int
+ sample: 3
+ http:
+ description: Additional Configuration of health checks with protocol http/https
+ returned: always
+ type: complex
+ contains:
+ domain:
+ description: Domain we will set within the HTTP HOST header
+ returned: always
+ type: str
+ sample: example.com
+ path:
+ description: Path we will try to access
+ returned: always
+ type: str
+ sample: /
+ response:
+ description: Response we expect, if response is not within the health check response the target is unhealthy
+ returned: always
+ type: str
+ status_codes:
+ description: List of HTTP status codes we expect to get when we perform the health check.
+ returned: always
+ type: list
+ elements: str
+ sample: ["2??","3??"]
+ tls:
+ description: Verify the TLS certificate, only available if health check protocol is https
+ returned: always
+ type: bool
+ sample: false
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudLoadBalancerInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_load_balancer_info")
+ self.hcloud_load_balancer_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for load_balancer in self.hcloud_load_balancer_info:
+ if load_balancer is not None:
+ services = [self._prepare_service_result(service) for service in load_balancer.services]
+ targets = [self._prepare_target_result(target) for target in load_balancer.targets]
+
+ private_ipv4_address = None if len(load_balancer.private_net) == 0 else to_native(
+ load_balancer.private_net[0].ip)
+ tmp.append({
+ "id": to_native(load_balancer.id),
+ "name": to_native(load_balancer.name),
+ "ipv4_address": to_native(load_balancer.public_net.ipv4.ip),
+ "ipv6_address": to_native(load_balancer.public_net.ipv6.ip),
+ "private_ipv4_address": private_ipv4_address,
+ "load_balancer_type": to_native(load_balancer.load_balancer_type.name),
+ "location": to_native(load_balancer.location.name),
+ "labels": load_balancer.labels,
+ "delete_protection": load_balancer.protection["delete"],
+ "disable_public_interface": False if load_balancer.public_net.enabled else True,
+ "targets": targets,
+ "services": services
+ })
+ return tmp
+
+ @staticmethod
+ def _prepare_service_result(service):
+ http = None
+ if service.protocol != "tcp":
+ http = {
+ "cookie_name": to_native(service.http.cookie_name),
+ "cookie_lifetime": service.http.cookie_name,
+ "redirect_http": service.http.redirect_http,
+ "sticky_sessions": service.http.sticky_sessions,
+ "certificates": [to_native(certificate.name) for certificate in
+ service.http.certificates],
+ }
+ health_check = {
+ "protocol": to_native(service.health_check.protocol),
+ "port": service.health_check.port,
+ "interval": service.health_check.interval,
+ "timeout": service.health_check.timeout,
+ "retries": service.health_check.retries,
+ }
+ if service.health_check.protocol != "tcp":
+ health_check["http"] = {
+ "domain": to_native(service.health_check.http.domain),
+ "path": to_native(service.health_check.http.path),
+ "response": to_native(service.health_check.http.response),
+ "certificates": [to_native(status_code) for status_code in
+ service.health_check.http.status_codes],
+ "tls": service.health_check.http.tls,
+ }
+ return {
+ "protocol": to_native(service.protocol),
+ "listen_port": service.listen_port,
+ "destination_port": service.destination_port,
+ "proxyprotocol": service.proxyprotocol,
+ "http": http,
+ "health_check": health_check,
+ }
+
+ @staticmethod
+ def _prepare_target_result(target):
+ result = {
+ "type": to_native(target.type),
+ "use_private_ip": target.use_private_ip
+ }
+ if target.type == "server":
+ result["server"] = to_native(target.server.name)
+ elif target.type == "label_selector":
+ result["label_selector"] = to_native(target.label_selector.selector)
+ elif target.type == "ip":
+ result["ip"] = to_native(target.ip.ip)
+ return result
+
+ def get_load_balancers(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_load_balancer_info = [self.client.load_balancers.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ params = {}
+ label_selector = self.module.params.get("label_selector")
+ if label_selector:
+ params["label_selector"] = label_selector
+
+ self.hcloud_load_balancer_info = self.client.load_balancers.get_all(**params)
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLoadBalancerInfo.define_module()
+
+ hcloud = AnsibleHcloudLoadBalancerInfo(module)
+ hcloud.get_load_balancers()
+ result = hcloud.get_result()
+
+ ansible_info = {
+ 'hcloud_load_balancer_info': result['hcloud_load_balancer_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py
new file mode 100644
index 000000000..63a7c5471
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_network.py
@@ -0,0 +1,209 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_load_balancer_network
+
+short_description: Manage the relationship between Hetzner Cloud Networks and Load Balancers
+
+
+description:
+ - Create and delete the relationship Hetzner Cloud Networks and Load Balancers
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+version_added: 0.1.0
+options:
+ network:
+ description:
+ - The name of the Hetzner Cloud Networks.
+ type: str
+ required: true
+ load_balancer:
+ description:
+ - The name of the Hetzner Cloud Load Balancer.
+ type: str
+ required: true
+ ip:
+ description:
+ - The IP the Load Balancer should have.
+ type: str
+ state:
+ description:
+ - State of the load_balancer_network.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.8.1
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic Load Balancer network
+ hcloud_load_balancer_network:
+ network: my-network
+ load_balancer: my-LoadBalancer
+ state: present
+
+- name: Create a Load Balancer network and specify the ip address
+ hcloud_load_balancer_network:
+ network: my-network
+ load_balancer: my-LoadBalancer
+ ip: 10.0.0.1
+ state: present
+
+- name: Ensure the Load Balancer network is absent (remove if needed)
+ hcloud_load_balancer_network:
+ network: my-network
+ load_balancer: my-LoadBalancer
+ state: absent
+"""
+
+RETURN = """
+hcloud_load_balancer_network:
+ description: The relationship between a Load Balancer and a network
+ returned: always
+ type: complex
+ contains:
+ network:
+ description: Name of the Network
+ type: str
+ returned: always
+ sample: my-network
+ load_balancer:
+ description: Name of the Load Balancer
+ type: str
+ returned: always
+ sample: my-LoadBalancer
+ ip:
+ description: IP of the Load Balancer within the Network ip range
+ type: str
+ returned: always
+ sample: 10.0.0.8
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudLoadBalancerNetwork(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_load_balancer_network")
+ self.hcloud_network = None
+ self.hcloud_load_balancer = None
+ self.hcloud_load_balancer_network = None
+
+ def _prepare_result(self):
+ return {
+ "network": to_native(self.hcloud_network.name),
+ "load_balancer": to_native(self.hcloud_load_balancer.name),
+ "ip": to_native(self.hcloud_load_balancer_network.ip),
+ }
+
+ def _get_load_balancer_and_network(self):
+ try:
+ network = self.module.params.get("network")
+ self.hcloud_network = self.client.networks.get_by_name(network)
+ if not self.hcloud_network:
+ self.module.fail_json(msg="Network does not exist: %s" % network)
+
+ load_balancer_name = self.module.params.get("load_balancer")
+ self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
+ load_balancer_name
+ )
+ if not self.hcloud_load_balancer:
+ self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name)
+
+ self.hcloud_load_balancer_network = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _get_load_balancer_network(self):
+ for privateNet in self.hcloud_load_balancer.private_net:
+ if privateNet.network.id == self.hcloud_network.id:
+ self.hcloud_load_balancer_network = privateNet
+
+ def _create_load_balancer_network(self):
+ params = {
+ "network": self.hcloud_network
+ }
+
+ if self.module.params.get("ip") is not None:
+ params["ip"] = self.module.params.get("ip")
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_load_balancer.attach_to_network(**params).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ self._mark_as_changed()
+ self._get_load_balancer_and_network()
+ self._get_load_balancer_network()
+
+ def present_load_balancer_network(self):
+ self._get_load_balancer_and_network()
+ self._get_load_balancer_network()
+ if self.hcloud_load_balancer_network is None:
+ self._create_load_balancer_network()
+
+ def delete_load_balancer_network(self):
+ self._get_load_balancer_and_network()
+ self._get_load_balancer_network()
+ if self.hcloud_load_balancer_network is not None and self.hcloud_load_balancer is not None:
+ if not self.module.check_mode:
+ try:
+ self.hcloud_load_balancer.detach_from_network(
+ self.hcloud_load_balancer_network.network).wait_until_finished()
+ self._mark_as_changed()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ self.hcloud_load_balancer_network = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ network={"type": "str", "required": True},
+ load_balancer={"type": "str", "required": True},
+ ip={"type": "str"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLoadBalancerNetwork.define_module()
+
+ hcloud = AnsibleHcloudLoadBalancerNetwork(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_load_balancer_network()
+ elif state == "present":
+ hcloud.present_load_balancer_network()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py
new file mode 100644
index 000000000..b5edcc6b5
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_service.py
@@ -0,0 +1,620 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_load_balancer_service
+
+short_description: Create and manage the services of cloud Load Balancers on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage the services of cloud Load Balancers on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+version_added: 0.1.0
+options:
+ load_balancer:
+ description:
+ - The Name of the Hetzner Cloud Load Balancer the service belongs to
+ type: str
+ required: true
+ listen_port:
+ description:
+ - The port the service listens on, i.e. the port users can connect to.
+ type: int
+ required: true
+ destination_port:
+ description:
+ - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on.
+ - Required if services does not exist and protocol is tcp.
+ type: int
+ protocol:
+ description:
+ - Protocol of the service.
+ - Required if Load Balancer does not exist.
+ type: str
+ choices: [ http, https, tcp ]
+ proxyprotocol:
+ description:
+ - Enable the PROXY protocol.
+ type: bool
+ default: False
+ http:
+ description:
+ - Configuration for HTTP and HTTPS services
+ type: dict
+ suboptions:
+ cookie_name:
+ description:
+ - Name of the cookie which will be set when you enable sticky sessions
+ type: str
+ cookie_lifetime:
+ description:
+ - Lifetime of the cookie which will be set when you enable sticky sessions, in seconds
+ type: int
+ certificates:
+ description:
+ - List of Names or IDs of certificates
+ type: list
+ elements: str
+ sticky_sessions:
+ description:
+ - Enable or disable sticky_sessions
+ type: bool
+ default: False
+ redirect_http:
+ description:
+ - Redirect Traffic from Port 80 to Port 443, only available if protocol is https
+ type: bool
+ default: False
+ health_check:
+ description:
+ - Configuration for health checks
+ type: dict
+ suboptions:
+ protocol:
+ description:
+ - Protocol the health checks will be performed over
+ type: str
+ choices: [ http, https, tcp ]
+ port:
+ description:
+ - Port the health check will be performed on
+ type: int
+ interval:
+ description:
+ - Interval of health checks, in seconds
+ type: int
+ timeout:
+ description:
+ - Timeout of health checks, in seconds
+ type: int
+ retries:
+ description:
+ - Number of retries until a target is marked as unhealthy
+ type: int
+ http:
+ description:
+ - Additional Configuration of health checks with protocol http/https
+ type: dict
+ suboptions:
+ domain:
+ description:
+ - Domain we will set within the HTTP HOST header
+ type: str
+ path:
+ description:
+ - Path we will try to access
+ type: str
+ response:
+ description:
+ - Response we expect, if response is not within the health check response the target is unhealthy
+ type: str
+ status_codes:
+ description:
+ - List of HTTP status codes we expect to get when we perform the health check.
+ type: list
+ elements: str
+ tls:
+ description:
+ - Verify the TLS certificate, only available if health check protocol is https
+ type: bool
+ default: False
+ state:
+ description:
+ - State of the Load Balancer.
+ default: present
+ choices: [ absent, present ]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+requirements:
+ - hcloud-python >= 1.8.1
+'''
+
+EXAMPLES = """
+- name: Create a basic Load Balancer service with Port 80
+ hcloud_load_balancer_service:
+ load_balancer: my-load-balancer
+ protocol: http
+ listen_port: 80
+ state: present
+
+- name: Ensure the Load Balancer is absent (remove if needed)
+ hcloud_load_balancer_service:
+ load_balancer: my-Load Balancer
+ protocol: http
+ listen_port: 80
+ state: absent
+"""
+
+RETURN = """
+hcloud_load_balancer_service:
+ description: The Load Balancer service instance
+ returned: Always
+ type: complex
+ contains:
+ load_balancer:
+ description: The name of the Load Balancer where the service belongs to
+ returned: always
+ type: str
+ sample: my-load-balancer
+ listen_port:
+ description: The port the service listens on, i.e. the port users can connect to.
+ returned: always
+ type: int
+ sample: 443
+ protocol:
+ description: Protocol of the service
+ returned: always
+ type: str
+ sample: http
+ destination_port:
+ description:
+ - The port traffic is forwarded to, i.e. the port the targets are listening and accepting connections on.
+ returned: always
+ type: int
+ sample: 80
+ proxyprotocol:
+ description:
+ - Enable the PROXY protocol.
+ returned: always
+ type: bool
+ sample: false
+ http:
+ description: Configuration for HTTP and HTTPS services
+ returned: always
+ type: complex
+ contains:
+ cookie_name:
+ description: Name of the cookie which will be set when you enable sticky sessions
+ returned: always
+ type: str
+ sample: HCLBSTICKY
+ cookie_lifetime:
+ description: Lifetime of the cookie which will be set when you enable sticky sessions, in seconds
+ returned: always
+ type: int
+ sample: 3600
+ certificates:
+ description: List of Names or IDs of certificates
+ returned: always
+ type: list
+ elements: str
+ sticky_sessions:
+ description: Enable or disable sticky_sessions
+ returned: always
+ type: bool
+ sample: true
+ redirect_http:
+ description: Redirect Traffic from Port 80 to Port 443, only available if protocol is https
+ returned: always
+ type: bool
+ sample: false
+ health_check:
+ description: Configuration for health checks
+ returned: always
+ type: complex
+ contains:
+ protocol:
+ description: Protocol the health checks will be performed over
+ returned: always
+ type: str
+ sample: http
+ port:
+ description: Port the health check will be performed on
+ returned: always
+ type: int
+ sample: 80
+ interval:
+ description: Interval of health checks, in seconds
+ returned: always
+ type: int
+ sample: 15
+ timeout:
+ description: Timeout of health checks, in seconds
+ returned: always
+ type: int
+ sample: 10
+ retries:
+ description: Number of retries until a target is marked as unhealthy
+ returned: always
+ type: int
+ sample: 3
+ http:
+ description: Additional Configuration of health checks with protocol http/https
+ returned: always
+ type: complex
+ contains:
+ domain:
+ description: Domain we will set within the HTTP HOST header
+ returned: always
+ type: str
+ sample: example.com
+ path:
+ description: Path we will try to access
+ returned: always
+ type: str
+ sample: /
+ response:
+ description: Response we expect, if response is not within the health check response the target is unhealthy
+ returned: always
+ type: str
+ status_codes:
+ description: List of HTTP status codes we expect to get when we perform the health check.
+ returned: always
+ type: list
+ elements: str
+ sample: ["2??","3??"]
+ tls:
+ description: Verify the TLS certificate, only available if health check protocol is https
+ returned: always
+ type: bool
+ sample: false
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+try:
+ from hcloud.load_balancers.domain import LoadBalancerService, LoadBalancerServiceHttp, \
+ LoadBalancerHealthCheck, LoadBalancerHealtCheckHttp
+ from hcloud import APIException
+except ImportError:
+ APIException = None
+
+
+class AnsibleHcloudLoadBalancerService(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_load_balancer_service")
+ self.hcloud_load_balancer = None
+ self.hcloud_load_balancer_service = None
+
+ def _prepare_result(self):
+ http = None
+ if self.hcloud_load_balancer_service.protocol != "tcp":
+ http = {
+ "cookie_name": to_native(self.hcloud_load_balancer_service.http.cookie_name),
+ "cookie_lifetime": self.hcloud_load_balancer_service.http.cookie_name,
+ "redirect_http": self.hcloud_load_balancer_service.http.redirect_http,
+ "sticky_sessions": self.hcloud_load_balancer_service.http.sticky_sessions,
+ "certificates": [to_native(certificate.name) for certificate in
+ self.hcloud_load_balancer_service.http.certificates],
+ }
+ health_check = {
+ "protocol": to_native(self.hcloud_load_balancer_service.health_check.protocol),
+ "port": self.hcloud_load_balancer_service.health_check.port,
+ "interval": self.hcloud_load_balancer_service.health_check.interval,
+ "timeout": self.hcloud_load_balancer_service.health_check.timeout,
+ "retries": self.hcloud_load_balancer_service.health_check.retries,
+ }
+ if self.hcloud_load_balancer_service.health_check.protocol != "tcp":
+ health_check["http"] = {
+ "domain": to_native(self.hcloud_load_balancer_service.health_check.http.domain),
+ "path": to_native(self.hcloud_load_balancer_service.health_check.http.path),
+ "response": to_native(self.hcloud_load_balancer_service.health_check.http.response),
+ "certificates": [to_native(status_code) for status_code in
+ self.hcloud_load_balancer_service.health_check.http.status_codes],
+ "tls": self.hcloud_load_balancer_service.health_check.http.tls,
+ }
+ return {
+ "load_balancer": to_native(self.hcloud_load_balancer.name),
+ "protocol": to_native(self.hcloud_load_balancer_service.protocol),
+ "listen_port": self.hcloud_load_balancer_service.listen_port,
+ "destination_port": self.hcloud_load_balancer_service.destination_port,
+ "proxyprotocol": self.hcloud_load_balancer_service.proxyprotocol,
+ "http": http,
+ "health_check": health_check,
+ }
+
+ def _get_load_balancer(self):
+ try:
+ load_balancer_name = self.module.params.get("load_balancer")
+ self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
+ load_balancer_name
+ )
+ if not self.hcloud_load_balancer:
+ self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name)
+
+ self._get_load_balancer_service()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_load_balancer_service(self):
+
+ self.module.fail_on_missing_params(
+ required_params=["protocol"]
+ )
+ if self.module.params.get("protocol") == "tcp":
+ self.module.fail_on_missing_params(
+ required_params=["destination_port"]
+ )
+
+ params = {
+ "protocol": self.module.params.get("protocol"),
+ "listen_port": self.module.params.get("listen_port"),
+ "proxyprotocol": self.module.params.get("proxyprotocol")
+ }
+
+ if self.module.params.get("destination_port"):
+ params["destination_port"] = self.module.params.get("destination_port")
+
+ if self.module.params.get("http"):
+ params["http"] = self.__get_service_http(http_arg=self.module.params.get("http"))
+
+ if self.module.params.get("health_check"):
+ params["health_check"] = self.__get_service_health_checks(
+ health_check=self.module.params.get("health_check"))
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_load_balancer.add_service(LoadBalancerService(**params)).wait_until_finished(
+ max_retries=1000)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_load_balancer()
+ self._get_load_balancer_service()
+
+ def __get_service_http(self, http_arg):
+ service_http = LoadBalancerServiceHttp(certificates=[])
+ if http_arg.get("cookie_name") is not None:
+ service_http.cookie_name = http_arg.get("cookie_name")
+ if http_arg.get("cookie_lifetime") is not None:
+ service_http.cookie_lifetime = http_arg.get("cookie_lifetime")
+ if http_arg.get("sticky_sessions") is not None:
+ service_http.sticky_sessions = http_arg.get("sticky_sessions")
+ if http_arg.get("redirect_http") is not None:
+ service_http.redirect_http = http_arg.get("redirect_http")
+ if http_arg.get("certificates") is not None:
+ certificates = http_arg.get("certificates")
+ if certificates is not None:
+ for certificate in certificates:
+ hcloud_cert = None
+ try:
+ try:
+ hcloud_cert = self.client.certificates.get_by_name(
+ certificate
+ )
+ except Exception:
+ hcloud_cert = self.client.certificates.get_by_id(
+ certificate
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ service_http.certificates.append(hcloud_cert)
+
+ return service_http
+
+ def __get_service_health_checks(self, health_check):
+ service_health_check = LoadBalancerHealthCheck()
+ if health_check.get("protocol") is not None:
+ service_health_check.protocol = health_check.get("protocol")
+ if health_check.get("port") is not None:
+ service_health_check.port = health_check.get("port")
+ if health_check.get("interval") is not None:
+ service_health_check.interval = health_check.get("interval")
+ if health_check.get("timeout") is not None:
+ service_health_check.timeout = health_check.get("timeout")
+ if health_check.get("retries") is not None:
+ service_health_check.retries = health_check.get("retries")
+ if health_check.get("http") is not None:
+ health_check_http = health_check.get("http")
+ service_health_check.http = LoadBalancerHealtCheckHttp()
+ if health_check_http.get("domain") is not None:
+ service_health_check.http.domain = health_check_http.get("domain")
+ if health_check_http.get("path") is not None:
+ service_health_check.http.path = health_check_http.get("path")
+ if health_check_http.get("response") is not None:
+ service_health_check.http.response = health_check_http.get("response")
+ if health_check_http.get("status_codes") is not None:
+ service_health_check.http.status_codes = health_check_http.get("status_codes")
+ if health_check_http.get("tls") is not None:
+ service_health_check.http.tls = health_check_http.get("tls")
+
+ return service_health_check
+
+ def _update_load_balancer_service(self):
+ changed = False
+ try:
+ params = {
+ "listen_port": self.module.params.get("listen_port"),
+ }
+
+ if self.module.params.get("destination_port") is not None:
+ if self.hcloud_load_balancer_service.destination_port != self.module.params.get("destination_port"):
+ params["destination_port"] = self.module.params.get("destination_port")
+ changed = True
+
+ if self.module.params.get("protocol") is not None:
+ if self.hcloud_load_balancer_service.protocol != self.module.params.get("protocol"):
+ params["protocol"] = self.module.params.get("protocol")
+ changed = True
+
+ if self.module.params.get("proxyprotocol") is not None:
+ if self.hcloud_load_balancer_service.proxyprotocol != self.module.params.get("proxyprotocol"):
+ params["proxyprotocol"] = self.module.params.get("proxyprotocol")
+ changed = True
+
+ if self.module.params.get("http") is not None:
+ params["http"] = self.__get_service_http(http_arg=self.module.params.get("http"))
+ changed = True
+
+ if self.module.params.get("health_check") is not None:
+ params["health_check"] = self.__get_service_health_checks(
+ health_check=self.module.params.get("health_check"))
+ changed = True
+
+ if not self.module.check_mode:
+ self.hcloud_load_balancer.update_service(LoadBalancerService(**params)).wait_until_finished(
+ max_retries=1000)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._get_load_balancer()
+
+ if changed:
+ self._mark_as_changed()
+
+ def _get_load_balancer_service(self):
+ for service in self.hcloud_load_balancer.services:
+ if self.module.params.get("listen_port") == service.listen_port:
+ self.hcloud_load_balancer_service = service
+
+ def present_load_balancer_service(self):
+ self._get_load_balancer()
+ if self.hcloud_load_balancer_service is None:
+ self._create_load_balancer_service()
+ else:
+ self._update_load_balancer_service()
+
+ def delete_load_balancer_service(self):
+ try:
+ self._get_load_balancer()
+ if self.hcloud_load_balancer_service is not None:
+ if not self.module.check_mode:
+ try:
+ self.hcloud_load_balancer.delete_service(self.hcloud_load_balancer_service).wait_until_finished(
+ max_retries=1000)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_load_balancer_service = None
+ except APIException as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ load_balancer={"type": "str", "required": True},
+ listen_port={"type": "int", "required": True},
+ destination_port={"type": "int"},
+ protocol={
+ "type": "str",
+ "choices": ["http", "https", "tcp"],
+ },
+ proxyprotocol={"type": "bool", "default": False},
+ http={
+ "type": "dict",
+ "options": dict(
+ cookie_name={
+ "type": "str"
+ },
+ cookie_lifetime={
+ "type": "int"
+ },
+ sticky_sessions={
+ "type": "bool",
+ "default": False
+ },
+ redirect_http={
+ "type": "bool",
+ "default": False
+ },
+ certificates={
+ "type": "list",
+ "elements": "str"
+ },
+
+ )
+ },
+ health_check={
+ "type": "dict",
+ "options": dict(
+ protocol={
+ "type": "str",
+ "choices": ["http", "https", "tcp"],
+ },
+ port={
+ "type": "int"
+ },
+ interval={
+ "type": "int"
+ },
+ timeout={
+ "type": "int"
+ },
+ retries={
+ "type": "int"
+ },
+ http={
+ "type": "dict",
+ "options": dict(
+ domain={
+ "type": "str"
+ },
+ path={
+ "type": "str"
+ },
+ response={
+ "type": "str"
+ },
+ status_codes={
+ "type": "list",
+ "elements": "str"
+ },
+ tls={
+ "type": "bool",
+ "default": False
+ },
+ )
+ }
+ )
+
+ },
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLoadBalancerService.define_module()
+
+ hcloud = AnsibleHcloudLoadBalancerService(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_load_balancer_service()
+ elif state == "present":
+ hcloud.present_load_balancer_service()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py
new file mode 100644
index 000000000..760884466
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_target.py
@@ -0,0 +1,321 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_load_balancer_target
+
+short_description: Manage Hetzner Cloud Load Balancer targets
+
+
+description:
+ - Create and delete Hetzner Cloud Load Balancer targets
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+version_added: 0.1.0
+options:
+ type:
+ description:
+ - The type of the target.
+ type: str
+ choices: [ server, label_selector, ip ]
+ required: true
+ load_balancer:
+ description:
+ - The name of the Hetzner Cloud Load Balancer.
+ type: str
+ required: true
+ server:
+ description:
+ - The name of the Hetzner Cloud Server.
+ - Required if I(type) is server
+ type: str
+ label_selector:
+ description:
+ - A Label Selector that will be used to determine the targets dynamically
+ - Required if I(type) is label_selector
+ type: str
+ ip:
+ description:
+ - An IP from a Hetzner Dedicated Server, needs to belongs to the same user as the project.
+ - Required if I(type) is ip
+ type: str
+ use_private_ip:
+ description:
+ - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network.
+ - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network)
+ type: bool
+ default: False
+ state:
+ description:
+ - State of the load_balancer_network.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.8.1
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a server Load Balancer target
+ hetzner.hcloud.hcloud_load_balancer_target:
+ type: server
+ load_balancer: my-LoadBalancer
+ server: my-server
+ state: present
+
+- name: Create a label_selector Load Balancer target
+ hetzner.hcloud.hcloud_load_balancer_target:
+ type: label_selector
+ load_balancer: my-LoadBalancer
+ label_selector: application=backend
+ state: present
+
+- name: Create an IP Load Balancer target
+ hetzner.hcloud.hcloud_load_balancer_target:
+ type: ip
+ load_balancer: my-LoadBalancer
+ ip: 127.0.0.1
+ state: present
+
+- name: Ensure the Load Balancer target is absent (remove if needed)
+ hetzner.hcloud.hcloud_load_balancer_target:
+ type: server
+ load_balancer: my-LoadBalancer
+ server: my-server
+ state: absent
+"""
+
+RETURN = """
+hcloud_load_balancer_target:
+ description: The relationship between a Load Balancer and a network
+ returned: always
+ type: complex
+ contains:
+ type:
+ description: Type of the Load Balancer Target
+ type: str
+ returned: always
+ sample: server
+ load_balancer:
+ description: Name of the Load Balancer
+ type: str
+ returned: always
+ sample: my-LoadBalancer
+ server:
+ description: Name of the Server
+ type: str
+ returned: if I(type) is server
+ sample: my-server
+ label_selector:
+ description: Label Selector
+ type: str
+ returned: if I(type) is label_selector
+ sample: application=backend
+ ip:
+ description: IP of the dedicated server
+ type: str
+ returned: if I(type) is ip
+ sample: 127.0.0.1
+ use_private_ip:
+ description:
+ - Route the traffic over the private IP of the Load Balancer through a Hetzner Cloud Network.
+ - Load Balancer needs to be attached to a network. See M(hetzner.hcloud.hcloud.hcloud_load_balancer_network)
+ type: bool
+ sample: true
+ returned: always
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+try:
+ from hcloud.load_balancers.domain import LoadBalancerTarget, LoadBalancerTargetLabelSelector, LoadBalancerTargetIP
+except ImportError:
+ LoadBalancerTarget = None
+ LoadBalancerTargetLabelSelector = None
+ LoadBalancerTargetIP = None
+
+
+class AnsibleHcloudLoadBalancerTarget(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_load_balancer_target")
+ self.hcloud_load_balancer = None
+ self.hcloud_load_balancer_target = None
+ self.hcloud_server = None
+
+ def _prepare_result(self):
+ result = {
+ "type": to_native(self.hcloud_load_balancer_target.type),
+ "load_balancer": to_native(self.hcloud_load_balancer.name),
+ "use_private_ip": self.hcloud_load_balancer_target.use_private_ip
+ }
+
+ if self.hcloud_load_balancer_target.type == "server":
+ result["server"] = to_native(self.hcloud_load_balancer_target.server.name)
+ elif self.hcloud_load_balancer_target.type == "label_selector":
+ result["label_selector"] = to_native(self.hcloud_load_balancer_target.label_selector.selector)
+ elif self.hcloud_load_balancer_target.type == "ip":
+ result["ip"] = to_native(self.hcloud_load_balancer_target.ip.ip)
+ return result
+
+ def _get_load_balancer_and_target(self):
+ try:
+ load_balancer_name = self.module.params.get("load_balancer")
+ self.hcloud_load_balancer = self.client.load_balancers.get_by_name(
+ load_balancer_name
+ )
+ if not self.hcloud_load_balancer:
+ self.module.fail_json(msg="Load balancer does not exist: %s" % load_balancer_name)
+
+ if self.module.params.get("type") == "server":
+ server_name = self.module.params.get("server")
+ self.hcloud_server = self.client.servers.get_by_name(server_name)
+ if not self.hcloud_server:
+ self.module.fail_json(msg="Server not found: %s" % server_name)
+
+ self.hcloud_load_balancer_target = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _get_load_balancer_target(self):
+ for target in self.hcloud_load_balancer.targets:
+ if self.module.params.get("type") == "server" and target.type == "server":
+ if target.server.id == self.hcloud_server.id:
+ self.hcloud_load_balancer_target = target
+ elif self.module.params.get("type") == "label_selector" and target.type == "label_selector":
+ if target.label_selector.selector == self.module.params.get("label_selector"):
+ self.hcloud_load_balancer_target = target
+ elif self.module.params.get("type") == "ip" and target.type == "ip":
+ if target.ip.ip == self.module.params.get("ip"):
+ self.hcloud_load_balancer_target = target
+
+ def _create_load_balancer_target(self):
+ params = {
+ "target": None
+ }
+
+ if self.module.params.get("type") == "server":
+ self.module.fail_on_missing_params(
+ required_params=["server"]
+ )
+ params["target"] = LoadBalancerTarget(type=self.module.params.get("type"), server=self.hcloud_server,
+ use_private_ip=self.module.params.get("use_private_ip"))
+ elif self.module.params.get("type") == "label_selector":
+ self.module.fail_on_missing_params(
+ required_params=["label_selector"]
+ )
+ params["target"] = LoadBalancerTarget(type=self.module.params.get("type"),
+ label_selector=LoadBalancerTargetLabelSelector(
+ selector=self.module.params.get("label_selector")),
+ use_private_ip=self.module.params.get("use_private_ip"))
+ elif self.module.params.get("type") == "ip":
+ self.module.fail_on_missing_params(
+ required_params=["ip"]
+ )
+ params["target"] = LoadBalancerTarget(type=self.module.params.get("type"),
+ ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")),
+ use_private_ip=False)
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_load_balancer.add_target(**params).wait_until_finished()
+ except Exception as e:
+ if e.code == "locked" or e.code == "conflict":
+ self._create_load_balancer_target()
+ else:
+ self.module.fail_json(msg=e.message)
+
+ self._mark_as_changed()
+ self._get_load_balancer_and_target()
+ self._get_load_balancer_target()
+
+ def present_load_balancer_target(self):
+ self._get_load_balancer_and_target()
+ self._get_load_balancer_target()
+ if self.hcloud_load_balancer_target is None:
+ self._create_load_balancer_target()
+
+ def delete_load_balancer_target(self):
+ self._get_load_balancer_and_target()
+ self._get_load_balancer_target()
+ if self.hcloud_load_balancer_target is not None and self.hcloud_load_balancer is not None:
+ if not self.module.check_mode:
+ target = None
+ if self.module.params.get("type") == "server":
+ self.module.fail_on_missing_params(
+ required_params=["server"]
+ )
+ target = LoadBalancerTarget(type=self.module.params.get("type"),
+ server=self.hcloud_server)
+ elif self.module.params.get("type") == "label_selector":
+ self.module.fail_on_missing_params(
+ required_params=["label_selector"]
+ )
+ target = LoadBalancerTarget(type=self.module.params.get("type"),
+ label_selector=LoadBalancerTargetLabelSelector(
+ selector=self.module.params.get("label_selector")),
+ use_private_ip=self.module.params.get("use_private_ip"))
+ elif self.module.params.get("type") == "ip":
+ self.module.fail_on_missing_params(
+ required_params=["ip"]
+ )
+ target = LoadBalancerTarget(type=self.module.params.get("type"),
+ ip=LoadBalancerTargetIP(ip=self.module.params.get("ip")),
+ use_private_ip=False)
+ try:
+ self.hcloud_load_balancer.remove_target(target).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_load_balancer_target = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ type={"type": "str", "required": True, "choices": ["server", "label_selector", "ip"]},
+ load_balancer={"type": "str", "required": True},
+ server={"type": "str"},
+ label_selector={"type": "str"},
+ ip={"type": "str"},
+ use_private_ip={"type": "bool", "default": False},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLoadBalancerTarget.define_module()
+
+ hcloud = AnsibleHcloudLoadBalancerTarget(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_load_balancer_target()
+ elif state == "present":
+ hcloud.present_load_balancer_target()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py
new file mode 100644
index 000000000..a481ea9c9
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_load_balancer_type_info.py
@@ -0,0 +1,158 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_load_balancer_type_info
+
+short_description: Gather infos about the Hetzner Cloud Load Balancer types.
+
+
+description:
+ - Gather infos about your Hetzner Cloud Load Balancer types.
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+version_added: 0.1.0
+options:
+ id:
+ description:
+ - The ID of the Load Balancer type you want to get.
+ type: int
+ name:
+ description:
+ - The name of the Load Balancer type you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud Load Balancer type infos
+ hcloud_load_balancer_type_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_load_balancer_type_info
+"""
+
+RETURN = """
+hcloud_load_balancer_type_info:
+ description: The Load Balancer type infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Load Balancer type
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Load Balancer type
+ returned: always
+ type: str
+ sample: lb11
+ description:
+ description: Description of the Load Balancer type
+ returned: always
+ type: str
+ sample: LB11
+ max_connections:
+ description: Number of maximum simultaneous open connections
+ returned: always
+ type: int
+ sample: 1
+ max_services:
+ description: Number of services a Load Balancer of this type can have
+ returned: always
+ type: int
+ sample: 1
+ max_targets:
+ description: Number of targets a single Load Balancer can have
+ returned: always
+ type: int
+ sample: 25
+ max_assigned_certificates:
+ description: Number of SSL Certificates that can be assigned to a single Load Balancer
+ returned: always
+ type: int
+ sample: 5
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudLoadBalancerTypeInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_load_balancer_type_info")
+ self.hcloud_load_balancer_type_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for load_balancer_type in self.hcloud_load_balancer_type_info:
+ if load_balancer_type is not None:
+ tmp.append({
+ "id": to_native(load_balancer_type.id),
+ "name": to_native(load_balancer_type.name),
+ "description": to_native(load_balancer_type.description),
+ "max_connections": load_balancer_type.max_connections,
+ "max_services": load_balancer_type.max_services,
+ "max_targets": load_balancer_type.max_targets,
+ "max_assigned_certificates": load_balancer_type.max_assigned_certificates
+ })
+ return tmp
+
+ def get_load_balancer_types(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_load_balancer_type_info = [self.client.load_balancer_types.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_load_balancer_type_info = self.client.load_balancer_types.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLoadBalancerTypeInfo.define_module()
+
+ hcloud = AnsibleHcloudLoadBalancerTypeInfo(module)
+ hcloud.get_load_balancer_types()
+ result = hcloud.get_result()
+ ansible_info = {
+ 'hcloud_load_balancer_type_info': result['hcloud_load_balancer_type_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py
new file mode 100644
index 000000000..623c6ab68
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_facts.py
@@ -0,0 +1,159 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_location_info
+
+short_description: Gather infos about your Hetzner Cloud locations.
+
+
+description:
+ - Gather infos about your Hetzner Cloud locations.
+ - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
+ Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the location you want to get.
+ type: int
+ name:
+ description:
+ - The name of the location you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud location infos
+ hcloud_location_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_location_info:
+ description: The location infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the location
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the location
+ returned: always
+ type: str
+ sample: fsn1
+ description:
+ description: Detail description of the location
+ returned: always
+ type: str
+ sample: Falkenstein DC Park 1
+ country:
+ description: Country code of the location
+ returned: always
+ type: str
+ sample: DE
+ city:
+ description: City of the location
+ returned: always
+ type: str
+ sample: Falkenstein
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudLocationInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_location_info")
+ self.hcloud_location_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for location in self.hcloud_location_info:
+ if location is not None:
+ tmp.append({
+ "id": to_native(location.id),
+ "name": to_native(location.name),
+ "description": to_native(location.description),
+ "city": to_native(location.city),
+ "country": to_native(location.country)
+ })
+ return tmp
+
+ def get_locations(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_location_info = [self.client.locations.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_location_info = [self.client.locations.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_location_info = self.client.locations.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLocationInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_location_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudLocationInfo(module)
+ hcloud.get_locations()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_location_facts': result['hcloud_location_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_location_info': result['hcloud_location_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py
new file mode 100644
index 000000000..623c6ab68
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_location_info.py
@@ -0,0 +1,159 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_location_info
+
+short_description: Gather infos about your Hetzner Cloud locations.
+
+
+description:
+ - Gather infos about your Hetzner Cloud locations.
+ - This module was called C(hcloud_location_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_location_facts).
+ Note that the M(hetzner.hcloud.hcloud_location_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_location_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the location you want to get.
+ type: int
+ name:
+ description:
+ - The name of the location you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud location infos
+ hcloud_location_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output
+"""
+
+RETURN = """
+hcloud_location_info:
+ description: The location infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the location
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the location
+ returned: always
+ type: str
+ sample: fsn1
+ description:
+ description: Detail description of the location
+ returned: always
+ type: str
+ sample: Falkenstein DC Park 1
+ country:
+ description: Country code of the location
+ returned: always
+ type: str
+ sample: DE
+ city:
+ description: City of the location
+ returned: always
+ type: str
+ sample: Falkenstein
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudLocationInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_location_info")
+ self.hcloud_location_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for location in self.hcloud_location_info:
+ if location is not None:
+ tmp.append({
+ "id": to_native(location.id),
+ "name": to_native(location.name),
+ "description": to_native(location.description),
+ "city": to_native(location.city),
+ "country": to_native(location.country)
+ })
+ return tmp
+
+ def get_locations(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_location_info = [self.client.locations.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_location_info = [self.client.locations.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_location_info = self.client.locations.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudLocationInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_location_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_location_info' module has been renamed to 'hcloud_location_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudLocationInfo(module)
+ hcloud.get_locations()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_location_facts': result['hcloud_location_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_location_info': result['hcloud_location_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py
new file mode 100644
index 000000000..9c005d29f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network.py
@@ -0,0 +1,243 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_network
+
+short_description: Create and manage cloud Networks on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage cloud Networks on the Hetzner Cloud.
+ - You need at least hcloud-python 1.3.0.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud Networks to manage.
+ - Only required if no Network I(name) is given.
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud Network to manage.
+ - Only required if no Network I(id) is given or a Network does not exist.
+ type: str
+ ip_range:
+ description:
+ - IP range of the Network.
+ - Required if Network does not exist.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs).
+ type: dict
+ delete_protection:
+ description:
+ - Protect the Network for deletion.
+ type: bool
+ state:
+ description:
+ - State of the Network.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.3.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic network
+ hcloud_network:
+ name: my-network
+ ip_range: 10.0.0.0/8
+ state: present
+
+- name: Ensure the Network is absent (remove if needed)
+ hcloud_network:
+ name: my-network
+ state: absent
+"""
+
+RETURN = """
+hcloud_network:
+ description: The Network
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: ID of the Network
+ type: int
+ returned: always
+ sample: 12345
+ name:
+ description: Name of the Network
+ type: str
+ returned: always
+ sample: my-volume
+ ip_range:
+ description: IP range of the Network
+ type: str
+ returned: always
+ sample: 10.0.0.0/8
+ delete_protection:
+ description: True if Network is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+ labels:
+ description: User-defined labels (key-value pairs)
+ type: dict
+ returned: always
+ sample:
+ key: value
+ mylabel: 123
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudNetwork(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_network")
+ self.hcloud_network = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_network.id),
+ "name": to_native(self.hcloud_network.name),
+ "ip_range": to_native(self.hcloud_network.ip_range),
+ "delete_protection": self.hcloud_network.protection["delete"],
+ "labels": self.hcloud_network.labels,
+ }
+
+ def _get_network(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_network = self.client.networks.get_by_id(
+ self.module.params.get("id")
+ )
+ else:
+ self.hcloud_network = self.client.networks.get_by_name(
+ self.module.params.get("name")
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_network(self):
+
+ self.module.fail_on_missing_params(
+ required_params=["name", "ip_range"]
+ )
+ params = {
+ "name": self.module.params.get("name"),
+ "ip_range": self.module.params.get("ip_range"),
+ "labels": self.module.params.get("labels"),
+ }
+ try:
+ if not self.module.check_mode:
+ self.client.networks.create(**params)
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None:
+ self._get_network()
+ self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_network()
+
+ def _update_network(self):
+ try:
+ labels = self.module.params.get("labels")
+ if labels is not None and labels != self.hcloud_network.labels:
+ if not self.module.check_mode:
+ self.hcloud_network.update(labels=labels)
+ self._mark_as_changed()
+
+ ip_range = self.module.params.get("ip_range")
+ if ip_range is not None and ip_range != self.hcloud_network.ip_range:
+ if not self.module.check_mode:
+ self.hcloud_network.change_ip_range(ip_range=ip_range).wait_until_finished()
+ self._mark_as_changed()
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None and delete_protection != self.hcloud_network.protection["delete"]:
+ if not self.module.check_mode:
+ self.hcloud_network.change_protection(delete=delete_protection).wait_until_finished()
+ self._mark_as_changed()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._get_network()
+
+ def present_network(self):
+ self._get_network()
+ if self.hcloud_network is None:
+ self._create_network()
+ else:
+ self._update_network()
+
+ def delete_network(self):
+ try:
+ self._get_network()
+ if self.hcloud_network is not None:
+ if not self.module.check_mode:
+ self.client.networks.delete(self.hcloud_network)
+ self._mark_as_changed()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self.hcloud_network = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ ip_range={"type": "str"},
+ labels={"type": "dict"},
+ delete_protection={"type": "bool"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudNetwork.define_module()
+
+ hcloud = AnsibleHcloudNetwork(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_network()
+ elif state == "present":
+ hcloud.present_network()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py
new file mode 100644
index 000000000..382e447aa
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_network_info.py
@@ -0,0 +1,293 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_network_info
+
+short_description: Gather info about your Hetzner Cloud networks.
+
+
+description:
+ - Gather info about your Hetzner Cloud networks.
+
+author:
+ - Christopher Schmitt (@cschmitt-hcloud)
+
+options:
+ id:
+ description:
+ - The ID of the network you want to get.
+ type: int
+ name:
+ description:
+ - The name of the network you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the network you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud network info
+ local_action:
+ module: hcloud_network_info
+
+- name: Print the gathered info
+ debug:
+ var: hcloud_network_info
+"""
+
+RETURN = """
+hcloud_network_info:
+ description: The network info as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the network
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the network
+ returned: always
+ type: str
+ sample: awesome-network
+ ip_range:
+ description: IP range of the network
+ returned: always
+ type: str
+ sample: 10.0.0.0/16
+ subnetworks:
+ description: Subnetworks belonging to the network
+ returned: always
+ type: complex
+ contains:
+ type:
+ description: Type of the subnetwork.
+ returned: always
+ type: str
+ sample: cloud
+ network_zone:
+ description: Network of the subnetwork.
+ returned: always
+ type: str
+ sample: eu-central
+ ip_range:
+ description: IP range of the subnetwork
+ returned: always
+ type: str
+ sample: 10.0.0.0/24
+ gateway:
+ description: Gateway of this subnetwork
+ returned: always
+ type: str
+ sample: 10.0.0.1
+ routes:
+ description: Routes belonging to the network
+ returned: always
+ type: complex
+ contains:
+ ip_range:
+ description: Destination network or host of this route.
+ returned: always
+ type: str
+ sample: 10.0.0.0/16
+ gateway:
+ description: Gateway of this route
+ returned: always
+ type: str
+ sample: 10.0.0.1
+ servers:
+ description: Servers attached to the network
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the server
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the server
+ returned: always
+ type: str
+ sample: my-server
+ status:
+ description: Status of the server
+ returned: always
+ type: str
+ sample: running
+ server_type:
+ description: Name of the server type of the server
+ returned: always
+ type: str
+ sample: cx11
+ ipv4_address:
+ description: Public IPv4 address of the server, None if not existing
+ returned: always
+ type: str
+ sample: 116.203.104.109
+ ipv6:
+ description: IPv6 network of the server, None if not existing
+ returned: always
+ type: str
+ sample: 2a01:4f8:1c1c:c140::/64
+ location:
+ description: Name of the location of the server
+ returned: always
+ type: str
+ sample: fsn1
+ datacenter:
+ description: Name of the datacenter of the server
+ returned: always
+ type: str
+ sample: fsn1-dc14
+ rescue_enabled:
+ description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
+ returned: always
+ type: bool
+ sample: false
+ backup_window:
+ description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
+ returned: always
+ type: bool
+ sample: 22-02
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ delete_protection:
+ description: True if the network is protected for deletion
+ returned: always
+ type: bool
+ version_added: "0.1.0"
+ labels:
+ description: Labels of the network
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudNetworkInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_network_info")
+ self.hcloud_network_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for network in self.hcloud_network_info:
+ if network is not None:
+ subnets = []
+ for subnet in network.subnets:
+ prepared_subnet = {
+ "type": subnet.type,
+ "ip_range": subnet.ip_range,
+ "network_zone": subnet.network_zone,
+ "gateway": subnet.gateway,
+ }
+ subnets.append(prepared_subnet)
+ routes = []
+ for route in network.routes:
+ prepared_route = {
+ "destination": route.destination,
+ "gateway": route.gateway
+ }
+ routes.append(prepared_route)
+
+ servers = []
+ for server in network.servers:
+ image = None if server.image is None else to_native(server.image.name)
+ ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip)
+ ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip)
+ prepared_server = {
+ "id": to_native(server.id),
+ "name": to_native(server.name),
+ "ipv4_address": ipv4_address,
+ "ipv6": ipv6,
+ "image": image,
+ "server_type": to_native(server.server_type.name),
+ "datacenter": to_native(server.datacenter.name),
+ "location": to_native(server.datacenter.location.name),
+ "rescue_enabled": server.rescue_enabled,
+ "backup_window": to_native(server.backup_window),
+ "labels": server.labels,
+ "status": to_native(server.status),
+ }
+ servers.append(prepared_server)
+
+ tmp.append({
+ "id": to_native(network.id),
+ "name": to_native(network.name),
+ "ip_range": to_native(network.ip_range),
+ "subnetworks": subnets,
+ "routes": routes,
+ "servers": servers,
+ "labels": network.labels,
+ "delete_protection": network.protection["delete"],
+ })
+ return tmp
+
+ def get_networks(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_network_info = [self.client.networks.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_network_info = [self.client.networks.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_network_info = self.client.networks.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_network_info = self.client.networks.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudNetworkInfo.define_module()
+
+ hcloud = AnsibleHcloudNetworkInfo(module)
+ hcloud.get_networks()
+ result = hcloud.get_result()
+ info = {
+ 'hcloud_network_info': result['hcloud_network_info']
+ }
+ module.exit_json(**info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py
new file mode 100644
index 000000000..522bb679d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_placement_group.py
@@ -0,0 +1,230 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2020, 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
+
+DOCUMENTATION = """
+---
+module: hcloud_placement_group
+
+short_description: Create and manage placement groups on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage placement groups on the Hetzner Cloud.
+
+author:
+ - Adrian Huber (@Adi146)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud placement group to manage.
+ - Only required if no placement group I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud placement group to manage.
+ - Only required if no placement group I(id) is given, or a placement group does not exist.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs)
+ type: dict
+ type:
+ description:
+ - The Type of the Hetzner Cloud placement group.
+ type: str
+ state:
+ description:
+ - State of the placement group.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.15.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+"""
+
+EXAMPLES = """
+- name: Create a basic placement group
+ hcloud_placement_group:
+ name: my-placement-group
+ state: present
+ type: spread
+
+- name: Create a placement group with labels
+ hcloud_placement_group:
+ name: my-placement-group
+ type: spread
+ labels:
+ key: value
+ mylabel: 123
+ state: present
+
+- name: Ensure the placement group is absent (remove if needed)
+ hcloud_placement_group:
+ name: my-placement-group
+ state: absent
+"""
+
+RETURN = """
+hcloud_placement_group:
+ description: The placement group instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the placement group
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the placement group
+ returned: always
+ type: str
+ sample: my placement group
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ type:
+ description: Type of the placement group
+ returned: always
+ type: str
+ sample: spread
+ servers:
+ description: Server IDs of the placement group
+ returned: always
+ type: list
+ elements: int
+ sample:
+ - 4711
+ - 4712
+"""
+
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+
+
+class AnsibleHcloudPlacementGroup(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_placement_group")
+ self.hcloud_placement_group = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_placement_group.id),
+ "name": to_native(self.hcloud_placement_group.name),
+ "labels": self.hcloud_placement_group.labels,
+ "type": to_native(self.hcloud_placement_group.type),
+ "servers": self.hcloud_placement_group.servers,
+ }
+
+ def _get_placement_group(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_placement_group = self.client.placement_groups.get_by_id(
+ self.module.params.get("id")
+ )
+ elif self.module.params.get("name") is not None:
+ self.hcloud_placement_group = self.client.placement_groups.get_by_name(
+ self.module.params.get("name")
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_placement_group(self):
+ self.module.fail_on_missing_params(
+ required_params=["name"]
+ )
+ params = {
+ "name": self.module.params.get("name"),
+ "type": self.module.params.get("type"),
+ "labels": self.module.params.get("labels"),
+ }
+ if not self.module.check_mode:
+ try:
+ self.client.placement_groups.create(**params)
+ except Exception as e:
+ self.module.fail_json(msg=e.message, **params)
+ self._mark_as_changed()
+ self._get_placement_group()
+
+ def _update_placement_group(self):
+ name = self.module.params.get("name")
+ if name is not None and self.hcloud_placement_group.name != name:
+ self.module.fail_on_missing_params(
+ required_params=["id"]
+ )
+ if not self.module.check_mode:
+ self.hcloud_placement_group.update(name=name)
+ self._mark_as_changed()
+
+ labels = self.module.params.get("labels")
+ if labels is not None and self.hcloud_placement_group.labels != labels:
+ if not self.module.check_mode:
+ self.hcloud_placement_group.update(labels=labels)
+ self._mark_as_changed()
+
+ self._get_placement_group()
+
+ def present_placement_group(self):
+ self._get_placement_group()
+ if self.hcloud_placement_group is None:
+ self._create_placement_group()
+ else:
+ self._update_placement_group()
+
+ def delete_placement_group(self):
+ self._get_placement_group()
+ if self.hcloud_placement_group is not None:
+ if not self.module.check_mode:
+ self.client.placement_groups.delete(self.hcloud_placement_group)
+ self._mark_as_changed()
+ self.hcloud_placement_group = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ labels={"type": "dict"},
+ type={"type": "str"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ required_if=[['state', 'present', ['name']]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudPlacementGroup.define_module()
+
+ hcloud = AnsibleHcloudPlacementGroup(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_placement_group()
+ elif state == "present":
+ hcloud.present_placement_group()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py
new file mode 100644
index 000000000..c192d5fec
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_primary_ip.py
@@ -0,0 +1,271 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# Copyright: (c) 2022, 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
+
+DOCUMENTATION = '''
+---
+module: hcloud_primary_ip
+
+short_description: Create and manage cloud Primary IPs on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage cloud Primary IPs on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+version_added: 1.8.0
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud Primary IPs to manage.
+ - Only required if no Primary IP I(name) is given.
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud Primary IPs to manage.
+ - Only required if no Primary IP I(id) is given or a Primary IP does not exist.
+ type: str
+ datacenter:
+ description:
+ - Home Location of the Hetzner Cloud Primary IP.
+ - Required if no I(server) is given and Primary IP does not exist.
+ type: str
+ type:
+ description:
+ - Type of the Primary IP.
+ - Required if Primary IP does not exist
+ choices: [ ipv4, ipv6 ]
+ type: str
+ auto_delete:
+ description:
+ - Delete this Primary IP when the resource it is assigned to is deleted
+ type: bool
+ default: no
+ delete_protection:
+ description:
+ - Protect the Primary IP for deletion.
+ type: bool
+ labels:
+ description:
+ - User-defined labels (key-value pairs).
+ type: dict
+ state:
+ description:
+ - State of the Primary IP.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.9.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic IPv4 Primary IP
+ hcloud_primary_ip:
+ name: my-primary-ip
+ datacenter: fsn1-dc14
+ type: ipv4
+ state: present
+- name: Create a basic IPv6 Primary IP
+ hcloud_primary_ip:
+ name: my-primary-ip
+ datacenter: fsn1-dc14
+ type: ipv6
+ state: present
+- name: Primary IP should be absent
+ hcloud_primary_ip:
+ name: my-primary-ip
+ state: absent
+"""
+
+RETURN = """
+hcloud_primary_ip:
+ description: The Primary IP instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: ID of the Primary IP
+ type: int
+ returned: Always
+ sample: 12345
+ name:
+ description: Name of the Primary IP
+ type: str
+ returned: Always
+ sample: my-primary-ip
+ ip:
+ description: IP Address of the Primary IP
+ type: str
+ returned: Always
+ sample: 116.203.104.109
+ type:
+ description: Type of the Primary IP
+ type: str
+ returned: Always
+ sample: ipv4
+ datacenter:
+ description: Name of the datacenter of the Primary IP
+ type: str
+ returned: Always
+ sample: fsn1-dc14
+ delete_protection:
+ description: True if Primary IP is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ labels:
+ description: User-defined labels (key-value pairs)
+ type: dict
+ returned: Always
+ sample:
+ key: value
+ mylabel: 123
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudPrimaryIP(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_primary_ip")
+ self.hcloud_primary_ip = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_primary_ip.id),
+ "name": to_native(self.hcloud_primary_ip.name),
+ "ip": to_native(self.hcloud_primary_ip.ip),
+ "type": to_native(self.hcloud_primary_ip.type),
+ "datacenter": to_native(self.hcloud_primary_ip.datacenter.name),
+ "labels": self.hcloud_primary_ip.labels,
+ "delete_protection": self.hcloud_primary_ip.protection["delete"],
+ }
+
+ def _get_primary_ip(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_primary_ip = self.client.primary_ips.get_by_id(
+ self.module.params.get("id")
+ )
+ else:
+ self.hcloud_primary_ip = self.client.primary_ips.get_by_name(
+ self.module.params.get("name")
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_primary_ip(self):
+ self.module.fail_on_missing_params(
+ required_params=["type", "datacenter"]
+ )
+ try:
+ params = {
+ "type": self.module.params.get("type"),
+ "name": self.module.params.get("name"),
+ "datacenter": self.client.datacenters.get_by_name(
+ self.module.params.get("datacenter")
+ )
+ }
+
+ if self.module.params.get("labels") is not None:
+ params["labels"] = self.module.params.get("labels")
+ if not self.module.check_mode:
+ resp = self.client.primary_ips.create(**params)
+ self.hcloud_primary_ip = resp.primary_ip
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None:
+ self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e)
+ self._mark_as_changed()
+ self._get_primary_ip()
+
+ def _update_primary_ip(self):
+ try:
+ labels = self.module.params.get("labels")
+ if labels is not None and labels != self.hcloud_primary_ip.labels:
+ if not self.module.check_mode:
+ self.hcloud_primary_ip.update(labels=labels)
+ self._mark_as_changed()
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None and delete_protection != self.hcloud_primary_ip.protection["delete"]:
+ if not self.module.check_mode:
+ self.hcloud_primary_ip.change_protection(delete=delete_protection).wait_until_finished()
+ self._mark_as_changed()
+
+ self._get_primary_ip()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def present_primary_ip(self):
+ self._get_primary_ip()
+ if self.hcloud_primary_ip is None:
+ self._create_primary_ip()
+ else:
+ self._update_primary_ip()
+
+ def delete_primary_ip(self):
+ try:
+ self._get_primary_ip()
+ if self.hcloud_primary_ip is not None:
+ if not self.module.check_mode:
+ self.client.primary_ips.delete(self.hcloud_primary_ip)
+ self._mark_as_changed()
+ self.hcloud_primary_ip = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ datacenter={"type": "str"},
+ auto_delete={"type": "bool", "default": False},
+ type={"choices": ["ipv4", "ipv6"]},
+ labels={"type": "dict"},
+ delete_protection={"type": "bool"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudPrimaryIP.define_module()
+
+ hcloud = AnsibleHcloudPrimaryIP(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_primary_ip()
+ elif state == "present":
+ hcloud.present_primary_ip()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py
new file mode 100644
index 000000000..9f79fbe70
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_rdns.py
@@ -0,0 +1,360 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_rdns
+
+short_description: Create and manage reverse DNS entries on the Hetzner Cloud.
+
+
+description:
+ - Create, update and delete reverse DNS entries on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ server:
+ description:
+ - The name of the Hetzner Cloud server you want to add the reverse DNS entry to.
+ type: str
+ floating_ip:
+ description:
+ - The name of the Hetzner Cloud Floating IP you want to add the reverse DNS entry to.
+ type: str
+ primary_ip:
+ description:
+ - The name of the Hetzner Cloud Primary IP you want to add the reverse DNS entry to.
+ type: str
+ load_balancer:
+ description:
+ - The name of the Hetzner Cloud Load Balancer you want to add the reverse DNS entry to.
+ type: str
+ ip_address:
+ description:
+ - The IP address that should point to I(dns_ptr).
+ type: str
+ required: true
+ dns_ptr:
+ description:
+ - The DNS address the I(ip_address) should resolve to.
+ - Omit the param to reset the reverse DNS entry to the default value.
+ type: str
+ state:
+ description:
+ - State of the reverse DNS entry.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.3.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a reverse DNS entry for a server
+ hcloud_rdns:
+ server: my-server
+ ip_address: 123.123.123.123
+ dns_ptr: example.com
+ state: present
+
+- name: Create a reverse DNS entry for a Floating IP
+ hcloud_rdns:
+ floating_ip: my-floating-ip
+ ip_address: 123.123.123.123
+ dns_ptr: example.com
+ state: present
+
+- name: Create a reverse DNS entry for a Primary IP
+ hcloud_rdns:
+ primary_ip: my-primary-ip
+ ip_address: 123.123.123.123
+ dns_ptr: example.com
+ state: present
+
+- name: Create a reverse DNS entry for a Load Balancer
+ hcloud_rdns:
+ load_balancer: my-load-balancer
+ ip_address: 123.123.123.123
+ dns_ptr: example.com
+ state: present
+
+- name: Ensure the reverse DNS entry is absent (remove if needed)
+ hcloud_rdns:
+ server: my-server
+ ip_address: 123.123.123.123
+ dns_ptr: example.com
+ state: absent
+"""
+
+RETURN = """
+hcloud_rdns:
+ description: The reverse DNS entry
+ returned: always
+ type: complex
+ contains:
+ server:
+ description: Name of the server
+ type: str
+ returned: always
+ sample: my-server
+ floating_ip:
+ description: Name of the Floating IP
+ type: str
+ returned: always
+ sample: my-floating-ip
+ primary_ip:
+ description: Name of the Primary IP
+ type: str
+ returned: always
+ sample: my-primary-ip
+ load_balancer:
+ description: Name of the Load Balancer
+ type: str
+ returned: always
+ sample: my-load-balancer
+ ip_address:
+ description: The IP address that point to the DNS ptr
+ type: str
+ returned: always
+ sample: 123.123.123.123
+ dns_ptr:
+ description: The DNS that resolves to the IP
+ type: str
+ returned: always
+ sample: example.com
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+from ansible_collections.ansible.netcommon.plugins.module_utils.network.common import utils
+
+
+class AnsibleHcloudReverseDNS(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_rdns")
+ self.hcloud_resource = None
+ self.hcloud_rdns = None
+
+ def _prepare_result(self):
+ result = {
+ "server": None,
+ "floating_ip": None,
+ "load_balancer": None,
+ "ip_address": to_native(self.hcloud_rdns["ip_address"]),
+ "dns_ptr": to_native(self.hcloud_rdns["dns_ptr"]),
+ }
+
+ if self.module.params.get("server"):
+ result["server"] = to_native(self.hcloud_resource.name)
+ elif self.module.params.get("floating_ip"):
+ result["floating_ip"] = to_native(self.hcloud_resource.name)
+ elif self.module.params.get("load_balancer"):
+ result["load_balancer"] = to_native(self.hcloud_resource.name)
+ elif self.module.params.get("primary_ip"):
+ result["primary_ip"] = to_native(self.hcloud_resource.name)
+ return result
+
+ def _get_resource(self):
+ try:
+ if self.module.params.get("server"):
+ self.hcloud_resource = self.client.servers.get_by_name(
+ self.module.params.get("server")
+ )
+ if self.hcloud_resource is None:
+ self.module.fail_json(msg="The selected server does not exist")
+ elif self.module.params.get("floating_ip"):
+ self.hcloud_resource = self.client.floating_ips.get_by_name(
+ self.module.params.get("floating_ip")
+ )
+ if self.hcloud_resource is None:
+ self.module.fail_json(msg="The selected Floating IP does not exist")
+ elif self.module.params.get("primary_ip"):
+ self.hcloud_resource = self.client.primary_ips.get_by_name(
+ self.module.params.get("primary_ip")
+ )
+ if self.hcloud_resource is None:
+ self.module.fail_json(msg="The selected Floating IP does not exist")
+ elif self.module.params.get("load_balancer"):
+ self.hcloud_resource = self.client.load_balancers.get_by_name(
+ self.module.params.get("load_balancer")
+ )
+ if self.hcloud_resource is None:
+ self.module.fail_json(msg="The selected Load Balancer does not exist")
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _get_rdns(self):
+ ip_address = self.module.params.get("ip_address")
+ if utils.validate_ip_address(ip_address):
+ if self.module.params.get("server"):
+ if self.hcloud_resource.public_net.ipv4.ip == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": self.hcloud_resource.public_net.ipv4.ip,
+ "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr,
+ }
+ else:
+ self.module.fail_json(msg="The selected server does not have this IP address")
+ elif self.module.params.get("floating_ip"):
+ if self.hcloud_resource.ip == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": self.hcloud_resource.ip,
+ "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"],
+ }
+ else:
+ self.module.fail_json(msg="The selected Floating IP does not have this IP address")
+ elif self.module.params.get("primary_ip"):
+ if self.hcloud_resource.ip == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": self.hcloud_resource.ip,
+ "dns_ptr": self.hcloud_resource.dns_ptr[0]["dns_ptr"],
+ }
+ else:
+ self.module.fail_json(msg="The selected Primary IP does not have this IP address")
+ elif self.module.params.get("load_balancer"):
+ if self.hcloud_resource.public_net.ipv4.ip == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": self.hcloud_resource.public_net.ipv4.ip,
+ "dns_ptr": self.hcloud_resource.public_net.ipv4.dns_ptr,
+ }
+ else:
+ self.module.fail_json(msg="The selected Load Balancer does not have this IP address")
+
+ elif utils.validate_ip_v6_address(ip_address):
+ if self.module.params.get("server"):
+ for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr:
+ if ipv6_address_dns_ptr["ip"] == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": ipv6_address_dns_ptr["ip"],
+ "dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
+ }
+ elif self.module.params.get("floating_ip"):
+ for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr:
+ if ipv6_address_dns_ptr["ip"] == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": ipv6_address_dns_ptr["ip"],
+ "dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
+ }
+ elif self.module.params.get("primary_ip"):
+ for ipv6_address_dns_ptr in self.hcloud_resource.dns_ptr:
+ if ipv6_address_dns_ptr["ip"] == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": ipv6_address_dns_ptr["ip"],
+ "dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
+ }
+ elif self.module.params.get("load_balancer"):
+ for ipv6_address_dns_ptr in self.hcloud_resource.public_net.ipv6.dns_ptr:
+ if ipv6_address_dns_ptr["ip"] == ip_address:
+ self.hcloud_rdns = {
+ "ip_address": ipv6_address_dns_ptr["ip"],
+ "dns_ptr": ipv6_address_dns_ptr["dns_ptr"],
+ }
+ else:
+ self.module.fail_json(msg="The given IP address is not valid")
+
+ def _create_rdns(self):
+ self.module.fail_on_missing_params(
+ required_params=["dns_ptr"]
+ )
+ params = {
+ "ip": self.module.params.get("ip_address"),
+ "dns_ptr": self.module.params.get("dns_ptr"),
+ }
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_resource.change_dns_ptr(**params).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_resource()
+ self._get_rdns()
+
+ def _update_rdns(self):
+ dns_ptr = self.module.params.get("dns_ptr")
+ if dns_ptr != self.hcloud_rdns["dns_ptr"]:
+ params = {
+ "ip": self.module.params.get("ip_address"),
+ "dns_ptr": dns_ptr,
+ }
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_resource.change_dns_ptr(**params).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_resource()
+ self._get_rdns()
+
+ def present_rdns(self):
+ self._get_resource()
+ self._get_rdns()
+ if self.hcloud_rdns is None:
+ self._create_rdns()
+ else:
+ self._update_rdns()
+
+ def delete_rdns(self):
+ self._get_resource()
+ self._get_rdns()
+ if self.hcloud_rdns is not None:
+ if not self.module.check_mode:
+ try:
+ self.hcloud_resource.change_dns_ptr(ip=self.hcloud_rdns['ip_address'], dns_ptr=None)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_rdns = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ server={"type": "str"},
+ floating_ip={"type": "str"},
+ load_balancer={"type": "str"},
+ primary_ip={"type": "str"},
+ ip_address={"type": "str", "required": True},
+ dns_ptr={"type": "str"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['server', 'floating_ip', 'load_balancer', 'primary_ip']],
+ mutually_exclusive=[["server", "floating_ip", 'load_balancer', 'primary_ip']],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudReverseDNS.define_module()
+
+ hcloud = AnsibleHcloudReverseDNS(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_rdns()
+ elif state == "present":
+ hcloud.present_rdns()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py
new file mode 100644
index 000000000..c75177953
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_route.py
@@ -0,0 +1,196 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_route
+
+short_description: Create and delete cloud routes on the Hetzner Cloud.
+
+
+description:
+ - Create, update and delete cloud routes on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ network:
+ description:
+ - The name of the Hetzner Cloud Network.
+ type: str
+ required: true
+ destination:
+ description:
+ - Destination network or host of this route.
+ type: str
+ required: true
+ gateway:
+ description:
+ - Gateway for the route.
+ type: str
+ required: true
+ state:
+ description:
+ - State of the route.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.3.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic route
+ hcloud_route:
+ network: my-network
+ destination: 10.100.1.0/24
+ gateway: 10.0.1.1
+ state: present
+
+- name: Ensure the route is absent
+ hcloud_route:
+ network: my-network
+ destination: 10.100.1.0/24
+ gateway: 10.0.1.1
+ state: absent
+"""
+
+RETURN = """
+hcloud_route:
+ description: One Route of a Network
+ returned: always
+ type: complex
+ contains:
+ network:
+ description: Name of the Network
+ type: str
+ returned: always
+ sample: my-network
+ destination:
+ description: Destination network or host of this route
+ type: str
+ returned: always
+ sample: 10.0.0.0/8
+ gateway:
+ description: Gateway of the route
+ type: str
+ returned: always
+ sample: 10.0.0.1
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+try:
+ from hcloud.networks.domain import NetworkRoute
+except ImportError:
+ NetworkRoute = None
+
+
+class AnsibleHcloudRoute(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_route")
+ self.hcloud_network = None
+ self.hcloud_route = None
+
+ def _prepare_result(self):
+ return {
+ "network": to_native(self.hcloud_network.name),
+ "destination": to_native(self.hcloud_route.destination),
+ "gateway": self.hcloud_route.gateway,
+ }
+
+ def _get_network(self):
+ try:
+ self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network"))
+ self.hcloud_route = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _get_route(self):
+ destination = self.module.params.get("destination")
+ gateway = self.module.params.get("gateway")
+ for route in self.hcloud_network.routes:
+ if route.destination == destination and route.gateway == gateway:
+ self.hcloud_route = route
+
+ def _create_route(self):
+ route = NetworkRoute(
+ destination=self.module.params.get("destination"),
+ gateway=self.module.params.get('gateway')
+ )
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_network.add_route(route=route).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ self._mark_as_changed()
+ self._get_network()
+ self._get_route()
+
+ def present_route(self):
+ self._get_network()
+ self._get_route()
+ if self.hcloud_route is None:
+ self._create_route()
+
+ def delete_route(self):
+ self._get_network()
+ self._get_route()
+ if self.hcloud_route is not None and self.hcloud_network is not None:
+ if not self.module.check_mode:
+ try:
+ self.hcloud_network.delete_route(self.hcloud_route).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_route = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ network={"type": "str", "required": True},
+ gateway={"type": "str", "required": True},
+ destination={"type": "str", "required": True},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudRoute.define_module()
+
+ hcloud = AnsibleHcloudRoute(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_route()
+ elif state == "present":
+ hcloud.present_route()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py
new file mode 100644
index 000000000..3a77da695
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server.py
@@ -0,0 +1,928 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_server
+
+short_description: Create and manage cloud servers on the Hetzner Cloud.
+
+
+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 exist.
+ type: str
+ server_type:
+ description:
+ - The Server Type of the Hetzner Cloud server to manage.
+ - Required if server does not exist.
+ type: str
+ ssh_keys:
+ description:
+ - List of SSH key names
+ - The key names correspond to the SSH keys configured for your
+ Hetzner Cloud account access.
+ type: list
+ elements: str
+ volumes:
+ description:
+ - List of Volumes IDs that should be attached to the server on server creation.
+ type: list
+ elements: str
+ firewalls:
+ description:
+ - List of Firewall IDs that should be attached to the server on server creation.
+ type: list
+ elements: str
+ image:
+ description:
+ - Image the server should be created from.
+ - Required if server does not exist.
+ type: str
+ location:
+ description:
+ - Location of Server.
+ - Required if no I(datacenter) is given and server does not exist.
+ type: str
+ datacenter:
+ description:
+ - Datacenter of Server.
+ - Required of no I(location) is given and server does not exist.
+ type: str
+ backups:
+ description:
+ - Enable or disable Backups for the given Server.
+ type: bool
+ 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
+ enable_ipv4:
+ description:
+ - Enables the public ipv4 address
+ type: bool
+ default: yes
+ enable_ipv6:
+ description:
+ - Enables the public ipv6 address
+ type: bool
+ default: yes
+ ipv4:
+ description:
+ - ID of the ipv4 Primary IP to use. If omitted and enable_ipv4 is true, a new ipv4 Primary IP will automatically be created
+ type: str
+ ipv6:
+ description:
+ - ID of the ipv6 Primary IP to use. If omitted and enable_ipv6 is true, a new ipv6 Primary IP will automatically be created.
+ type: str
+ private_networks:
+ description:
+ - List of private networks the server is attached to (name or ID)
+ - If None, private networks are left as they are (e.g. if previously added by hcloud_server_network),
+ if it has any other value (including []), only those networks are attached to the server.
+ type: list
+ elements: str
+ force_upgrade:
+ description:
+ - Deprecated
+ - Force the upgrade of the server.
+ - Power off the server if it is running on upgrade.
+ type: bool
+ default: no
+ force:
+ description:
+ - Force the update of the server.
+ - May power off the server if update.
+ type: bool
+ default: no
+ allow_deprecated_image:
+ description:
+ - Allows the creation of servers with deprecated images.
+ type: bool
+ default: no
+ user_data:
+ description:
+ - User Data to be passed to the server on creation.
+ - Only used if server does not exist.
+ type: str
+ rescue_mode:
+ description:
+ - Add the Hetzner rescue system type you want the server to be booted into.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs).
+ type: dict
+ delete_protection:
+ description:
+ - Protect the Server for deletion.
+ - Needs to be the same as I(rebuild_protection).
+ type: bool
+ rebuild_protection:
+ description:
+ - Protect the Server for rebuild.
+ - Needs to be the same as I(delete_protection).
+ type: bool
+ placement_group:
+ description:
+ - Placement Group of the server.
+ type: str
+ state:
+ description:
+ - State of the server.
+ default: present
+ choices: [ absent, present, restarted, started, stopped, rebuild ]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.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_keys:
+ - me@myorganisation
+ state: present
+
+- name: Resize an existing server
+ hcloud_server:
+ name: my-server
+ server_type: cx21
+ upgrade_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
+
+- name: Ensure the server is will be booted in rescue mode and therefore restarted
+ hcloud_server:
+ name: my-server
+ rescue_mode: linux64
+ state: restarted
+
+- name: Ensure the server is rebuild
+ hcloud_server:
+ name: my-server
+ image: ubuntu-18.04
+ state: rebuild
+
+- name: Add server to placement group
+ hcloud_server:
+ name: my-server
+ placement_group: my-placement-group
+ force: True
+ state: present
+
+- name: Remove server from placement group
+ hcloud_server:
+ name: my-server
+ placement_group: null
+ state: present
+
+- name: Add server with private network only
+ hcloud_server:
+ name: my-server
+ enable_ipv4: false
+ enable_ipv6: false
+ private_networks:
+ - my-network
+ - 4711
+ state: present
+"""
+
+RETURN = """
+hcloud_server:
+ description: The server instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the server
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the server
+ returned: always
+ type: str
+ sample: my-server
+ status:
+ description: Status of the server
+ returned: always
+ type: str
+ sample: running
+ server_type:
+ description: Name of the server type of the server
+ returned: always
+ type: str
+ sample: cx11
+ ipv4_address:
+ description: Public IPv4 address of the server
+ returned: always
+ type: str
+ sample: 116.203.104.109
+ ipv6:
+ description: IPv6 network of the server
+ returned: always
+ type: str
+ sample: 2a01:4f8:1c1c:c140::/64
+ private_networks:
+ description: List of private networks the server is attached to (name or ID)
+ returned: always
+ type: list
+ elements: str
+ sample: ['my-network', 'another-network', '4711']
+ private_networks_info:
+ description: List of private networks the server is attached to (dict with name and ip)
+ returned: always
+ type: list
+ elements: dict
+ sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}]
+ location:
+ description: Name of the location of the server
+ returned: always
+ type: str
+ sample: fsn1
+ placement_group:
+ description: Placement Group of the server
+ type: str
+ returned: always
+ sample: 4711
+ version_added: "1.5.0"
+ datacenter:
+ description: Name of the datacenter of the server
+ returned: always
+ type: str
+ sample: fsn1-dc14
+ rescue_enabled:
+ description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
+ returned: always
+ type: bool
+ sample: false
+ backup_window:
+ description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
+ returned: always
+ type: bool
+ sample: 22-02
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ delete_protection:
+ description: True if server is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+ rebuild_protection:
+ description: True if server is protected for rebuild
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+from datetime import timedelta
+
+try:
+ from hcloud.volumes.domain import Volume
+ from hcloud.ssh_keys.domain import SSHKey
+ from hcloud.servers.domain import Server, ServerCreatePublicNetwork
+ from hcloud.firewalls.domain import FirewallResource
+except ImportError:
+ Volume = None
+ SSHKey = None
+ Server = None
+ ServerCreatePublicNetwork = None
+ FirewallResource = None
+
+
+class AnsibleHcloudServer(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_server")
+ self.hcloud_server = None
+
+ def _prepare_result(self):
+ image = None if self.hcloud_server.image is None else to_native(self.hcloud_server.image.name)
+ placement_group = None if self.hcloud_server.placement_group is None else to_native(
+ self.hcloud_server.placement_group.name)
+ ipv4_address = None if self.hcloud_server.public_net.ipv4 is None else to_native(
+ self.hcloud_server.public_net.ipv4.ip)
+ ipv6 = None if self.hcloud_server.public_net.ipv6 is None else to_native(self.hcloud_server.public_net.ipv6.ip)
+ backup_window = None if self.hcloud_server.backup_window is None else to_native(self.hcloud_server.backup_window)
+ return {
+ "id": to_native(self.hcloud_server.id),
+ "name": to_native(self.hcloud_server.name),
+ "ipv4_address": ipv4_address,
+ "ipv6": ipv6,
+ "private_networks": [to_native(net.network.name) for net in self.hcloud_server.private_net],
+ "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in self.hcloud_server.private_net],
+ "image": image,
+ "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),
+ "placement_group": placement_group,
+ "rescue_enabled": self.hcloud_server.rescue_enabled,
+ "backup_window": backup_window,
+ "labels": self.hcloud_server.labels,
+ "delete_protection": self.hcloud_server.protection["delete"],
+ "rebuild_protection": self.hcloud_server.protection["rebuild"],
+ "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 Exception 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"]
+ )
+
+ server_type = self._get_server_type()
+
+ params = {
+ "name": self.module.params.get("name"),
+ "server_type": server_type,
+ "user_data": self.module.params.get("user_data"),
+ "labels": self.module.params.get("labels"),
+ "image": self._get_image(server_type),
+ "placement_group": self._get_placement_group(),
+ "public_net": ServerCreatePublicNetwork(
+ enable_ipv4=self.module.params.get("enable_ipv4"),
+ enable_ipv6=self.module.params.get("enable_ipv6")
+ )
+ }
+
+ if self.module.params.get("ipv4") is not None:
+ p = self.client.primary_ips.get_by_name(self.module.params.get("ipv4"))
+ if not p:
+ p = self.client.primary_ips.get_by_id(self.module.params.get("ipv4"))
+ params["public_net"].ipv4 = p
+
+ if self.module.params.get("ipv6") is not None:
+ p = self.client.primary_ips.get_by_name(self.module.params.get("ipv6"))
+ if not p:
+ p = self.client.primary_ips.get_by_id(self.module.params.get("ipv6"))
+ params["public_net"].ipv6 = p
+
+ if self.module.params.get("private_networks") is not None:
+ _networks = []
+ for network_name_or_id in self.module.params.get("private_networks"):
+ _networks.append(
+ self.client.networks.get_by_name(network_name_or_id)
+ or self.client.networks.get_by_id(network_name_or_id)
+ )
+ params["networks"] = _networks
+
+ 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("firewalls") is not None:
+ params["firewalls"] = []
+ for fw in self.module.params.get("firewalls"):
+ f = self.client.firewalls.get_by_name(fw)
+ if f is not None:
+ # When firewall name is not available look for id instead
+ params["firewalls"].append(f)
+ else:
+ params["firewalls"].append(self.client.firewalls.get_by_id(fw))
+
+ 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 self.module.params.get("state") == "stopped":
+ params["start_after_create"] = False
+ if not self.module.check_mode:
+ try:
+ resp = self.client.servers.create(**params)
+ self.result["root_password"] = resp.root_password
+ resp.action.wait_until_finished(max_retries=1000)
+ [action.wait_until_finished() for action in resp.next_actions]
+
+ rescue_mode = self.module.params.get("rescue_mode")
+ if rescue_mode:
+ self._get_server()
+ self._set_rescue_mode(rescue_mode)
+
+ backups = self.module.params.get("backups")
+ if backups:
+ self._get_server()
+ self.hcloud_server.enable_backup().wait_until_finished()
+
+ delete_protection = self.module.params.get("delete_protection")
+ rebuild_protection = self.module.params.get("rebuild_protection")
+ if delete_protection is not None and rebuild_protection is not None:
+ self._get_server()
+ self.hcloud_server.change_protection(delete=delete_protection,
+ rebuild=rebuild_protection).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_server()
+
+ def _get_image(self, server_type):
+ image_resp = self.client.images.get_list(name=self.module.params.get("image"), architecture=server_type.architecture, include_deprecated=True)
+ images = getattr(image_resp, 'images')
+ image = None
+ if images is not None and len(images) > 0:
+ # If image name is not available look for id instead
+ image = images[0]
+ else:
+ try:
+ image = self.client.images.get_by_id(self.module.params.get("image"))
+ except Exception:
+ self.module.fail_json(msg="Image %s was not found" % self.module.params.get('image'))
+ if image.deprecated is not None:
+ available_until = image.deprecated + timedelta(days=90)
+ if self.module.params.get("allow_deprecated_image"):
+ self.module.warn(
+ "You try to use a deprecated image. The image %s will continue to be available until %s.") % (
+ image.name, available_until.strftime('%Y-%m-%d'))
+ else:
+ self.module.fail_json(
+ msg=("You try to use a deprecated image. The image %s will continue to be available until %s." +
+ " If you want to use this image use allow_deprecated_image=yes."
+ ) % (image.name, available_until.strftime('%Y-%m-%d')))
+ return image
+
+ def _get_server_type(self):
+ server_type = self.client.server_types.get_by_name(
+ self.module.params.get("server_type")
+ )
+ if server_type is None:
+ try:
+ server_type = self.client.server_types.get_by_id(self.module.params.get("server_type"))
+ except Exception:
+ self.module.fail_json(msg="server_type %s was not found" % self.module.params.get('server_type'))
+
+ return server_type
+
+ def _get_placement_group(self):
+ if self.module.params.get("placement_group") is None:
+ return None
+
+ placement_group = self.client.placement_groups.get_by_name(
+ self.module.params.get("placement_group")
+ )
+ if placement_group is None:
+ try:
+ placement_group = self.client.placement_groups.get_by_id(self.module.params.get("placement_group"))
+ except Exception:
+ self.module.fail_json(
+ msg="placement_group %s was not found" % self.module.params.get("placement_group"))
+
+ return placement_group
+
+ def _get_primary_ip(self, field):
+ if self.module.params.get(field) is None:
+ return None
+
+ primary_ip = self.client.primary_ips.get_by_name(
+ self.module.params.get(field)
+ )
+ if primary_ip is None:
+ try:
+ primary_ip = self.client.primary_ips.get_by_id(self.module.params.get(field))
+ except Exception as e:
+ self.module.fail_json(
+ msg="primary_ip %s was not found" % self.module.params.get(field))
+
+ return primary_ip
+
+ def _update_server(self):
+ if "force_upgrade" in self.module.params:
+ self.module.warn("force_upgrade is deprecated, use force instead")
+
+ try:
+ previous_server_status = self.hcloud_server.status
+
+ rescue_mode = self.module.params.get("rescue_mode")
+ if rescue_mode and self.hcloud_server.rescue_enabled is False:
+ if not self.module.check_mode:
+ self._set_rescue_mode(rescue_mode)
+ self._mark_as_changed()
+ elif not rescue_mode and self.hcloud_server.rescue_enabled is True:
+ if not self.module.check_mode:
+ self.hcloud_server.disable_rescue().wait_until_finished()
+ self._mark_as_changed()
+
+ backups = self.module.params.get("backups")
+ if 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 backups is not None and not 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()
+
+ labels = self.module.params.get("labels")
+ if labels is not None and labels != self.hcloud_server.labels:
+ if not self.module.check_mode:
+ self.hcloud_server.update(labels=labels)
+ self._mark_as_changed()
+
+ wanted_firewalls = self.module.params.get("firewalls")
+ if wanted_firewalls is not None:
+ # Removing existing but not wanted firewalls
+ for current_firewall in self.hcloud_server.public_net.firewalls:
+ if current_firewall.firewall.name not in wanted_firewalls:
+ self._mark_as_changed()
+ if not self.module.check_mode:
+ r = FirewallResource(type="server", server=self.hcloud_server)
+ actions = self.client.firewalls.remove_from_resources(current_firewall.firewall, [r])
+ for a in actions:
+ a.wait_until_finished()
+
+ # Adding wanted firewalls that doesn't exist yet
+ for fname in wanted_firewalls:
+ found = False
+ for f in self.hcloud_server.public_net.firewalls:
+ if f.firewall.name == fname:
+ found = True
+ break
+
+ if not found:
+ self._mark_as_changed()
+ if not self.module.check_mode:
+ fw = self.client.firewalls.get_by_name(fname)
+ if fw is None:
+ self.module.fail_json(msg="firewall %s was not found" % fname)
+ r = FirewallResource(type="server", server=self.hcloud_server)
+ actions = self.client.firewalls.apply_to_resources(fw, [r])
+ for a in actions:
+ a.wait_until_finished()
+
+ if "placement_group" in self.module.params:
+ if self.module.params["placement_group"] is None and self.hcloud_server.placement_group is not None:
+ if not self.module.check_mode:
+ self.hcloud_server.remove_from_placement_group().wait_until_finished()
+ self._mark_as_changed()
+ else:
+ placement_group = self._get_placement_group()
+ if (
+ placement_group is not None and
+ (
+ self.hcloud_server.placement_group is None or
+ self.hcloud_server.placement_group.id != placement_group.id
+ )
+ ):
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ self.hcloud_server.add_to_placement_group(placement_group).wait_until_finished()
+ self._mark_as_changed()
+
+ if "ipv4" in self.module.params:
+ if (
+ self.module.params["ipv4"] is None and
+ self.hcloud_server.public_net.primary_ipv4 is not None and
+ not self.module.params.get("enable_ipv4")
+ ):
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished()
+ self._mark_as_changed()
+ else:
+ primary_ip = self._get_primary_ip("ipv4")
+ if (
+ primary_ip is not None and
+ (
+ self.hcloud_server.public_net.primary_ipv4 is None or
+ self.hcloud_server.public_net.primary_ipv4.id != primary_ip.id
+ )
+ ):
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ if self.hcloud_server.public_net.primary_ipv4:
+ self.hcloud_server.public_net.primary_ipv4.unassign().wait_until_finished()
+ primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished()
+ self._mark_as_changed()
+ if "ipv6" in self.module.params:
+ if (
+ (self.module.params["ipv6"] is None or self.module.params["ipv6"] == "") and
+ self.hcloud_server.public_net.primary_ipv6 is not None and
+ not self.module.params.get("enable_ipv6")
+ ):
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished()
+ self._mark_as_changed()
+ else:
+ primary_ip = self._get_primary_ip("ipv6")
+ if (
+ primary_ip is not None and
+ (
+ self.hcloud_server.public_net.primary_ipv6 is None or
+ self.hcloud_server.public_net.primary_ipv6.id != primary_ip.id
+ )
+ ):
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ if self.hcloud_server.public_net.primary_ipv6 is not None:
+ self.hcloud_server.public_net.primary_ipv6.unassign().wait_until_finished()
+ primary_ip.assign(self.hcloud_server.id, "server").wait_until_finished()
+ self._mark_as_changed()
+ if "private_networks" in self.module.params and self.module.params["private_networks"] is not None:
+ if not bool(self.module.params["private_networks"]):
+ # This handles None, "" and []
+ networks_target = {}
+ else:
+ _networks = {}
+ for network_name_or_id in self.module.params.get("private_networks"):
+ _found_network = self.client.networks.get_by_name(network_name_or_id) \
+ or self.client.networks.get_by_id(network_name_or_id)
+ _networks.update(
+ {_found_network.id: _found_network}
+ )
+ networks_target = _networks
+ networks_is = dict()
+ for p_network in self.hcloud_server.private_net:
+ networks_is.update({p_network.network.id: p_network.network})
+ for network_id in set(list(networks_is) + list(networks_target)):
+ if network_id in networks_is and network_id not in networks_target:
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ self.hcloud_server.detach_from_network(networks_is[network_id]).wait_until_finished()
+ self._mark_as_changed()
+ elif network_id in networks_target and network_id not in networks_is:
+ self.stop_server_if_forced()
+ if not self.module.check_mode:
+ self.hcloud_server.attach_to_network(networks_target[network_id]).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:
+ self.stop_server_if_forced()
+
+ timeout = 100
+ if self.module.params.get("upgrade_disk"):
+ timeout = (
+ 1000
+ ) # When we upgrade the disk to the resize progress takes some more time.
+ if not self.module.check_mode:
+ self.hcloud_server.change_type(
+ server_type=self._get_server_type(),
+ upgrade_disk=self.module.params.get("upgrade_disk"),
+ ).wait_until_finished(timeout)
+ self._mark_as_changed()
+
+ if (
+ not self.module.check_mode and
+ (
+ (
+ self.module.params.get("state") == "present" and
+ previous_server_status == Server.STATUS_RUNNING
+ ) or
+ self.module.params.get("state") == "started"
+ )
+ ):
+ self.start_server()
+
+ delete_protection = self.module.params.get("delete_protection")
+ rebuild_protection = self.module.params.get("rebuild_protection")
+ if (delete_protection is not None and rebuild_protection is not None) and (
+ delete_protection != self.hcloud_server.protection["delete"] or rebuild_protection !=
+ self.hcloud_server.protection["rebuild"]):
+ if not self.module.check_mode:
+ self.hcloud_server.change_protection(delete=delete_protection,
+ rebuild=rebuild_protection).wait_until_finished()
+ self._mark_as_changed()
+ self._get_server()
+ except Exception as e:
+ self.module.fail_json(msg=e)
+
+ def _set_rescue_mode(self, rescue_mode):
+ if self.module.params.get("ssh_keys"):
+ resp = self.hcloud_server.enable_rescue(type=rescue_mode,
+ ssh_keys=[self.client.ssh_keys.get_by_name(ssh_key_name).id
+ for
+ ssh_key_name in
+ self.module.params.get("ssh_keys")])
+ else:
+ resp = self.hcloud_server.enable_rescue(type=rescue_mode)
+ resp.action.wait_until_finished()
+ self.result["root_password"] = resp.root_password
+
+ def start_server(self):
+ try:
+ if self.hcloud_server:
+ 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()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def stop_server(self):
+ try:
+ if self.hcloud_server:
+ 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()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def stop_server_if_forced(self):
+ previous_server_status = self.hcloud_server.status
+ if previous_server_status == Server.STATUS_RUNNING and not self.module.check_mode:
+ if (
+ self.module.params.get("force_upgrade") or
+ self.module.params.get("force") or
+ self.module.params.get("state") == "stopped"
+ ):
+ self.stop_server() # Only stopped server can be upgraded
+ return previous_server_status
+ else:
+ self.module.warn(
+ "You can not upgrade a running instance %s. You need to stop the instance or use force=yes."
+ % self.hcloud_server.name
+ )
+
+ return None
+
+ def rebuild_server(self):
+ self.module.fail_on_missing_params(
+ required_params=["image"]
+ )
+ try:
+ if not self.module.check_mode:
+ image = self._get_image(self.hcloud_server.server_type)
+ self.client.servers.rebuild(self.hcloud_server, image).wait_until_finished(1000) # When we rebuild the server progress takes some more time.
+ self._mark_as_changed()
+
+ self._get_server()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def present_server(self):
+ self._get_server()
+ if self.hcloud_server is None:
+ self._create_server()
+ else:
+ self._update_server()
+
+ def delete_server(self):
+ try:
+ 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
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @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", "elements": "str", "no_log": False},
+ volumes={"type": "list", "elements": "str"},
+ firewalls={"type": "list", "elements": "str"},
+ labels={"type": "dict"},
+ backups={"type": "bool"},
+ upgrade_disk={"type": "bool", "default": False},
+ enable_ipv4={"type": "bool", "default": True},
+ enable_ipv6={"type": "bool", "default": True},
+ ipv4={"type": "str"},
+ ipv6={"type": "str"},
+ private_networks={"type": "list", "elements": "str", "default": None},
+ force={"type": "bool", "default": False},
+ force_upgrade={"type": "bool", "default": False},
+ allow_deprecated_image={"type": "bool", "default": False},
+ rescue_mode={"type": "str"},
+ delete_protection={"type": "bool"},
+ rebuild_protection={"type": "bool"},
+ placement_group={"type": "str"},
+ state={
+ "choices": ["absent", "present", "restarted", "started", "stopped", "rebuild"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ mutually_exclusive=[["location", "datacenter"]],
+ required_together=[["delete_protection", "rebuild_protection"]],
+ 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()
+ elif state == "rebuild":
+ hcloud.present_server()
+ hcloud.rebuild_server()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py
new file mode 100644
index 000000000..102ceec0d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_facts.py
@@ -0,0 +1,244 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_server_info
+
+short_description: Gather infos about your Hetzner Cloud servers.
+
+
+description:
+ - Gather infos about your Hetzner Cloud servers.
+ - This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts).
+ Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the server you want to get.
+ type: int
+ name:
+ description:
+ - The name of the server you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the server you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud server infos
+ hcloud_server_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_server_info
+"""
+
+RETURN = """
+hcloud_server_info:
+ description: The server infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the server
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the server
+ returned: always
+ type: str
+ sample: my-server
+ status:
+ description: Status of the server
+ returned: always
+ type: str
+ sample: running
+ server_type:
+ description: Name of the server type of the server
+ returned: always
+ type: str
+ sample: cx11
+ ipv4_address:
+ description: Public IPv4 address of the server
+ returned: always
+ type: str
+ sample: 116.203.104.109
+ ipv6:
+ description: IPv6 network of the server
+ returned: always
+ type: str
+ sample: 2a01:4f8:1c1c:c140::/64
+ private_networks:
+ description: List of private networks the server is attached to (name)
+ returned: always
+ type: list
+ elements: str
+ sample: ['my-network', 'another-network']
+ private_networks_info:
+ description: List of private networks the server is attached to (dict with name and ip)
+ returned: always
+ type: list
+ elements: dict
+ sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}]
+ location:
+ description: Name of the location of the server
+ returned: always
+ type: str
+ sample: fsn1
+ placement_group:
+ description: Placement Group of the server
+ type: str
+ returned: always
+ sample: 4711
+ version_added: "1.5.0"
+ datacenter:
+ description: Name of the datacenter of the server
+ returned: always
+ type: str
+ sample: fsn1-dc14
+ rescue_enabled:
+ description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
+ returned: always
+ type: bool
+ sample: false
+ backup_window:
+ description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
+ returned: always
+ type: bool
+ sample: 22-02
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ delete_protection:
+ description: True if server is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+ rebuild_protection:
+ description: True if server is protected for rebuild
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudServerInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_server_info")
+ self.hcloud_server_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for server in self.hcloud_server_info:
+ if server is not None:
+ image = None if server.image is None else to_native(server.image.name)
+ placement_group = None if server.placement_group is None else to_native(server.placement_group.name)
+ ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip)
+ ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip)
+ backup_window = None if server.backup_window is None else to_native(server.backup_window)
+ tmp.append({
+ "id": to_native(server.id),
+ "name": to_native(server.name),
+ "ipv4_address": ipv4_address,
+ "ipv6": ipv6,
+ "private_networks": [to_native(net.network.name) for net in server.private_net],
+ "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net],
+ "image": image,
+ "server_type": to_native(server.server_type.name),
+ "datacenter": to_native(server.datacenter.name),
+ "location": to_native(server.datacenter.location.name),
+ "placement_group": placement_group,
+ "rescue_enabled": server.rescue_enabled,
+ "backup_window": backup_window,
+ "labels": server.labels,
+ "status": to_native(server.status),
+ "delete_protection": server.protection["delete"],
+ "rebuild_protection": server.protection["rebuild"],
+ })
+ return tmp
+
+ def get_servers(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_server_info = [self.client.servers.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_server_info = [self.client.servers.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_server_info = self.client.servers.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_server_info = self.client.servers.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudServerInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_server_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudServerInfo(module)
+ hcloud.get_servers()
+ result = hcloud.get_result()
+
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_server_facts': result['hcloud_server_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_server_info': result['hcloud_server_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py
new file mode 100644
index 000000000..102ceec0d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_info.py
@@ -0,0 +1,244 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_server_info
+
+short_description: Gather infos about your Hetzner Cloud servers.
+
+
+description:
+ - Gather infos about your Hetzner Cloud servers.
+ - This module was called C(hcloud_server_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_facts).
+ Note that the M(hetzner.hcloud.hcloud_server_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the server you want to get.
+ type: int
+ name:
+ description:
+ - The name of the server you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the server you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud server infos
+ hcloud_server_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_server_info
+"""
+
+RETURN = """
+hcloud_server_info:
+ description: The server infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the server
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the server
+ returned: always
+ type: str
+ sample: my-server
+ status:
+ description: Status of the server
+ returned: always
+ type: str
+ sample: running
+ server_type:
+ description: Name of the server type of the server
+ returned: always
+ type: str
+ sample: cx11
+ ipv4_address:
+ description: Public IPv4 address of the server
+ returned: always
+ type: str
+ sample: 116.203.104.109
+ ipv6:
+ description: IPv6 network of the server
+ returned: always
+ type: str
+ sample: 2a01:4f8:1c1c:c140::/64
+ private_networks:
+ description: List of private networks the server is attached to (name)
+ returned: always
+ type: list
+ elements: str
+ sample: ['my-network', 'another-network']
+ private_networks_info:
+ description: List of private networks the server is attached to (dict with name and ip)
+ returned: always
+ type: list
+ elements: dict
+ sample: [{'name': 'my-network', 'ip': '192.168.1.1'}, {'name': 'another-network', 'ip': '10.185.50.40'}]
+ location:
+ description: Name of the location of the server
+ returned: always
+ type: str
+ sample: fsn1
+ placement_group:
+ description: Placement Group of the server
+ type: str
+ returned: always
+ sample: 4711
+ version_added: "1.5.0"
+ datacenter:
+ description: Name of the datacenter of the server
+ returned: always
+ type: str
+ sample: fsn1-dc14
+ rescue_enabled:
+ description: True if rescue mode is enabled, Server will then boot into rescue system on next reboot
+ returned: always
+ type: bool
+ sample: false
+ backup_window:
+ description: Time window (UTC) in which the backup will run, or null if the backups are not enabled
+ returned: always
+ type: bool
+ sample: 22-02
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+ delete_protection:
+ description: True if server is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+ rebuild_protection:
+ description: True if server is protected for rebuild
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudServerInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_server_info")
+ self.hcloud_server_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for server in self.hcloud_server_info:
+ if server is not None:
+ image = None if server.image is None else to_native(server.image.name)
+ placement_group = None if server.placement_group is None else to_native(server.placement_group.name)
+ ipv4_address = None if server.public_net.ipv4 is None else to_native(server.public_net.ipv4.ip)
+ ipv6 = None if server.public_net.ipv6 is None else to_native(server.public_net.ipv6.ip)
+ backup_window = None if server.backup_window is None else to_native(server.backup_window)
+ tmp.append({
+ "id": to_native(server.id),
+ "name": to_native(server.name),
+ "ipv4_address": ipv4_address,
+ "ipv6": ipv6,
+ "private_networks": [to_native(net.network.name) for net in server.private_net],
+ "private_networks_info": [{"name": to_native(net.network.name), "ip": net.ip} for net in server.private_net],
+ "image": image,
+ "server_type": to_native(server.server_type.name),
+ "datacenter": to_native(server.datacenter.name),
+ "location": to_native(server.datacenter.location.name),
+ "placement_group": placement_group,
+ "rescue_enabled": server.rescue_enabled,
+ "backup_window": backup_window,
+ "labels": server.labels,
+ "status": to_native(server.status),
+ "delete_protection": server.protection["delete"],
+ "rebuild_protection": server.protection["rebuild"],
+ })
+ return tmp
+
+ def get_servers(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_server_info = [self.client.servers.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_server_info = [self.client.servers.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_server_info = self.client.servers.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_server_info = self.client.servers.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudServerInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_server_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_server_facts' module has been renamed to 'hcloud_server_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudServerInfo(module)
+ hcloud.get_servers()
+ result = hcloud.get_result()
+
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_server_facts': result['hcloud_server_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_server_info': result['hcloud_server_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py
new file mode 100644
index 000000000..79f6838fd
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_network.py
@@ -0,0 +1,246 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_server_network
+
+short_description: Manage the relationship between Hetzner Cloud Networks and servers
+
+
+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
+ elements: str
+ state:
+ description:
+ - State of the server_network.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.3.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.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_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+try:
+ from hcloud import APIException
+except ImportError:
+ APIException = None
+
+
+class AnsibleHcloudServerNetwork(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, 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 Exception 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 Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ self._mark_as_changed()
+ self._get_server_and_network()
+ self._get_server_network()
+
+ def _update_server_network(self):
+ params = {
+ "network": self.hcloud_network
+ }
+ alias_ips = self.module.params.get("alias_ips")
+ if alias_ips is not None and sorted(self.hcloud_server_network.alias_ips) != sorted(alias_ips):
+ params["alias_ips"] = alias_ips
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_server.change_alias_ips(**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()
+ else:
+ self._update_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:
+ try:
+ self.hcloud_server.detach_from_network(self.hcloud_server_network.network).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ 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", "elements": "str"},
+ 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/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py
new file mode 100644
index 000000000..a84067c32
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_facts.py
@@ -0,0 +1,183 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_server_type_info
+
+short_description: Gather infos about the Hetzner Cloud server types.
+
+
+description:
+ - Gather infos about your Hetzner Cloud server types.
+ - This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts).
+ Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the server type you want to get.
+ type: int
+ name:
+ description:
+ - The name of the server type you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud server type infos
+ hcloud_server_type_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_server_type_info
+"""
+
+RETURN = """
+hcloud_server_type_info:
+ description: The server type infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the server type
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the server type
+ returned: always
+ type: str
+ sample: fsn1
+ description:
+ description: Detail description of the server type
+ returned: always
+ type: str
+ sample: Falkenstein DC Park 1
+ cores:
+ description: Number of cpu cores a server of this type will have
+ returned: always
+ type: int
+ sample: 1
+ memory:
+ description: Memory a server of this type will have in GB
+ returned: always
+ type: int
+ sample: 1
+ disk:
+ description: Disk size a server of this type will have in GB
+ returned: always
+ type: int
+ sample: 25
+ storage_type:
+ description: Type of server boot drive
+ returned: always
+ type: str
+ sample: local
+ cpu_type:
+ description: Type of cpu
+ returned: always
+ type: str
+ sample: shared
+ architecture:
+ description: Architecture of cpu
+ returned: always
+ type: str
+ sample: x86
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudServerTypeInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_server_type_info")
+ self.hcloud_server_type_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for server_type in self.hcloud_server_type_info:
+ if server_type is not None:
+ tmp.append({
+ "id": to_native(server_type.id),
+ "name": to_native(server_type.name),
+ "description": to_native(server_type.description),
+ "cores": server_type.cores,
+ "memory": server_type.memory,
+ "disk": server_type.disk,
+ "storage_type": to_native(server_type.storage_type),
+ "cpu_type": to_native(server_type.cpu_type),
+ "architecture": to_native(server_type.architecture)
+ })
+ return tmp
+
+ def get_server_types(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_server_type_info = [self.client.server_types.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_server_type_info = [self.client.server_types.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_server_type_info = self.client.server_types.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudServerTypeInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_server_type_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudServerTypeInfo(module)
+ hcloud.get_server_types()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_server_type_info': result['hcloud_server_type_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_server_type_info': result['hcloud_server_type_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py
new file mode 100644
index 000000000..a84067c32
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_server_type_info.py
@@ -0,0 +1,183 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_server_type_info
+
+short_description: Gather infos about the Hetzner Cloud server types.
+
+
+description:
+ - Gather infos about your Hetzner Cloud server types.
+ - This module was called C(hcloud_server_type_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_server_type_facts).
+ Note that the M(hetzner.hcloud.hcloud_server_type_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_server_type_info)!
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the server type you want to get.
+ type: int
+ name:
+ description:
+ - The name of the server type you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud server type infos
+ hcloud_server_type_info:
+ register: output
+
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_server_type_info
+"""
+
+RETURN = """
+hcloud_server_type_info:
+ description: The server type infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the server type
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the server type
+ returned: always
+ type: str
+ sample: fsn1
+ description:
+ description: Detail description of the server type
+ returned: always
+ type: str
+ sample: Falkenstein DC Park 1
+ cores:
+ description: Number of cpu cores a server of this type will have
+ returned: always
+ type: int
+ sample: 1
+ memory:
+ description: Memory a server of this type will have in GB
+ returned: always
+ type: int
+ sample: 1
+ disk:
+ description: Disk size a server of this type will have in GB
+ returned: always
+ type: int
+ sample: 25
+ storage_type:
+ description: Type of server boot drive
+ returned: always
+ type: str
+ sample: local
+ cpu_type:
+ description: Type of cpu
+ returned: always
+ type: str
+ sample: shared
+ architecture:
+ description: Architecture of cpu
+ returned: always
+ type: str
+ sample: x86
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudServerTypeInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_server_type_info")
+ self.hcloud_server_type_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for server_type in self.hcloud_server_type_info:
+ if server_type is not None:
+ tmp.append({
+ "id": to_native(server_type.id),
+ "name": to_native(server_type.name),
+ "description": to_native(server_type.description),
+ "cores": server_type.cores,
+ "memory": server_type.memory,
+ "disk": server_type.disk,
+ "storage_type": to_native(server_type.storage_type),
+ "cpu_type": to_native(server_type.cpu_type),
+ "architecture": to_native(server_type.architecture)
+ })
+ return tmp
+
+ def get_server_types(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_server_type_info = [self.client.server_types.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_server_type_info = [self.client.server_types.get_by_name(
+ self.module.params.get("name")
+ )]
+ else:
+ self.hcloud_server_type_info = self.client.server_types.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudServerTypeInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_server_type_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_server_type_info' module has been renamed to 'hcloud_server_type_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudServerTypeInfo(module)
+ hcloud.get_server_types()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_server_type_info': result['hcloud_server_type_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_server_type_info': result['hcloud_server_type_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py
new file mode 100644
index 000000000..59a5197f5
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key.py
@@ -0,0 +1,243 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_ssh_key
+
+short_description: Create and manage ssh keys on the Hetzner Cloud.
+
+
+description:
+ - Create, update and manage ssh keys on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud ssh_key to manage.
+ - Only required if no ssh_key I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud ssh_key to manage.
+ - Only required if no ssh_key I(id) is given or a ssh_key does not exist.
+ type: str
+ fingerprint:
+ description:
+ - The Fingerprint of the Hetzner Cloud ssh_key to manage.
+ - Only required if no ssh_key I(id) or I(name) is given.
+ type: str
+ labels:
+ description:
+ - User-defined labels (key-value pairs)
+ type: dict
+ public_key:
+ description:
+ - The Public Key to add.
+ - Required if ssh_key does not exist.
+ type: str
+ state:
+ description:
+ - State of the ssh_key.
+ default: present
+ choices: [ absent, present ]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic ssh_key
+ hcloud_ssh_key:
+ name: my-ssh_key
+ public_key: "ssh-rsa AAAjjk76kgf...Xt"
+ state: present
+
+- name: Create a ssh_key with labels
+ hcloud_ssh_key:
+ name: my-ssh_key
+ public_key: "ssh-rsa AAAjjk76kgf...Xt"
+ labels:
+ key: value
+ mylabel: 123
+ state: present
+
+- name: Ensure the ssh_key is absent (remove if needed)
+ hcloud_ssh_key:
+ name: my-ssh_key
+ state: absent
+"""
+
+RETURN = """
+hcloud_ssh_key:
+ description: The ssh_key instance
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: ID of the ssh_key
+ type: int
+ returned: Always
+ sample: 12345
+ name:
+ description: Name of the ssh_key
+ type: str
+ returned: Always
+ sample: my-ssh-key
+ fingerprint:
+ description: Fingerprint of the ssh_key
+ type: str
+ returned: Always
+ sample: b7:2f:30:a0:2f:6c:58:6c:21:04:58:61:ba:06:3b:2f
+ public_key:
+ description: Public key of the ssh_key
+ type: str
+ returned: Always
+ sample: "ssh-rsa AAAjjk76kgf...Xt"
+ labels:
+ description: User-defined labels (key-value pairs)
+ type: dict
+ returned: Always
+ sample:
+ key: value
+ mylabel: 123
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudSSHKey(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_ssh_key")
+ self.hcloud_ssh_key = None
+
+ def _prepare_result(self):
+ return {
+ "id": to_native(self.hcloud_ssh_key.id),
+ "name": to_native(self.hcloud_ssh_key.name),
+ "fingerprint": to_native(self.hcloud_ssh_key.fingerprint),
+ "public_key": to_native(self.hcloud_ssh_key.public_key),
+ "labels": self.hcloud_ssh_key.labels,
+ }
+
+ def _get_ssh_key(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_ssh_key = self.client.ssh_keys.get_by_id(
+ self.module.params.get("id")
+ )
+ elif self.module.params.get("fingerprint") is not None:
+ self.hcloud_ssh_key = self.client.ssh_keys.get_by_fingerprint(
+ self.module.params.get("fingerprint")
+ )
+ elif self.module.params.get("name") is not None:
+ self.hcloud_ssh_key = self.client.ssh_keys.get_by_name(
+ self.module.params.get("name")
+ )
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_ssh_key(self):
+ self.module.fail_on_missing_params(
+ required_params=["name", "public_key"]
+ )
+ params = {
+ "name": self.module.params.get("name"),
+ "public_key": self.module.params.get("public_key"),
+ "labels": self.module.params.get("labels")
+ }
+
+ if not self.module.check_mode:
+ try:
+ self.client.ssh_keys.create(**params)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_ssh_key()
+
+ def _update_ssh_key(self):
+ name = self.module.params.get("name")
+ if name is not None and self.hcloud_ssh_key.name != name:
+ self.module.fail_on_missing_params(
+ required_params=["id"]
+ )
+ if not self.module.check_mode:
+ self.hcloud_ssh_key.update(name=name)
+ self._mark_as_changed()
+
+ labels = self.module.params.get("labels")
+ if labels is not None and self.hcloud_ssh_key.labels != labels:
+ if not self.module.check_mode:
+ self.hcloud_ssh_key.update(labels=labels)
+ self._mark_as_changed()
+
+ self._get_ssh_key()
+
+ def present_ssh_key(self):
+ self._get_ssh_key()
+ if self.hcloud_ssh_key is None:
+ self._create_ssh_key()
+ else:
+ self._update_ssh_key()
+
+ def delete_ssh_key(self):
+ self._get_ssh_key()
+ if self.hcloud_ssh_key is not None:
+ if not self.module.check_mode:
+ try:
+ self.client.ssh_keys.delete(self.hcloud_ssh_key)
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_ssh_key = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ public_key={"type": "str"},
+ fingerprint={"type": "str"},
+ labels={"type": "dict"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name', 'fingerprint']],
+ required_if=[['state', 'present', ['name']]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudSSHKey.define_module()
+
+ hcloud = AnsibleHcloudSSHKey(module)
+ state = module.params.get("state")
+ if state == "absent":
+ hcloud.delete_ssh_key()
+ elif state == "present":
+ hcloud.present_ssh_key()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py
new file mode 100644
index 000000000..aab98ed60
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_facts.py
@@ -0,0 +1,169 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_ssh_key_info
+short_description: Gather infos about your Hetzner Cloud ssh_keys.
+description:
+ - Gather facts about your Hetzner Cloud ssh_keys.
+ - This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts).
+ Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)!
+author:
+ - Christopher Schmitt (@cschmitt-hcloud)
+options:
+ id:
+ description:
+ - The ID of the ssh key you want to get.
+ type: int
+ name:
+ description:
+ - The name of the ssh key you want to get.
+ type: str
+ fingerprint:
+ description:
+ - The fingerprint of the ssh key you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the ssh key you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud sshkey infos
+ hcloud_ssh_key_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_ssh_key_info
+"""
+
+RETURN = """
+hcloud_ssh_key_info:
+ description: The ssh key instances
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the ssh_key
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the ssh_key
+ returned: always
+ type: str
+ sample: my-ssh-key
+ fingerprint:
+ description: Fingerprint of the ssh key
+ returned: always
+ type: str
+ sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3
+ public_key:
+ description: The actual public key
+ returned: always
+ type: str
+ sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com"
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudSSHKeyInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_ssh_key_info")
+ self.hcloud_ssh_key_info = None
+
+ def _prepare_result(self):
+ ssh_keys = []
+
+ for ssh_key in self.hcloud_ssh_key_info:
+ if ssh_key:
+ ssh_keys.append({
+ "id": to_native(ssh_key.id),
+ "name": to_native(ssh_key.name),
+ "fingerprint": to_native(ssh_key.fingerprint),
+ "public_key": to_native(ssh_key.public_key),
+ "labels": ssh_key.labels
+ })
+ return ssh_keys
+
+ def get_ssh_keys(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("fingerprint") is not None:
+ self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint(
+ self.module.params.get("fingerprint")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_ssh_key_info = self.client.ssh_keys.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_ssh_key_info = self.client.ssh_keys.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ fingerprint={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudSSHKeyInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_ssh_key_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudSSHKeyInfo(module)
+ hcloud.get_ssh_keys()
+ result = hcloud.get_result()
+
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_ssh_key_facts': result['hcloud_ssh_key_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_ssh_key_info': result['hcloud_ssh_key_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py
new file mode 100644
index 000000000..aab98ed60
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_ssh_key_info.py
@@ -0,0 +1,169 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_ssh_key_info
+short_description: Gather infos about your Hetzner Cloud ssh_keys.
+description:
+ - Gather facts about your Hetzner Cloud ssh_keys.
+ - This module was called C(hcloud_ssh_key_facts) before Ansible 2.9, returning C(ansible_facts) and C(hcloud_ssh_key_facts).
+ Note that the M(hetzner.hcloud.hcloud_ssh_key_info) module no longer returns C(ansible_facts) and the value was renamed to C(hcloud_ssh_key_info)!
+author:
+ - Christopher Schmitt (@cschmitt-hcloud)
+options:
+ id:
+ description:
+ - The ID of the ssh key you want to get.
+ type: int
+ name:
+ description:
+ - The name of the ssh key you want to get.
+ type: str
+ fingerprint:
+ description:
+ - The fingerprint of the ssh key you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the ssh key you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud sshkey infos
+ hcloud_ssh_key_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_ssh_key_info
+"""
+
+RETURN = """
+hcloud_ssh_key_info:
+ description: The ssh key instances
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the ssh_key
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the ssh_key
+ returned: always
+ type: str
+ sample: my-ssh-key
+ fingerprint:
+ description: Fingerprint of the ssh key
+ returned: always
+ type: str
+ sample: 0e:e0:bd:c7:2d:1f:69:49:94:44:91:f1:19:fd:35:f3
+ public_key:
+ description: The actual public key
+ returned: always
+ type: str
+ sample: "ssh-ed25519 AAAAC3NzaC1lZDI1NTE5AAAAIGpl/tnk74nnQJxxLAtutUApUZMRJxryKh7VXkNbd4g9 john@example.com"
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudSSHKeyInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_ssh_key_info")
+ self.hcloud_ssh_key_info = None
+
+ def _prepare_result(self):
+ ssh_keys = []
+
+ for ssh_key in self.hcloud_ssh_key_info:
+ if ssh_key:
+ ssh_keys.append({
+ "id": to_native(ssh_key.id),
+ "name": to_native(ssh_key.name),
+ "fingerprint": to_native(ssh_key.fingerprint),
+ "public_key": to_native(ssh_key.public_key),
+ "labels": ssh_key.labels
+ })
+ return ssh_keys
+
+ def get_ssh_keys(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("fingerprint") is not None:
+ self.hcloud_ssh_key_info = [self.client.ssh_keys.get_by_fingerprint(
+ self.module.params.get("fingerprint")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_ssh_key_info = self.client.ssh_keys.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_ssh_key_info = self.client.ssh_keys.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ fingerprint={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudSSHKeyInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_ssh_key_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_ssh_key_facts' module has been renamed to 'hcloud_ssh_key_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudSSHKeyInfo(module)
+ hcloud.get_ssh_keys()
+ result = hcloud.get_result()
+
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_ssh_key_facts': result['hcloud_ssh_key_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_ssh_key_info': result['hcloud_ssh_key_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py
new file mode 100644
index 000000000..c2ba66d80
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_subnetwork.py
@@ -0,0 +1,247 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_subnetwork
+
+short_description: Manage cloud subnetworks on the Hetzner Cloud.
+
+
+description:
+ - Create, update and delete cloud subnetworks on the Hetzner Cloud.
+
+author:
+ - Lukas Kaemmerling (@lkaemmerling)
+
+options:
+ network:
+ description:
+ - The ID or Name of the Hetzner Cloud Networks.
+ type: str
+ required: true
+ ip_range:
+ description:
+ - IP range of the subnetwork.
+ type: str
+ required: true
+ type:
+ description:
+ - Type of subnetwork.
+ type: str
+ choices: [ server, cloud, vswitch ]
+ required: true
+ network_zone:
+ description:
+ - Name of network zone.
+ type: str
+ required: true
+ vswitch_id:
+ description:
+ - ID of the vSwitch you want to couple with your Network.
+ - Required if type == vswitch
+ type: int
+ state:
+ description:
+ - State of the subnetwork.
+ default: present
+ choices: [ absent, present ]
+ type: str
+
+requirements:
+ - hcloud-python >= 1.10.0
+
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a basic subnetwork
+ hcloud_subnetwork:
+ network: my-network
+ ip_range: 10.0.0.0/16
+ network_zone: eu-central
+ type: cloud
+ state: present
+
+- name: Create a basic subnetwork
+ hcloud_subnetwork:
+ network: my-vswitch-network
+ ip_range: 10.0.0.0/24
+ network_zone: eu-central
+ type: vswitch
+ vswitch_id: 123
+ state: present
+
+- name: Ensure the subnetwork is absent (remove if needed)
+ hcloud_subnetwork:
+ network: my-network
+ ip_range: 10.0.0.0/8
+ network_zone: eu-central
+ type: cloud
+ state: absent
+"""
+
+RETURN = """
+hcloud_subnetwork:
+ description: One Subnet of a Network
+ returned: always
+ type: complex
+ contains:
+ network:
+ description: Name of the Network
+ type: str
+ returned: always
+ sample: my-network
+ ip_range:
+ description: IP range of the Network
+ type: str
+ returned: always
+ sample: 10.0.0.0/8
+ type:
+ description: Type of subnetwork
+ type: str
+ returned: always
+ sample: server
+ network_zone:
+ description: Name of network zone
+ type: str
+ returned: always
+ sample: eu-central
+ vswitch_id:
+ description: ID of the vswitch, null if not type vswitch
+ type: int
+ returned: always
+ sample: 123
+ gateway:
+ description: Gateway of the subnetwork
+ type: str
+ returned: always
+ sample: 10.0.0.1
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+try:
+ from hcloud.networks.domain import NetworkSubnet
+except ImportError:
+ NetworkSubnet = None
+
+
+class AnsibleHcloudSubnetwork(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_subnetwork")
+ self.hcloud_network = None
+ self.hcloud_subnetwork = None
+
+ def _prepare_result(self):
+ return {
+ "network": to_native(self.hcloud_network.name),
+ "ip_range": to_native(self.hcloud_subnetwork.ip_range),
+ "type": to_native(self.hcloud_subnetwork.type),
+ "network_zone": to_native(self.hcloud_subnetwork.network_zone),
+ "gateway": self.hcloud_subnetwork.gateway,
+ "vswitch_id": self.hcloud_subnetwork.vswitch_id,
+ }
+
+ def _get_network(self):
+ try:
+ self.hcloud_network = self.client.networks.get_by_name(self.module.params.get("network"))
+ self.hcloud_subnetwork = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _get_subnetwork(self):
+ subnet_ip_range = self.module.params.get("ip_range")
+ for subnetwork in self.hcloud_network.subnets:
+ if subnetwork.ip_range == subnet_ip_range:
+ self.hcloud_subnetwork = subnetwork
+
+ def _create_subnetwork(self):
+ params = {
+ "ip_range": self.module.params.get("ip_range"),
+ "type": self.module.params.get('type'),
+ "network_zone": self.module.params.get('network_zone')
+ }
+ if self.module.params.get('type') == NetworkSubnet.TYPE_VSWITCH:
+ self.module.fail_on_missing_params(
+ required_params=["vswitch_id"]
+ )
+ params["vswitch_id"] = self.module.params.get('vswitch_id')
+
+ if not self.module.check_mode:
+ try:
+ self.hcloud_network.add_subnet(subnet=NetworkSubnet(**params)).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ self._mark_as_changed()
+ self._get_network()
+ self._get_subnetwork()
+
+ def present_subnetwork(self):
+ self._get_network()
+ self._get_subnetwork()
+ if self.hcloud_subnetwork is None:
+ self._create_subnetwork()
+
+ def delete_subnetwork(self):
+ self._get_network()
+ self._get_subnetwork()
+ if self.hcloud_subnetwork is not None and self.hcloud_network is not None:
+ if not self.module.check_mode:
+ try:
+ self.hcloud_network.delete_subnet(self.hcloud_subnetwork).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self.hcloud_subnetwork = None
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ network={"type": "str", "required": True},
+ network_zone={"type": "str", "required": True},
+ type={
+ "type": "str",
+ "required": True,
+ "choices": ["server", "cloud", "vswitch"]
+ },
+ ip_range={"type": "str", "required": True},
+ vswitch_id={"type": "int"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudSubnetwork.define_module()
+
+ hcloud = AnsibleHcloudSubnetwork(module)
+ state = module.params["state"]
+ if state == "absent":
+ hcloud.delete_subnetwork()
+ elif state == "present":
+ hcloud.present_subnetwork()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py
new file mode 100644
index 000000000..623a399b4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume.py
@@ -0,0 +1,340 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_volume
+
+short_description: Create and manage block Volume on the Hetzner Cloud.
+
+
+description:
+ - Create, update and attach/detach block Volume on the Hetzner Cloud.
+
+author:
+ - Christopher Schmitt (@cschmitt-hcloud)
+
+options:
+ id:
+ description:
+ - The ID of the Hetzner Cloud Block Volume to manage.
+ - Only required if no volume I(name) is given
+ type: int
+ name:
+ description:
+ - The Name of the Hetzner Cloud Block Volume to manage.
+ - Only required if no volume I(id) is given or a volume does not exist.
+ type: str
+ size:
+ description:
+ - The size of the Block Volume in GB.
+ - Required if volume does not yet exists.
+ type: int
+ automount:
+ description:
+ - Automatically mount the Volume.
+ type: bool
+ default: False
+ format:
+ description:
+ - Automatically Format the volume on creation
+ - Can only be used in case the Volume does not exist.
+ type: str
+ choices: [xfs, ext4]
+ location:
+ description:
+ - Location of the Hetzner Cloud Volume.
+ - Required if no I(server) is given and Volume does not exist.
+ type: str
+ server:
+ description:
+ - Server Name the Volume should be assigned to.
+ - Required if no I(location) is given and Volume does not exist.
+ type: str
+ delete_protection:
+ description:
+ - Protect the Volume for deletion.
+ type: bool
+ labels:
+ description:
+ - User-defined key-value pairs.
+ type: dict
+ state:
+ description:
+ - State of the Volume.
+ default: present
+ choices: [absent, present]
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Create a Volume
+ hcloud_volume:
+ name: my-volume
+ location: fsn1
+ size: 100
+ state: present
+- name: Create a Volume and format it with ext4
+ hcloud_volume:
+ name: my-volume
+ location: fsn
+ format: ext4
+ size: 100
+ state: present
+- name: Mount a existing Volume and automount
+ hcloud_volume:
+ name: my-volume
+ server: my-server
+ automount: yes
+ state: present
+- name: Mount a existing Volume and automount
+ hcloud_volume:
+ name: my-volume
+ server: my-server
+ automount: yes
+ state: present
+- name: Ensure the Volume is absent (remove if needed)
+ hcloud_volume:
+ name: my-volume
+ state: absent
+"""
+
+RETURN = """
+hcloud_volume:
+ description: The block Volume
+ returned: Always
+ type: complex
+ contains:
+ id:
+ description: ID of the Volume
+ type: int
+ returned: Always
+ sample: 12345
+ name:
+ description: Name of the Volume
+ type: str
+ returned: Always
+ sample: my-volume
+ size:
+ description: Size in GB of the Volume
+ type: int
+ returned: Always
+ sample: 1337
+ linux_device:
+ description: Path to the device that contains the Volume.
+ returned: always
+ type: str
+ sample: /dev/disk/by-id/scsi-0HC_Volume_12345
+ version_added: "0.1.0"
+ location:
+ description: Location name where the Volume is located at
+ type: str
+ returned: Always
+ sample: "fsn1"
+ labels:
+ description: User-defined labels (key-value pairs)
+ type: dict
+ returned: Always
+ sample:
+ key: value
+ mylabel: 123
+ server:
+ description: Server name where the Volume is attached to
+ type: str
+ returned: Always
+ sample: "my-server"
+ delete_protection:
+ description: True if Volume is protected for deletion
+ type: bool
+ returned: always
+ sample: false
+ version_added: "0.1.0"
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudVolume(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_volume")
+ self.hcloud_volume = None
+
+ def _prepare_result(self):
+ server_name = None
+ if self.hcloud_volume.server is not None:
+ server_name = to_native(self.hcloud_volume.server.name)
+
+ return {
+ "id": to_native(self.hcloud_volume.id),
+ "name": to_native(self.hcloud_volume.name),
+ "size": self.hcloud_volume.size,
+ "location": to_native(self.hcloud_volume.location.name),
+ "labels": self.hcloud_volume.labels,
+ "server": server_name,
+ "linux_device": to_native(self.hcloud_volume.linux_device),
+ "delete_protection": self.hcloud_volume.protection["delete"],
+ }
+
+ def _get_volume(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_volume = self.client.volumes.get_by_id(
+ self.module.params.get("id")
+ )
+ else:
+ self.hcloud_volume = self.client.volumes.get_by_name(
+ self.module.params.get("name")
+ )
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def _create_volume(self):
+ self.module.fail_on_missing_params(
+ required_params=["name", "size"]
+ )
+ params = {
+ "name": self.module.params.get("name"),
+ "size": self.module.params.get("size"),
+ "automount": self.module.params.get("automount"),
+ "format": self.module.params.get("format"),
+ "labels": self.module.params.get("labels")
+ }
+ if self.module.params.get("server") is not None:
+ params['server'] = self.client.servers.get_by_name(self.module.params.get("server"))
+ elif self.module.params.get("location") is not None:
+ params['location'] = self.client.locations.get_by_name(self.module.params.get("location"))
+ else:
+ self.module.fail_json(msg="server or location is required")
+
+ if not self.module.check_mode:
+ try:
+ resp = self.client.volumes.create(**params)
+ resp.action.wait_until_finished()
+ [action.wait_until_finished() for action in resp.next_actions]
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None:
+ self._get_volume()
+ self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+ self._mark_as_changed()
+ self._get_volume()
+
+ def _update_volume(self):
+ try:
+ size = self.module.params.get("size")
+ if size:
+ if self.hcloud_volume.size < size:
+ if not self.module.check_mode:
+ self.hcloud_volume.resize(size).wait_until_finished()
+ self._mark_as_changed()
+ elif self.hcloud_volume.size > size:
+ self.module.warn("Shrinking of volumes is not supported")
+
+ server_name = self.module.params.get("server")
+ if server_name:
+ server = self.client.servers.get_by_name(server_name)
+ if self.hcloud_volume.server is None or self.hcloud_volume.server.name != server.name:
+ if not self.module.check_mode:
+ automount = self.module.params.get("automount", False)
+ self.hcloud_volume.attach(server, automount=automount).wait_until_finished()
+ self._mark_as_changed()
+ else:
+ if self.hcloud_volume.server is not None:
+ if not self.module.check_mode:
+ self.hcloud_volume.detach().wait_until_finished()
+ self._mark_as_changed()
+
+ labels = self.module.params.get("labels")
+ if labels is not None and labels != self.hcloud_volume.labels:
+ if not self.module.check_mode:
+ self.hcloud_volume.update(labels=labels)
+ self._mark_as_changed()
+
+ delete_protection = self.module.params.get("delete_protection")
+ if delete_protection is not None and delete_protection != self.hcloud_volume.protection["delete"]:
+ if not self.module.check_mode:
+ self.hcloud_volume.change_protection(delete=delete_protection).wait_until_finished()
+ self._mark_as_changed()
+
+ self._get_volume()
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ def present_volume(self):
+ self._get_volume()
+ if self.hcloud_volume is None:
+ self._create_volume()
+ else:
+ self._update_volume()
+
+ def delete_volume(self):
+ try:
+ self._get_volume()
+ if self.hcloud_volume is not None:
+ if not self.module.check_mode:
+ if self.hcloud_volume.server is not None:
+ self.hcloud_volume.detach().wait_until_finished()
+ self.client.volumes.delete(self.hcloud_volume)
+ self._mark_as_changed()
+ self.hcloud_volume = None
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ size={"type": "int"},
+ location={"type": "str"},
+ server={"type": "str"},
+ labels={"type": "dict"},
+ automount={"type": "bool", "default": False},
+ format={"type": "str",
+ "choices": ['xfs', 'ext4'],
+ },
+ delete_protection={"type": "bool"},
+ state={
+ "choices": ["absent", "present"],
+ "default": "present",
+ },
+ **Hcloud.base_module_arguments()
+ ),
+ required_one_of=[['id', 'name']],
+ mutually_exclusive=[["location", "server"]],
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudVolume.define_module()
+
+ hcloud = AnsibleHcloudVolume(module)
+ state = module.params.get("state")
+ if state == "absent":
+ module.fail_on_missing_params(
+ required_params=["name"]
+ )
+ hcloud.delete_volume()
+ else:
+ hcloud.present_volume()
+
+ module.exit_json(**hcloud.get_result())
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py
new file mode 100644
index 000000000..9520bfa14
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_facts.py
@@ -0,0 +1,186 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_volume_info
+
+short_description: Gather infos about your Hetzner Cloud Volumes.
+
+description:
+ - Gather infos about your Hetzner Cloud Volumes.
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Volume you want to get.
+ type: int
+ name:
+ description:
+ - The name of the Volume you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the Volume you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud Volume infos
+ hcloud_volume_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_volume_info
+"""
+
+RETURN = """
+hcloud_volume_info:
+ description: The Volume infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Volume
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Volume
+ returned: always
+ type: str
+ sample: my-volume
+ size:
+ description: Size of the Volume
+ returned: always
+ type: str
+ sample: 10
+ linux_device:
+ description: Path to the device that contains the Volume.
+ returned: always
+ type: str
+ sample: /dev/disk/by-id/scsi-0HC_Volume_12345
+ version_added: "0.1.0"
+ location:
+ description: Name of the location where the Volume resides in
+ returned: always
+ type: str
+ sample: fsn1
+ server:
+ description: Name of the server where the Volume is attached to
+ returned: always
+ type: str
+ sample: my-server
+ delete_protection:
+ description: True if the Volume is protected for deletion
+ returned: always
+ type: bool
+ version_added: "0.1.0"
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudVolumeInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_volume_info")
+ self.hcloud_volume_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for volume in self.hcloud_volume_info:
+ if volume is not None:
+ server_name = None
+ if volume.server is not None:
+ server_name = to_native(volume.server.name)
+ tmp.append({
+ "id": to_native(volume.id),
+ "name": to_native(volume.name),
+ "size": volume.size,
+ "location": to_native(volume.location.name),
+ "labels": volume.labels,
+ "server": server_name,
+ "linux_device": to_native(volume.linux_device),
+ "delete_protection": volume.protection["delete"],
+ })
+
+ return tmp
+
+ def get_volumes(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_volume_info = [self.client.volumes.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_volume_info = [self.client.volumes.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_volume_info = self.client.volumes.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_volume_info = self.client.volumes.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudVolumeInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_volume_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudVolumeInfo(module)
+
+ hcloud.get_volumes()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_volume_facts': result['hcloud_volume_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_volume_info': result['hcloud_volume_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py
new file mode 100644
index 000000000..9520bfa14
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/plugins/modules/hcloud_volume_info.py
@@ -0,0 +1,186 @@
+#!/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
+
+DOCUMENTATION = '''
+---
+module: hcloud_volume_info
+
+short_description: Gather infos about your Hetzner Cloud Volumes.
+
+description:
+ - Gather infos about your Hetzner Cloud Volumes.
+
+author:
+ - Lukas Kaemmerling (@LKaemmerling)
+
+options:
+ id:
+ description:
+ - The ID of the Volume you want to get.
+ type: int
+ name:
+ description:
+ - The name of the Volume you want to get.
+ type: str
+ label_selector:
+ description:
+ - The label selector for the Volume you want to get.
+ type: str
+extends_documentation_fragment:
+- hetzner.hcloud.hcloud
+
+'''
+
+EXAMPLES = """
+- name: Gather hcloud Volume infos
+ hcloud_volume_info:
+ register: output
+- name: Print the gathered infos
+ debug:
+ var: output.hcloud_volume_info
+"""
+
+RETURN = """
+hcloud_volume_info:
+ description: The Volume infos as list
+ returned: always
+ type: complex
+ contains:
+ id:
+ description: Numeric identifier of the Volume
+ returned: always
+ type: int
+ sample: 1937415
+ name:
+ description: Name of the Volume
+ returned: always
+ type: str
+ sample: my-volume
+ size:
+ description: Size of the Volume
+ returned: always
+ type: str
+ sample: 10
+ linux_device:
+ description: Path to the device that contains the Volume.
+ returned: always
+ type: str
+ sample: /dev/disk/by-id/scsi-0HC_Volume_12345
+ version_added: "0.1.0"
+ location:
+ description: Name of the location where the Volume resides in
+ returned: always
+ type: str
+ sample: fsn1
+ server:
+ description: Name of the server where the Volume is attached to
+ returned: always
+ type: str
+ sample: my-server
+ delete_protection:
+ description: True if the Volume is protected for deletion
+ returned: always
+ type: bool
+ version_added: "0.1.0"
+ labels:
+ description: User-defined labels (key-value pairs)
+ returned: always
+ type: dict
+"""
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils._text import to_native
+from ansible_collections.hetzner.hcloud.plugins.module_utils.hcloud import Hcloud
+
+
+class AnsibleHcloudVolumeInfo(Hcloud):
+ def __init__(self, module):
+ Hcloud.__init__(self, module, "hcloud_volume_info")
+ self.hcloud_volume_info = None
+
+ def _prepare_result(self):
+ tmp = []
+
+ for volume in self.hcloud_volume_info:
+ if volume is not None:
+ server_name = None
+ if volume.server is not None:
+ server_name = to_native(volume.server.name)
+ tmp.append({
+ "id": to_native(volume.id),
+ "name": to_native(volume.name),
+ "size": volume.size,
+ "location": to_native(volume.location.name),
+ "labels": volume.labels,
+ "server": server_name,
+ "linux_device": to_native(volume.linux_device),
+ "delete_protection": volume.protection["delete"],
+ })
+
+ return tmp
+
+ def get_volumes(self):
+ try:
+ if self.module.params.get("id") is not None:
+ self.hcloud_volume_info = [self.client.volumes.get_by_id(
+ self.module.params.get("id")
+ )]
+ elif self.module.params.get("name") is not None:
+ self.hcloud_volume_info = [self.client.volumes.get_by_name(
+ self.module.params.get("name")
+ )]
+ elif self.module.params.get("label_selector") is not None:
+ self.hcloud_volume_info = self.client.volumes.get_all(
+ label_selector=self.module.params.get("label_selector"))
+ else:
+ self.hcloud_volume_info = self.client.volumes.get_all()
+
+ except Exception as e:
+ self.module.fail_json(msg=e.message)
+
+ @staticmethod
+ def define_module():
+ return AnsibleModule(
+ argument_spec=dict(
+ id={"type": "int"},
+ name={"type": "str"},
+ label_selector={"type": "str"},
+ **Hcloud.base_module_arguments()
+ ),
+ supports_check_mode=True,
+ )
+
+
+def main():
+ module = AnsibleHcloudVolumeInfo.define_module()
+
+ is_old_facts = module._name == 'hcloud_volume_facts'
+ if is_old_facts:
+ module.deprecate("The 'hcloud_volume_facts' module has been renamed to 'hcloud_volume_info', "
+ "and the renamed one no longer returns ansible_facts", version='2.0.0', collection_name="hetzner.hcloud")
+
+ hcloud = AnsibleHcloudVolumeInfo(module)
+
+ hcloud.get_volumes()
+ result = hcloud.get_result()
+ if is_old_facts:
+ ansible_info = {
+ 'hcloud_volume_facts': result['hcloud_volume_info']
+ }
+ module.exit_json(ansible_facts=ansible_info)
+ else:
+ ansible_info = {
+ 'hcloud_volume_info': result['hcloud_volume_info']
+ }
+ module.exit_json(**ansible_info)
+
+
+if __name__ == "__main__":
+ main()
diff --git a/ansible_collections/hetzner/hcloud/tests/.gitignore b/ansible_collections/hetzner/hcloud/tests/.gitignore
new file mode 100644
index 000000000..ea1472ec1
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/.gitignore
@@ -0,0 +1 @@
+output/
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/constraints.txt b/ansible_collections/hetzner/hcloud/tests/integration/constraints.txt
new file mode 100644
index 000000000..19d5ecf28
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/constraints.txt
@@ -0,0 +1 @@
+hcloud >= 1.10.0 # minimum version
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/requirements.txt b/ansible_collections/hetzner/hcloud/tests/integration/requirements.txt
new file mode 100644
index 000000000..d3249deff
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/requirements.txt
@@ -0,0 +1,2 @@
+netaddr
+hcloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml
new file mode 100644
index 000000000..58312aec1
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_certificate_name: "{{hcloud_prefix}}-integration"
+hcloud_dns_test_domain: "{{hcloud_prefix | truncate(19, False, 'ans')}}-{{100 | random }}.hc-certs.de"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml
new file mode 100644
index 000000000..e531064ca
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/meta/main.yml
@@ -0,0 +1,5 @@
+dependencies:
+ - setup_selfsigned_certificate
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml
new file mode 100644
index 000000000..615c89d0e
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate/tasks/main.yml
@@ -0,0 +1,155 @@
+# Copyright: (c) 2020, 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: test missing required parameters on create certificate
+ hcloud_certificate:
+ name: "{{ hcloud_certificate_name }}"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create certificate
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: certificate, private_key"'
+
+- name: test create certificate with check mode
+ hcloud_certificate:
+ name: "{{ hcloud_certificate_name }}"
+ certificate: "{{ certificate_example_com }}"
+ private_key: "{{ certificate_example_com_key }}"
+ register: result
+ check_mode: yes
+- name: test create certificate with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test create certificate
+ hcloud_certificate:
+ name: "{{ hcloud_certificate_name }}"
+ certificate: "{{ certificate_example_com }}"
+ private_key: "{{ certificate_example_com_key }}"
+ labels:
+ key: value
+ my-label: label
+ register: certificate
+- name: verify create certificate
+ assert:
+ that:
+ - certificate is changed
+ - certificate.hcloud_certificate.name == "{{ hcloud_certificate_name }}"
+ - certificate.hcloud_certificate.domain_names[0] == "www.example.com"
+ - certificate.hcloud_certificate.labels.key == "value"
+
+- name: test create certificate idempotence
+ hcloud_certificate:
+ name: "{{ hcloud_certificate_name }}"
+ certificate: "{{ certificate_example_com }}"
+ private_key: "{{ certificate_example_com_key }}"
+ register: result
+- name: verify create certificate idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test update certificate with check mode
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ name: "changed-{{ hcloud_certificate_name }}"
+ register: result
+ check_mode: yes
+- name: test create certificate with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test update certificate
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ name: "changed-{{ hcloud_certificate_name }}"
+ labels:
+ key: value
+ register: result
+- name: test update certificate
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_certificate.name == "changed-{{ hcloud_certificate_name }}"
+
+- name: test update certificate with same labels
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ name: "changed-{{ hcloud_certificate_name }}"
+ labels:
+ key: value
+ register: result
+- name: test update certificate with same labels
+ assert:
+ that:
+ - result is not changed
+
+- name: test update certificate with other labels
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ name: "changed-{{ hcloud_certificate_name }}"
+ labels:
+ key: value
+ test: "val123"
+ register: result
+- name: test update certificate with other labels
+ assert:
+ that:
+ - result is changed
+
+- name: test rename certificate
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ name: "{{ hcloud_certificate_name }}"
+ register: result
+- name: test rename certificate
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_certificate.name == "{{ hcloud_certificate_name }}"
+
+- name: absent certificate
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ state: absent
+ register: result
+- name: verify absent server
+ assert:
+ that:
+ - result is success
+
+- name: generate dns domain name
+ set_fact:
+ # hcloud_dns_test_domain uses random, which generates a new random number
+ # on every invocation, by saving it into a fact we generate the number once
+ hcloud_dns_test_domain: "{{ hcloud_dns_test_domain }}"
+
+- name: test create managed certificate
+ hcloud_certificate:
+ name: "{{ hcloud_certificate_name }}"
+ domain_names:
+ - "{{ hcloud_dns_test_domain }}"
+ type: managed
+ labels:
+ HC-Use-Staging-CA: "true"
+ register: result
+- name: verify create managed certificate
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_certificate.name == "{{ hcloud_certificate_name }}"
+ - result.hcloud_certificate.domain_names[0] == "{{ hcloud_dns_test_domain }}"
+
+- name: absent certificate
+ hcloud_certificate:
+ id: "{{ result.hcloud_certificate.id }}"
+ state: absent
+ register: result
+- name: verify absent certificate
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/defaults/main.yml
new file mode 100644
index 000000000..6205b19b4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/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_certificate_name: "always-there-cert"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml
new file mode 100644
index 000000000..34657c1c2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/meta/main.yml
@@ -0,0 +1,4 @@
+dependencies:
+ - setup_selfsigned_certificate
+collections:
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml
new file mode 100644
index 000000000..d7128db34
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_certificate_info/tasks/main.yml
@@ -0,0 +1,66 @@
+# 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: create certificate
+ hcloud_certificate:
+ name: "{{ hcloud_certificate_name }}"
+ certificate: "{{ certificate_example_com }}"
+ private_key: "{{ certificate_example_com_key }}"
+ labels:
+ key: value
+ my-label: label
+ register: certificate
+- name: verify create certificate
+ assert:
+ that:
+ - certificate is changed
+ - certificate.hcloud_certificate.name == "{{ hcloud_certificate_name }}"
+ - certificate.hcloud_certificate.domain_names[0] == "www.example.com"
+ - certificate.hcloud_certificate.labels.key == "value"
+
+- name: test gather hcloud certificate infos in check mode
+ hcloud_certificate_info:
+ register: hcloud_certificate
+ check_mode: yes
+- name: verify test gather hcloud certificate infos in check mode
+ assert:
+ that:
+ - hcloud_certificate.hcloud_certificate_info| list | count >= 1
+
+- name: test gather hcloud certificate infos
+ hcloud_certificate_info:
+ register: hcloud_certificate
+ check_mode: yes
+- name: verify test gather hcloud certificate infos
+ assert:
+ that:
+ - hcloud_certificate.hcloud_certificate_info| list | count >= 1
+
+- name: test gather hcloud certificate infos with correct label selector
+ hcloud_certificate_info:
+ label_selector: "key=value"
+ register: hcloud_certificate
+- name: verify test gather hcloud certificate infos with correct label selector
+ assert:
+ that:
+ - hcloud_certificate.hcloud_certificate_info|selectattr('name','equalto','{{ hcloud_certificate_name }}') | list | count == 1
+
+- name: test gather hcloud certificate infos with wrong label selector
+ hcloud_certificate_info:
+ label_selector: "key!=value"
+ register: hcloud_certificate
+- name: verify test gather hcloud certificate infos with wrong label selector
+ assert:
+ that:
+ - hcloud_certificate.hcloud_certificate_info | list | count == 0
+
+- name: absent certificate
+ hcloud_certificate:
+ id: "{{ certificate.hcloud_certificate.id }}"
+ state: absent
+ register: result
+- name: verify absent certificate
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml
new file mode 100644
index 000000000..b9e045f40
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_datacenter_name: "fsn1-dc14"
+hcloud_datacenter_id: 4
+hcloud_location_name: "fsn1"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml
new file mode 100644
index 000000000..3d144ae47
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_datacenter_info/tasks/main.yml
@@ -0,0 +1,39 @@
+# 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: test gather hcloud datacenter infos
+ hcloud_datacenter_info:
+ register: hcloud_datacenters
+
+- name: verify test gather hcloud datacenter infos
+ assert:
+ that:
+ - hcloud_datacenters.hcloud_datacenter_info| list | count >= 5
+
+- name: test gather hcloud datacenter infos in check mode
+ hcloud_datacenter_info:
+ register: hcloud_datacenters
+ check_mode: yes
+
+- name: verify test gather hcloud datacenter infos in check mode
+ assert:
+ that:
+ - hcloud_datacenters.hcloud_datacenter_info| list | count >= 5
+
+- name: test gather hcloud datacenter infos with correct name
+ hcloud_datacenter_info:
+ name: "{{hcloud_datacenter_name}}"
+ register: hcloud_datacenter
+- name: verify test gather hcloud datacenter with correct name
+ assert:
+ that:
+ - hcloud_datacenter.hcloud_datacenter_info|selectattr('name','equalto','{{ hcloud_datacenter_name }}') |selectattr('location','equalto','{{ hcloud_location_name }}') | list | count == 1
+
+- name: test gather hcloud datacenter infos with correct id
+ hcloud_datacenter_info:
+ id: "{{hcloud_datacenter_id}}"
+ register: hcloud_datacenter
+- name: verify test gather hcloud datacenter with correct id
+ assert:
+ that:
+ - hcloud_datacenter.hcloud_datacenter_info|selectattr('name','equalto','{{ hcloud_datacenter_name }}') | list | count == 1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/defaults/main.yml
new file mode 100644
index 000000000..e7eff02a7
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/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_firewall_name: "{{hcloud_prefix}}-integration"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml
new file mode 100644
index 000000000..f54d351b2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_firewall/tasks/main.yml
@@ -0,0 +1,210 @@
+# Copyright: (c) 2020, 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 firewall to be absent
+ hcloud_firewall:
+ name: "{{ hcloud_firewall_name }}"
+ state: absent
+
+- name: test missing required parameters on create firewall
+ hcloud_firewall:
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create firewall
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "one of the following is required: id, name"'
+
+- name: test create firewall with check mode
+ hcloud_firewall:
+ name: "{{ hcloud_firewall_name }}"
+ register: result
+ check_mode: yes
+- name: test create firewall with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test create firewall
+ hcloud_firewall:
+ name: "{{ hcloud_firewall_name }}"
+ rules:
+ - direction: in
+ protocol: icmp
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ description: "allow icmp in"
+ labels:
+ key: value
+ my-label: label
+ register: firewall
+- name: verify create firewall
+ assert:
+ that:
+ - firewall is changed
+ - firewall.hcloud_firewall.name == "{{ hcloud_firewall_name }}"
+ - firewall.hcloud_firewall.rules | list | count == 1
+ - firewall.hcloud_firewall.rules | selectattr('direction','equalto','in') | list | count == 1
+ - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','icmp') | list | count == 1
+ - firewall.hcloud_firewall.rules | selectattr('description', 'equalto', 'allow icmp in') | list | count == 1
+
+- name: test create firewall idempotence
+ hcloud_firewall:
+ name: "{{ hcloud_firewall_name }}"
+ rules:
+ - direction: in
+ protocol: icmp
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ description: "allow icmp in"
+ labels:
+ key: value
+ my-label: label
+ register: result
+- name: verify create firewall idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test update firewall rules
+ hcloud_firewall:
+ name: "{{ hcloud_firewall_name }}"
+ rules:
+ - direction: in
+ protocol: icmp
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ - direction: in
+ protocol: tcp
+ port: 80
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ - direction: out
+ protocol: tcp
+ port: 80
+ destination_ips:
+ - 0.0.0.0/0
+ - ::/0
+ description: allow tcp out
+ labels:
+ key: value
+ my-label: label
+ register: firewall
+- name: verify update firewall rules
+ assert:
+ that:
+ - firewall is changed
+ - firewall.hcloud_firewall.name == "{{ hcloud_firewall_name }}"
+ - firewall.hcloud_firewall.rules | list | count == 3
+ - firewall.hcloud_firewall.rules | selectattr('direction','equalto','in') | list | count == 2
+ - firewall.hcloud_firewall.rules | selectattr('direction','equalto','out') | list | count == 1
+ - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','icmp') | list | count == 1
+ - firewall.hcloud_firewall.rules | selectattr('protocol','equalto','tcp') | list | count == 2
+ - firewall.hcloud_firewall.rules | selectattr('port','equalto','80') | list | count == 2
+ - firewall.hcloud_firewall.rules | selectattr('description', 'equalto', 'allow tcp out') | list | count == 1
+
+- name: test update firewall rules idempotence
+ hcloud_firewall:
+ name: "{{ hcloud_firewall_name }}"
+ rules:
+ - direction: in
+ protocol: icmp
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ - direction: in
+ protocol: tcp
+ port: 80
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ - direction: out
+ protocol: tcp
+ port: 80
+ destination_ips:
+ - 0.0.0.0/0
+ - ::/0
+ description: allow tcp out
+ labels:
+ key: value
+ my-label: label
+ register: result
+- name: verify update firewall rules idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test update firewall with check mode
+ hcloud_firewall:
+ id: "{{ firewall.hcloud_firewall.id }}"
+ name: "changed-{{ hcloud_firewall_name }}"
+ register: result
+ check_mode: yes
+- name: test create firewall with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test update firewall
+ hcloud_firewall:
+ id: "{{ firewall.hcloud_firewall.id }}"
+ name: "changed-{{ hcloud_firewall_name }}"
+ labels:
+ key: value
+ register: result
+- name: test update firewall
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_firewall.name == "changed-{{ hcloud_firewall_name }}"
+
+- name: test update firewall with same labels
+ hcloud_firewall:
+ id: "{{ firewall.hcloud_firewall.id }}"
+ name: "changed-{{ hcloud_firewall_name }}"
+ labels:
+ key: value
+ register: result
+- name: test update firewall with same labels
+ assert:
+ that:
+ - result is not changed
+
+- name: test update firewall with other labels
+ hcloud_firewall:
+ id: "{{ firewall.hcloud_firewall.id }}"
+ name: "changed-{{ hcloud_firewall_name }}"
+ labels:
+ key: value
+ test: "val123"
+ register: result
+- name: test update firewall with other labels
+ assert:
+ that:
+ - result is changed
+
+- name: test rename firewall
+ hcloud_firewall:
+ id: "{{ firewall.hcloud_firewall.id }}"
+ name: "{{ hcloud_firewall_name }}"
+ register: result
+- name: test rename firewall
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_firewall.name == "{{ hcloud_firewall_name }}"
+
+- name: absent firewall
+ hcloud_firewall:
+ id: "{{ firewall.hcloud_firewall.id }}"
+ state: absent
+ register: result
+- name: verify absent server
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml
new file mode 100644
index 000000000..ebd5ccc38
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_floating_ip_name: "{{hcloud_prefix}}-i"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-fip-t"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml
new file mode 100644
index 000000000..8ada4172f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip/tasks/main.yml
@@ -0,0 +1,470 @@
+# 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 server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: ubuntu-18.04
+ state: started
+ location: "fsn1"
+ register: main_server
+- name: verify setup server
+ assert:
+ that:
+ - main_server is changed
+
+- name: setup another server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}2"
+ server_type: cx11
+ image: ubuntu-18.04
+ state: started
+ register: main_server2
+- name: verify setup another server
+ assert:
+ that:
+ - main_server2 is changed
+
+- name: test missing type parameter on create Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing type parameter on create Floating IP
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: type"'
+
+- name: test missing required parameters on create Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create Floating IP
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "one of the following is required: home_location, server"'
+
+- name: test missing type parameter on delete Floating IP
+ hcloud_floating_ip:
+ type: ipv4
+ home_location: "fsn1"
+ state: "absent"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing type parameter on delete Floating IP
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "one of the following is required: id, name"'
+
+
+- name: test invalid type
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv5
+ home_location: "fsn1"
+ register: result
+ ignore_errors: yes
+- name: verify invalid type
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "value of type must be one of: ipv4, ipv6, got: ipv5"'
+
+- name: test invalid location
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "abc"
+ register: result
+ ignore_errors: yes
+- name: verify invalid location
+ assert:
+ that:
+ - result is failed
+ - result.msg == "invalid input in fields 'server', 'home_location'"
+
+- name: test create Floating IP with check mode
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "Web Server"
+ type: ipv4
+ home_location: "fsn1"
+ register: floatingIP
+ check_mode: yes
+- name: verify test create Floating IP with check mode
+ assert:
+ that:
+ - floatingIP is changed
+
+- name: test create Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "Web Server"
+ type: ipv4
+ home_location: "fsn1"
+ register: floatingIP
+- name: verify test create Floating IP
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.name ==hcloud_floating_ip_name
+ - floatingIP.hcloud_floating_ip.description == "Web Server"
+ - floatingIP.hcloud_floating_ip.home_location == "fsn1"
+
+- name: test create Floating IP idempotency
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "Web Server"
+ type: ipv4
+ home_location: "fsn1"
+ register: floatingIP
+- name: verify test create Floating IP idempotency
+ assert:
+ that:
+ - floatingIP is not changed
+
+- name: test update Floating IP with check mode
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "changed-description"
+ type: ipv4
+ home_location: "fsn1"
+ check_mode: yes
+ register: floatingIP
+- name: verify test create Floating IP with check mode
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.description == "Web Server"
+
+- name: test update Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "changed-description"
+ type: ipv4
+ home_location: "fsn1"
+ labels:
+ key: value
+ register: floatingIP
+- name: verify test update Floating IP
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.description == "changed-description"
+
+- name: test update Floating IP idempotency
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "changed-description"
+ type: ipv4
+ home_location: "fsn1"
+ labels:
+ key: value
+ register: floatingIP
+- name: verify test update Floating IP idempotency
+ assert:
+ that:
+ - floatingIP is not changed
+
+- name: test update Floating IP with same labels
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "fsn1"
+ labels:
+ key: value
+ register: floatingIP
+- name: verify test update Floating IP with same labels
+ assert:
+ that:
+ - floatingIP is not changed
+
+- name: test update Floating IP with other labels
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "fsn1"
+ labels:
+ key: value
+ other: label
+ register: floatingIP
+- name: verify test update Floating IP with other labels
+ assert:
+ that:
+ - floatingIP is changed
+
+- name: test update Floating IP with other labels in different order
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "fsn1"
+ labels:
+ other: label
+ key: value
+ register: floatingIP
+- name: verify test update Floating IP with other labels in different order
+ assert:
+ that:
+ - floatingIP is not changed
+
+- name: test assign Floating IP with checkmode
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "changed-description"
+ type: ipv4
+ server: "{{ main_server.hcloud_server.name }}"
+ check_mode: yes
+ register: floatingIP
+- name: verify test assign Floating IP with checkmode
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.server != "{{ main_server.hcloud_server.name }}"
+
+- name: test assign Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "changed-description"
+ type: ipv4
+ server: "{{ main_server.hcloud_server.name }}"
+ register: floatingIP
+- name: verify test assign Floating IP
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.server == "{{ main_server.hcloud_server.name }}"
+
+- name: test assign Floating IP idempotency
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ description: "changed-description"
+ type: ipv4
+ server: "{{ main_server.hcloud_server.name }}"
+ register: floatingIP
+- name: verify test unassign Floating IPidempotency
+ assert:
+ that:
+ - floatingIP is not changed
+
+- name: test unassign Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "fsn1"
+ register: floatingIP
+- name: verify test unassign Floating IP
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.server != "{{ main_server.hcloud_server.name }}"
+
+- name: test unassign Floating IP idempotency
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "fsn1"
+ register: floatingIP
+- name: verify test unassign Floating IPidempotency
+ assert:
+ that:
+ - floatingIP is not changed
+
+- name: test assign Floating IP again
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ server: "{{ main_server.hcloud_server.name }}"
+ register: floatingIP
+- name: verify test assign Floating IP again
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.server == "{{ main_server.hcloud_server.name }}"
+
+- name: test already assigned Floating IP assign without force
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ server: "{{ main_server2.hcloud_server.name }}"
+ register: floatingIP
+- name: verify test already assigned Floating IP assign without force
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.server == "{{ main_server.hcloud_server.name }}"
+
+- name: test already assigned Floating IP assign with force
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ force: yes
+ server: "{{ main_server2.hcloud_server.name }}"
+ register: floatingIP
+- name: verify test already assigned Floating IP assign with force
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.server == "{{ main_server2.hcloud_server.name }}"
+
+- name: test update Floating IP delete protection
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ delete_protection: true
+ register: floatingIP
+- name: verify update Floating IP delete protection
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.delete_protection is sameas true
+
+- name: test update Floating IP delete protection idempotency
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ delete_protection: true
+ register: floatingIP
+- name: verify update Floating IP delete protection idempotency
+ assert:
+ that:
+ - floatingIP is not changed
+ - floatingIP.hcloud_floating_ip.delete_protection is sameas true
+
+- name: test Floating IP without delete protection set to be idempotent
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ register: floatingIP
+- name: verify Floating IP without delete protection set to be idempotent
+ assert:
+ that:
+ - floatingIP is not changed
+ - floatingIP.hcloud_floating_ip.delete_protection is sameas true
+
+- name: test delete Floating IP fails if it is protected
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: "absent"
+ register: result
+ ignore_errors: yes
+- name: verify test delete floating ip
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "Floating IP deletion is protected"'
+
+- name: test update Floating IP delete protection
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ delete_protection: false
+ register: floatingIP
+- name: verify update Floating IP delete protection
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.delete_protection is sameas false
+
+- name: test delete floating ip
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete floating ip
+ assert:
+ that:
+ - result is changed
+
+- name: test create ipv6 floating ip
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv6
+ home_location: "fsn1"
+ state: "present"
+ register: result
+- name: verify test create ipv6 floating ip
+ assert:
+ that:
+ - result is changed
+
+- name: test delete ipv6 floating ip
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete ipv6 floating ip
+ assert:
+ that:
+ - result is changed
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is changed
+- name: cleanup another server
+ hcloud_server:
+ name: "{{ main_server2.hcloud_server.name }}"
+ state: absent
+ register: result
+- name: verify cleanup another server
+ assert:
+ that:
+ - result is changed
+
+- name: test create Floating IP with delete protection
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: fsn1
+ delete_protection: true
+ register: floatingIP
+- name: verify create Floating IP with delete protection
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.delete_protection is sameas true
+
+- name: test delete Floating IP fails if it is protected
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: "absent"
+ register: result
+ ignore_errors: yes
+- name: verify test delete floating ip
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "Floating IP deletion is protected"'
+
+- name: test update Floating IP delete protection
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ delete_protection: false
+ register: floatingIP
+- name: verify update Floating IP delete protection
+ assert:
+ that:
+ - floatingIP is changed
+ - floatingIP.hcloud_floating_ip.delete_protection is sameas false
+
+- name: test delete floating ip
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete floating ip
+ assert:
+ that:
+ - result is changed
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/defaults/main.yml
new file mode 100644
index 000000000..f4c6a9fc9
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/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_floating_ip_name: "{{hcloud_prefix}}-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml
new file mode 100644
index 000000000..9ca1c2a4a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_floating_ip_info/tasks/main.yml
@@ -0,0 +1,87 @@
+# 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 ensure floating ip is absent
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: absent
+
+- name: setup floating ip
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ home_location: "fsn1"
+ type: ipv4
+ labels:
+ key: value
+ register: test_floating_ip
+
+- name: verify setup floating ip
+ assert:
+ that:
+ - test_floating_ip is changed
+
+- name: test gather hcloud floating ip infos
+ hcloud_floating_ip_info:
+ register: hcloud_floating_ips
+- name: verify test gather hcloud floating ip infos
+ assert:
+ that:
+ - hcloud_floating_ips.hcloud_floating_ip_info| list | count >= 1
+
+- name: test gather hcloud floating ip infos in check mode
+ hcloud_floating_ip_info:
+ check_mode: yes
+ register: hcloud_floating_ips
+
+- name: verify test gather hcloud floating ip infos in check mode
+ assert:
+ that:
+ - hcloud_floating_ips.hcloud_floating_ip_info| list | count >= 1
+
+
+- name: test gather hcloud floating ip infos with correct label selector
+ hcloud_floating_ip_info:
+ label_selector: "key=value"
+ register: hcloud_floating_ips
+- name: verify test gather hcloud floating ip with correct label selector
+ assert:
+ that:
+ - hcloud_floating_ips.hcloud_floating_ip_info|selectattr('name','equalto','{{ test_floating_ip.hcloud_floating_ip.name }}') | list | count == 1
+
+- name: test gather hcloud floating ip infos with wrong label selector
+ hcloud_floating_ip_info:
+ label_selector: "key!=value"
+ register: hcloud_floating_ips
+- name: verify test gather hcloud floating ip with wrong label selector
+ assert:
+ that:
+ - hcloud_floating_ips.hcloud_floating_ip_info | list | count == 0
+
+- name: test gather hcloud floating ip infos with correct id
+ hcloud_floating_ip_info:
+ id: "{{test_floating_ip.hcloud_floating_ip.id}}"
+ register: hcloud_floating_ips
+- name: verify test gather hcloud floating ip with correct id
+ assert:
+ that:
+ - hcloud_floating_ips.hcloud_floating_ip_info|selectattr('name','equalto','{{ test_floating_ip.hcloud_floating_ip.name }}') | list | count == 1
+
+- name: test gather hcloud floating ip infos with wrong id
+ hcloud_floating_ip_info:
+ id: "{{test_floating_ip.hcloud_floating_ip.id}}1"
+ register: result
+ ignore_errors: yes
+- name: verify test gather hcloud floating ip with wrong id
+ assert:
+ that:
+ - result is failed
+
+- name: cleanup
+ hcloud_floating_ip:
+ id: "{{ test_floating_ip.hcloud_floating_ip.id }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml
new file mode 100644
index 000000000..7c25d171d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/defaults/main.yml
@@ -0,0 +1,7 @@
+# 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_test_image_name: "always-there-snapshot"
+hcloud_test_image_id: 10164049
+hcloud_test_image_name_os: "ubuntu-22.04"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml
new file mode 100644
index 000000000..16ed44a28
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_image_info/tasks/main.yml
@@ -0,0 +1,93 @@
+# 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: test gather hcloud image infos with type system
+ hcloud_image_info:
+ register: hcloud_images
+- name: verify test gather hcloud image infos in check mode
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info| list | count > 2
+
+- name: test gather hcloud image infos in check mode
+ hcloud_image_info:
+ check_mode: yes
+ register: hcloud_images
+
+- name: verify test gather hcloud image infos in check mode
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info| list | count > 2
+
+
+- name: test gather hcloud image infos with correct label selector
+ hcloud_image_info:
+ label_selector: "key=value"
+ type: snapshot
+ register: hcloud_images
+- name: verify test gather hcloud image with correct label selector
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info|selectattr('description','equalto','{{ hcloud_test_image_name }}') | list | count == 1
+
+- name: test gather hcloud image infos with wrong label selector
+ hcloud_image_info:
+ label_selector: "key!=value"
+ type: snapshot
+ register: hcloud_images
+- name: verify test gather hcloud image with wrong label selector
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info | list | count == 0
+
+- name: test gather hcloud image infos with correct id
+ hcloud_image_info:
+ id: "{{hcloud_test_image_id}}"
+ type: snapshot
+ register: hcloud_images
+- name: verify test gather hcloud image with correct id
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info|selectattr('description','equalto','{{ hcloud_test_image_name }}') | list | count == 1
+
+- name: test gather hcloud image infos with wrong id
+ hcloud_image_info:
+ id: "{{hcloud_test_image_id}}1"
+ type: snapshot
+ ignore_errors: yes
+ register: result
+- name: verify test gather hcloud image with wrong id
+ assert:
+ that:
+ - result is failed
+
+- name: test gather hcloud image infos with name
+ hcloud_image_info:
+ name: "{{ hcloud_test_image_name_os }}"
+ register: hcloud_images
+- name: verify test gather hcloud image infos with name
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info | list | count == 1
+ - hcloud_images.hcloud_image_info[0].architecture == "x86"
+
+- name: test gather hcloud image infos with name and architecture
+ hcloud_image_info:
+ name: "{{ hcloud_test_image_name_os }}"
+ architecture: arm
+ register: hcloud_images
+- name: verify test gather hcloud image infos with name
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info | list | count == 1
+ - hcloud_images.hcloud_image_info[0].architecture == "arm"
+
+- name: test gather hcloud image infos with architecture
+ hcloud_image_info:
+ architecture: arm
+ register: hcloud_images
+- name: verify test gather hcloud image infos with name
+ assert:
+ that:
+ - hcloud_images.hcloud_image_info | selectattr('architecture','equalto','x86') | list | count == 0
+ - hcloud_images.hcloud_image_info | selectattr('architecture','equalto','arm') | list | count > 2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml
new file mode 100644
index 000000000..7f431cd8d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/defaults/main.yml
@@ -0,0 +1,5 @@
+# Copyright: (c) 2020, 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_load_balancer_name: "{{hcloud_prefix}}-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml
new file mode 100644
index 000000000..a25e550d0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer/tasks/main.yml
@@ -0,0 +1,247 @@
+# Copyright: (c) 2020, 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_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+- name: verify setup
+ assert:
+ that:
+ - result is success
+- name: test missing required parameters on create Load Balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create Load Balancer
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: load_balancer_type"'
+
+- name: test create Load Balancer with check mode
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ state: present
+ register: result
+ check_mode: yes
+- name: test create Load Balancer with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test create Load Balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ state: present
+ register: main_load_balancer
+- name: verify create Load Balancer
+ assert:
+ that:
+ - main_load_balancer is changed
+ - main_load_balancer.hcloud_load_balancer.name == "{{ hcloud_load_balancer_name }}"
+ - main_load_balancer.hcloud_load_balancer.load_balancer_type == "lb11"
+
+- name: test create Load Balancer idempotence
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ state: present
+ register: result
+- name: verify create Load Balancer idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test change Load Balancer type
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ load_balancer_type: lb21
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify change Load Balancer type
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_load_balancer.load_balancer_type == "lb21"
+
+- name: test Load Balancer without type set to be idempotent
+ hcloud_load_balancer:
+ name: "{{hcloud_load_balancer_name}}"
+ register: result_after_test
+- name: verify test Load Balancer without type set to be idempotent
+ assert:
+ that:
+ - result_after_test is not changed
+ - result_after_test.hcloud_load_balancer.load_balancer_type == "lb21"
+
+- name: test update Load Balancer protection
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ delete_protection: true
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify update Load Balancer protection
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_load_balancer.delete_protection is sameas true
+
+- name: test Load Balancer without protection set to be idempotent
+ hcloud_load_balancer:
+ name: "{{hcloud_load_balancer_name}}"
+ register: result_after_test
+- name: verify test Load Balancer without protection set to be idempotent
+ assert:
+ that:
+ - result_after_test is not changed
+ - result_after_test.hcloud_load_balancer.delete_protection is sameas true
+
+- name: test delete Load Balancer fails if it is protected
+ hcloud_load_balancer:
+ name: "{{hcloud_load_balancer_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete Load Balancer fails if it is protected
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "load balancer deletion is protected"'
+
+- name: test remove Load Balancer protection
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ delete_protection: false
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify remove Load Balancer protection
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_load_balancer.delete_protection is sameas false
+
+- name: absent Load Balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+- name: verify absent Load Balancer
+ assert:
+ that:
+ - result is success
+
+- name: test create Load Balancer with labels
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ labels:
+ key: value
+ mylabel: "val123"
+ state: present
+ register: main_load_balancer
+- name: verify create Load Balancer with labels
+ assert:
+ that:
+ - main_load_balancer is changed
+ - main_load_balancer.hcloud_load_balancer.labels.key == "value"
+ - main_load_balancer.hcloud_load_balancer.labels.mylabel == "val123"
+
+- name: test update Load Balancer with labels
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ labels:
+ key: other
+ mylabel: "val123"
+ state: present
+ register: main_load_balancer
+- name: verify update Load Balancer with labels
+ assert:
+ that:
+ - main_load_balancer is changed
+ - main_load_balancer.hcloud_load_balancer.labels.key == "other"
+ - main_load_balancer.hcloud_load_balancer.labels.mylabel == "val123"
+
+- name: test update Load Balancer with labels in other order
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ labels:
+ mylabel: "val123"
+ key: other
+ state: present
+ register: main_load_balancer
+- name: verify update Load Balancer with labels in other order
+ assert:
+ that:
+ - main_load_balancer is not changed
+
+- name: cleanup with labels
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
+
+- name: test create Load Balancer with delete protection
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ delete_protection: true
+ register: main_load_balancer
+- name: verify create Load Balancer with delete protection
+ assert:
+ that:
+ - main_load_balancer is changed
+ - main_load_balancer.hcloud_load_balancer.delete_protection is sameas true
+
+- name: test delete Load Balancer fails if it is protected
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: "absent"
+ register: result
+ ignore_errors: yes
+- name: verify test delete Load Balancer
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "load balancer deletion is protected"'
+
+- name: test update Load Balancer delete protection
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ delete_protection: false
+ register: main_load_balancer
+- name: verify update Load Balancer delete protection
+ assert:
+ that:
+ - main_load_balancer is changed
+ - main_load_balancer.hcloud_load_balancer.delete_protection is sameas false
+
+- name: test delete Load Balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete Load Balancer
+ assert:
+ that:
+ - result is changed
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml
new file mode 100644
index 000000000..326973a78
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_load_balancer_name: "{{hcloud_prefix}}-i"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-lb-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml
new file mode 100644
index 000000000..9e6528858
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_info/tasks/main.yml
@@ -0,0 +1,128 @@
+# 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 ensure Load Balancer is absent
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+- name: setup server
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ server_type: cx11
+ image: ubuntu-20.04
+ state: started
+ register: server
+- name: verify setup server
+ assert:
+ that:
+ - server is success
+- name: setup Load Balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ labels:
+ key: value
+ register: test_load_balancer
+
+- name: verify setup Load Balancer
+ assert:
+ that:
+ - test_load_balancer is changed
+
+- name: test create load_balancer target
+ hcloud_load_balancer_target:
+ type: "server"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ server: "{{hcloud_server_name}}"
+ state: present
+ register: load_balancer_target
+- name: verify create load_balancer target
+ assert:
+ that:
+ - load_balancer_target is success
+- name: test create load_balancer service
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "http"
+ listen_port: 80
+ state: present
+ register: load_balancer_service
+- name: verify create load_balancer service
+ assert:
+ that:
+ - load_balancer_service is success
+
+- name: test gather hcloud Load Balancer infos
+ hcloud_load_balancer_info:
+ id: "{{test_load_balancer.hcloud_load_balancer.id}}"
+ register: hcloud_load_balancers
+- name: verify test gather hcloud Load Balancer infos
+ assert:
+ that:
+ - hcloud_load_balancers.hcloud_load_balancer_info| list | count >= 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].targets | list | count == 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].targets | selectattr('type','equalto','server') | list | count == 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].targets | selectattr('server','equalto','{{ hcloud_server_name }}') | list | count == 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].services | list | count == 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].services | selectattr('protocol','equalto','http') | list | count == 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].services | selectattr('listen_port','equalto',80) | list | count == 1
+ - hcloud_load_balancers.hcloud_load_balancer_info[0].services | selectattr('destination_port','equalto',80) | list | count == 1
+
+- name: test gather hcloud Load Balancer infos in check mode
+ hcloud_load_balancer_info:
+ check_mode: yes
+ register: hcloud_load_balancers
+
+- name: verify test gather hcloud Load Balancer infos in check mode
+ assert:
+ that:
+ - hcloud_load_balancers.hcloud_load_balancer_info| list | count >= 1
+
+
+- name: test gather hcloud Load Balancer infos with correct label selector
+ hcloud_load_balancer_info:
+ label_selector: "key=value"
+ register: hcloud_load_balancers
+- name: verify test gather hcloud Load Balancer with correct label selector
+ assert:
+ that:
+ - hcloud_load_balancers.hcloud_load_balancer_info|selectattr('name','equalto','{{ test_load_balancer.hcloud_load_balancer.name }}') | list | count == 1
+
+- name: test gather hcloud Load Balancer infos with wrong label selector
+ hcloud_load_balancer_info:
+ label_selector: "key!=value"
+ register: hcloud_load_balancers
+- name: verify test gather hcloud Load Balancer with wrong label selector
+ assert:
+ that:
+ - hcloud_load_balancers.hcloud_load_balancer_info | list | count == 0
+
+- name: test gather hcloud Load Balancer infos with correct id
+ hcloud_load_balancer_info:
+ id: "{{test_load_balancer.hcloud_load_balancer.id}}"
+ register: hcloud_load_balancers
+- name: verify test gather hcloud Load Balancer with correct id
+ assert:
+ that:
+ - hcloud_load_balancers.hcloud_load_balancer_info|selectattr('name','equalto','{{ test_load_balancer.hcloud_load_balancer.name }}') | list | count == 1
+
+- name: test gather hcloud Load Balancer infos with wrong id
+ hcloud_load_balancer_info:
+ id: "{{test_load_balancer.hcloud_load_balancer.id}}1"
+ register: result
+ ignore_errors: yes
+- name: verify test gather hcloud Load Balancer with wrong id
+ assert:
+ that:
+ - result is failed
+
+- name: cleanup
+ hcloud_load_balancer:
+ id: "{{ test_load_balancer.hcloud_load_balancer.id }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml
new file mode 100644
index 000000000..6abf9ceec
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_network_name: "{{hcloud_prefix}}-lb-n"
+hcloud_load_balancer_name: "{{hcloud_prefix}}-lb-n"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml
new file mode 100644
index 000000000..9a1bf5175
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_network/tasks/main.yml
@@ -0,0 +1,181 @@
+# 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 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: "cloud"
+ network_zone: "eu-central"
+ state: present
+ register: subnetwork
+- name: verify subnetwork
+ assert:
+ that:
+ - subnetwork is success
+
+- name: setup load_balancer
+ hcloud_load_balancer:
+ name: "{{hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ state: present
+ location: "fsn1"
+ register: load_balancer
+- name: verify setup load_balancer
+ assert:
+ that:
+ - load_balancer is success
+
+- name: test missing required parameters on create load_balancer network
+ hcloud_load_balancer_network:
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create load_balancer network
+ assert:
+ that:
+ - result is failed
+ - '"missing required arguments:" in result.msg'
+
+- name: test fail load balancer does not exist
+ hetzner.hcloud.hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: does-not-exist
+ state: present
+ register: result
+ ignore_errors: true
+- name: verify test fail load_balancer does not exist
+ assert:
+ that:
+ - result is failed
+ - "result.msg == 'Load balancer does not exist: does-not-exist'"
+
+- name: test fail network does not exist
+ hetzner.hcloud.hcloud_load_balancer_network:
+ network: does-not-exist
+ load_balancer: "{{ hcloud_load_balancer_name }}"
+ state: present
+ register: result
+ ignore_errors: true
+- name: verify test fail network does not exist
+ assert:
+ that:
+ - result is failed
+ - "result.msg == 'Network does not exist: does-not-exist'"
+
+- name: test create load_balancer network with checkmode
+ hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ state: present
+ register: result
+ check_mode: yes
+- name: verify test create load_balancer network with checkmode
+ assert:
+ that:
+ - result is changed
+
+- name: test create load_balancer network
+ hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ state: present
+ register: load_balancerNetwork
+- name: verify create load_balancer network
+ assert:
+ that:
+ - load_balancerNetwork is changed
+ - load_balancerNetwork.hcloud_load_balancer_network.network == hcloud_network_name
+ - load_balancerNetwork.hcloud_load_balancer_network.load_balancer == hcloud_load_balancer_name
+
+- name: test create load_balancer network idempotency
+ hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ state: present
+ register: load_balancerNetwork
+- name: verify create load_balancer network idempotency
+ assert:
+ that:
+ - load_balancerNetwork is not changed
+
+- name: test absent load_balancer network
+ hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ state: absent
+ register: result
+- name: verify test absent load_balancer network
+ assert:
+ that:
+ - result is changed
+
+- name: test create load_balancer network with specified ip
+ hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ ip: "10.0.0.2"
+ state: present
+ register: load_balancerNetwork
+- name: verify create load_balancer network with specified ip
+ assert:
+ that:
+ - load_balancerNetwork is changed
+ - load_balancerNetwork.hcloud_load_balancer_network.network == hcloud_network_name
+ - load_balancerNetwork.hcloud_load_balancer_network.load_balancer == hcloud_load_balancer_name
+ - load_balancerNetwork.hcloud_load_balancer_network.ip == "10.0.0.2"
+
+- name: cleanup create load_balancer network with specified ip
+ hcloud_load_balancer_network:
+ network: "{{ hcloud_network_name }}"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ state: absent
+ register: result
+- name: verify cleanup create load_balancer network with specified ip
+ assert:
+ that:
+ - result is changed
+
+- name: cleanup load_balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+- name: verify cleanup load_balancer
+ assert:
+ that:
+ - result is success
+
+- name: cleanup subnetwork
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "cloud"
+ 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
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/defaults/main.yml
new file mode 100644
index 000000000..ebf456312
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/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_load_balancer_name: "{{hcloud_prefix}}-lb-target"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml
new file mode 100644
index 000000000..b0db6bb63
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_service/tasks/main.yml
@@ -0,0 +1,126 @@
+# 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 load_balancer
+ hcloud_load_balancer:
+ name: "{{hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ state: present
+ location: "fsn1"
+ register: load_balancer
+- name: verify setup load_balancer
+ assert:
+ that:
+ - load_balancer is success
+
+- name: test fail load balancer does not exist
+ hetzner.hcloud.hcloud_load_balancer_service:
+ load_balancer: does-not-exist
+ protocol: http
+ listen_port: 80
+ state: present
+ register: result
+ ignore_errors: true
+- name: verify test fail load_balancer does not exist
+ assert:
+ that:
+ - result is failed
+ - "result.msg == 'Load balancer does not exist: does-not-exist'"
+
+- name: test create load_balancer service with checkmode
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "http"
+ listen_port: 80
+ state: present
+ register: result
+ check_mode: yes
+- name: verify test create load_balancer service with checkmode
+ assert:
+ that:
+ - result is changed
+
+- name: test create load_balancer service
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "http"
+ listen_port: 80
+ state: present
+ register: load_balancer_service
+- name: verify create load_balancer service
+ assert:
+ that:
+ - load_balancer_service is changed
+ - load_balancer_service.hcloud_load_balancer_service.protocol == "http"
+ - load_balancer_service.hcloud_load_balancer_service.listen_port == 80
+ - load_balancer_service.hcloud_load_balancer_service.destination_port == 80
+ - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false
+
+- name: test create load_balancer service idempotency
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "http"
+ listen_port: 80
+ state: present
+ register: load_balancer_service
+- name: verify create load_balancer service idempotency
+ assert:
+ that:
+ - load_balancer_service is not changed
+
+- name: test update load_balancer service
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "tcp"
+ listen_port: 80
+ state: present
+ register: load_balancer_service
+- name: verify create load_balancer service
+ assert:
+ that:
+ - load_balancer_service is changed
+ - load_balancer_service.hcloud_load_balancer_service.protocol == "tcp"
+ - load_balancer_service.hcloud_load_balancer_service.listen_port == 80
+ - load_balancer_service.hcloud_load_balancer_service.destination_port == 80
+ - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false
+
+- name: test absent load_balancer service
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "http"
+ listen_port: 80
+ state: absent
+ register: result
+- name: verify test absent load_balancer service
+ assert:
+ that:
+ - result is changed
+
+- name: test create load_balancer service with http
+ hcloud_load_balancer_service:
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ protocol: "http"
+ listen_port: 80
+ http:
+ cookie_name: "Test"
+ sticky_sessions: yes
+ state: present
+ register: load_balancer_service
+- name: verify create load_balancer service
+ assert:
+ that:
+ - load_balancer_service is changed
+ - load_balancer_service.hcloud_load_balancer_service.protocol == "http"
+ - load_balancer_service.hcloud_load_balancer_service.listen_port == 80
+ - load_balancer_service.hcloud_load_balancer_service.destination_port == 80
+ - load_balancer_service.hcloud_load_balancer_service.proxyprotocol is sameas false
+
+- name: cleanup load_balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+- name: verify cleanup load_balancer
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml
new file mode 100644
index 000000000..180133fde
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/defaults/main.yml
@@ -0,0 +1,7 @@
+# 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 | truncate(45, True, '', 0) }}-lb-t"
+hcloud_load_balancer_name: "{{hcloud_prefix}}-lb-target"
+hcloud_testing_ip: "176.9.59.39"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml
new file mode 100644
index 000000000..bd96c1a54
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_target/tasks/main.yml
@@ -0,0 +1,154 @@
+# 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 server
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ server_type: cx11
+ image: ubuntu-20.04
+ state: started
+ location: "fsn1"
+ register: server
+- name: verify setup server
+ assert:
+ that:
+ - server is success
+
+- name: setup load_balancer
+ hcloud_load_balancer:
+ name: "{{hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ state: present
+ location: "fsn1"
+ register: load_balancer
+- name: verify setup load_balancer
+ assert:
+ that:
+ - load_balancer is success
+
+- name: test fail load balancer does not exist
+ hetzner.hcloud.hcloud_load_balancer_target:
+ type: server
+ load_balancer: does-not-exist
+ server: "{{ hcloud_server_name }}"
+ register: result
+ ignore_errors: true
+- name: verify test fail load_balancer does not exist
+ assert:
+ that:
+ - result is failed
+ - "result.msg == 'Load balancer does not exist: does-not-exist'"
+
+- name: test fail server does not exist
+ hetzner.hcloud.hcloud_load_balancer_target:
+ type: server
+ load_balancer: "{{ hcloud_load_balancer_name }}"
+ server: does-not-exist
+ register: result
+ ignore_errors: true
+- name: verify test fail server does not exist
+ assert:
+ that:
+ - result is failed
+ - "result.msg == 'Server not found: does-not-exist'"
+
+- name: test create load_balancer target with checkmode
+ hcloud_load_balancer_target:
+ type: "server"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ server: "{{hcloud_server_name}}"
+ state: present
+ register: result
+ check_mode: yes
+- name: verify test create load_balancer target with checkmode
+ assert:
+ that:
+ - result is changed
+
+- name: test create load_balancer target
+ hcloud_load_balancer_target:
+ type: "server"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ server: "{{hcloud_server_name}}"
+ state: present
+ register: load_balancer_target
+- name: verify create load_balancer target
+ assert:
+ that:
+ - load_balancer_target is changed
+ - load_balancer_target.hcloud_load_balancer_target.type == "server"
+ - load_balancer_target.hcloud_load_balancer_target.server == hcloud_server_name
+ - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name
+
+- name: test create load_balancer target idempotency
+ hcloud_load_balancer_target:
+ type: "server"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ server: "{{hcloud_server_name}}"
+ state: present
+ register: load_balancer_target
+- name: verify create load_balancer target idempotency
+ assert:
+ that:
+ - load_balancer_target is not changed
+
+- name: test absent load_balancer target
+ hcloud_load_balancer_target:
+ type: "server"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ server: "{{hcloud_server_name}}"
+ state: absent
+ register: result
+- name: verify test absent load_balancer target
+ assert:
+ that:
+ - result is changed
+
+- name: test create label_selector target
+ hcloud_load_balancer_target:
+ type: "label_selector"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ label_selector: "application=backend"
+ state: present
+ register: load_balancer_target
+- name: verify create label_selector target
+ assert:
+ that:
+ - load_balancer_target is changed
+ - load_balancer_target.hcloud_load_balancer_target.type == "label_selector"
+ - load_balancer_target.hcloud_load_balancer_target.label_selector == "application=backend"
+ - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name
+
+- name: test create ip target
+ hcloud_load_balancer_target:
+ type: "ip"
+ load_balancer: "{{hcloud_load_balancer_name}}"
+ ip: "{{hcloud_testing_ip}}"
+ state: present
+ register: load_balancer_target
+- name: verify create ip target
+ assert:
+ that:
+ - load_balancer_target is changed
+ - load_balancer_target.hcloud_load_balancer_target.type == "ip"
+ - load_balancer_target.hcloud_load_balancer_target.ip == hcloud_testing_ip
+ - load_balancer_target.hcloud_load_balancer_target.load_balancer == hcloud_load_balancer_name
+
+- name: cleanup load_balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+ until: result is not failed
+ retries: 5
+ delay: 2
+
+- name: cleanup
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml
new file mode 100644
index 000000000..b7fd86316
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/defaults/main.yml
@@ -0,0 +1,5 @@
+# Copyright: (c) 2020, 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_load_balancer_type_name: "lb11"
+hcloud_load_balancer_type_id: 1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml
new file mode 100644
index 000000000..bcd805a83
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_load_balancer_type_info/tasks/main.yml
@@ -0,0 +1,38 @@
+# Copyright: (c) 2020, 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: test gather hcloud Load Balancer type infos
+ hcloud_load_balancer_type_info:
+ register: hcloud_load_balancer_types
+- name: verify test gather hcloud Load Balancer type infos
+ assert:
+ that:
+ - hcloud_load_balancer_types.hcloud_load_balancer_type_info| list | count >= 1
+
+- name: test gather hcloud Load Balancer type infos in check mode
+ hcloud_load_balancer_type_info:
+ check_mode: yes
+ register: hcloud_load_balancer_types
+
+- name: verify test gather hcloud Load Balancer type infos in check mode
+ assert:
+ that:
+ - hcloud_load_balancer_types.hcloud_load_balancer_type_info| list | count >= 1
+
+- name: test gather hcloud Load Balancer type infos with name
+ hcloud_load_balancer_type_info:
+ name: "{{hcloud_load_balancer_type_name}}"
+ register: hcloud_load_balancer_types
+- name: verify test gather hcloud Load Balancer type with name
+ assert:
+ that:
+ - hcloud_load_balancer_types.hcloud_load_balancer_type_info|selectattr('name','equalto','{{ hcloud_load_balancer_type_name }}') | list | count == 1
+
+- name: test gather hcloud Load Balancer type infos with correct id
+ hcloud_load_balancer_type_info:
+ id: "{{hcloud_load_balancer_type_id}}"
+ register: hcloud_load_balancer_types
+- name: verify test gather hcloud Load Balancer type with correct id
+ assert:
+ that:
+ - hcloud_load_balancer_types.hcloud_load_balancer_type_info|selectattr('name','equalto','{{ hcloud_load_balancer_type_name }}') | list | count == 1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/defaults/main.yml
new file mode 100644
index 000000000..0d72a75c2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/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_location_name: "fsn1"
+hcloud_location_id: 1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml
new file mode 100644
index 000000000..99d5880ab
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_location_info/tasks/main.yml
@@ -0,0 +1,57 @@
+# 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: test gather hcloud location infos
+ hcloud_location_info:
+ register: hcloud_location
+
+- name: verify test gather hcloud location infos
+ assert:
+ that:
+ - hcloud_location.hcloud_location_info | list | count >= 5
+
+- name: test gather hcloud location infos in check mode
+ hcloud_location_info:
+ check_mode: yes
+ register: hcloud_location
+
+- name: verify test gather hcloud location infos in check mode
+ assert:
+ that:
+ - hcloud_location.hcloud_location_info | list | count >= 5
+
+- name: test gather hcloud location infos with correct name
+ hcloud_location_info:
+ name: "{{hcloud_location_name}}"
+ register: hcloud_location
+- name: verify test gather hcloud location with correct name
+ assert:
+ that:
+ - hcloud_location.hcloud_location_info|selectattr('name','equalto','{{ hcloud_location_name }}') | list | count == 1
+
+- name: test gather hcloud location infos with wrong name
+ hcloud_location_info:
+ name: "{{hcloud_location_name}}1"
+ register: hcloud_location
+- name: verify test gather hcloud location with wrong name
+ assert:
+ that:
+ - hcloud_location.hcloud_location_info | list | count == 0
+
+- name: test gather hcloud location infos with correct id
+ hcloud_location_info:
+ id: "{{hcloud_location_id}}"
+ register: hcloud_location
+- name: verify test gather hcloud location with correct id
+ assert:
+ that:
+ - hcloud_location.hcloud_location_info|selectattr('name','equalto','{{ hcloud_location_name }}') | list | count == 1
+
+- name: test gather hcloud location infos with wrong id
+ hcloud_location_info:
+ name: "4711"
+ register: hcloud_location
+- name: verify test gather hcloud location with wrong id
+ assert:
+ that:
+ - hcloud_location.hcloud_location_info | list | count == 0
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases
new file mode 100644
index 000000000..4b3a9b36f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/aliases
@@ -0,0 +1,3 @@
+cloud/hcloud
+shippable/hcloud/group1
+disabled
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/defaults/main.yml
new file mode 100644
index 000000000..081eb1472
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/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_network_name: "{{hcloud_prefix}}-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml
new file mode 100644
index 000000000..6c40e4e01
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network/tasks/main.yml
@@ -0,0 +1,215 @@
+# 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_network:
+ name: "{{ hcloud_network_name }}"
+ state: absent
+ register: result
+- name: verify setup
+ assert:
+ that:
+ - result is success
+
+- name: test missing ip_range parameter on create Network
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ register: result
+ ignore_errors: yes
+- name: verify fail missing ip_range parameter on create Network result
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: ip_range"'
+
+- name: test create Network with check mode
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/16"
+ register: result
+ check_mode: yes
+- name: verify create Network with check mode result
+ assert:
+ that:
+ - result is changed
+
+- name: test create Network
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/16"
+ register: network
+- name: verify test create Network result
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.name == "{{hcloud_network_name}}"
+ - network.hcloud_network.ip_range == "10.0.0.0/16"
+
+- name: test create Network idempotence
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/16"
+ register: network
+- name: verify test create network
+ assert:
+ that:
+ - network is not changed
+
+- name: test update Network label
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ labels:
+ key: value
+ register: network
+- name: verify test update Network label
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.labels.key == "value"
+
+- name: test update Network label idempotency
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ labels:
+ key: value
+ register: network
+- name: verify test update Network label idempotency
+ assert:
+ that:
+ - network is not changed
+
+- name: test update Network ip range
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ register: network
+- name: verify test update Network ip range
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.ip_range == "10.0.0.0/8"
+
+- name: test update Network ip range idempotency
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ register: network
+- name: verify test update Network ip range idempotency
+ assert:
+ that:
+ - network is not changed
+
+- name: test update Network delete protection
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ delete_protection: true
+ register: network
+- name: verify test update Network delete protection
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.delete_protection is sameas true
+
+- name: test update Network delete protection idempotency
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ delete_protection: true
+ register: network
+- name: verify test update Network delete protection idempotency
+ assert:
+ that:
+ - network is not changed
+ - network.hcloud_network.delete_protection is sameas true
+
+- name: test Network without delete protection set to be idempotent
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ register: network
+- name: verify test Network without delete protection set to be idempotent
+ assert:
+ that:
+ - network is not changed
+ - network.hcloud_network.delete_protection is sameas true
+
+- name: test delete Network fails if it is protected
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete Network
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "network deletion is protected"'
+
+- name: test update Network delete protection
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ delete_protection: false
+ register: network
+- name: verify test update Network delete protection
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.delete_protection is sameas false
+
+- name: test delete Network
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ state: absent
+ register: result
+- name: verify delete Network
+ assert:
+ that:
+ - result is success
+
+
+- name: test create Network with delete protection
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ ip_range: "10.0.0.0/8"
+ delete_protection: true
+ register: network
+- name: verify create Network with delete protection
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.delete_protection is sameas true
+
+- name: test delete Network fails if it is protected
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete Network
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "network deletion is protected"'
+
+- name: test update Network delete protection
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ delete_protection: false
+ register: network
+- name: verify test update Network delete protection
+ assert:
+ that:
+ - network is changed
+ - network.hcloud_network.delete_protection is sameas false
+
+- name: test delete Network
+ hcloud_network:
+ name: "{{hcloud_network_name}}"
+ state: absent
+ register: result
+- name: verify delete Network
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/defaults/main.yml
new file mode 100644
index 000000000..f8a5279fb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/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_network_name: "{{hcloud_prefix}}-integration"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml
new file mode 100644
index 000000000..e7924a8d0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_network_info/tasks/main.yml
@@ -0,0 +1,117 @@
+# 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 ensure network is absent
+ hcloud_network:
+ name: "{{ hcloud_network_name }}"
+ state: absent
+ register: result
+
+- name: create network
+ hcloud_network:
+ name: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ labels:
+ key: value
+ register: main_network
+- name: verify create network
+ assert:
+ that:
+ - main_network is changed
+ - main_network.hcloud_network.name == "{{ hcloud_network_name }}"
+ - main_network.hcloud_network.ip_range == "10.0.0.0/16"
+- name: create subnetwork
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ type: server
+ network_zone: eu-central
+ ip_range: "10.0.1.0/24"
+ register: main_subnetwork
+- name: verify create subnetwork
+ assert:
+ that:
+ - main_subnetwork is changed
+ - main_subnetwork.hcloud_subnetwork.network == "{{ hcloud_network_name }}"
+- name: create route
+ hcloud_route:
+ network: "{{ hcloud_network_name }}"
+ destination: "10.0.3.0/24"
+ gateway: "10.0.2.1"
+ register: main_route
+- name: verify create route
+ assert:
+ that:
+ - main_route is changed
+ - main_route.hcloud_route.network == "{{ hcloud_network_name }}"
+
+- name: test gather hcloud network info in check mode
+ hcloud_network_info:
+ check_mode: yes
+ register: hcloud_network
+- name: verify test gather hcloud network info in check mode
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count >= 1
+
+
+- name: test gather hcloud network info with correct label selector
+ hcloud_network_info:
+ label_selector: "key=value"
+ register: hcloud_network
+- name: verify test gather hcloud network with correct label selector
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count >= 1
+
+- name: test gather hcloud network info with wrong label selector
+ hcloud_network_info:
+ label_selector: "key!=value"
+ register: hcloud_network
+- name: verify test gather hcloud network with wrong label selector
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | list | count == 0
+
+- name: test gather hcloud network info with correct name
+ hcloud_network_info:
+ name: "{{hcloud_network_name}}"
+ register: hcloud_network
+- name: verify test gather hcloud network with correct name
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count == 1
+ - hcloud_network.hcloud_network_info[0].subnetworks | list | count >= 1
+ - hcloud_network.hcloud_network_info[0].routes | list | count >= 1
+
+- name: test gather hcloud network info with wrong name
+ hcloud_network_info:
+ name: "{{hcloud_network_name}}1"
+ register: hcloud_network
+- name: verify test gather hcloud network with wrong name
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | list | count == 0
+
+- name: test gather hcloud network info with correct id
+ hcloud_network_info:
+ id: "{{main_network.hcloud_network.id}}"
+ register: hcloud_network
+- name: verify test gather hcloud network with correct id
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | selectattr('name','equalto','{{ hcloud_network_name }}') | list | count == 1
+
+- name: test gather hcloud network info with wrong id
+ hcloud_network_info:
+ name: "4711"
+ register: hcloud_network
+- name: verify test gather hcloud network with wrong id
+ assert:
+ that:
+ - hcloud_network.hcloud_network_info | list | count == 0
+
+- name: cleanup
+ hcloud_network:
+ name: "{{ hcloud_network_name }}"
+ state: absent
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml
new file mode 100644
index 000000000..21ce3429a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_placement_group_name: "{{hcloud_prefix}}-i"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml
new file mode 100644
index 000000000..d79aa0c35
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_placement_group/tasks/main.yml
@@ -0,0 +1,169 @@
+# Copyright: (c) 2020, 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 placement group to be absent
+ hcloud_placement_group:
+ name: "{{ hcloud_placement_group_name }}"
+ state: absent
+
+- name: setup server to be absent
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+
+- name: test missing required parameters on create placement group
+ hcloud_placement_group:
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create placement group
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "one of the following is required: id, name"'
+
+- name: test create placement group with check mode
+ hcloud_placement_group:
+ name: "{{ hcloud_placement_group_name }}"
+ type: spread
+ register: result
+ check_mode: yes
+- name: test create placement group with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test create placement group
+ hcloud_placement_group:
+ name: "{{ hcloud_placement_group_name }}"
+ type: spread
+ labels:
+ key: value
+ my-label: label
+ register: placement_group
+- name: verify create placement group
+ assert:
+ that:
+ - placement_group is changed
+ - placement_group.hcloud_placement_group.name == "{{ hcloud_placement_group_name }}"
+ - placement_group.hcloud_placement_group.type == "spread"
+ - placement_group.hcloud_placement_group.servers | list | count == 0
+
+- name: test create placement group idempotence
+ hcloud_placement_group:
+ name: "{{ hcloud_placement_group_name }}"
+ type: spread
+ labels:
+ key: value
+ my-label: label
+ register: result
+- name: verify create placement group idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test create server with placement group
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ placement_group: "{{ hcloud_placement_group_name }}"
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: server
+- name: verify create server with placement group
+ assert:
+ that:
+ - server is changed
+ - server.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}"
+
+- name: test remove server from placement group
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ placement_group: null
+ state: present
+ register: result
+- name: verify remove server from placement group
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.placement_group == None
+
+- name: test add server to placement group
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ placement_group: "{{ hcloud_placement_group_name }}"
+ force: True
+ state: present
+ register: result
+- name: verify add server to placement group
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}"
+ - result.hcloud_server.status == "running"
+
+- name: test add server to placement group idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ placement_group: "{{ hcloud_placement_group_name }}"
+ force: True
+ state: present
+ register: result
+- name: verify add server to placement group idempotence
+ assert:
+ that:
+ - result is not changed
+ - result.hcloud_server.placement_group == "{{ hcloud_placement_group_name }}"
+ - result.hcloud_server.status == "running"
+
+- name: test update placement group with check mode
+ hcloud_placement_group:
+ id: "{{ placement_group.hcloud_placement_group.id }}"
+ name: "changed-{{ hcloud_placement_group_name }}"
+ register: result
+ check_mode: yes
+- name: verify update placement group with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test update placement group
+ hcloud_placement_group:
+ id: "{{ placement_group.hcloud_placement_group.id }}"
+ name: "changed-{{ hcloud_placement_group_name }}"
+ labels:
+ key: value
+ register: result
+- name: verify update placement group
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_placement_group.name == "changed-{{ hcloud_placement_group_name }}"
+
+- name: test update placement group idempotence
+ hcloud_placement_group:
+ id: "{{ placement_group.hcloud_placement_group.id }}"
+ name: "changed-{{ hcloud_placement_group_name }}"
+ labels:
+ key: value
+ register: result
+- name: verify update placement group idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: absent server
+ hcloud_server:
+ id: "{{ server.hcloud_server.id }}"
+ state: absent
+
+- name: absent placement group
+ hcloud_placement_group:
+ id: "{{ placement_group.hcloud_placement_group.id }}"
+ state: absent
+ register: result
+- name: verify absent placement group
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml
new file mode 100644
index 000000000..98aa28eea
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_primary_ip_name: "{{hcloud_prefix}}-i"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-fip-t"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml
new file mode 100644
index 000000000..d4efc606b
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_primary_ip/tasks/main.yml
@@ -0,0 +1,243 @@
+# 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: test create Primary IP with check mode
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ register: primaryIP
+ check_mode: yes
+- name: verify test create Primary IP with check mode
+ assert:
+ that:
+ - primaryIP is changed
+
+- name: test create Primary IP
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ register: primaryIP
+- name: verify test create Primary IP
+ assert:
+ that:
+ - primaryIP is changed
+ - primaryIP.hcloud_primary_ip.name ==hcloud_primary_ip_name
+ - primaryIP.hcloud_primary_ip.datacenter == "fsn1-dc14"
+
+- name: test create Primary IP idempotency
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ register: primaryIP
+- name: verify test create Primary IP idempotency
+ assert:
+ that:
+ - primaryIP is not changed
+
+- name: test update Primary IP
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ labels:
+ key: value
+ register: primaryIP
+- name: verify test update Primary IP
+ assert:
+ that:
+ - primaryIP is changed
+
+- name: test update Primary IP idempotency
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ labels:
+ key: value
+ register: primaryIP
+- name: verify test update Primary IP idempotency
+ assert:
+ that:
+ - primaryIP is not changed
+
+- name: test update Primary IP with same labels
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ labels:
+ key: value
+ register: primaryIP
+- name: verify test update Primary IP with same labels
+ assert:
+ that:
+ - primaryIP is not changed
+
+- name: test update Primary IP with other labels
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ labels:
+ key: value
+ other: label
+ register: primaryIP
+- name: verify test update Primary IP with other labels
+ assert:
+ that:
+ - primaryIP is changed
+
+- name: test update Primary IP with other labels in different order
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ labels:
+ other: label
+ key: value
+ register: primaryIP
+- name: verify test update Primary IP with other labels in different order
+ assert:
+ that:
+ - primaryIP is not changed
+
+- name: test update Primary IP delete protection
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ delete_protection: true
+ register: primaryIP
+- name: verify update Primary IP delete protection
+ assert:
+ that:
+ - primaryIP is changed
+ - primaryIP.hcloud_primary_ip.delete_protection is sameas true
+
+- name: test update Primary IP delete protection idempotency
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ delete_protection: true
+ register: primaryIP
+- name: verify update Primary IP delete protection idempotency
+ assert:
+ that:
+ - primaryIP is not changed
+ - primaryIP.hcloud_primary_ip.delete_protection is sameas true
+
+- name: test Primary IP without delete protection set to be idempotent
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ register: primaryIP
+- name: verify Primary IP without delete protection set to be idempotent
+ assert:
+ that:
+ - primaryIP is not changed
+ - primaryIP.hcloud_primary_ip.delete_protection is sameas true
+
+- name: test delete Primary IP fails if it is protected
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ state: "absent"
+ register: result
+ ignore_errors: yes
+- name: verify test delete primary ip
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "Primary IP deletion is protected"'
+
+- name: test update Primary IP delete protection
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ delete_protection: false
+ register: primaryIP
+- name: verify update Primary IP delete protection
+ assert:
+ that:
+ - primaryIP is changed
+ - primaryIP.hcloud_primary_ip.delete_protection is sameas false
+
+- name: test delete primary ip
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete primary ip
+ assert:
+ that:
+ - result is changed
+
+- name: test create ipv6 primary ip
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv6
+ datacenter: "fsn1-dc14"
+ state: "present"
+ register: result
+- name: verify test create ipv6 primary ip
+ assert:
+ that:
+ - result is changed
+
+- name: test delete ipv6 primary ip
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete ipv6 primary ip
+ assert:
+ that:
+ - result is changed
+
+- name: test create Primary IP with delete protection
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: fsn1-dc14
+ delete_protection: true
+ register: primaryIP
+- name: verify create Primary IP with delete protection
+ assert:
+ that:
+ - primaryIP is changed
+ - primaryIP.hcloud_primary_ip.delete_protection is sameas true
+
+- name: test delete Primary IP fails if it is protected
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ state: "absent"
+ register: result
+ ignore_errors: yes
+- name: verify test delete primary ip
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "Primary IP deletion is protected"'
+
+- name: test update Primary IP delete protection
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ delete_protection: false
+ register: primaryIP
+- name: verify update Primary IP delete protection
+ assert:
+ that:
+ - primaryIP is changed
+ - primaryIP.hcloud_primary_ip.delete_protection is sameas false
+
+- name: test delete primary ip
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ state: "absent"
+ register: result
+- name: verify test delete primary ip
+ assert:
+ that:
+ - result is changed
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml
new file mode 100644
index 000000000..50117a8a5
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/defaults/main.yml
@@ -0,0 +1,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)
+---
+hcloud_prefix: "tests"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}"
+hcloud_floating_ip_name: "{{hcloud_prefix}}"
+hcloud_primary_ip_name: "{{hcloud_prefix}}"
+hcloud_load_balancer_name: "{{hcloud_prefix}}"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml
new file mode 100644
index 000000000..67d54d732
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - ansible.netcommon
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml
new file mode 100644
index 000000000..dddbac0d0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_rdns/tasks/main.yml
@@ -0,0 +1,224 @@
+# 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 }}"
+ server_type: cx11
+ image: "ubuntu-22.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: setup
+- name: verify setup
+ assert:
+ that:
+ - setup is success
+
+- name: setup Floating IP
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ type: ipv4
+ home_location: "fsn1"
+ register: floatingIP
+- name: verify setup Floating IP
+ assert:
+ that:
+ - floatingIP is success
+
+- name: setup Load Balancer
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name}}"
+ load_balancer_type: lb11
+ network_zone: eu-central
+ state: present
+ register: load_balancer
+- name: verify setup
+ assert:
+ that:
+ - load_balancer is success
+
+- name: setup Primary IP
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ register: primaryIP
+- name: verify setup Primary IP
+ assert:
+ that:
+ - primaryIP is success
+
+- name: test missing required parameter
+ hcloud_rdns:
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: ip_address"'
+- name: test fail on not existing resource
+ hcloud_rdns:
+ server: "not-existing"
+ ip_address: "127.0.0.1"
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail on not existing resou
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "The selected server does not exist"'
+- name: test create rdns
+ hcloud_rdns:
+ server: "{{ hcloud_server_name }}"
+ ip_address: "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}"
+ dns_ptr: "example.com"
+ state: present
+ register: rdns
+- name: verify create rdns
+ assert:
+ that:
+ - rdns is changed
+ - rdns.hcloud_rdns.server == "{{ hcloud_server_name }}"
+ - rdns.hcloud_rdns.ip_address == "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}"
+ - rdns.hcloud_rdns.dns_ptr == "example.com"
+
+- name: test create rdns idempotency
+ hcloud_rdns:
+ server: "{{ hcloud_server_name }}"
+ ip_address: "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}"
+ dns_ptr: "example.com"
+ state: present
+ register: result
+- name: verify create rdns idempotency
+ assert:
+ that:
+ - result is not changed
+
+- name: test absent rdns
+ hcloud_rdns:
+ server: "{{ hcloud_server_name }}"
+ ip_address: "{{ setup.hcloud_server.ipv6 | ansible.netcommon.ipaddr('next_usable') }}"
+ state: absent
+ register: result
+- name: verify test absent rdns
+ assert:
+ that:
+ - result is changed
+
+- name: test update rdns
+ hcloud_rdns:
+ server: "{{ hcloud_server_name }}"
+ ip_address: "{{ setup.hcloud_server.ipv4_address }}"
+ dns_ptr: "example.com"
+ state: present
+ register: rdns
+- name: verify update rdns
+ assert:
+ that:
+ - rdns is changed
+ - rdns.hcloud_rdns.server == "{{ hcloud_server_name }}"
+ - rdns.hcloud_rdns.ip_address == "{{ setup.hcloud_server.ipv4_address }}"
+ - rdns.hcloud_rdns.dns_ptr == "example.com"
+
+- name: test reset rdns
+ hcloud_rdns:
+ server: "{{ hcloud_server_name }}"
+ ip_address: "{{ setup.hcloud_server.ipv4_address }}"
+ state: present
+ register: rdns
+- name: verify reset rdns
+ assert:
+ that:
+ - rdns is changed
+ - rdns.hcloud_rdns.server == "{{ hcloud_server_name }}"
+ - rdns.hcloud_rdns.ip_address == "{{ setup.hcloud_server.ipv4_address }}"
+ - rdns.hcloud_rdns.dns_ptr != "example.com"
+
+- name: test create rdns with floating IP
+ hcloud_rdns:
+ floating_ip: "{{ hcloud_floating_ip_name }}"
+ ip_address: "{{ floatingIP.hcloud_floating_ip.ip}}"
+ dns_ptr: "example.com"
+ state: present
+ register: rdns
+- name: verify create rdns
+ assert:
+ that:
+ - rdns is changed
+ - rdns.hcloud_rdns.floating_ip == "{{ hcloud_floating_ip_name }}"
+ - rdns.hcloud_rdns.ip_address == "{{ floatingIP.hcloud_floating_ip.ip}}"
+ - rdns.hcloud_rdns.dns_ptr == "example.com"
+
+- name: test create rdns with primary IP
+ hcloud_rdns:
+ primary_ip: "{{ hcloud_primary_ip_name }}"
+ ip_address: "{{ primaryIP.hcloud_primary_ip.ip}}"
+ dns_ptr: "example.com"
+ state: present
+ register: rdns
+- name: verify create rdns
+ assert:
+ that:
+ - rdns is changed
+ - rdns.hcloud_rdns.primary_ip == "{{ hcloud_primary_ip_name }}"
+ - rdns.hcloud_rdns.ip_address == "{{ primaryIP.hcloud_primary_ip.ip}}"
+ - rdns.hcloud_rdns.dns_ptr == "example.com"
+
+- name: test create rdns with load balancer
+ hcloud_rdns:
+ load_balancer: "{{ hcloud_load_balancer_name }}"
+ ip_address: "{{ load_balancer.hcloud_load_balancer.ipv4_address }}"
+ dns_ptr: "example.com"
+ state: present
+ register: rdns
+- name: verify create rdns with load balancer
+ assert:
+ that:
+ - rdns is changed
+ - rdns.hcloud_rdns.load_balancer == "{{ hcloud_load_balancer_name }}"
+ - rdns.hcloud_rdns.ip_address == "{{ load_balancer.hcloud_load_balancer.ipv4_address }}"
+ - rdns.hcloud_rdns.dns_ptr == "example.com"
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
+
+- name: cleanup
+ hcloud_floating_ip:
+ name: "{{ hcloud_floating_ip_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
+
+- name: cleanup
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
+
+- name: cleanup
+ hcloud_load_balancer:
+ name: "{{ hcloud_load_balancer_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/defaults/main.yml
new file mode 100644
index 000000000..c93c7495e
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/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_network_name: "{{hcloud_prefix}}-ro"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml
new file mode 100644
index 000000000..67d54d732
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - ansible.netcommon
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml
new file mode 100644
index 000000000..7d816bf5c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_route/tasks/main.yml
@@ -0,0 +1,99 @@
+# 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_network:
+ name: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/8"
+ state: present
+ register: network
+- name: verify setup
+ assert:
+ that:
+ - network is success
+
+- name: test missing required parameters on create route
+ hcloud_route:
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create route
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: destination, gateway, network"'
+
+- name: test create route with checkmode
+ hcloud_route:
+ network: "{{ hcloud_network_name }}"
+ destination: "10.100.1.0/24"
+ gateway: "10.0.1.1"
+ state: present
+ register: result
+ check_mode: yes
+- name: verify test create route with checkmode
+ assert:
+ that:
+ - result is changed
+
+- name: test create route
+ hcloud_route:
+ network: "{{ hcloud_network_name }}"
+ destination: "10.100.1.0/24"
+ gateway: "10.0.1.1"
+ state: present
+ register: route
+- name: verify create route
+ assert:
+ that:
+ - route is changed
+ - route.hcloud_route.network == "{{ hcloud_network_name }}"
+ - route.hcloud_route.destination == "10.100.1.0/24"
+ - route.hcloud_route.gateway == "10.0.1.1"
+
+- name: test create route idempotency
+ hcloud_route:
+ network: "{{ hcloud_network_name }}"
+ destination: "10.100.1.0/24"
+ gateway: "10.0.1.1"
+ state: present
+ register: result
+- name: verify create route idempotency
+ assert:
+ that:
+ - result is not changed
+
+- name: test fail create route with wrong gateway
+ hcloud_route:
+ network: "{{ hcloud_network_name }}"
+ destination: "10.100.1.0/24"
+ gateway: "10.0.1.2"
+ state: present
+ register: route
+ ignore_errors: yes
+- name: verfiy fail create route with wrong gateway
+ assert:
+ that:
+ - route is failed
+
+- name: test absent route
+ hcloud_route:
+ network: "{{ hcloud_network_name }}"
+ destination: "10.100.1.0/24"
+ gateway: "10.0.1.1"
+ state: absent
+ register: result
+- name: verify test absent route
+ 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
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases
new file mode 100644
index 000000000..18dc30b6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml
new file mode 100644
index 000000000..4e1c4dc45
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/defaults/main.yml
@@ -0,0 +1,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)
+---
+hcloud_prefix: "tests"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-i"
+hcloud_firewall_name: "{{hcloud_prefix}}-i"
+hcloud_primary_ip_name: "{{hcloud_prefix}}-i"
+hcloud_network_name: "{{hcloud_prefix}}-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml
new file mode 100644
index 000000000..ac609fc6c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/basic.yml
@@ -0,0 +1,615 @@
+- name: test create server with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: ubuntu-20.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-20.04
+ enable_ipv6: False
+ 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: test backups are not accidentally disabled
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ # Make sure that backups are not disabled because a partial server object without "backups" was supplied somewhere
+ # to update some unrelated properties.
+ # Regression test for https://github.com/ansible-collections/hetzner.hcloud/pull/196
+ # backups: true
+ state: stopped
+ register: result
+- name: verify backups are not accidentally disabled
+ assert:
+ that:
+ - result is not changed
+ - result.hcloud_server.backup_window != ""
+
+- name: test rebuild server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ image: ubuntu-20.04
+ state: rebuild
+ register: result_after_test
+- name: verify rebuild server
+ assert:
+ that:
+ - result_after_test is changed
+ - result.hcloud_server.id == result_after_test.hcloud_server.id
+
+- name: test rebuild server with check mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ image: ubuntu-20.04
+ state: rebuild
+ register: result_after_test
+ check_mode: true
+- name: verify rebuild server with check mode
+ assert:
+ that:
+ - result_after_test is changed
+
+- name: test update server protection booth protection arguments are required
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ delete_protection: true
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify update server protection booth protection arguments are required
+ assert:
+ that:
+ - result_after_test is failed
+ - 'result_after_test.msg == "parameters are required together: delete_protection, rebuild_protection"'
+
+- name: test update server protection fails if they are not the same
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ delete_protection: true
+ rebuild_protection: false
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify update server protection fails if they are not the same
+ assert:
+ that:
+ - result_after_test is failed
+
+- name: test update server protection
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ delete_protection: true
+ rebuild_protection: true
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify update server protection
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_server.delete_protection is sameas true
+ - result_after_test.hcloud_server.rebuild_protection is sameas true
+
+- name: test server without protection set to be idempotent
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ register: result_after_test
+- name: verify test server without protection set to be idempotent
+ assert:
+ that:
+ - result_after_test is not changed
+ - result_after_test.hcloud_server.delete_protection is sameas true
+ - result_after_test.hcloud_server.rebuild_protection is sameas true
+
+- name: test delete server fails if it is protected
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete server fails if it is protected
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "server deletion is protected"'
+
+- name: test rebuild server fails if it is protected
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ image: ubuntu-20.04
+ state: rebuild
+ ignore_errors: yes
+ register: result
+- name: verify rebuild server fails if it is protected
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "server rebuild is protected"'
+
+- name: test remove server protection
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ delete_protection: false
+ rebuild_protection: false
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify remove server protection
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_server.delete_protection is sameas false
+ - result_after_test.hcloud_server.rebuild_protection is sameas false
+
+- 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-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: started
+ register: main_server
+- name: verify create server with ssh key
+ 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 activate rescue mode with check_mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ rescue_mode: "linux64"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: main_server
+ check_mode: true
+- name: verify activate rescue mode
+ assert:
+ that:
+ - main_server is changed
+
+- name: test activate rescue mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ rescue_mode: "linux64"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: main_server
+- name: verify activate rescue mode
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.rescue_enabled is sameas true
+
+- name: test disable rescue mode
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: main_server
+- name: verify activate rescue mode
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.rescue_enabled is sameas false
+
+- name: test activate rescue mode without ssh keys
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ rescue_mode: "linux64"
+ state: present
+ register: main_server
+- name: verify activate rescue mode without ssh keys
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.rescue_enabled is sameas true
+
+- 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 rescue_mode
+ hcloud_server:
+ name: "{{ hcloud_server_name}}"
+ server_type: cx11
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ rescue_mode: "linux64"
+ state: started
+ register: main_server
+- name: verify create server with rescue_mode
+ 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 != ""
+ - main_server.hcloud_server.rescue_enabled is sameas true
+
+
+- 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 labels
+ hcloud_server:
+ name: "{{ hcloud_server_name}}"
+ server_type: cx11
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ labels:
+ key: value
+ mylabel: "val123"
+ state: started
+ register: main_server
+- name: verify create server with labels
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.labels.key == "value"
+ - main_server.hcloud_server.labels.mylabel == "val123"
+
+- name: test update server with labels
+ hcloud_server:
+ name: "{{ hcloud_server_name}}"
+ server_type: cx11
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ labels:
+ key: other
+ mylabel: "val123"
+ state: started
+ register: main_server
+- name: verify update server with labels
+ assert:
+ that:
+ - main_server is changed
+ - main_server.hcloud_server.labels.key == "other"
+ - main_server.hcloud_server.labels.mylabel == "val123"
+
+- name: test update server with labels in other order
+ hcloud_server:
+ name: "{{ hcloud_server_name}}"
+ server_type: cx11
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ labels:
+ mylabel: "val123"
+ key: other
+ state: started
+ register: main_server
+- name: verify update server with labels in other order
+ assert:
+ that:
+ - main_server is not changed
+
+- name: cleanup with labels
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
+
+- name: test create server with enabled backups
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ backups: true
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: result
+- name: verify enable backups
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_server.backup_window != ""
+
+- name: cleanup test create server with enabled backups
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
+
+- name: test create server with protection
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ delete_protection: true
+ rebuild_protection: true
+ server_type: cpx11
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify create server with protection
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_server.delete_protection is sameas true
+ - result_after_test.hcloud_server.rebuild_protection is sameas true
+
+- name: test delete server fails if it is protected
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete server fails if it is protected
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "server deletion is protected"'
+
+- name: remove protection from server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ delete_protection: false
+ rebuild_protection: false
+ state: present
+ register: result_after_test
+ ignore_errors: true
+- name: verify update server protection
+ assert:
+ that:
+ - result_after_test is changed
+ - result_after_test.hcloud_server.delete_protection is sameas false
+ - result_after_test.hcloud_server.rebuild_protection is sameas false
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml
new file mode 100644
index 000000000..18fa89e25
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/firewalls.yml
@@ -0,0 +1,105 @@
+# 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: test add not existing firewall should fail
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ firewalls:
+ - not-existing
+ state: present
+ ignore_errors: yes
+ register: result
+- name: verify add not existing firewall should fail
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "firewall not-existing was not found"'
+- name: setup create firewalls
+ hcloud_firewall:
+ name: "{{ item }}"
+ rules:
+ - direction: in
+ protocol: icmp
+ source_ips:
+ - 0.0.0.0/0
+ - ::/0
+ with_items:
+ - "{{ hcloud_firewall_name }}"
+ - "{{ hcloud_firewall_name }}2"
+
+- name: test create server with firewalls
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ firewalls:
+ - "{{ hcloud_firewall_name }}"
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: result
+- name: verify test create server with firewalls
+ assert:
+ that:
+ - result is changed
+
+- name: test create server with firewalls idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ firewalls:
+ - "{{ hcloud_firewall_name }}"
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: result
+- name: verify test create server with firewalls idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test update server with firewalls
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ firewalls:
+ - "{{ hcloud_firewall_name }}2"
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: result
+- name: verify test update server with firewalls
+ assert:
+ that:
+ - result is changed
+
+- name: test update server with firewalls idempotence
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ firewalls:
+ - "{{ hcloud_firewall_name }}2"
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: present
+ register: result
+- name: verify test update server with firewalls idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: cleanup server with firewalls
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+
+- name: cleanup test create firewall
+ hcloud_firewall:
+ name: "{{ item }}"
+ state: absent
+ with_items:
+ - "{{ hcloud_firewall_name }}"
+ - "{{ hcloud_firewall_name }}2"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml
new file mode 100644
index 000000000..209d9bd48
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/main.yml
@@ -0,0 +1,8 @@
+# Copyright: (c) 2022, 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)
+---
+#- ansible.builtin.include_tasks: validation.yml
+- ansible.builtin.include_tasks: basic.yml
+#- ansible.builtin.include_tasks: firewalls.yml
+- ansible.builtin.include_tasks: primary_ips.yml
+- ansible.builtin.include_tasks: private_network_only.yml
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml
new file mode 100644
index 000000000..000c294de
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/primary_ips.yml
@@ -0,0 +1,82 @@
+# Copyright: (c) 2022, 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 create primary ipv4
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}v4"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ register: primaryIPv4
+
+- name: setup create second primary ipv4
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}v42"
+ type: ipv4
+ datacenter: "fsn1-dc14"
+ register: secondPrimaryIPv4
+
+- name: setup create primary ipv6
+ hcloud_primary_ip:
+ name: "{{ hcloud_primary_ip_name }}v6"
+ type: ipv6
+ datacenter: "fsn1-dc14"
+ register: primaryIPv6
+
+- name: test create server with primary ips
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ datacenter: "fsn1-dc14"
+ image: "ubuntu-20.04"
+ ipv4: "{{primaryIPv4.hcloud_primary_ip.id}}"
+ ipv6: "{{primaryIPv6.hcloud_primary_ip.id}}"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: stopped
+ register: result
+- name: verify test create server with primary ips
+ assert:
+ that:
+ - result is changed
+
+- name: test update server with primary ips
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ datacenter: "fsn1-dc14"
+ image: "ubuntu-20.04"
+ ipv4: "{{secondPrimaryIPv4.hcloud_primary_ip.id}}"
+ ipv6: ""
+ enable_ipv6: no
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: stopped
+ register: result
+- name: verify test create server with primary ips
+ assert:
+ that:
+ - result is changed
+
+- name: cleanup server with primary ips
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+
+- name: cleanup test create primary ips
+ hcloud_primary_ip:
+ name: "{{ hcloud_server_name }}v4"
+ state: absent
+- name: cleanup test create primary ips
+ hcloud_primary_ip:
+ name: "{{ hcloud_server_name }}v42"
+ state: absent
+ until: result is not failed
+ retries: 5
+ delay: 2
+- name: cleanup test create primary ips
+ hcloud_primary_ip:
+ name: "{{ hcloud_server_name }}v6"
+ state: absent
+ until: result is not failed
+ retries: 5
+ delay: 2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml
new file mode 100644
index 000000000..a56832873
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/private_network_only.yml
@@ -0,0 +1,135 @@
+# Copyright: (c) 2022, 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 network 1 to be absent
+ hcloud_network:
+ name: "{{ hcloud_network_name }}-1"
+ state: absent
+
+- name: setup network 2 to be absent
+ hcloud_network:
+ name: "{{ hcloud_network_name }}-2"
+ state: absent
+
+- name: setup server to be absent
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+
+- name: setup create network
+ hcloud_network:
+ name: "{{ hcloud_network_name }}-1"
+ ip_range: 192.168.0.0/23
+ register: primaryNetwork
+
+- name: setup create network subnet 1
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}-1"
+ ip_range: 192.168.0.0/24
+ network_zone: eu-central
+ type: cloud
+ state: present
+
+- name: setup create network subnet 2
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}-1"
+ ip_range: 192.168.1.0/24
+ network_zone: eu-central
+ type: cloud
+ state: present
+
+- name: setup create secondary network
+ hcloud_network:
+ name: "{{ hcloud_network_name }}-2"
+ ip_range: 192.168.2.0/23
+ register: secondaryNetwork
+
+- name: setup create secondary network subnet 1
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}-2"
+ ip_range: 192.168.2.0/24
+ network_zone: eu-central
+ type: cloud
+ state: present
+
+- name: setup create secondary network subnet 2
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}-2"
+ ip_range: 192.168.3.0/24
+ network_zone: eu-central
+ type: cloud
+ state: present
+
+- name: test create server with primary network and no internet
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ datacenter: "fsn1-dc14"
+ image: "ubuntu-20.04"
+ enable_ipv4: no
+ enable_ipv6: no
+ private_networks:
+ - "{{ primaryNetwork.hcloud_network.name }}"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: stopped
+ register: result
+- name: verify test create server with primary network
+ assert:
+ that:
+ - result is changed
+
+- name: test update server by adding secondary network
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ datacenter: "fsn1-dc14"
+ image: "ubuntu-20.04"
+ enable_ipv4: no
+ enable_ipv6: no
+ private_networks:
+ - "{{ primaryNetwork.hcloud_network.name }}"
+ - "{{ secondaryNetwork.hcloud_network.id }}"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: stopped
+ register: result
+- name: verify test update server by adding secondary network
+ assert:
+ that:
+ - result is changed
+
+- name: test update server idem
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cpx11
+ datacenter: "fsn1-dc14"
+ image: "ubuntu-20.04"
+ enable_ipv4: no
+ enable_ipv6: no
+ private_networks:
+ - "{{ primaryNetwork.hcloud_network.name }}"
+ - "{{ secondaryNetwork.hcloud_network.id }}"
+ ssh_keys:
+ - ci@ansible.hetzner.cloud
+ state: stopped
+ register: result
+- name: verify test update server idem
+ assert:
+ that:
+ - result is not changed
+
+- name: cleanup server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+- name: cleanup networks
+ hcloud_network:
+ name: "{{ item }}"
+ state: absent
+ with_items:
+ - "{{ primaryNetwork.hcloud_network.name }}"
+ - "{{ secondaryNetwork.hcloud_network.id }}"
+ until: result is not failed
+ retries: 5
+ delay: 2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml
new file mode 100644
index 000000000..f507e87cf
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server/tasks/validation.yml
@@ -0,0 +1,51 @@
+# 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 not existing server type
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: not-existing-server-type
+ image: ubuntu-20.04
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail test create server with not existing server type
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "server_type not-existing-server-type was not found"'
+
+- name: test create server with not existing image
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: my-not-existing-image-20.04
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail test create server with not existing image
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "Image my-not-existing-image-20.04 was not found"'
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/defaults/main.yml
new file mode 100644
index 000000000..aa27d6452
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/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 | truncate(45, True, '', 0) }}-ii"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml
new file mode 100644
index 000000000..b425b4127
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_info/tasks/main.yml
@@ -0,0 +1,128 @@
+# 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 ensure server is absent
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+
+- name: create server
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: ubuntu-22.04
+ state: started
+ labels:
+ key: value
+ 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 gather hcloud server infos in check mode
+ hcloud_server_info:
+ register: server
+ check_mode: yes
+
+- name: verify test gather hcloud server infos in check mode
+ assert:
+ that:
+ - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1
+
+
+- name: test gather hcloud server infos with correct label selector
+ hcloud_server_info:
+ label_selector: "key=value"
+ register: server
+- name: verify test gather hcloud server infos with correct label selector
+ assert:
+ that:
+ - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1
+
+- name: test gather hcloud server infos with wrong label selector
+ hcloud_server_info:
+ label_selector: "key!=value"
+ register: server
+- name: verify test gather hcloud server infos with wrong label selector
+ assert:
+ that:
+ - server.hcloud_server_info | list | count == 0
+
+- name: test gather hcloud server infos with correct name
+ hcloud_server_info:
+ name: "{{hcloud_server_name}}"
+ register: server
+- name: verify test gather hcloud server infos with correct name
+ assert:
+ that:
+ - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1
+
+- name: test gather hcloud server infos with wrong name
+ hcloud_server_info:
+ name: "{{hcloud_server_name}}1"
+ register: server
+- name: verify test gather hcloud server infos with wrong name
+ assert:
+ that:
+ - server.hcloud_server_info | list | count == 0
+
+- name: test gather hcloud server infos with correct id
+ hcloud_server_info:
+ id: "{{main_server.hcloud_server.id}}"
+ register: server
+- name: verify test gather hcloud server infos with correct id
+ assert:
+ that:
+ - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1
+
+- name: test gather hcloud server infos with wrong id
+ hcloud_server_info:
+ name: "4711"
+ register: server
+- name: verify test gather hcloud server infos with wrong id
+ assert:
+ that:
+ - server.hcloud_server_info | list | count == 0
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+
+- name: create server without ips
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: ubuntu-22.04
+ state: stopped
+ labels:
+ key: value
+ enable_ipv4: no
+ enable_ipv6: no
+ 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.root_password != ""
+- name: test gather hcloud server infos with correct id
+ hcloud_server_info:
+ id: "{{main_server.hcloud_server.id}}"
+ register: server
+- name: verify test gather hcloud server infos with correct id
+ assert:
+ that:
+ - server.hcloud_server_info|selectattr('name','equalto','{{ hcloud_server_name }}') | list | count == 1
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases
new file mode 100644
index 000000000..7f17468b0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/aliases
@@ -0,0 +1,3 @@
+cloud/hcloud
+shippable/hcloud/group2
+disabled
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml
new file mode 100644
index 000000000..2e020c495
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_network_name: "{{hcloud_prefix}}-sn"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-sn"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml
new file mode 100644
index 000000000..754018a66
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_network/tasks/main.yml
@@ -0,0 +1,222 @@
+# 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 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: test update 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.2.3"
+ - "10.0.3.1"
+ 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.3.1"'
+
+- name: test update server network with alias ips idempotency
+ hcloud_server_network:
+ network: "{{ hcloud_network_name }}"
+ server: "{{hcloud_server_name}}"
+ ip: "10.0.0.2"
+ alias_ips:
+ - "10.0.2.3"
+ - "10.0.3.1"
+ state: present
+ register: serverNetwork
+- name: verify create server network with alias ips idempotency
+ assert:
+ that:
+ - serverNetwork is not changed
+
+- 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
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/defaults/main.yml
new file mode 100644
index 000000000..05502aa91
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/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_server_type_name: "cx11"
+hcloud_server_type_id: 1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml
new file mode 100644
index 000000000..3c1fce8c0
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_server_type_info/tasks/main.yml
@@ -0,0 +1,38 @@
+# 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: test gather hcloud server type infos
+ hcloud_server_type_info:
+ register: hcloud_server_types
+- name: verify test gather hcloud server type infos
+ assert:
+ that:
+ - hcloud_server_types.hcloud_server_type_info| list | count > 2
+
+- name: test gather hcloud server type infos in check mode
+ hcloud_server_type_info:
+ check_mode: yes
+ register: hcloud_server_types
+
+- name: verify test gather hcloud server type infos in check mode
+ assert:
+ that:
+ - hcloud_server_types.hcloud_server_type_info| list | count > 2
+
+- name: test gather hcloud server type infos with name
+ hcloud_server_type_info:
+ name: "{{hcloud_server_type_name}}"
+ register: hcloud_server_types
+- name: verify test gather hcloud server type with name
+ assert:
+ that:
+ - hcloud_server_types.hcloud_server_type_info|selectattr('name','equalto','{{ hcloud_server_type_name }}') | list | count == 1
+
+- name: test gather hcloud server type infos with correct id
+ hcloud_server_type_info:
+ id: "{{hcloud_server_type_id}}"
+ register: hcloud_server_types
+- name: verify test gather hcloud server type with correct id
+ assert:
+ that:
+ - hcloud_server_types.hcloud_server_type_info|selectattr('name','equalto','{{ hcloud_server_type_name }}') | list | count == 1
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml
new file mode 100644
index 000000000..cee1d4691
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/defaults/main.yml
@@ -0,0 +1,11 @@
+# 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 | truncate(45, True, '', 0) }}"
+hcloud_ssh_key_name: "{{hcloud_prefix}}"
+hcloud_ssh_key_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQDnaTPfKaX1QKcRLOfr34buVLh5FhJAThI9NYB0xNdXsMd4Y0zLyyCQzHbx4eWCVZxym/s6csWSeLaAhO1GOHeAw3hQFMqf1oTBx6Y8g0pKpeotKPa/PDSUzdZF9Lc+DadtpQd8kFVHAu1Kd3zoEUnk1u6kP7I4qu4Z/6F9qBDF+M3aobiPVxdS7GwaVRW3nZu+FcQDLiBiNOjuRDyjHcDfEUkoh2SOu25RrFtGPzFu5mGmBJwotKpWAocLGfHzyn/fAHxgw3jKZVH/t+XWQFnl82Ie8yE3Z1EZ7oDkNRqFQT9AdXEQOLycTTYTQMJZpgeFTv3sAo6lPRCusiFmmLcf ci@ansible.hetzner.cloud"
+hcloud_ssh_key_fingerprint: "56:89:c4:d6:a7:4a:79:82:f4:c2:58:9c:e1:d2:2d:4e"
+
+hcloud_doubled_ssh_key_public_key: "ssh-rsa AAAAB3NzaC1yc2EAAAADAQABAAABAQC1AiuN3UMQKzOs4tNudmlDSkSebC+savc6CivoHGflUKeli7nKb5pKgGiqH+zeWZc+8+flUa2BxsJWmi7d1nGJ++W4BnzmqW78ApelpJnGtuX8IKNcq/trhVTQyaShPiLluoBs7bXyyZpAKNGkk3jHrgwwYD/QQDN0CJnQUM18fjH5CUes2vmaG/kkhn7ctuVHDOvDcEy8KdBX3fYyrtXw5GgWDC5borG6yT1f3E9AXfRPL9OQjMTeC+G4FHscJAZjNnYav+jLrQLdV1xJ0JgbjRyBgTAfBszx9oKIjzCUPvpj4npju0WFGu10pIh0w7bluMoVn1tS6Y3gxE/Cepwt ci@ansible.hetzner.cloud"
+hcloud_doubled_ssh_key_fingerprint: "f9:33:40:ff:77:f3:3e:85:f2:9e:8f:98:71:fd:a0:58"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml
new file mode 100644
index 000000000..5dcc0725d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/meta/main.yml
@@ -0,0 +1,5 @@
+dependencies:
+ - setup_sshkey
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml
new file mode 100644
index 000000000..9208e143d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key/tasks/main.yml
@@ -0,0 +1,156 @@
+# 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: test missing required parameters on create ssh_key
+ hcloud_ssh_key:
+ name: "{{ hcloud_ssh_key_name }}"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create ssh_key
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: public_key"'
+
+- name: test create ssh key with check mode
+ hcloud_ssh_key:
+ name: "{{ hcloud_ssh_key_name }}"
+ public_key: "{{ key_material }}"
+ register: result
+ check_mode: yes
+- name: test create ssh key with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test create ssh key
+ hcloud_ssh_key:
+ name: "{{ hcloud_ssh_key_name }}"
+ public_key: "{{ key_material }}"
+ labels:
+ key: value
+ my-label: label
+ register: sshKey
+- name: verify create ssh key
+ assert:
+ that:
+ - sshKey is changed
+ - sshKey.hcloud_ssh_key.name == "{{ hcloud_ssh_key_name }}"
+ - sshKey.hcloud_ssh_key.public_key == "{{ key_material }}"
+ - sshKey.hcloud_ssh_key.labels.key == "value"
+
+- name: test create ssh key idempotence
+ hcloud_ssh_key:
+ name: "{{ hcloud_ssh_key_name }}"
+ public_key: "{{ key_material }}"
+ register: result
+- name: verify create ssh key idempotence
+ assert:
+ that:
+ - result is not changed
+
+- name: test update ssh key with check mode
+ hcloud_ssh_key:
+ id: "{{ sshKey.hcloud_ssh_key.id }}"
+ name: "changed-{{ hcloud_ssh_key_name }}"
+ register: result
+ check_mode: yes
+- name: test create ssh key with check mode
+ assert:
+ that:
+ - result is changed
+
+- name: test update ssh key
+ hcloud_ssh_key:
+ id: "{{ sshKey.hcloud_ssh_key.id }}"
+ name: "changed-{{ hcloud_ssh_key_name }}"
+ labels:
+ key: value
+ register: result
+- name: test update ssh key
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_ssh_key.name == "changed-{{ hcloud_ssh_key_name }}"
+
+- name: test update ssh key with same labels
+ hcloud_ssh_key:
+ id: "{{ sshKey.hcloud_ssh_key.id }}"
+ name: "changed-{{ hcloud_ssh_key_name }}"
+ labels:
+ key: value
+ register: result
+- name: test update ssh key with same labels
+ assert:
+ that:
+ - result is not changed
+
+- name: test update ssh key with other labels
+ hcloud_ssh_key:
+ id: "{{ sshKey.hcloud_ssh_key.id }}"
+ name: "changed-{{ hcloud_ssh_key_name }}"
+ labels:
+ key: value
+ test: "val123"
+ register: result
+- name: test update ssh key with other labels
+ assert:
+ that:
+ - result is changed
+
+- name: test rename ssh key
+ hcloud_ssh_key:
+ id: "{{ sshKey.hcloud_ssh_key.id }}"
+ name: "{{ hcloud_ssh_key_name }}"
+ register: result
+- name: test rename ssh key
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_ssh_key.name == "{{ hcloud_ssh_key_name }}"
+
+- name: test create server with ssh key
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ server_type: cx11
+ image: "ubuntu-20.04"
+ ssh_keys:
+ - "{{ hcloud_ssh_key_name }}"
+ state: started
+ register: main_server
+- name: verify create server with ssh key
+ assert:
+ that:
+ - main_server is changed
+
+- name: absent ssh key
+ hcloud_ssh_key:
+ id: "{{ sshKey.hcloud_ssh_key.id }}"
+ state: absent
+ register: result
+- name: verify absent sshkey
+ assert:
+ that:
+ - result is success
+
+- name: test fail cleanly on double created ssh key
+ hcloud_ssh_key:
+ name: "{{ hcloud_ssh_key_name }}othername"
+ public_key: "{{ hcloud_doubled_ssh_key_public_key }}"
+ register: result
+ ignore_errors: yes
+- name: verify failed correctly
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "SSH key with the same fingerprint already exists"'
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/defaults/main.yml
new file mode 100644
index 000000000..15188e181
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/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_ssh_key_name: "{{hcloud_prefix}}-f"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml
new file mode 100644
index 000000000..5dcc0725d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/meta/main.yml
@@ -0,0 +1,5 @@
+dependencies:
+ - setup_sshkey
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml
new file mode 100644
index 000000000..87cbd2626
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_ssh_key_info/tasks/main.yml
@@ -0,0 +1,68 @@
+# 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 ensure ssh key is absent
+ hcloud_ssh_key:
+ name: "{{ hcloud_ssh_key_name }}"
+ state: absent
+ register: result
+
+- name: setup test ssh_key
+ hcloud_ssh_key:
+ name: "{{hcloud_ssh_key_name}}"
+ public_key: "{{ key_material }}"
+ labels:
+ key: value
+ register: result
+- name: verify create test ssh_key
+ assert:
+ that:
+ - result is changed
+ - result.hcloud_ssh_key.public_key == "{{ key_material }}"
+
+- name: test gather hcloud ssh key infos in check mode
+ hcloud_ssh_key_info:
+ register: hcloud_ssh_key
+ check_mode: yes
+- name: verify test gather hcloud ssh key infos in check mode
+ assert:
+ that:
+ - hcloud_ssh_key.hcloud_ssh_key_info| list | count >= 1
+
+- name: test gather hcloud ssh key infos
+ hcloud_ssh_key_info:
+ register: hcloud_ssh_key
+ check_mode: yes
+- name: verify test gather hcloud ssh key infos
+ assert:
+ that:
+ - hcloud_ssh_key.hcloud_ssh_key_info| list | count >= 1
+
+- name: test gather hcloud ssh key infos with correct label selector
+ hcloud_ssh_key_info:
+ label_selector: "key=value"
+ register: hcloud_ssh_key
+- name: verify test gather hcloud ssh key infos with correct label selector
+ assert:
+ that:
+ - hcloud_ssh_key.hcloud_ssh_key_info|selectattr('name','equalto','{{ hcloud_ssh_key_name }}') | list | count == 1
+
+- name: test gather hcloud ssh key infos with wrong label selector
+ hcloud_ssh_key_info:
+ label_selector: "key!=value"
+ register: hcloud_ssh_key
+- name: verify test gather hcloud ssh key infos with wrong label selector
+ assert:
+ that:
+ - hcloud_ssh_key.hcloud_ssh_key_info | list | count == 0
+
+- name: cleanup
+ hcloud_ssh_key:
+ name: "{{hcloud_ssh_key_name}}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases
new file mode 100644
index 000000000..af1d98c3d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group3
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml
new file mode 100644
index 000000000..79f0d8783
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_network_name: "{{hcloud_prefix}}-s"
+hetzner_vswitch_id: 15311
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml
new file mode 100644
index 000000000..0453f9d13
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_subnetwork/tasks/main.yml
@@ -0,0 +1,125 @@
+# 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_network:
+ name: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/8"
+ state: present
+ register: network
+- name: verify setup
+ assert:
+ that:
+ - network is success
+
+- name: test missing required parameters on create route
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ state: present
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing required parameters on create route
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: ip_range, network_zone, type"'
+
+- name: test create subnetwork with checkmode
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "server"
+ network_zone: "eu-central"
+ state: present
+ register: result
+ check_mode: yes
+- name: verify test create subnetwork with checkmode
+ assert:
+ that:
+ - result is changed
+
+- name: test create subnetwork
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "cloud"
+ network_zone: "eu-central"
+ state: present
+ register: subnet
+- name: verify create subnetwork
+ assert:
+ that:
+ - subnet is changed
+ - subnet.hcloud_subnetwork.network == "{{ hcloud_network_name }}"
+ - subnet.hcloud_subnetwork.ip_range == "10.0.0.0/16"
+ - subnet.hcloud_subnetwork.type == "cloud"
+ - subnet.hcloud_subnetwork.network_zone == "eu-central"
+
+- name: test create subnetwork idempotency
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "cloud"
+ network_zone: "eu-central"
+ state: present
+ register: result
+- name: verify create subnetwork idempotency
+ assert:
+ that:
+ - result is not changed
+
+- name: test absent subnetwork
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "cloud"
+ network_zone: "eu-central"
+ state: absent
+ register: result
+- name: verify test absent subnetwork
+ assert:
+ that:
+ - result is changed
+
+- name: test vswitch subnetwork
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "vswitch"
+ network_zone: "eu-central"
+ vswitch_id: "{{ hetzner_vswitch_id }}"
+ state: present
+ register: subnet
+- name: verify test vswitch subnetwork
+ assert:
+ that:
+ - subnet is changed
+ - subnet.hcloud_subnetwork.network == "{{ hcloud_network_name }}"
+ - subnet.hcloud_subnetwork.ip_range == "10.0.0.0/16"
+ - subnet.hcloud_subnetwork.type == "vswitch"
+ - subnet.hcloud_subnetwork.network_zone == "eu-central"
+ - subnet.hcloud_subnetwork.vswitch_id == hetzner_vswitch_id
+
+- name: test absent subnetwork
+ hcloud_subnetwork:
+ network: "{{ hcloud_network_name }}"
+ ip_range: "10.0.0.0/16"
+ type: "vswitch"
+ network_zone: "eu-central"
+ vswitch_id: "{{ hetzner_vswitch_id }}"
+ state: absent
+ register: subnet
+- name: verify test absent 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
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml
new file mode 100644
index 000000000..ff16ce28f
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/defaults/main.yml
@@ -0,0 +1,6 @@
+# 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_volume_name: "{{ hcloud_prefix | truncate(60, True, '', 0) }}-i"
+hcloud_server_name: "{{ hcloud_prefix | truncate(45, True, '', 0) }}-vs"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml
new file mode 100644
index 000000000..f763a52f2
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume/tasks/main.yml
@@ -0,0 +1,289 @@
+# 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 server
+ hcloud_server:
+ name: "{{hcloud_server_name}}"
+ server_type: cx11
+ image: ubuntu-18.04
+ state: started
+ location: "fsn1"
+ register: vol_server
+- name: verify setup server
+ assert:
+ that:
+ - vol_server is changed
+
+- name: test missing size parameter on create Volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ server: "{{hcloud_server_name}}"
+ register: result
+ ignore_errors: yes
+- name: verify fail test missing size parameter on create Volume
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "missing required arguments: size"'
+
+- name: test create Volume with check mode
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 10
+ location: "fsn1"
+ register: result
+ check_mode: yes
+- name: verify create Volume with check mode result
+ assert:
+ that:
+ - result is changed
+
+- name: test create Volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 10
+ location: "fsn1"
+ register: volume
+- name: verify test create Volume
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.name == "{{hcloud_volume_name}}"
+ - volume.hcloud_volume.location == "fsn1"
+ - volume.hcloud_volume.size == 10
+ - volume.hcloud_volume.server != "{{hcloud_server_name}}"
+ - volume.hcloud_volume.linux_device is defined
+
+- name: test create Volume idempotence
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 10
+ location: "fsn1"
+ register: volume
+- name: verify test create Volume
+ assert:
+ that:
+ - volume is not changed
+
+- name: test attach Volume with checkmode
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ server: "{{hcloud_server_name}}"
+ check_mode: yes
+ register: volume
+- name: verify test attach Volume with checkmode
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.server != "{{hcloud_server_name}}"
+
+- name: test attach Volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ server: "{{hcloud_server_name}}"
+ register: volume
+- name: verify attach volume
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.server == "{{hcloud_server_name}}"
+
+- name: test attach Volume idempotence
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ server: "{{hcloud_server_name}}"
+ register: volume
+- name: verify attach Volume idempotence
+ assert:
+ that:
+ - volume is not changed
+ - volume.hcloud_volume.server == "{{hcloud_server_name}}"
+
+- name: test detach Volume with checkmode
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ check_mode: yes
+ register: volume
+- name: verify detach Volume with checkmode
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.server == "{{hcloud_server_name}}"
+
+- name: test detach Volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ register: volume
+- name: verify detach volume
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.location == "fsn1"
+ - volume.hcloud_volume.server != "{{hcloud_server_name}}"
+
+- name: test update Volume label
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ labels:
+ key: value
+ register: volume
+- name: verify test update Volume label
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.labels.key == "value"
+
+- name: test update Volume label with the same label
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ labels:
+ key: value
+ register: volume
+- name: verify test update Volume lable with the same label
+ assert:
+ that:
+ - volume is not changed
+
+- name: test increase Volume size
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 11
+ register: volume
+- name: verify test increase Volume size
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.size == 11
+
+- name: test decreace Volume size
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 10
+ register: volume
+- name: verify test decreace Volume size
+ assert:
+ that:
+ - volume is not changed
+ - volume.hcloud_volume.size == 11
+
+- name: test update Volume delete protection
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ delete_protection: true
+ register: volume
+- name: verify test update Volume delete protection
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.delete_protection is sameas true
+
+- name: test update Volume delete protection idempotency
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ delete_protection: true
+ register: volume
+- name: verify test update Volume delete protection idempotency
+ assert:
+ that:
+ - volume is not changed
+ - volume.hcloud_volume.delete_protection is sameas true
+
+- name: test Volume without delete protection set to be idempotent
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ register: volume
+- name: verify test Volume without delete protection set to be idempotent
+ assert:
+ that:
+ - volume is not changed
+ - volume.hcloud_volume.delete_protection is sameas true
+
+- name: test delete Volume fails if it is protected
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete Volume fails if it is protected
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "volume deletion is protected"'
+
+- name: test update Volume delete protection
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ delete_protection: false
+ register: volume
+- name: verify test update Volume delete protection
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.delete_protection is sameas false
+
+- name: test delete Volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ state: absent
+ register: result
+- name: verify delete Volume
+ assert:
+ that:
+ - result is success
+
+
+- name: test create Volume with delete protection
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 10
+ location: "fsn1"
+ delete_protection: true
+ register: volume
+- name: verify create Volume with delete protection
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.delete_protection is sameas true
+
+- name: test delete Volume fails if it is protected
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ state: absent
+ ignore_errors: yes
+ register: result
+- name: verify delete Volume fails if it is protected
+ assert:
+ that:
+ - result is failed
+ - 'result.msg == "volume deletion is protected"'
+
+- name: test update Volume delete protection
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ delete_protection: false
+ register: volume
+- name: verify test update Volume delete protection
+ assert:
+ that:
+ - volume is changed
+ - volume.hcloud_volume.delete_protection is sameas false
+
+- name: test delete Volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ state: absent
+ register: result
+- name: verify delete Volume
+ assert:
+ that:
+ - result is success
+
+- name: cleanup
+ hcloud_server:
+ name: "{{ hcloud_server_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases
new file mode 100644
index 000000000..55ec821a4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/aliases
@@ -0,0 +1,2 @@
+cloud/hcloud
+shippable/hcloud/group2
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/defaults/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/defaults/main.yml
new file mode 100644
index 000000000..52c468eeb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/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_volume_name: "{{ hcloud_prefix | truncate(60, True, '', 0) }}-i"
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml
new file mode 100644
index 000000000..407c9018a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/meta/main.yml
@@ -0,0 +1,3 @@
+collections:
+ - community.general.ipfilter
+ - hetzner.cloud
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml
new file mode 100644
index 000000000..ecea7ad31
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/hcloud_volume_info/tasks/main.yml
@@ -0,0 +1,101 @@
+# 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 ensure volume is absent
+ hcloud_volume:
+ name: "{{ hcloud_volume_name }}"
+ state: absent
+ register: result
+
+- name: setup volume
+ hcloud_volume:
+ name: "{{hcloud_volume_name}}"
+ size: 10
+ location: "fsn1"
+ labels:
+ key: value
+ register: main_volume
+- name: verify setup volume
+ assert:
+ that:
+ - main_volume is changed
+
+- name: test gather hcloud volume infos in check mode
+ hcloud_volume_info:
+ register: hcloud_volume
+ check_mode: yes
+
+- name: verify test gather hcloud volume infos in check mode
+ vars:
+ volume: "{{ hcloud_volume.hcloud_volume_info|selectattr('name','equalto',hcloud_volume_name) | first }}"
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1
+ - volume.name == "{{hcloud_volume_name}}"
+ - volume.location == "fsn1"
+ - volume.size == 10
+ - volume.linux_device is defined
+
+- name: test gather hcloud volume infos with correct label selector
+ hcloud_volume_info:
+ label_selector: "key=value"
+ register: hcloud_volume
+- name: verify test gather hcloud volume infos with correct label selector
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1
+
+- name: test gather hcloud volume infos with wrong label selector
+ hcloud_volume_info:
+ label_selector: "key!=value"
+ register: hcloud_volume
+- name: verify test gather hcloud volume infos with wrong label selector
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info | list | count == 0
+
+- name: test gather hcloud volume infos with correct name
+ hcloud_volume_info:
+ name: "{{hcloud_volume_name}}"
+ register: hcloud_volume
+- name: verify test gather hcloud volume infos with correct name
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1
+
+- name: test gather hcloud volume infos with wrong name
+ hcloud_volume_info:
+ name: "{{hcloud_volume_name}}1"
+ register: hcloud_volume
+- name: verify test gather hcloud volume infos with wrong name
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info | list | count == 0
+
+- name: test gather hcloud volume facts with correct id
+ hcloud_volume_info:
+ id: "{{main_volume.hcloud_volume.id}}"
+ register: hcloud_volume
+- name: verify test gather hcloud volume with correct id
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info|selectattr('name','equalto','{{ hcloud_volume_name }}') | list | count == 1
+
+- name: test gather hcloud volume infos with wrong id
+ hcloud_volume_info:
+ name: "4711"
+ register: hcloud_volume
+- name: verify test gather hcloud volume infos with wrong id
+ assert:
+ that:
+ - hcloud_volume.hcloud_volume_info | list | count == 0
+
+- name: cleanup
+ hcloud_volume:
+ name: "{{ hcloud_volume_name }}"
+ state: absent
+ register: result
+- name: verify cleanup
+ assert:
+ that:
+ - result is success
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml
new file mode 100644
index 000000000..27defe44c
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_selfsigned_certificate/tasks/main.yml
@@ -0,0 +1,27 @@
+# Copyright: (c) 2020, 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: create a cert temp file
+ tempfile:
+ state: file
+ register: certificate_example_com
+ tags:
+ - prepare
+- name: create a key temp file
+ tempfile:
+ state: file
+ register: certificate_example_com_key
+ tags:
+ - prepare
+ -
+- name: generate certificate
+ shell: openssl req -nodes -new -x509 -keyout {{ certificate_example_com_key.path }} -out {{ certificate_example_com.path }} -subj "/C=DE/ST=Munich/L=Bavaria/O=Dis/CN=www.example.com"
+ tags:
+ - prepare
+
+- name: set facts for future roles
+ set_fact:
+ certificate_example_com: "{{ lookup('file',certificate_example_com.path) }}"
+ certificate_example_com_key: "{{ lookup('file',certificate_example_com_key.path) }}"
+ tags:
+ - prepare
diff --git a/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml
new file mode 100644
index 000000000..18c571b67
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/integration/targets/setup_sshkey/tasks/main.yml
@@ -0,0 +1,55 @@
+# (c) 2014, James Laska <jlaska@ansible.com>
+
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
+
+- name: create a temp file
+ tempfile:
+ state: file
+ register: sshkey_file
+ tags:
+ - prepare
+
+- name: generate sshkey
+ shell: echo 'y' | ssh-keygen -P '' -f {{ sshkey_file.path }}
+ tags:
+ - prepare
+
+- name: create another temp file
+ tempfile:
+ state: file
+ register: another_sshkey_file
+ tags:
+ - prepare
+
+- name: generate another_sshkey
+ shell: echo 'y' | ssh-keygen -P '' -f {{ another_sshkey_file.path }}
+ tags:
+ - prepare
+
+- name: record fingerprint
+ shell: openssl rsa -in {{ sshkey_file.path }} -pubout -outform DER 2>/dev/null | openssl md5 -c
+ register: fingerprint
+ tags:
+ - prepare
+
+- name: set facts for future roles
+ set_fact:
+ sshkey: '{{ sshkey_file.path }}'
+ key_material: "{{ lookup('file', sshkey_file.path ~ '.pub') }}"
+ another_key_material: "{{ lookup('file', another_sshkey_file.path ~ '.pub') }}"
+ fingerprint: '{{ fingerprint.stdout.split()[1] }}'
+ tags:
+ - prepare
diff --git a/ansible_collections/hetzner/hcloud/tests/requirements.yml b/ansible_collections/hetzner/hcloud/tests/requirements.yml
new file mode 100644
index 000000000..6c49d0d9e
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/requirements.yml
@@ -0,0 +1,3 @@
+integration_tests_dependencies:
+- community.general
+- ansible.netcommon
diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt
new file mode 100644
index 000000000..caf221794
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.12.txt
@@ -0,0 +1,2 @@
+tests/utils/shippable/check_matrix.py replace-urlopen
+tests/utils/shippable/timing.py shebang
diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt
new file mode 100644
index 000000000..caf221794
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.13.txt
@@ -0,0 +1,2 @@
+tests/utils/shippable/check_matrix.py replace-urlopen
+tests/utils/shippable/timing.py shebang
diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt
new file mode 100644
index 000000000..caf221794
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.14.txt
@@ -0,0 +1,2 @@
+tests/utils/shippable/check_matrix.py replace-urlopen
+tests/utils/shippable/timing.py shebang
diff --git a/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt
new file mode 100644
index 000000000..caf221794
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/sanity/ignore-2.15.txt
@@ -0,0 +1,2 @@
+tests/utils/shippable/check_matrix.py replace-urlopen
+tests/utils/shippable/timing.py shebang
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh
new file mode 100755
index 000000000..b09bd2f3a
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/gitlab.sh
@@ -0,0 +1,99 @@
+#!/usr/bin/env bash
+
+set -o pipefail -eux
+
+declare -a args
+
+IFS='/:' read -ra args <<< "$1"
+
+ansible_version="${args[0]}"
+# shellcheck disable=SC2034
+script="${args[1]}"
+
+function join {
+ local IFS="$1";
+ shift;
+ echo "$*";
+}
+
+test="$(join / "${args[@]:1}")"
+command -v python
+python -V
+
+function retry
+{
+ # shellcheck disable=SC2034
+ for repetition in 1 2 3; do
+ set +e
+ "$@"
+ result=$?
+ set -e
+ if [ ${result} == 0 ]; then
+ return ${result}
+ fi
+ echo "@* -> ${result}"
+ done
+ echo "Command '@*' failed 3 times!"
+ exit 1
+}
+
+command -v pip
+pip --version
+pip list --disable-pip-version-check
+if [ "${ansible_version}" == "devel" ]; then
+ retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
+else
+ retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check
+fi
+export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible"
+# shellcheck disable=SC2034
+SHIPPABLE_RESULT_DIR="$(pwd)/shippable"
+TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/hetzner/hcloud"
+rm -rf "${TEST_DIR}"
+mkdir -p "${TEST_DIR}"
+cp -r "." "${TEST_DIR}"
+cd "${TEST_DIR}"
+
+# STAR: HACK install dependencies
+retry ansible-galaxy -vvv collection install community.general
+retry ansible-galaxy -vvv collection install ansible.netcommon
+retry ansible-galaxy -vvv collection install community.internal_test_tools
+retry pip install netaddr --disable-pip-version-check
+retry pip install hcloud
+retry pip install rstcheck
+# END: HACK
+
+export PYTHONIOENCODING='utf-8'
+
+if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then
+ COMPLETE=yes
+fi
+
+
+if [ -n "${COMPLETE:-}" ]; then
+ # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value
+ export CHANGED=""
+elif [[ "${CI_COMMIT_MESSAGE}" =~ ci_complete ]]; then
+ # disable change detection triggered by having 'ci_complete' in the latest commit message
+ export CHANGED=""
+else
+ # enable change detection (default behavior)
+ export CHANGED=""
+fi
+
+
+export UNSTABLE="--allow-unstable-changed"
+
+# remove empty core/extras module directories from PRs created prior to the repo-merge
+find plugins -type d -empty -print -delete
+
+ansible-test env --dump --show --timeout "50" --color -v
+
+group="${args[1]}"
+echo "$test"
+if [[ "${test}" =~ hcloud ]]; then
+ group="${args[3]}"
+ bash tests/utils/gitlab/integration.sh "shippable/hcloud/group${group}/"
+else
+ bash tests/utils/gitlab/sanity.sh "sanity/${group}"
+fi
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh
new file mode 100755
index 000000000..c94392b85
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/integration.sh
@@ -0,0 +1,20 @@
+#!/usr/bin/env bash
+
+target="$1"
+
+HCLOUD_TOKEN=$(cat hcloud_token.txt)
+# shellcheck disable=SC2034,SC2154
+changed_all_target="shippable/${cloud}/smoketest/"
+
+# shellcheck disable=SC2046
+echo "[default]
+hcloud_api_token=${HCLOUD_TOKEN}
+" >> $(pwd)/tests/integration/cloud-config-hcloud.ini
+export SHIPPABLE="true"
+
+# shellcheck disable=SC2155
+export SHIPPABLE_BUILD_NUMBER="gl-$(cat prefix.txt)"
+
+# shellcheck disable=SC2155,SC2002
+export SHIPPABLE_JOB_NUMBER="$(cat /dev/urandom | tr -dc 'a-zA-Z0-9' | fold -w 2 | head -n 1)"
+ansible-test integration --color --local -vv "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"}
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh
new file mode 100755
index 000000000..4ee96aefe
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/gitlab/sanity.sh
@@ -0,0 +1,47 @@
+#!/usr/bin/env bash
+
+set -o pipefail -eux
+
+declare -a args
+IFS='/:' read -ra args <<< "$1"
+
+group="${args[1]}"
+
+if [ "${BASE_BRANCH:-}" ]; then
+ base_branch="origin/${BASE_BRANCH}"
+else
+ base_branch=""
+fi
+
+if [ "${group}" == "extra" ]; then
+ ../internal_test_tools/tools/run.py --color
+ exit
+fi
+
+case "${group}" in
+ 1) options=(--skip-test pylint --skip-test ansible-doc --skip-test validate-modules) ;;
+ 2) options=( --test ansible-doc --test validate-modules) ;;
+ 3) options=(--test pylint plugins/modules/) ;;
+ 4) options=(--test pylint --exclude plugins/modules/) ;;
+esac
+
+# allow collection migration sanity tests for groups 3 and 4 to pass without updating this script during migration
+network_path="lib/ansible/modules/network/"
+
+if [ -d "${network_path}" ]; then
+ if [ "${group}" -eq 3 ]; then
+ options+=(--exclude "${network_path}")
+ elif [ "${group}" -eq 4 ]; then
+ options+=("${network_path}")
+ fi
+fi
+
+pip install pycodestyle
+pip install yamllint
+pip install voluptuous
+pip install pylint==2.5.3
+# shellcheck disable=SC2086
+ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \
+ --base-branch "${base_branch}" \
+ --exclude tests/utils/ \
+ "${options[@]}" --allow-disabled
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py b/ansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py
new file mode 100755
index 000000000..dfcca3e6d
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/check_matrix.py
@@ -0,0 +1,120 @@
+#!/usr/bin/env python
+"""Verify the currently executing Shippable test matrix matches the one defined in the "shippable.yml" file."""
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import datetime
+import json
+import os
+import re
+import sys
+import time
+
+try:
+ from typing import NoReturn
+except ImportError:
+ NoReturn = None
+
+try:
+ # noinspection PyCompatibility
+ from urllib2 import urlopen # pylint: disable=ansible-bad-import-from
+except ImportError:
+ # noinspection PyCompatibility
+ from urllib.request import urlopen
+
+
+def main(): # type: () -> None
+ """Main entry point."""
+ repo_full_name = os.environ['REPO_FULL_NAME']
+ required_repo_full_name = 'ansible-collections/hetzner.hcloud'
+
+ if repo_full_name != required_repo_full_name:
+ sys.stderr.write('Skipping matrix check on repo "%s" which is not "%s".\n' % (repo_full_name, required_repo_full_name))
+ return
+
+ with open('shippable.yml', 'rb') as yaml_file:
+ yaml = yaml_file.read().decode('utf-8').splitlines()
+
+ defined_matrix = [match.group(1) for match in [re.search(r'^ *- env: T=(.*)$', line) for line in yaml] if match and match.group(1) != 'none']
+
+ if not defined_matrix:
+ fail('No matrix entries found in the "shippable.yml" file.',
+ 'Did you modify the "shippable.yml" file?')
+
+ run_id = os.environ['SHIPPABLE_BUILD_ID']
+ sleep = 1
+ jobs = []
+
+ for attempts_remaining in range(4, -1, -1):
+ try:
+ jobs = json.loads(urlopen('https://api.shippable.com/jobs?runIds=%s' % run_id).read())
+
+ if not isinstance(jobs, list):
+ raise Exception('Shippable run %s data is not a list.' % run_id)
+
+ break
+ except Exception as ex:
+ if not attempts_remaining:
+ fail('Unable to retrieve Shippable run %s matrix.' % run_id,
+ str(ex))
+
+ sys.stderr.write('Unable to retrieve Shippable run %s matrix: %s\n' % (run_id, ex))
+ sys.stderr.write('Trying again in %d seconds...\n' % sleep)
+ time.sleep(sleep)
+ sleep *= 2
+
+ if len(jobs) != len(defined_matrix):
+ if len(jobs) == 1:
+ hint = '\n\nMake sure you do not use the "Rebuild with SSH" option.'
+ else:
+ hint = ''
+
+ fail('Shippable run %s has %d jobs instead of the expected %d jobs.' % (run_id, len(jobs), len(defined_matrix)),
+ 'Try re-running the entire matrix.%s' % hint)
+
+ actual_matrix = dict((job.get('jobNumber'), dict(tuple(line.split('=', 1)) for line in job.get('env', [])).get('T', '')) for job in jobs)
+ errors = [(job_number, test, actual_matrix.get(job_number)) for job_number, test in enumerate(defined_matrix, 1) if actual_matrix.get(job_number) != test]
+
+ if len(errors):
+ error_summary = '\n'.join('Job %s expected "%s" but found "%s" instead.' % (job_number, expected, actual) for job_number, expected, actual in errors)
+
+ fail('Shippable run %s has a job matrix mismatch.' % run_id,
+ 'Try re-running the entire matrix.\n\n%s' % error_summary)
+
+
+def fail(message, output): # type: (str, str) -> NoReturn
+ # Include a leading newline to improve readability on Shippable "Tests" tab.
+ # Without this, the first line becomes indented.
+ output = '\n' + output.strip()
+
+ timestamp = datetime.datetime.utcnow().replace(microsecond=0).isoformat()
+
+ # hack to avoid requiring junit-xml, which isn't pre-installed on Shippable outside our test containers
+ xml = '''
+<?xml version="1.0" encoding="utf-8"?>
+<testsuites disabled="0" errors="1" failures="0" tests="1" time="0.0">
+\t<testsuite disabled="0" errors="1" failures="0" file="None" log="None" name="ansible-test" skipped="0" tests="1" time="0" timestamp="%s" url="None">
+\t\t<testcase classname="timeout" name="timeout">
+\t\t\t<error message="%s" type="error">%s</error>
+\t\t</testcase>
+\t</testsuite>
+</testsuites>
+''' % (timestamp, message, output)
+
+ path = 'shippable/testresults/check-matrix.xml'
+ dir_path = os.path.dirname(path)
+
+ if not os.path.exists(dir_path):
+ os.makedirs(dir_path)
+
+ with open(path, 'w') as junit_fd:
+ junit_fd.write(xml.lstrip())
+
+ sys.stderr.write(message + '\n')
+ sys.stderr.write(output + '\n')
+
+ sys.exit(1)
+
+
+if __name__ == '__main__':
+ main()
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh
new file mode 100755
index 000000000..da037e09e
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/hcloud.sh
@@ -0,0 +1,34 @@
+#!/usr/bin/env bash
+
+set -o pipefail -eux
+
+declare -a args
+IFS='/:' read -ra args <<< "$1"
+
+cloud="${args[0]}"
+python="${args[1]}"
+group="${args[2]}"
+
+target="shippable/${cloud}/group${group}/"
+
+stage="${S:-prod}"
+
+changed_all_target="shippable/${cloud}/smoketest/"
+
+if ! ansible-test integration "${changed_all_target}" --list-targets > /dev/null 2>&1; then
+ # no smoketest tests are available for this cloud
+ changed_all_target="none"
+fi
+
+if [ "${group}" == "1" ]; then
+ # only run smoketest tests for group1
+ changed_all_mode="include"
+else
+ # smoketest tests already covered by group1
+ changed_all_mode="exclude"
+fi
+
+# shellcheck disable=SC2086
+ansible-test integration --color -v --retry-on-error "${target}" ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} ${UNSTABLE:+"$UNSTABLE"} \
+ --remote-terminate always --remote-stage "${stage}" \
+ --docker --python "${python}" --changed-all-target "${changed_all_target}" --changed-all-mode "${changed_all_mode}"
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh
new file mode 100755
index 000000000..9339aeda4
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/sanity.sh
@@ -0,0 +1,27 @@
+#!/usr/bin/env bash
+
+set -o pipefail -eux
+
+declare -a args
+IFS='/:' read -ra args <<< "$1"
+
+group="${args[1]}"
+
+if [ "${BASE_BRANCH:-}" ]; then
+ base_branch="origin/${BASE_BRANCH}"
+else
+ base_branch=""
+fi
+
+if [ "${group}" == "extra" ]; then
+ # ansible-galaxy -vvv collection install community.internal_test_tools
+ git clone --single-branch --depth 1 https://github.com/ansible-collections/community.internal_test_tools.git ../../community/internal_test_tools
+
+ ../internal_test_tools/tools/run.py --color
+ exit
+fi
+
+# shellcheck disable=SC2086
+ansible-test sanity --color -v --junit ${COVERAGE:+"$COVERAGE"} ${CHANGED:+"$CHANGED"} \
+ --docker --base-branch "${base_branch}" \
+ --allow-disabled
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh
new file mode 100755
index 000000000..8c0bd6deb
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/shippable.sh
@@ -0,0 +1,213 @@
+#!/usr/bin/env bash
+
+set -o pipefail -eux
+
+declare -a args
+IFS='/:' read -ra args <<< "$1"
+
+ansible_version="${args[0]}"
+script="${args[1]}"
+
+function join {
+ local IFS="$1";
+ shift;
+ echo "$*";
+}
+
+# Ensure we can write other collections to this dir
+sudo chown "$(whoami)" "${PWD}/../../"
+
+test="$(join / "${args[@]:1}")"
+
+docker images ansible/ansible
+docker images quay.io/ansible/*
+docker ps
+
+for container in $(docker ps --format '{{.Image}} {{.ID}}' | grep -v -e '^drydock/' -e '^quay.io/ansible/azure-pipelines-test-container:' | sed 's/^.* //'); do
+ docker rm -f "${container}" || true # ignore errors
+done
+
+docker ps
+
+if [ -d /home/shippable/cache/ ]; then
+ ls -la /home/shippable/cache/
+fi
+
+command -v python
+python -V
+
+function retry
+{
+ # shellcheck disable=SC2034
+ for repetition in 1 2 3; do
+ set +e
+ "$@"
+ result=$?
+ set -e
+ if [ ${result} == 0 ]; then
+ return ${result}
+ fi
+ echo "@* -> ${result}"
+ done
+ echo "Command '@*' failed 3 times!"
+ exit 1
+}
+
+command -v pip
+pip --version
+pip list --disable-pip-version-check
+if [ "${ansible_version}" == "devel" ]; then
+ retry pip install https://github.com/ansible/ansible/archive/devel.tar.gz --disable-pip-version-check
+else
+ retry pip install "https://github.com/ansible/ansible/archive/stable-${ansible_version}.tar.gz" --disable-pip-version-check
+fi
+
+if [ "${SHIPPABLE_BUILD_ID:-}" ]; then
+ export ANSIBLE_COLLECTIONS_PATHS="${HOME}/.ansible"
+ SHIPPABLE_RESULT_DIR="$(pwd)/shippable"
+ TEST_DIR="${ANSIBLE_COLLECTIONS_PATHS}/ansible_collections/hetzner/hcloud"
+ mkdir -p "${TEST_DIR}"
+ cp -aT "${SHIPPABLE_BUILD_DIR}" "${TEST_DIR}"
+ cd "${TEST_DIR}"
+else
+ export ANSIBLE_COLLECTIONS_PATHS="${PWD}/../../../"
+fi
+
+# STAR: HACK install dependencies
+retry ansible-galaxy -vvv collection install community.general
+retry ansible-galaxy -vvv collection install ansible.netcommon
+
+retry pip install hcloud
+retry pip install netaddr --disable-pip-version-check
+retry ansible-galaxy -vvv collection install community.internal_test_tools
+# END: HACK
+
+export PYTHONIOENCODING='utf-8'
+
+if [ "${JOB_TRIGGERED_BY_NAME:-}" == "nightly-trigger" ]; then
+ COVERAGE=yes
+ COMPLETE=yes
+fi
+
+if [ -n "${COVERAGE:-}" ]; then
+ # on-demand coverage reporting triggered by setting the COVERAGE environment variable to a non-empty value
+ export COVERAGE="--coverage"
+elif [[ "${COMMIT_MESSAGE}" =~ ci_coverage ]]; then
+ # on-demand coverage reporting triggered by having 'ci_coverage' in the latest commit message
+ export COVERAGE="--coverage"
+else
+ # on-demand coverage reporting disabled (default behavior, always-on coverage reporting remains enabled)
+ export COVERAGE="--coverage-check"
+fi
+
+if [ -n "${COMPLETE:-}" ]; then
+ # disable change detection triggered by setting the COMPLETE environment variable to a non-empty value
+ export CHANGED=""
+elif [[ "${COMMIT_MESSAGE}" =~ ci_complete ]]; then
+ # disable change detection triggered by having 'ci_complete' in the latest commit message
+ export CHANGED=""
+else
+ # enable change detection (default behavior)
+ export CHANGED="--changed"
+fi
+
+if [ "${IS_PULL_REQUEST:-}" == "true" ]; then
+ # run unstable tests which are targeted by focused changes on PRs
+ export UNSTABLE="--allow-unstable-changed"
+else
+ # do not run unstable tests outside PRs
+ export UNSTABLE=""
+fi
+
+# remove empty core/extras module directories from PRs created prior to the repo-merge
+find plugins -type d -empty -print -delete
+
+function cleanup
+{
+ # for complete on-demand coverage generate a report for all files with no coverage on the "sanity/5" job so we only have one copy
+ if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ] && [ "${test}" == "sanity/5" ]; then
+ stub="--stub"
+ # trigger coverage reporting for stubs even if no other coverage data exists
+ mkdir -p tests/output/coverage/
+ else
+ stub=""
+ fi
+
+ if [ -d tests/output/coverage/ ]; then
+ if find tests/output/coverage/ -mindepth 1 -name '.*' -prune -o -print -quit | grep -q .; then
+ process_coverage='yes' # process existing coverage files
+ elif [ "${stub}" ]; then
+ process_coverage='yes' # process coverage when stubs are enabled
+ else
+ process_coverage=''
+ fi
+
+ if [ "${process_coverage}" ]; then
+ # use python 3.7 for coverage to avoid running out of memory during coverage xml processing
+ # only use it for coverage to avoid the additional overhead of setting up a virtual environment for a potential no-op job
+ virtualenv --python /usr/bin/python3.7 ~/ansible-venv
+ set +ux
+ . ~/ansible-venv/bin/activate
+ set -ux
+
+ # shellcheck disable=SC2086
+ ansible-test coverage xml --color -v --requirements --group-by command --group-by version ${stub:+"$stub"}
+ cp -a tests/output/reports/coverage=*.xml "$SHIPPABLE_RESULT_DIR/codecoverage/"
+
+ if [ "${ansible_version}" != "2.9" ]; then
+ # analyze and capture code coverage aggregated by integration test target
+ ansible-test coverage analyze targets generate -v "$SHIPPABLE_RESULT_DIR/testresults/coverage-analyze-targets.json"
+ fi
+
+ # upload coverage report to codecov.io only when using complete on-demand coverage
+ if [ "${COVERAGE}" == "--coverage" ] && [ "${CHANGED}" == "" ]; then
+ for file in tests/output/reports/coverage=*.xml; do
+ flags="${file##*/coverage=}"
+ flags="${flags%-powershell.xml}"
+ flags="${flags%.xml}"
+ # remove numbered component from stub files when converting to tags
+ flags="${flags//stub-[0-9]*/stub}"
+ flags="${flags//=/,}"
+ flags="${flags//[^a-zA-Z0-9_,]/_}"
+
+ bash <(curl -s https://ansible-ci-files.s3.us-east-1.amazonaws.com/codecov/codecov.sh) \
+ -f "${file}" \
+ -F "${flags}" \
+ -n "${test}" \
+ -t 8a86e979-f37b-4d5d-95a4-960c280d5eaa \
+ -X coveragepy \
+ -X gcov \
+ -X fix \
+ -X search \
+ -X xcode \
+ || echo "Failed to upload code coverage report to codecov.io: ${file}"
+ done
+ fi
+ fi
+ fi
+
+ if [ -d tests/output/junit/ ]; then
+ cp -aT tests/output/junit/ "$SHIPPABLE_RESULT_DIR/testresults/"
+ fi
+
+ if [ -d tests/output/data/ ]; then
+ cp -a tests/output/data/ "$SHIPPABLE_RESULT_DIR/testresults/"
+ fi
+
+ if [ -d tests/output/bot/ ]; then
+ cp -aT tests/output/bot/ "$SHIPPABLE_RESULT_DIR/testresults/"
+ fi
+}
+
+if [ "${SHIPPABLE_BUILD_ID:-}" ]; then trap cleanup EXIT; fi
+
+if [[ "${COVERAGE:-}" == "--coverage" ]]; then
+ timeout=60
+else
+ timeout=45
+fi
+
+ansible-test env --dump --show --timeout "${timeout}" --color -v
+
+if [ "${SHIPPABLE_BUILD_ID:-}" ]; then "tests/utils/shippable/check_matrix.py"; fi
+"tests/utils/shippable/${script}.sh" "${test}"
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py
new file mode 100755
index 000000000..fb538271b
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.py
@@ -0,0 +1,16 @@
+#!/usr/bin/env python3.7
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+import sys
+import time
+
+start = time.time()
+
+sys.stdin.reconfigure(errors='surrogateescape')
+sys.stdout.reconfigure(errors='surrogateescape')
+
+for line in sys.stdin:
+ seconds = time.time() - start
+ sys.stdout.write('%02d:%02d %s' % (seconds // 60, seconds % 60, line))
+ sys.stdout.flush()
diff --git a/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh
new file mode 100755
index 000000000..77e257830
--- /dev/null
+++ b/ansible_collections/hetzner/hcloud/tests/utils/shippable/timing.sh
@@ -0,0 +1,5 @@
+#!/usr/bin/env bash
+
+set -o pipefail -eu
+
+"$@" 2>&1 | "$(dirname "$0")/timing.py"