From a536ecf1ef326c9e7b49ba825d5ef428a66f7c95 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Julian=20T=C3=B6lle?= Date: Thu, 26 Jan 2023 15:50:41 +0100 Subject: [PATCH] feat(inventory): handle servers that dont work with connect_with setting Currently if any of the servers in the inventory do not work with the selected `connect_with` mode, the script exits and returns 0 servers. This can happen for example if one of your servers does not have a public ipv4 address, but you set `connect_with: public_ipv4` (default). This commit changes the behaviour to log a warning message, and just skip setting `ansible_host` for this server. This server will not be reachable by ansible by default, but users can use `compose` to override the `ansible_host` that we set based on the other variables. --- .../inventory-handle-invalid-connect-with.yml | 2 + plugins/inventory/hcloud.py | 63 ++++++++++++++----- 2 files changed, 48 insertions(+), 17 deletions(-) create mode 100644 changelogs/fragments/inventory-handle-invalid-connect-with.yml diff --git a/changelogs/fragments/inventory-handle-invalid-connect-with.yml b/changelogs/fragments/inventory-handle-invalid-connect-with.yml new file mode 100644 index 00000000..c7dd7602 --- /dev/null +++ b/changelogs/fragments/inventory-handle-invalid-connect-with.yml @@ -0,0 +1,2 @@ +minor_changes: + - inventory plugin - Log warning instead of crashing when some servers do not work with global connect_with setting. diff --git a/plugins/inventory/hcloud.py b/plugins/inventory/hcloud.py index bba015d6..303dd0ab 100644 --- a/plugins/inventory/hcloud.py +++ b/plugins/inventory/hcloud.py @@ -37,7 +37,11 @@ type: str required: false connect_with: - description: Connect to the server using the value from this field. + 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: @@ -237,22 +241,14 @@ def _set_server_attributes(self, server): if server_private_network.network.id == self.network.id: self.inventory.set_variable(server.name, "private_ipv4", to_native(server_private_network.ip)) - if self.get_option("connect_with") == "public_ipv4": - self.inventory.set_variable(server.name, "ansible_host", to_native(server.public_net.ipv4.ip)) - if self.get_option("connect_with") == "public_ipv6": - self.inventory.set_variable(server.name, "ansible_host", to_native(self._first_ipv6_address(server.public_net.ipv6.ip))) - elif self.get_option("connect_with") == "hostname": - self.inventory.set_variable(server.name, "ansible_host", to_native(server.name)) - elif self.get_option("connect_with") == "ipv4_dns_ptr": - self.inventory.set_variable(server.name, "ansible_host", to_native(server.public_net.ipv4.dns_ptr)) - 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: - self.inventory.set_variable(server.name, "ansible_host", to_native(server_private_network.ip)) - else: - raise AnsibleError( - "You can only connect via private IPv4 if you specify a network") + 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: @@ -278,6 +274,39 @@ def _set_server_attributes(self, server): # 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())