Repository: ansible/ansible

Issue:
NIOS ptr record
##### SUMMARY
Support to add a PTR record via the nios module. eg: nios_ptr_record.py

The PTR option would allow the recording of an individual reverse DNS record

##### ISSUE TYPE
 - Feature Idea

##### COMPONENT NAME
nios

##### ANSIBLE VERSION
```
ansible 2.5.2
config file = /etc/ansible/ansible.cfg
configured module search path = [u'/home/USERNAME/.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, Feb 20 2018, 09:19:12) [GCC 4.8.5 20150623 (Red Hat 4.8.5-28)]
```



Discussion:
Files identified in the description:
* [lib/ansible/plugins/lookup/nios.py](https://github.com/ansible/ansible/blob/devel/lib/ansible/plugins/lookup/nios.py)

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 --->
cc @Qalthos @ganeshrn @gdpak @gundalow @privateip @rcarrillocruz @sganesh-infoblox @trishnaguha
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py
--- a/lib/ansible/module_utils/net_tools/nios/api.py
+++ b/lib/ansible/module_utils/net_tools/nios/api.py
@@ -46,6 +46,8 @@
 NIOS_IPV4_NETWORK = 'network'
 NIOS_IPV6_NETWORK = 'ipv6network'
 NIOS_ZONE = 'zone_auth'
+NIOS_PTR_RECORD = 'record:ptr'
+
 
 NIOS_PROVIDER_SPEC = {
     'host': dict(),
diff --git a/lib/ansible/modules/net_tools/nios/nios_ptr_record.py b/lib/ansible/modules/net_tools/nios/nios_ptr_record.py
new file mode 100644
--- /dev/null
+++ b/lib/ansible/modules/net_tools/nios/nios_ptr_record.py
@@ -0,0 +1,159 @@
+#!/usr/bin/python
+# Copyright (c) 2018 Red Hat, Inc.
+# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
+
+from __future__ import absolute_import, division, print_function
+
+__metaclass__ = type
+
+ANSIBLE_METADATA = {'metadata_version': '1.1',
+                    'status': ['preview'],
+                    'supported_by': 'community'}
+
+DOCUMENTATION = '''
+---
+module: nios_ptr_record
+version_added: "2.7"
+author: "Trebuchet Clement"
+short_description: Configure Infoblox NIOS PTR records
+description:
+  - Adds and/or removes instances of PTR record objects from
+    Infoblox NIOS servers.  This module manages NIOS C(record:ptr) objects
+    using the Infoblox WAPI interface over REST.
+requirements:
+  - infoblox_client
+extends_documentation_fragment: nios
+options:
+  name:
+    description:
+      - The name of the DNS PTR record in FQDN format to add or remove from
+        the system.
+        The field is required only for an PTR object in Forward Mapping Zone.
+    required: false
+  view:
+    description:
+      - Sets the DNS view to associate this a record with. The DNS
+        view must already be configured on the system
+    required: false
+    aliases:
+      - dns_view
+  ipv4addr:
+    description:
+      - The IPv4 Address of the record. Mutually exclusive with the ipv6addr.
+    required: true
+    aliases:
+      - ipv4
+  ipv6addr:
+    description:
+      - The IPv6 Address of the record. Mutually exclusive with the ipv4addr.
+    required: true
+    aliases:
+      - ipv6
+  ptrdname:
+    description:
+      - The domain name of the DNS PTR record in FQDN format.
+    required: true
+  ttl:
+    description:
+      - Time To Live (TTL) value for the record.
+        A 32-bit unsigned integer that represents the duration, in seconds, that the record is valid (cached).
+        Zero indicates that the record should not be cached.
+  extattrs:
+    description:
+      - Allows for the configuration of Extensible Attributes on the
+        instance of the object.  This argument accepts a set of key / value
+        pairs for configuration.
+  comment:
+    description:
+      - Configures a text string comment to be associated with the instance
+        of this object.  The provided text string will be configured on the
+        object instance. Maximum 256 characters.
+  state:
+    description:
+      - Configures the intended state of the instance of the object on
+        the NIOS server.  When this value is set to C(present), the object
+        is configured on the device and when this value is set to C(absent)
+        the value is removed (if necessary) from the device.
+    default: present
+    choices:
+      - present
+      - absent
+'''
+
+EXAMPLES = '''
+- name: Create a PTR Record
+  nios_ptr_record:
+    ipv4: 192.168.10.1
+    ptrdname: host.ansible.com
+    state: present
+    provider:
+      host: "{{ inventory_hostname_short }}"
+      username: admin
+      password: admin
+  connection: local
+
+- name: Delete a PTR Record
+  nios_ptr_record:
+    ipv4: 192.168.10.1
+    ptrdname: host.ansible.com
+    state: absent
+    provider:
+      host: "{{ inventory_hostname_short }}"
+      username: admin
+      password: admin
+  connection: local
+'''
+
+RETURN = ''' # '''
+
+from ansible.module_utils.basic import AnsibleModule
+from ansible.module_utils.net_tools.nios.api import WapiModule
+from ansible.module_utils.net_tools.nios.api import NIOS_PTR_RECORD
+
+
+def main():
+    # Module entry point
+    ib_spec = dict(
+        name=dict(required=False),
+        view=dict(aliases=['dns_view']),
+        ipv4addr=dict(aliases=['ipv4'], ib_req=True),
+        ipv6addr=dict(aliases=['ipv6'], ib_req=True),
+        ptrdname=dict(ib_req=True),
+
+        ttl=dict(type='int'),
+
+        extattrs=dict(type='dict'),
+        comment=dict(),
+    )
+
+    argument_spec = dict(
+        provider=dict(required=True),
+        state=dict(default='present', choices=['present', 'absent'])
+    )
+
+    argument_spec.update(ib_spec)
+    argument_spec.update(WapiModule.provider_spec)
+
+    mutually_exclusive = [('ipv4addr', 'ipv6addr')]
+    required_one_of = [
+        ['ipv4addr', 'ipv6addr']
+    ]
+
+    module = AnsibleModule(argument_spec=argument_spec,
+                           mutually_exclusive=mutually_exclusive,
+                           supports_check_mode=True,
+                           required_one_of=required_one_of)
+
+    if module.params['ipv4addr']:
+        del ib_spec['ipv6addr']
+    elif module.params['ipv6addr']:
+        del ib_spec['ipv4addr']
+
+    wapi = WapiModule(module)
+    result = wapi.run(NIOS_PTR_RECORD, ib_spec)
+
+    module.exit_json(**result)
+
+
+if __name__ == '__main__':
+    main()


Test patch:
diff --git a/test/integration/targets/nios_ptr_record/aliases b/test/integration/targets/nios_ptr_record/aliases
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nios_ptr_record/aliases
@@ -0,0 +1,3 @@
+posix/ci/cloud/group4/nios
+cloud/nios
+destructive
diff --git a/test/integration/targets/nios_ptr_record/defaults/main.yaml b/test/integration/targets/nios_ptr_record/defaults/main.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nios_ptr_record/defaults/main.yaml
@@ -0,0 +1,3 @@
+---
+testcase: "*"
+test_items: []
\ No newline at end of file
diff --git a/test/integration/targets/nios_ptr_record/meta/main.yaml b/test/integration/targets/nios_ptr_record/meta/main.yaml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nios_ptr_record/meta/main.yaml
@@ -0,0 +1,2 @@
+dependencies:
+  - prepare_nios_tests
\ No newline at end of file
diff --git a/test/integration/targets/nios_ptr_record/tasks/main.yml b/test/integration/targets/nios_ptr_record/tasks/main.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nios_ptr_record/tasks/main.yml
@@ -0,0 +1 @@
+- include: nios_ptr_record_idempotence.yml
diff --git a/test/integration/targets/nios_ptr_record/tasks/nios_ptr_record_idempotence.yml b/test/integration/targets/nios_ptr_record/tasks/nios_ptr_record_idempotence.yml
new file mode 100644
--- /dev/null
+++ b/test/integration/targets/nios_ptr_record/tasks/nios_ptr_record_idempotence.yml
@@ -0,0 +1,78 @@
+---
+
+- name: create an ipv4 ptr record
+  nios_ptr_record:
+    name: ptr.ansible.com
+    ptrdname: ptr.ansible.com
+    ipv4: 192.168.10.1
+    state: present
+    provider: "{{ nios_provider }}"
+    view: default
+  register: ipv4_ptr_create1
+
+- name: create the same ipv4 ptr record
+  nios_ptr_record:
+    name: ptr.ansible.com
+    ptrdname: ptr.ansible.com
+    ipv4: 192.168.10.1
+    state: present
+    view: default
+    provider: "{{ nios_provider }}"
+  register: ipv4_ptr_create2
+
+- name: add a comment to an existing ipv4 ptr record
+  nios_ptr_record:
+    name: ptr.ansible.com
+    ptrdname: ptr.ansible.com
+    ipv4: 192.168.10.1
+    comment: this is a test comment
+    state: present
+    provider: "{{ nios_provider }}"
+  register: ipv4_ptr_update1
+
+- name: add the same comment to the same ipv4 ptr host record
+  nios_ptr_record:
+    name: ptr.ansible.com
+    ptrdname: ptr.ansible.com
+    ipv4: 192.168.10.1
+    comment: this is a test comment
+    state: present
+    provider: "{{ nios_provider }}"
+  register: ipv4_ptr_update2
+
+- name: remove a ptr record from the system
+  nios_ptr_record:
+    name: ptr.ansible.com
+    ptrdname: ptr.ansible.com
+    ipv4: 192.168.10.1
+    state: absent
+    provider: "{{ nios_provider }}"
+  register: ipv4_ptr_delete1
+
+- name: remove the same ptr record from the system
+  nios_ptr_record:
+    ptrdname: ptr.ansible.com
+    name: ptr.ansible.com
+    ipv4: 192.168.10.1
+    state: absent
+    provider: "{{ nios_provider }}"
+  register: ipv4_ptr_delete2
+
+- name: create an ipv6 ptr record
+  nios_ptr_record:
+    ptrdname: ptr6.ansible.com
+    name: ptr6.ansible.com
+    ipv6: "2002:8ac3:802d:1242:20d:60ff:fe38:6d16"
+    state: present
+    provider: "{{ nios_provider }}"
+  register: ipv6_ptr_create1
+
+- assert:
+    that:
+      - "ipv4_ptr_create1.changed"
+      - "not ipv4_ptr_create2.changed"
+      - "ipv4_ptr_update1.changed"
+      - "not ipv4_ptr_update2.changed"
+      - "ipv4_ptr_delete1.changed"
+      - "not ipv4_ptr_delete2.changed"
+      - "ipv6_ptr_create1.changed"
diff --git a/test/units/modules/net_tools/nios/test_nios_ptr_record.py b/test/units/modules/net_tools/nios/test_nios_ptr_record.py
new file mode 100644
--- /dev/null
+++ b/test/units/modules/net_tools/nios/test_nios_ptr_record.py
@@ -0,0 +1,175 @@
+# This file is part of Ansible
+#
+# Ansible is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# Ansible is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with Ansible.  If not, see <http://www.gnu.org/licenses/>.
+
+# Make coding more python3-ish
+from __future__ import (absolute_import, division, print_function)
+__metaclass__ = type
+
+
+from ansible.modules.net_tools.nios import nios_ptr_record
+from ansible.module_utils.net_tools.nios import api
+from ansible.compat.tests.mock import patch, MagicMock, Mock
+from .test_nios_module import TestNiosModule, load_fixture
+
+
+class TestNiosPTRRecordModule(TestNiosModule):
+
+    module = nios_ptr_record
+
+    def setUp(self):
+
+        super(TestNiosPTRRecordModule, self).setUp()
+        self.module = MagicMock(name='ansible.modules.net_tools.nios.nios_ptr_record.WapiModule')
+        self.module.check_mode = False
+        self.module.params = {'provider': None}
+
+        self.mock_wapi = patch('ansible.modules.net_tools.nios.nios_ptr_record.WapiModule')
+        self.exec_command = self.mock_wapi.start()
+        self.mock_wapi_run = patch('ansible.modules.net_tools.nios.nios_ptr_record.WapiModule.run')
+        self.mock_wapi_run.start()
+
+        self.load_config = self.mock_wapi_run.start()
+
+    def tearDown(self):
+        super(TestNiosPTRRecordModule, self).tearDown()
+        self.mock_wapi.stop()
+
+    def _get_wapi(self, test_object):
+        wapi = api.WapiModule(self.module)
+        wapi.get_object = Mock(name='get_object', return_value=test_object)
+        wapi.create_object = Mock(name='create_object')
+        wapi.update_object = Mock(name='update_object')
+        wapi.delete_object = Mock(name='delete_object')
+        return wapi
+
+    def load_fixtures(self, commands=None):
+        self.exec_command.return_value = (0, load_fixture('nios_result.txt').strip(), None)
+        self.load_config.return_value = dict(diff=None, session='session')
+
+    def test_nios_ptr_record_create(self):
+        self.module.params = {'provider': None, 'state': 'present', 'ptrdname': 'ansible.test.com',
+                              'ipv4addr': '10.36.241.14', 'comment': None, 'extattrs': None}
+
+        test_object = None
+        test_spec = {
+            "ipv4addr": {"ib_req": True},
+            "ptrdname": {"ib_req": True},
+            "comment": {},
+            "extattrs": {}
+        }
+
+        wapi = self._get_wapi(test_object)
+        print("WAPI: ", wapi)
+        res = wapi.run('testobject', test_spec)
+
+        self.assertTrue(res['changed'])
+        wapi.create_object.assert_called_once_with('testobject', {'ipv4addr': '10.36.241.14', 'ptrdname': 'ansible.test.com'})
+
+    def test_nios_ptr_record_remove(self):
+        self.module.params = {'provider': None, 'state': 'absent', 'ptrdname': 'ansible.test.com',
+                              'ipv4addr': '10.36.241.14', 'comment': None, 'extattrs': None}
+
+        ref = "record:ptr/ZG5zLm5ldHdvcmtfdmlldyQw:14.241.36.10.in-addr.arpa/default"
+
+        test_object = [{
+            "comment": "test comment",
+            "_ref": ref,
+            "ptrdname": "ansible.test.com",
+            "ipv4addr": "10.36.241.14",
+            "extattrs": {'Site': {'value': 'test'}}
+        }]
+
+        test_spec = {
+            "ipv4addr": {"ib_req": True},
+            "ptrdname": {"ib_req": True},
+            "comment": {},
+            "extattrs": {}
+        }
+
+        wapi = self._get_wapi(test_object)
+        res = wapi.run('testobject', test_spec)
+        self.assertTrue(res['changed'])
+        wapi.delete_object.assert_called_once_with(ref)
+
+    def test_nios_ptr_record_update_comment(self):
+        self.module.params = {'provider': None, 'state': 'present', 'ptrdname': 'ansible.test.com',
+                              'ipv4addr': '10.36.241.14', 'comment': 'updated comment', 'extattrs': None}
+
+        test_object = [
+            {
+                "comment": "test comment",
+                "_ref": "record:ptr/ZG5zLm5ldHdvcmtfdmlldyQw:14.241.36.10.in-addr.arpa/default",
+                "ptrdname": "ansible.test.com",
+                "ipv4addr": "10.36.241.14",
+                "extattrs": {}
+            }
+        ]
+
+        test_spec = {
+            "ipv4addr": {"ib_req": True},
+            "ptrdname": {"ib_req": True},
+            "comment": {},
+            "extattrs": {}
+        }
+
+        wapi = self._get_wapi(test_object)
+        res = wapi.run('testobject', test_spec)
+
+        self.assertTrue(res['changed'])
+        wapi.update_object.called_once_with(test_object)
+
+    def test_nios_ptr_record_update_record_ptrdname(self):
+        self.module.params = {'provider': None, 'state': 'present', 'ptrdname': 'ansible.test.org',
+                              'ipv4addr': '10.36.241.14', 'comment': 'comment', 'extattrs': None}
+
+        test_object = [
+            {
+                "comment": "test comment",
+                "_ref": "record:ptr/ZG5zLm5ldHdvcmtfdmlldyQw:14.241.36.10.in-addr.arpa/default",
+                "ptrdname": "ansible.test.com",
+                "ipv4addr": "10.36.241.14",
+                "extattrs": {}
+            }
+        ]
+
+        test_spec = {
+            "ipv4addr": {"ib_req": True},
+            "ptrdname": {"ib_req": True},
+            "comment": {},
+            "extattrs": {}
+        }
+
+        wapi = self._get_wapi(test_object)
+        res = wapi.run('testobject', test_spec)
+
+        self.assertTrue(res['changed'])
+        wapi.update_object.called_once_with(test_object)
+
+    def test_nios_ptr6_record_create(self):
+        self.module.params = {'provider': None, 'state': 'present', 'ptrdname': 'ansible6.test.com',
+                              'ipv6addr': '2002:8ac3:802d:1242:20d:60ff:fe38:6d16', 'comment': None, 'extattrs': None}
+
+        test_object = None
+        test_spec = {"ipv6addr": {"ib_req": True},
+                     "ptrdname": {"ib_req": True},
+                     "comment": {},
+                     "extattrs": {}}
+
+        wapi = self._get_wapi(test_object)
+        print("WAPI: ", wapi)
+        res = wapi.run('testobject', test_spec)
+
+        self.assertTrue(res['changed'])
+        wapi.create_object.assert_called_once_with('testobject', {'ipv6addr': '2002:8ac3:802d:1242:20d:60ff:fe38:6d16', 'ptrdname': 'ansible6.test.com'})