Repository: saltstack/salt
Pull request: Fix #59863
### What issues does this PR fix or reference?
Fixes: #59863 

Problem statement:
[BUG] Hetzner cloud driver does not recognize machines when rolling out a map
**Description**
When you are using the hetzner driver for saltcloud and try to rollout a machine, via a map

```
salt-cloud -m my.map
```

there is an error which pops up, caused by trying to convert a `PrivateNet`-model from package `hcloud` into an dictionary by using a property which doesn't exist in the model.

Without DEBUG output level, this bug causes the map rollout to not recognize that this machine is already existing and tries to roll it out nevertheless.

```
[DEBUG   ] Failed to execute 'hetzner.list_nodes()' while querying for running nodes: 'PrivateNet' object has no attribute 'ipv4'
Traceback (most recent call last):
  File "/usr/local/lib/python3.7/dist-packages/salt/cloud/__init__.py", line 2367, in run_parallel_map_providers_query
    salt.utils.data.simple_types_filter(cloud.clouds[data["fun"]]()),
  File "/usr/local/lib/python3.7/dist-packages/salt/cloud/clouds/hetzner.py", line 184, in list_nodes
    nodes = list_nodes_full()
  File "/usr/local/lib/python3.7/dist-packages/salt/cloud/clouds/hetzner.py", line 167, in list_nodes_full
    "private_ips": list(map(_network_to_dict, node.private_net)),
  File "/usr/local/lib/python3.7/dist-packages/salt/cloud/clouds/hetzner.py", line 87, in _network_to_dict
    log.info(net.ipv4)
AttributeError: 'PrivateNet' object has no attribute 'ipv4'
```

**Expected behavior**
`salt-cloud -m my.map` should not even try to rollout a machine which already exists in a hetzner project.

**Screenshots**
If applicable, add screenshots to help explain your problem.

**Versions Report**
<details><summary>salt --versions-report</summary>
(Provided by running salt --versions-report. Please also mention any differences in master/minion versions.) 

```
Salt Version:
          Salt: 3002.5
 
Dependency Versions:
          cffi: Not Installed
      cherrypy: Not Installed
      dateutil: 2.8.1
     docker-py: Not Installed
         gitdb: Not Installed
     gitpython: Not Installed
        Jinja2: 2.11.3
       libgit2: Not Installed
      M2Crypto: Not Installed
          Mako: Not Installed
       msgpack: 1.0.2
  msgpack-pure: Not Installed
  mysql-python: Not Installed
     pycparser: Not Installed
      pycrypto: Not Installed
  pycryptodome: 3.10.1
        pygit2: Not Installed
        Python: 3.7.3 (default, Jul 25 2020, 13:03:44)
  python-gnupg: Not Installed
        PyYAML: 5.4.1
         PyZMQ: 22.0.3
         smmap: Not Installed
       timelib: Not Installed
       Tornado: 4.5.3
           ZMQ: 4.3.4
 
System Versions:
          dist: debian 10 buster
        locale: UTF-8
       machine: x86_64
       release: 4.19.0-13-amd64
        system: Linux
       version: Debian GNU/Linux 10 buster
```
</details>

Hints from issue discussion:
Hi there! Welcome to the Salt Community! Thank you for making your first contribution. We have a lengthy process for issues and PRs. Someone from the Core Team will follow up as soon as possible. In the meantime, here’s some information that may help as you continue your Salt journey.
Please be sure to review our [Code of Conduct](https://github.com/saltstack/salt/blob/master/CODE_OF_CONDUCT.md). Also, check out some of our community resources including:

  - [Community Wiki](https://github.com/saltstack/community/wiki)
  - [Salt’s Contributor Guide](https://docs.saltproject.io/en/master/topics/development/contributing.html)
  - [Join our Community Slack](https://saltstackcommunity.herokuapp.com/)
  - [IRC on Freenode](https://webchat.freenode.net/#salt)
  - [SaltStack YouTube channel](https://www.youtube.com/user/SaltStack)
  - [SaltStackInc Twitch channel](https://www.twitch.tv/saltprojectoss)

There are lots of ways to get involved in our community. Every month, there are around a dozen opportunities to meet with other contributors and the Salt Core team and collaborate in real time. The best way to keep track is by subscribing to the Salt Community Events Calendar.
If you have additional questions, email us at core@saltstack.com. We’re glad you’ve joined our community and look forward to doing awesome things with you!

Commit message: Lint

Patch to review:
diff --git a/changelog/59864.fixed b/changelog/59864.fixed
new file mode 100644
index 000000000000..6c1d3b9b6002
--- /dev/null
+++ b/changelog/59864.fixed
@@ -0,0 +1 @@
+Fix Hetzner cloud driver does not recognize machines when rolling out a map
\ No newline at end of file
diff --git a/salt/cloud/clouds/hetzner.py b/salt/cloud/clouds/hetzner.py
index 1d113fa60c82..e305027ba841 100644
--- a/salt/cloud/clouds/hetzner.py
+++ b/salt/cloud/clouds/hetzner.py
@@ -83,13 +83,19 @@ def _datacenter_to_dict(datacenter):
     }
 
 
-def _network_to_dict(net):
+def _public_network_to_dict(net):
     return {
         "ipv4": getattr(net.ipv4, "ip", None),
         "ipv6": getattr(net.ipv6, "ip", None),
     }
 
 
+def _private_network_to_dict(net):
+    return {
+        "ipv4": getattr(net.ip, "ip", None),
+    }
+
+
 def _connect_client():
     provider = get_configured_provider()
     return hcloud.Client(provider["key"])
@@ -162,8 +168,8 @@ def list_nodes_full(call=None):
             "image": node.image.name,
             "size": node.server_type.name,
             "state": node.status,
-            "public_ips": _network_to_dict(node.public_net),
-            "private_ips": list(map(_network_to_dict, node.private_net)),
+            "public_ips": _public_network_to_dict(node.public_net),
+            "private_ips": list(map(_private_network_to_dict, node.private_net)),
             "labels": node.labels,
             "created": str(node.created),
             "datacenter": _datacenter_to_dict(node.datacenter),


Code review comments:
diff_hunk: @@ -83,13 +83,19 @@ def _datacenter_to_dict(datacenter):
     }
 
 
-def _network_to_dict(net):
+def _public_network_to_dict(net):
     return {
         "ipv4": getattr(net.ipv4, "ip", None),
         "ipv6": getattr(net.ipv6, "ip", None),
     }
 
 
+def _private_network_to_dict(net):
+    return {
+        "ipv4": getattr(net.ip, "ip", None),
path: salt/cloud/clouds/hetzner.py
text: @user1:
Is there not an instance where private will have an ipv6 address?

@author:
Hi! No. Private nets are always represented as IPv4 in the Hetzner Cloud API. 

https://docs.hetzner.cloud#servers-get-a-server

@user2:
Wouldn't it be better to just call it `ip`?