Repository: ansible/ansible

Issue:
`firewalld` module fails to create new zone on  Ubuntu 16.04
<!---
Verify first that your issue/request is not already reported on GitHub.
THIS FORM WILL BE READ BY A MACHINE, COMPLETE ALL SECTIONS AS DESCRIBED.
Also test if the latest release, and devel branch are affected too.
ALWAYS add information AFTER (OUTSIDE) these html comments.
Otherwise it may end up being automatically closed by our bot. -->

##### SUMMARY
<!--- Explain the problem briefly -->
`firewalld` module fails to create zone on Ubuntu 16.04 host

Little note: commands supplied below set `ansible_interpreter_version` because `firewalld` package on Ubuntu contains only Python3 bindings. This may or may not have impact on the issue

##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
<!--- Insert, BELOW THIS COMMENT, the name of the module, plugin, task or feature.
Do not include extra details here, e.g. "vyos_command" not "the network module vyos_command" or the full path-->
`firewalld`


##### ANSIBLE VERSION
<!--- Paste, BELOW THIS COMMENT, verbatim output from "ansible --version" between quotes below -->
```
ansible 2.6.0
  config file = /media/important/Repos/allani/dev-scripts/infrastructure/ansible/ansible.cfg
  configured module search path = [u'/media/important/Repos/allani/dev-scripts/infrastructure/ansible/library', u'/media/important/Repos/allani/dev-scripts/infrastructure/ansible/3drparty/kubespray/library', u'/usr/share/ansible']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.15 (default, May  1 2018, 20:16:04) [GCC 7.3.1 20180406]
```

##### CONFIGURATION
<!--- If using Ansible 2.4 or above, paste, BELOW THIS COMMENT, the results of "ansible-config dump --only-changed"
Otherwise, mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).-->

##### OS / ENVIRONMENT
<!--- Mention, BELOW THIS COMMENT, the OS you are running Ansible from, and the OS you are
managing, or say "N/A" for anything that is not platform-specific.
Also mention the specific version of what you are trying to control,
e.g. if this is a network bug the version of firmware on the network device.-->
* Ansible run on latest Archlinux
* managed system is up-to-date Ubuntu 16.04 LTS
* firewalld package on target version  == `0.4.0-1ubuntu0.1`
* it's possible to manually create zone on this system

  ```shell
  root@myhost# firewall-cmd --new-zone=test --permanent
  success
  ```

##### STEPS TO REPRODUCE
<!--- For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used. -->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
ansible -m firewalld -a "zone=test state=present permanent=True" -i myinventory myhost --become -e ansible_python_interpreter=python3
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
New zone 'test' created by firewalld module


##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
<!--- Paste verbatim command output between quotes below -->
```
$ ansible -m firewalld -a "zone=test state=present permanent=True" -i production_poland/hosts k1 --become -e ansible_python_interpreter=python3 -vvvv
ansible 2.6.0
  config file = /media/important/Repos/allani/dev-scripts/infrastructure/ansible/ansible.cfg
  configured module search path = [u'/media/important/Repos/allani/dev-scripts/infrastructure/ansible/library', u'/media/important/Repos/allani/dev-scripts/infrastructure/ansible/3drparty/kubespray/library', u'/usr/share/ansible']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/local/bin/ansible
  python version = 2.7.15 (default, May  1 2018, 20:16:04) [GCC 7.3.1 20180406]
Using /media/important/Repos/allani/dev-scripts/infrastructure/ansible/ansible.cfg as config file
setting up inventory plugins
Parsed /media/important/Repos/allani/dev-scripts/infrastructure/ansible/production_poland/hosts inventory source with ini plugin
Loading callback plugin minimal of type stdout, v2.0 from /usr/lib/python2.7/site-packages/ansible/plugins/callback/minimal.pyc
META: ran handlers
 [WARNING]: While constructing a mapping from /media/important/Repos/allani/dev-scripts/infrastructure/ansible/production_poland/group_vars/all/main.yml, line 61, column 5,
found a duplicate dict key (hetzner_failover_auto). Using last defined value only.

Trying secret FileVaultSecret(filename='/home/suawek/.ansible_allani_prod_vault') for vault_id=default
Trying secret FileVaultSecret(filename='/home/suawek/.ansible_allani_prod_vault') for vault_id=default
Trying secret FileVaultSecret(filename='/home/suawek/.ansible_allani_prod_vault') for vault_id=default
Using module file /usr/lib/python2.7/site-packages/ansible/modules/system/firewalld.py
<k1> ESTABLISH SSH CONNECTION FOR USER: ansible
<k1> SSH: EXEC ssh -vvv -oForwardAgent=yes -oGSSAPIAuthentication=no -o ControlMaster=auto -o ControlPersist=3600s -o KbdInteractiveAuthentication=no -o PreferredAuthentications=gssapi-with-mic,gssapi-keyex,hostbased,publickey -o PasswordAuthentication=no -o User=ansible -o ConnectTimeout=60 -o ControlPath=/home/suawek/.ansible/cp/ansible-ssh-%h-%p-%r k1 '/bin/sh -c '"'"'sudo -H -S -n -u root /bin/sh -c '"'"'"'"'"'"'"'"'echo BECOME-SUCCESS-bmeeuaxxobqclygxwhpkpyhmeaxfsous; python3'"'"'"'"'"'"'"'"' && sleep 0'"'"''
Escalation succeeded
<k1> (1, '\n{"failed": true, "invocation": {"module_args": {"source": null, "timeout": 0, "interface": null, "zone": "test", "permanent": true, "port": null, "masquerade": null, "offline": null, "immediate": false, "rich_rule": null, "service": null, "state": "present"}}, "msg": "ERROR: Exception caught: \'FirewallClientConfig\' object has no attribute \'getZoneNames\'", "exception": "  File \\"/tmp/ansible_y7_2ptsx/ansible_modlib.zip/ansible/module_utils/firewalld.py\\", line 103, in action_handler\\n    return action_func(*action_func_args)\\n  File \\"/tmp/ansible_y7_2ptsx/ansible_module_firewalld.py\\", line 501, in get_enabled_permanent\\n    if self.zone in self.fw.config().getZoneNames():\\n"}\n', 'OpenSSH_7.7p1, OpenSSL 1.1.0h  27 Mar 2018\r\ndebug1: Reading configuration data /home/suawek/.ssh/config\r\ndebug1: /home/suawek/.ssh/config line 6: Applying options for *\r\ndebug1: /home/suawek/.ssh/config line 27: Applying options for k?\r\ndebug1: Reading configuration data /etc/ssh/ssh_config\r\ndebug1: /etc/ssh/ssh_config line 20: Applying options for *\r\ndebug1: auto-mux: Trying existing master\r\ndebug2: fd 3 setting O_NONBLOCK\r\ndebug2: mux_client_hello_exchange: master version 4\r\ndebug3: mux_client_forwards: request forwardings: 0 local, 0 remote\r\ndebug3: mux_client_request_session: entering\r\ndebug3: mux_client_request_alive: entering\r\ndebug3: mux_client_request_alive: done pid = 32601\r\ndebug3: mux_client_request_session: session request sent\r\ndebug1: mux_client_request_session: master session id: 2\r\ndebug3: mux_client_read_packet: read header failed: Broken pipe\r\ndebug2: Received exit status from master 1\r\n')
The full traceback is:
  File "/tmp/ansible_y7_2ptsx/ansible_modlib.zip/ansible/module_utils/firewalld.py", line 103, in action_handler
    return action_func(*action_func_args)
  File "/tmp/ansible_y7_2ptsx/ansible_module_firewalld.py", line 501, in get_enabled_permanent
    if self.zone in self.fw.config().getZoneNames():

k1 | FAILED! => {
    "changed": false, 
    "invocation": {
        "module_args": {
            "immediate": false, 
            "interface": null, 
            "masquerade": null, 
            "offline": null, 
            "permanent": true, 
            "port": null, 
            "rich_rule": null, 
            "service": null, 
            "source": null, 
            "state": "present", 
            "timeout": 0, 
            "zone": "test"
        }
    }, 
    "msg": "ERROR: Exception caught: 'FirewallClientConfig' object has no attribute 'getZoneNames'"
}
```



Discussion:
I confirmed that FirewallClientConfig  does not have method/attribute getZoneNames (maybe python3 bindings are too old in Ubuntu ? :thinking: )
cc @maxamillion
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Unfortunately it looks like the `getZoneNames` method was implemented in version `0.4.2` of firewalld upstream and since there's no way to introspect the server-side python `Zone` object from the client side, there's not really an alternative implementation that would allow for this to not require a new version of firewalld.
Nevermind, I just figured it out using the old API. Submitting a PR.

Resolving patch:
diff --git a/lib/ansible/modules/system/firewalld.py b/lib/ansible/modules/system/firewalld.py
--- a/lib/ansible/modules/system/firewalld.py
+++ b/lib/ansible/modules/system/firewalld.py
@@ -498,7 +498,9 @@ def get_enabled_immediate(self):
         self.module.fail_json(msg=self.tx_not_permanent_error_msg)
 
     def get_enabled_permanent(self):
-        if self.zone in self.fw.config().getZoneNames():
+        zones = self.fw.config().listZones()
+        zone_names = [self.fw.config().getZone(z).get_property("name") for z in zones]
+        if self.zone in zone_names:
             return True
         else:
             return False


Test patch: