Repository: ansible-collections/community.vmware

Issue:
community.vmware.vmware_cfg_backup: Failed to write backup file
<!--- Verify first that your issue is not already reported on GitHub -->
<!--- Also test if the latest release and devel branch are affected too -->
<!--- Complete *all* sections as described, this form is processed automatically -->

##### SUMMARY
When I run the ansible playbook it's return "Failed to write backup file" error.

```
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] *************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [ESXI backup test] ******************************************************************************************************************************************************************************************************************
fatal: [localhost -> localhost]: FAILED! => {"changed": false, "msg": "Failed to write backup file. Ensure that the dest path exists and is writable. Details : <urlopen error [Errno 111] Connection refused>"}

PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0
```


##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
<!--- Write the short name of the module, plugin, task or feature below, use your best guess if unsure -->
`community.vmware.vmware_cfg_backup`

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
ansible 2.9.6
  config file = /etc/ansible/ansible.cfg
  configured module search path = ['/home/sergen/.ansible/plugins/modules', '/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python3/dist-packages/ansible
  executable location = /usr/bin/ansible
  python version = 3.8.10 (default, Nov 26 2021, 20:14:08) [GCC 9.3.0]

```

##### COLLECTION VERSION
<!--- Paste verbatim output from "ansible-galaxy collection list <namespace>.<collection>"  between the quotes
for example: ansible-galaxy collection list community.general
-->
```paste below
ansible-galaxy collection list community.vmware
usage: ansible-galaxy collection [-h] COLLECTION_ACTION ...
ansible-galaxy collection: error: argument COLLECTION_ACTION: invalid choice: 'list' (choose from 'init', 'build', 'publish', 'install')

```
List command is not working but I install collection with this command

`ansible-galaxy collection install community.vmware`

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below

```
no output

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
Ubuntu 20.04
VMware ESXI 7.0.3

##YAML FILE

returns the same result in both files 

```
- hosts: localhost
  vars:
    esxi_hostname: "192.168.88.154"
    esxi_username: "root"
    esxi_password: "password"
  tasks:
  - name: ESXI backup test
    local_action:
      module: vmware_cfg_backup
      hostname: '{{esxi_hostname}}'
      username: '{{esxi_username}}'
      password: '{{esxi_password}}'
      state: saved
      dest: /tmp/
      validate_certs: no
```
```

cat vmware.yaml
- hosts: localhost
  vars:
    esxi_hostname: "192.168.88.154"
    esxi_username: "root"
    esxi_password: "password"
  tasks:
  - name: Save
    community.vmware.vmware_cfg_backup:
      hostname: '{{ esxi_hostname }}'
      username: '{{ esxi_username }}'
      password: '{{ esxi_password }}'
      state: saved
      dest: /tmp/
      validate_certs: no
    delegate_to: localhost

```


##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml

```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- Describe what you expected to happen when running the steps above -->


##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes -->
```
ansible-playbook vmware.yaml
[WARNING]: provided hosts list is empty, only localhost is available. Note that the implicit localhost does not match 'all'

PLAY [localhost] *************************************************************************************************************************************************************************************************************************

TASK [Gathering Facts] *******************************************************************************************************************************************************************************************************************
ok: [localhost]

TASK [Save] ******************************************************************************************************************************************************************************************************************************
fatal: [localhost -> localhost]: FAILED! => {"changed": false, "msg": "Failed to write backup file. Ensure that the dest path exists and is writable. Details : <urlopen error [Errno 111] Connection refused>"}

PLAY RECAP *******************************************************************************************************************************************************************************************************************************
localhost                  : ok=1    changed=0    unreachable=0    failed=1    skipped=0    rescued=0    ignored=0


```



Discussion:
Files identified in the description:
None

If these files are inaccurate, please update the `component name` section of the description or use the `!component` bot command.

[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: components_banner --->
!component
@sergenaras , did you manage to solve this? I am facing the same
@nb25186 no, I couldn't solve. I think this community is inactive or something like that
I've tested this:

> ```
> - hosts: localhost
>   tasks:
>   - name: Save
>     community.vmware.vmware_cfg_backup:
>       hostname: '192.168.88.154'
>       username: 'root'
>       password: 'password'
>       state: saved
>       dest: /tmp/
>       validate_certs: no
>     delegate_to: localhost
> ```

with

- ansible 2.9.27
- ansible 5.6.0 (ansible-core 2.12.4 + community.vmware 1.18.0)
- ansible 6.0.0a1 (ansible-core 2.13.0b0 + community.vmware 2.2.0)

but couldn't reproduce your issue. I thought you might have lockdown enabled, but in this case the error message is different:

`fatal: [localhost -> localhost]: FAILED! => {"changed": false, "msg": "User root does not have required permission to log on to vCenter or ESXi API at 192.168.88.154:443 : Permission to perform this operation was denied."}`

Although your error message says something about being unable to write backup file, this

`Details : <urlopen error [Errno 111] Connection refused>`

looks like a network connectivity problem. What happens if you run `curl https://192.168.88.154` on your ansible machine?
```
curl http://192.168.88.154
<HTML><BODY><H1>301 Moved Permanently</H1></BODY></HTML>
```

```
curl https://192.168.88.154
curl: (60) SSL certificate problem: unable to get local issuer certificate
More details here: https://curl.haxx.se/docs/sslcerts.html

curl failed to verify the legitimacy of the server and therefore could not
establish a secure connection to it. To learn more about this situation and
how to fix it, please visit the web page mentioned above.
```

Is it about ssl? But I say in ansible _validate_certs: no_
Too bad, I was hoping that it's a simple connectivity issue. I'm afraid I'm running out of ideas, especially since I can't reproduce the problem.

@Akasurde @sky-joker Do you have any ideas?

> Is it about ssl? But I say in ansible _validate_certs: no_

I doubt it. When I remove `validate_certs: no` I get an explicit error message about certificate verification:

```
fatal: [localhost]: FAILED! => {"changed": false, "msg": "Unable to connect to vCenter or ESXi API at 192.168.88.154 on TCP/443: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1123)"}
```

As a work-around, couldn't you get the configuration through vCenter?

```
- hosts: localhost
  gather_facts: false

  tasks:
    - name: Save
      vmware_cfg_backup:
        hostname: "vCenter"
        username: "vCenter_User"
        password: "vCenter_PW"
        esxi_hostname: "ESXi"
        state: saved
        dest: /tmp/
        validate_certs: no
      delegate_to: localhost
```
I'm doing all this on vmware workstation and unfortunately I don't have the resources to install vCenter. That's why I couldn't test in vCenter. 
Like Serge, I  am also working with a standalone ESXi, no vcenter available here. CURL from ansible machine to esxi host http/https (the later with -k to ignore cert verification) ran well

I ran a playbook like Mario's and still get the same issue (I added a netstat at the end of the ansible command)

<img width="770" alt="image" src="https://user-images.githubusercontent.com/92930247/164017710-3b68370f-a756-490e-98b6-9a831b78adeb.png">



Same problem here. 

I checked file permissions and firewall rules (ports 22, 443) so it seems to be no issue with that. However the error message is even not really clear if it is a disk write problem or a connection problem. So I check the code, stacktrace and network again. This time with ports (22, 80, 443).

### Stacktrace

```bash
The full traceback is:
  File "/tmp/ansible_vmware_cfg_backup_payload_mjf8j77e/ansible_vmware_cfg_backup_payload.zip/ansible_collections/community/vmware/plugins/modules/vmware_cfg_backup.py", line 183, in save_configuration
  File "/tmp/ansible_vmware_cfg_backup_payload_mjf8j77e/ansible_vmware_cfg_backup_payload.zip/ansible/module_utils/urls.py", line 1393, in open_url
    return Request().open(method, url, data=data, headers=headers, use_proxy=use_proxy,
  File "/tmp/ansible_vmware_cfg_backup_payload_mjf8j77e/ansible_vmware_cfg_backup_payload.zip/ansible/module_utils/urls.py", line 1304, in open
    return urllib_request.urlopen(request, None, timeout)
  File "/usr/lib/python3.8/urllib/request.py", line 222, in urlopen
    return opener.open(url, data, timeout)
  File "/usr/lib/python3.8/urllib/request.py", line 525, in open
    response = self._open(req, data)
  File "/usr/lib/python3.8/urllib/request.py", line 542, in _open
    result = self._call_chain(self.handle_open, protocol, protocol +
  File "/usr/lib/python3.8/urllib/request.py", line 502, in _call_chain
    result = func(*args)
  File "/usr/lib/python3.8/urllib/request.py", line 1383, in http_open
    return self.do_open(http.client.HTTPConnection, req)
  File "/usr/lib/python3.8/urllib/request.py", line 1357, in do_open
    raise URLError(err)
fatal: [esx_XY -> localhost]: FAILED! => changed=false
  invocation:
    module_args:
      dest: /tmp/LOCATION_XY
      esxi_hostname: null
      hostname: esx_XY
      password: VALUE_SPECIFIED_IN_NO_LOG_PARAMETER
      port: 443
      proxy_host: null
      proxy_port: null
      src: null
      state: saved
      username: nobody_XY
      validate_certs: false
  msg: 'Failed to write backup file. Ensure that the dest path exists and is writable. Details : <urlopen error timed out>'
```

### VMWare Collection Version

```bash
Collection       Version
---------------- -------
community.vmware 1.12.0
```

I know it is dead old version, but there seem to be no updates on the vmware_cfg_backup.py module since then.

### NMAP Ansible Controller

```bash
Host is up (0.00069s latency).

PORT    STATE    SERVICE
22/tcp  open     ssh
80/tcp  filtered http
443/tcp open     https
```

Have a look at the **filtered** port 80!

### Finding/Cross-Check

Interesting fact, the playbook works when executing locally.

### NMAP local machine

My nmap says this time

```bash
Host is up (0.00069s latency).

PORT    STATE SERVICE
22/tcp  open  ssh
80/tcp  open  http
443/tcp open  https
```

Have a look at the **open** port 80!

So...

### Solution

I found out that, the "preflight" or HEAD request probably needs HTTP with Port 80 open or another call of the script and that from our ansible controller we do not have port 80 open, but locally I did have access to port 80. 

So the solution was to **_open port 80_** and the vmware_cfg_backup.py works as expected.

### Security

However from a security perspective is it really necessary to open port 80? Should this be declared as a bug and be fixed in the module itself so not to use 80 and rewrite it to be able to work only with port 443?! I think it should.

Resolving patch:
diff --git a/plugins/modules/vmware_cfg_backup.py b/plugins/modules/vmware_cfg_backup.py
--- a/plugins/modules/vmware_cfg_backup.py
+++ b/plugins/modules/vmware_cfg_backup.py
@@ -172,6 +172,8 @@ def reset_configuration(self):
     def save_configuration(self):
         url = self.host.configManager.firmwareSystem.BackupFirmwareConfiguration()
         url = url.replace('*', self.host.name)
+        if self.module.params["port"] == 443:
+            url = url.replace("http:", "https:")
         if os.path.isdir(self.dest):
             filename = url.rsplit('/', 1)[1]
             self.dest = os.path.join(self.dest, filename)


Test patch: