Repository: ansible/ansible

Issue:
[RFE] Support one to many A records in nsupdate module
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
nsupdate

##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /etc/ansible/ansible.cfg
  configured module search path = [u'/srv/hpe-ansible/library']
  python version = 2.7.5 (default, Aug  2 2016, 04:20:16) [GCC 4.8.5 20150623 (Red Hat 4.8.5-4)]

```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->
N/A

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
N/A

##### SUMMARY
<!--- Explain the problem briefly -->
The nsupdate module was brought in to v2.3 and its great!

I had a need to create a wildcard DNS entry (for OpenShift/kubernetes routers) so the same A record is  pointing to multiple IPs. Out of the box this module would update the one entry rather than make additional ones.

It'd be sweet if the module supported this use case. For now, I made some modifications to a fork to accept a `state: multiple` so a call to the module won't overwrite the existing entry.

I submitted a PR to the original repo, not realising its now managed in github under ansible proper. The PR is here: https://github.com/mskarbek/ansible-nsupdate/pull/11

It might not be the preferred implementation, but I'd be happy to submit it if it was deemed acceptable.

##### 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.
-->
An example task would look like so:
<!--- Paste example playbooks or commands between quotes below -->
```yaml
- name: update wildcard DNS entry
  nsupdate:
    state: multiple
    key_name: "rndc-key"
    key_secret: "r/KKKKKEEEEEYYYYY"
    server: "192.168.1.1"
    zone: "example.com"
    record: "*.paas.example.com"
    value: "{{ ansible_default_ipv4.address }}"
  delegate_to: localhost
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->

##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->

<!--- Paste verbatim command output between quotes below -->
```

```



Discussion:
cc @nerzhul
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
Alternatively, maybe we could provide a list to the value argument?
@dcritch Please go ahead and file the PR, then we can help you shape it into whatever gets deemed mergeable.

needs_contributor

Resolving patch:
diff --git a/lib/ansible/modules/net_tools/nsupdate.py b/lib/ansible/modules/net_tools/nsupdate.py
--- a/lib/ansible/modules/net_tools/nsupdate.py
+++ b/lib/ansible/modules/net_tools/nsupdate.py
@@ -95,6 +95,15 @@
     record: "ansible"
     value: "192.168.1.1"
 
+- name: Add or modify ansible.example.org A to 192.168.1.1, 192.168.1.2 and 192.168.1.3"
+  nsupdate:
+    key_name: "nsupdate"
+    key_secret: "+bFQtBCta7j2vWkjPkAFtgA=="
+    server: "10.1.1.1"
+    zone: "example.org"
+    record: "ansible"
+    value: ["192.168.1.1", "192.168.1.2", "192.168.1.3"]
+
 - name: Remove puppet.example.org CNAME
   nsupdate:
     key_name: "nsupdate"
@@ -127,9 +136,9 @@
     type: string
     sample: 'CNAME'
 value:
-    description: DNS record value
+    description: DNS record value(s)
     returned: success
-    type: string
+    type: list
     sample: '192.168.1.1'
 zone:
     description: DNS record zone
@@ -237,27 +246,35 @@ def create_or_update_record(self):
 
     def create_record(self):
         update = dns.update.Update(self.zone, keyring=self.keyring, keyalgorithm=self.algorithm)
-        try:
-            update.add(self.module.params['record'],
-                       self.module.params['ttl'],
-                       self.module.params['type'],
-                       self.module.params['value'])
-        except AttributeError:
-            self.module.fail_json(msg='value needed when state=present')
-        except dns.exception.SyntaxError:
-            self.module.fail_json(msg='Invalid/malformed value')
+        for entry in self.module.params['value']:
+            try:
+                update.add(self.module.params['record'],
+                           self.module.params['ttl'],
+                           self.module.params['type'],
+                           entry)
+            except AttributeError:
+                self.module.fail_json(msg='value needed when state=present')
+            except dns.exception.SyntaxError:
+                self.module.fail_json(msg='Invalid/malformed value')
 
         response = self.__do_update(update)
         return dns.message.Message.rcode(response)
 
     def modify_record(self):
         update = dns.update.Update(self.zone, keyring=self.keyring, keyalgorithm=self.algorithm)
-        update.replace(self.module.params['record'],
-                       self.module.params['ttl'],
-                       self.module.params['type'],
-                       self.module.params['value'])
-
+        update.delete(self.module.params['record'], self.module.params['type'])
+        for entry in self.module.params['value']:
+            try:
+                update.add(self.module.params['record'],
+                           self.module.params['ttl'],
+                           self.module.params['type'],
+                           entry)
+            except AttributeError:
+                self.module.fail_json(msg='value needed when state=present')
+            except dns.exception.SyntaxError:
+                self.module.fail_json(msg='Invalid/malformed value')
         response = self.__do_update(update)
+
         return dns.message.Message.rcode(response)
 
     def remove_record(self):
@@ -297,12 +314,13 @@ def record_exists(self):
         if self.dns_rc == 0:
             if self.module.params['state'] == 'absent':
                 return 1
-            try:
-                update.present(self.module.params['record'], self.module.params['type'], self.module.params['value'])
-            except AttributeError:
-                self.module.fail_json(msg='value needed when state=present')
-            except dns.exception.SyntaxError:
-                self.module.fail_json(msg='Invalid/malformed value')
+            for entry in self.module.params['value']:
+                try:
+                    update.present(self.module.params['record'], self.module.params['type'], entry)
+                except AttributeError:
+                    self.module.fail_json(msg='value needed when state=present')
+                except dns.exception.SyntaxError:
+                    self.module.fail_json(msg='Invalid/malformed value')
             response = self.__do_update(update)
             self.dns_rc = dns.message.Message.rcode(response)
             if self.dns_rc == 0:
@@ -328,7 +346,7 @@ def main():
             record=dict(required=True, type='str'),
             type=dict(required=False, default='A', type='str'),
             ttl=dict(required=False, default=3600, type='int'),
-            value=dict(required=False, default=None, type='str')
+            value=dict(required=False, default=None, type='list')
         ),
         supports_check_mode=True
     )


Test patch: