Repository: ansible-collections/community.general

Issue:
nsupdate-module fails If nothing has to be changed
<!--- 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
<!--- Explain the problem briefly below -->
If you create an (MX) Entry via nsupdate and than run the same thing again, the playbook will fail the second time.

##### ISSUE TYPE
- Bug Report

##### COMPONENT NAME
nsupdate

##### ANSIBLE VERSION
<!--- Paste verbatim output from "ansible --version" between quotes -->
```paste below
ansible 2.9.11
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/root/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /bin/ansible
  python version = 2.7.5 (default, Sep 26 2019, 13:23:47) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
```

##### CONFIGURATION
<!--- Paste verbatim output from "ansible-config dump --only-changed" between quotes -->
```paste below
DEFAULT_MANAGED_STR(/etc/ansible/ansible.cfg) =
Diese Datei wird von Ansible verwaltet.
Modifizierungen sind NICHT persitent.
Letzte Modifizierung: %Y-%m-%d %H:%M:%S
Tower-Server: towerserver.fqdn.de (Cluster PROD)
DEFAULT_ROLES_PATH(/etc/ansible/ansible.cfg) = [u'/etc/ansible/roles', u'/usr/share/ansible/roles', u'/opt/ble/ans
HOST_KEY_CHECKING(/etc/ansible/ansible.cfg) = False
```

##### OS / ENVIRONMENT
<!--- Provide all relevant information below, e.g. target OS versions, network device firmware, etc. -->
RHEL 7
Bind: 9.11.4-P2-RedHat-9.11.4-16.P2.el7_8.6

##### STEPS TO REPRODUCE
<!--- Describe exactly how to reproduce the problem, using a minimal test-case -->
1. Setup a bind (we use CHROOT)
2. Create the Zone and a tsig-Key
3. Run the Playbook below
4. Run it again (this will fail)

<!--- Paste example playbooks or commands between quotes below -->
```yaml
- hosts: dns.example.com
  name: Set DNS Records
  vars:
    ddns_key_algorithm: hmac-sha512
    ddns_key_name: ansible
    ddns_key_secret: "****NSUPDATEKEY****"
    external_dns_record_records:
      - name: '@'
        type: MX
        value:
          - 10 mail.example.com.
    external_dns_record_zone: zone-example.com
    external_dns_record_zonedir: /etc/named/zones

  tasks:
    - name: TTL laden
      shell: "/bin/grep TTL {{ external_dns_record_zonedir }}/{{ external_dns_record_zone }} | /bin/head -n 1 | /bin/awk '{print $2}'"
      register: external_dns_record_ttl
      changed_when: false
      args:
        warn: false

    - name: Eintrag hinzufügen
      nsupdate:
        key_algorithm: '{{ ddns_key_algorithm }}'
        key_name: '{{ ddns_key_name }}'
        key_secret: '{{ ddns_key_secret }}'
        server: "127.0.0.1"
        record: '{{ item.name }}'
        type: '{{ item.type }}'
        ttl: '{{ external_dns_record_ttl.stdout }}'
        value: '{{ item.value }}'
        zone: '{{ external_dns_record_zone }}'
        state: present
      notify:
        - sync named-dynamic-zones
      loop: '{{ external_dns_record_records }}'
      loop_control:
        label: '{{ item.name }} ({{ item.type }})'

  handlers:
    - name: sync named-dynamic-zones # noqa 305
      shell: /sbin/rndc sync
      become: yes
```

<!--- HINT: You can paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
Playbook/Tasks results as  "ok"


##### ACTUAL RESULTS
<!--- Describe what actually happened. If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes -->
```paste below
{
    "exception": "Traceback (most recent call last):
          File \"/home/seransible/.ansible/tmp/ansible-tmp-1598520715.18-51-235193078687524/AnsiballZ_nsupdate.py\", line 102, in <module>
            _ansiballz_main()
          File \"/home/seransible/.ansible/tmp/ansible-tmp-1598520715.18-51-235193078687524/AnsiballZ_nsupdate.py\", line 94, in _ansiballz_main
            invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
          File \"/home/seransible/.ansible/tmp/ansible-tmp-1598520715.18-51-235193078687524/AnsiballZ_nsupdate.py\", line 40, in invoke_module
            runpy.run_module(mod_name='ansible.modules.net_tools.nsupdate', init_globals=None, run_name='__main__', alter_sys=True)
          File \"/usr/lib64/python2.7/runpy.py\", line 176, in run_module
            fname, loader, pkg_name)
          File \"/usr/lib64/python2.7/runpy.py\", line 82, in _run_module_code
            mod_name, mod_fname, mod_loader, pkg_name)
          File \"/usr/lib64/python2.7/runpy.py\", line 72, in _run_code
            exec code in run_globals
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 448, in <module>
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 431, in main
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 270, in create_or_update_record
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 374, in record_exists
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 394, in ttl_changed
        IndexError: list index out of range
        ",
    "ansible_loop_var": "item",
    "_ansible_item_label": "@ (MX)",
    "module_stderr": "Shared connection to dns.example.com closed.
        ",
    "rc": 1,
    "module_stdout": "Traceback (most recent call last):
          File \"/home/seransible/.ansible/tmp/ansible-tmp-1598520715.18-51-235193078687524/AnsiballZ_nsupdate.py\", line 102, in <module>
            _ansiballz_main()
          File \"/home/seransible/.ansible/tmp/ansible-tmp-1598520715.18-51-235193078687524/AnsiballZ_nsupdate.py\", line 94, in _ansiballz_main
            invoke_module(zipped_mod, temp_path, ANSIBALLZ_PARAMS)
          File \"/home/seransible/.ansible/tmp/ansible-tmp-1598520715.18-51-235193078687524/AnsiballZ_nsupdate.py\", line 40, in invoke_module
            runpy.run_module(mod_name='ansible.modules.net_tools.nsupdate', init_globals=None, run_name='__main__', alter_sys=True)
          File \"/usr/lib64/python2.7/runpy.py\", line 176, in run_module
            fname, loader, pkg_name)
          File \"/usr/lib64/python2.7/runpy.py\", line 82, in _run_module_code
            mod_name, mod_fname, mod_loader, pkg_name)
          File \"/usr/lib64/python2.7/runpy.py\", line 72, in _run_code
            exec code in run_globals
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 448, in <module>
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 431, in main
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 270, in create_or_update_record
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 374, in record_exists
          File \"/tmp/ansible_nsupdate_payload_5ONYcW/ansible_nsupdate_payload.zip/ansible/modules/net_tools/nsupdate.py\", line 394, in ttl_changed
        IndexError: list index out of range
        ",
    "item": {
        "type": "MX",
        "name": "@",
        "value": [
            "10 mail.example.com."
        ]
    },
    "changed": false,
    "msg": "MODULE FAILURE\nSee stdout/stderr for the exact error",
    "_ansible_no_log": false
```



Discussion:
I dug deeper in and it seems to be this function:
```python
def ttl_changed(self):
        query = dns.message.make_query(self.fqdn, self.module.params['type'])

        try:
            if self.module.params['protocol'] == 'tcp':
                lookup = dns.query.tcp(query, self.module.params['server'], timeout=10, port=self.module.params['port'])
            else:
                lookup = dns.query.udp(query, self.module.params['server'], timeout=10, port=self.module.params['port'])
        except (socket_error, dns.exception.Timeout) as e:
            self.module.fail_json(msg='DNS server error: (%s): %s' % (e.__class__.__name__, to_native(e)))

        current_ttl = lookup.answer[0].ttl
        return current_ttl != self.module.params['ttl']
```

as a workaround I did the following, to disable the check:
```python
def ttl_changed(self):
        return False
```
than everything seems to be working again
cc @nerzhul
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Hallo @fabianseelbach I am taking a look at this issue, but the line numbering between your traceback and `main` are different. ~~Would you please be as kind as to share which version of the collection that test output was captured with or, even better if you could re-run that with the most recent version of the code? Also,~~ I checked out v2.9.11 in the ansible repo and found the `nsupdate` module there matching the line numbers in the traceback. However, it would be great if you could share the version of `dnspython` you are/were using.

I will continue investigating here regardless of that, but it would help making us sure we're looking at the right thing.

The error indeed happens in that one specific line:
```python
        current_ttl = lookup.answer[0].ttl
```
Being an `IndexError: list index out of range`, it follows that `lookup.answer == []`. I am not familiar with `dnspython` and will need to understand it better before I can propose anything.
@russoz thanks for taking the issue.
I use dnspython 1.12.0
I can try to recreate the problem but this would take some time.
Since I opened the issue I did not change ansible neither the dnspython.
We are seeing the same issue for A records when using `@` as the record (i.e.: assigning an A record to the zone itself). It always works on the first try, but fails on secondary runs if the IP isn't changed. If a new value is set for the IP, the task completes successfully, but if no changes are made to the IP the task fails with the same trace as above, resulting in `... nsupdate.py\", line 394, in ttl_changed\nIndexError: list index out of range `
I ran into this again today using Ansible 2.9.22
The dnspython version did not change.
@fabianseelbach are you using this collection with Ansible 2.9, or are you using the module included in Ansible 2.9?
> @fabianseelbach are you using this collection with Ansible 2.9, or are you using the module included in Ansible 2.9?

@felixfontein the module included in the package distributed by Red Hat
@fabianseelbach what do you mean by "the package distributed by Red Hat"? If you are using Ansible 2.9, are you using FQCNs (community.general.nsupdate) or not? Or are you using Ansible >= 2.10, or ansible-base/ansible-core (see output of `ansible --version`)?
@felixfontein Sorry for the confusion.
Im using Ansible **without** FQCN.
The version output is the following:
```
2.9.22
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/home/me/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
  ansible python module location = /usr/lib/python2.7/site-packages/ansible
  executable location = /usr/bin/ansible
  python version = 2.7.5 (default, Aug 13 2020, 02:51:10) [GCC 4.8.5 20150623 (Red Hat 4.8.5-39)]
```
In that case, please note that the Ansible verion you are using only receives security fixes anymore, so this problem won't get fixed there. You will have to start using the community.general collection if you want an updated version of this module. (Though that problem needs to get fixed here first for that to actually make a difference :) )
Thanks, I am aware of that. I'm currently in the transition to collections.

Resolving patch:
diff --git a/plugins/modules/nsupdate.py b/plugins/modules/nsupdate.py
--- a/plugins/modules/nsupdate.py
+++ b/plugins/modules/nsupdate.py
@@ -467,10 +467,8 @@ def ttl_changed(self):
         if lookup.rcode() != dns.rcode.NOERROR:
             self.module.fail_json(msg='Failed to lookup TTL of existing matching record.')
 
-        if self.module.params['type'] == 'NS':
-            current_ttl = lookup.answer[0].ttl if lookup.answer else lookup.authority[0].ttl
-        else:
-            current_ttl = lookup.answer[0].ttl
+        current_ttl = lookup.answer[0].ttl if lookup.answer else lookup.authority[0].ttl
+
         return current_ttl != self.module.params['ttl']
 
 


Test patch: