From f76d7bdbefd0f8273449af128ff17a0547b3e2f2 Mon Sep 17 00:00:00 2001 From: Sumit Jaiswal Date: Thu, 20 Dec 2018 14:36:41 +0530 Subject: [PATCH] New Module for creating, managing Infoblox NIOS nameserver groups (#49124) * adding new nios_nsgroup module Signed-off-by: Sumit Jaiswal * adding new nios_nsgroup module Signed-off-by: Sumit Jaiswal * adding new nios_nsgroup module Signed-off-by: Sumit Jaiswal * fix shippable errors Signed-off-by: Sumit Jaiswal * added unit tests Signed-off-by: Sumit Jaiswal --- .../module_utils/net_tools/nios/api.py | 2 +- .../modules/net_tools/nios/nios_nsgroup.py | 349 ++++++++++++++++++ .../net_tools/nios/test_nios_nsgroup.py | 125 +++++++ 3 files changed, 475 insertions(+), 1 deletion(-) create mode 100644 lib/ansible/modules/net_tools/nios/nios_nsgroup.py create mode 100644 test/units/modules/net_tools/nios/test_nios_nsgroup.py diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py index 73016c9f86..64392942da 100644 --- a/lib/ansible/module_utils/net_tools/nios/api.py +++ b/lib/ansible/module_utils/net_tools/nios/api.py @@ -54,7 +54,7 @@ NIOS_MX_RECORD = 'record:mx' NIOS_SRV_RECORD = 'record:srv' NIOS_NAPTR_RECORD = 'record:naptr' NIOS_TXT_RECORD = 'record:txt' - +NIOS_NSGROUP = 'nsgroup' NIOS_PROVIDER_SPEC = { 'host': dict(), diff --git a/lib/ansible/modules/net_tools/nios/nios_nsgroup.py b/lib/ansible/modules/net_tools/nios/nios_nsgroup.py new file mode 100644 index 0000000000..fdb511123d --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_nsgroup.py @@ -0,0 +1,349 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +# 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': 'certified'} + +DOCUMENTATION = ''' +--- +module: nios_nsgroup +short_description: Configure InfoBlox DNS Nameserver Groups +extends_documentation_fragment: nios +author: + - Erich Birngruber (@ebirn) + - Sumit Jaiswal (@sjaiswal) +version_added: "2.8" +description: + - Adds and/or removes nameserver groups form Infoblox NIOS servers. + This module manages NIOS C(nsgroup) objects using the Infoblox. WAPI interface over REST. +requirements: + - infoblox_client +options: + name: + description: + - Specifies the name of the NIOS nameserver group to be managed. + required: true + grid_primary: + description: + - This host is to be used as primary server in this nameserver group. It must be a grid member. + This option is required when setting I(use_external_primaries) to C(false). + suboptions: + name: + description: + - Provide the name of the grid member to identify the host. + required: true + enable_preferred_primaries: + description: + - This flag represents whether the preferred_primaries field values of this member are used (see Infoblox WAPI docs). + default: false + type: bool + grid_replicate: + description: + - Use DNS zone transfers if set to C(True) or ID Grid Replication if set to C(False). + type: bool + default: false + lead: + description: + - This flag controls if the grid lead secondary nameserver performs zone transfers to non lead secondaries. + type: bool + default: false + stealth: + description: + - Configure the external nameserver as stealth server (without NS record) in the zones. + type: bool + default: false + grid_secondaries: + description: + - Configures the list of grid member hosts that act as secondary nameservers. + This option is required when setting I(use_external_primaries) to C(true). + suboptions: + name: + description: + - Provide the name of the grid member to identify the host. + required: true + enable_preferred_primaries: + description: + - This flag represents whether the preferred_primaries field values of this member are used (see Infoblox WAPI docs). + default: false + type: bool + grid_replicate: + description: + - Use DNS zone transfers if set to C(True) or ID Grid Replication if set to C(False) + type: bool + default: false + lead: + description: + - This flag controls if the grid lead secondary nameserver performs zone transfers to non lead secondaries. + type: bool + default: false + stealth: + description: + - Configure the external nameserver as stealth server (without NS record) in the zones. + type: bool + default: false + preferred_primaries: + description: + - Provide a list of elements like in I(external_primaries) to set the precedence of preferred primary nameservers. + is_grid_default: + description: + - If set to C(True) this nsgroup will become the default nameserver group for new zones. + type: bool + required: false + default: false + use_external_primary: + description: + - This flag controls whether the group is using an external primary nameserver. + Note that modification of this field requires passing values for I(grid_secondaries) and I(external_primaries). + type: bool + required: false + default: false + external_primaries: + description: + - Configures a list of external nameservers (non-members of the grid). + This option is required when setting I(use_external_primaries) to C(true). + suboptions: + address: + description: + - Configures the IP address of the external nameserver + required: true + name: + description: + - Set a label for the external nameserver + required: true + stealth: + description: + - Configure the external nameserver as stealth server (without NS record) in the zones. + type: bool + default: false + tsig_key_name: + description: + - Sets a label for the I(tsig_key) value + tsig_key_alg: + description: + - Provides the algorithm used for the I(tsig_key) in use. + choices: ['HMAC-MD5', 'HMAC-SHA256'] + default: 'HMAC-MD5' + tsig_key: + description: + - Set a DNS TSIG key for the nameserver to secure zone transfers (AFXRs). + required: false + external_secondaries: + description: + - Allows to provide a list of external secondary nameservers, that are not members of the grid. + suboptions: + address: + description: + - Configures the IP address of the external nameserver + required: true + name: + description: + - Set a label for the external nameserver + required: true + stealth: + description: + - Configure the external nameserver as stealth server (without NS record) in the zones. + type: bool + default: false + tsig_key_name: + description: + - Sets a label for the I(tsig_key) value + tsig_key_alg: + description: + - Provides the algorithm used for the I(tsig_key) in use. + choices: ['HMAC-MD5', 'HMAC-SHA256'] + default: 'HMAC-MD5' + tsig_key: + description: + - Set a DNS TSIG key for the nameserver to secure zone transfers (AFXRs). + 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. + required: false + 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. + required: false + 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. + choices: [present, absent] + default: present +''' + +EXAMPLES = ''' +- name: create simple infoblox nameserver group + nios_nsgroup: + name: my-simple-group + comment: "this is a simple nameserver group" + grid_primary: + - name: infoblox-test.example.com + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + connection: local + +- name: create infoblox nameserver group with external primaries + nios_nsgroup: + name: my-example-group + use_external_primary: true + comment: "this is my example nameserver group" + external_primaries: "{{ ext_nameservers }}" + grid_secondaries: + - name: infoblox-test.example.com + lead: True + preferred_primaries: "{{ ext_nameservers }}" + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + connection: local + +- name: delete infoblox nameserver group + nios_nsgroup: + name: my-simple-group + comment: "this is a simple nameserver group" + grid_primary: + - name: infoblox-test.example.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_NSGROUP + + +# from infoblox documentation +# Fields List +# Field Type Req R/O Base Search +# comment String N N Y : = ~ +# extattrs Extattr N N N ext +# external_primaries [struct] N N N N/A +# external_secondaries [struct] N N N N/A +# grid_primary [struct] N N N N/A +# grid_secondaries [struct] N N N N/A +# is_grid_default Bool N N N N/A +# is_multimaster Bool N Y N N/A +# name String Y N Y : = ~ +# use_external_primary Bool N N N N/A + + +def main(): + '''entrypoint for module execution.''' + argument_spec = dict( + provider=dict(required=True), + state=dict(default='present', choices=['present', 'absent']), + ) + + # cleanup tsig fields + def clean_tsig(ext): + if 'tsig_key' in ext and not ext['tsig_key']: + del ext['tsig_key'] + if 'tsig_key' not in ext and 'tsig_key_name' in ext and not ext['tsig_key_name']: + del ext['tsig_key_name'] + if 'tsig_key' not in ext and 'tsig_key_alg' in ext: + del ext['tsig_key_alg'] + + def clean_grid_member(member): + if member['preferred_primaries']: + for ext in member['preferred_primaries']: + clean_tsig(ext) + if member['enable_preferred_primaries'] is False: + del member['enable_preferred_primaries'] + del member['preferred_primaries'] + if member['lead'] is False: + del member['lead'] + if member['grid_replicate'] is False: + del member['grid_replicate'] + + def ext_primaries_transform(module): + if module.params['external_primaries']: + for ext in module.params['external_primaries']: + clean_tsig(ext) + return module.params['external_primaries'] + + def ext_secondaries_transform(module): + if module.params['external_secondaries']: + for ext in module.params['external_secondaries']: + clean_tsig(ext) + return module.params['external_secondaries'] + + def grid_primary_preferred_transform(module): + for member in module.params['grid_primary']: + clean_grid_member(member) + return module.params['grid_primary'] + + def grid_secondaries_preferred_primaries_transform(module): + for member in module.params['grid_secondaries']: + clean_grid_member(member) + return module.params['grid_secondaries'] + + extserver_spec = dict( + address=dict(required=True, ib_req=True), + name=dict(required=True, ib_req=True), + stealth=dict(type='bool', default=False), + tsig_key=dict(), + tsig_key_alg=dict(choices=['HMAC-MD5', 'HMAC-SHA256'], default='HMAC-MD5'), + tsig_key_name=dict(required=True) + ) + + memberserver_spec = dict( + name=dict(required=True, ib_req=True), + enable_preferred_primaries=dict(type='bool', default=False), + grid_replicate=dict(type='bool', default=False), + lead=dict(type='bool', default=False), + preferred_primaries=dict(type='list', elements='dict', options=extserver_spec, default=[]), + stealth=dict(type='bool', default=False), + ) + + ib_spec = dict( + name=dict(required=True, ib_req=True), + grid_primary=dict(type='list', elements='dict', options=memberserver_spec, + transform=grid_primary_preferred_transform), + grid_secondaries=dict(type='list', elements='dict', options=memberserver_spec, + transform=grid_secondaries_preferred_primaries_transform), + external_primaries=dict(type='list', elements='dict', options=extserver_spec, transform=ext_primaries_transform), + external_secondaries=dict(type='list', elements='dict', options=extserver_spec, + transform=ext_secondaries_transform), + is_grid_default=dict(type='bool', default=False), + use_external_primary=dict(type='bool', default=False), + extattrs=dict(), + comment=dict(), + ) + + argument_spec.update(ib_spec) + argument_spec.update(WapiModule.provider_spec) + + module = AnsibleModule(argument_spec=argument_spec, supports_check_mode=True) + + wapi = WapiModule(module) + result = wapi.run(NIOS_NSGROUP, ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/test/units/modules/net_tools/nios/test_nios_nsgroup.py b/test/units/modules/net_tools/nios/test_nios_nsgroup.py new file mode 100644 index 0000000000..463fc24687 --- /dev/null +++ b/test/units/modules/net_tools/nios/test_nios_nsgroup.py @@ -0,0 +1,125 @@ +# 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 . + +# Make coding more python3-ish +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + + +from ansible.modules.net_tools.nios import nios_nsgroup +from ansible.module_utils.net_tools.nios import api +from units.compat.mock import patch, MagicMock, Mock +from .test_nios_module import TestNiosModule, load_fixture + + +class TestNiosNSGroupModule(TestNiosModule): + + module = nios_nsgroup + + def setUp(self): + + super(TestNiosNSGroupModule, self).setUp() + self.module = MagicMock(name='ansible.modules.net_tools.nios.nios_nsgroup.WapiModule') + self.module.check_mode = False + self.module.params = {'provider': None} + + self.mock_wapi = patch('ansible.modules.net_tools.nios.nios_nsgroup.WapiModule') + self.exec_command = self.mock_wapi.start() + self.mock_wapi_run = patch('ansible.modules.net_tools.nios.nios_nsgroup.WapiModule.run') + self.mock_wapi_run.start() + + self.load_config = self.mock_wapi_run.start() + + def tearDown(self): + super(TestNiosNSGroupModule, 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_nsgroup_create(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'my-simple-group', + 'comment': None, 'grid_primary': None} + + test_object = None + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "grid_primary": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.create_object.assert_called_once_with('testobject', {'name': self.module._check_type_dict().__getitem__()}) + + def test_nios_nsgroup_remove(self): + self.module.params = {'provider': None, 'state': 'absent', 'name': 'my-simple-group', + 'comment': None, 'grid_primary': None} + + ref = "nsgroup/ZG5zLm5ldHdvcmtfdmlldyQw:ansible/false" + + test_object = [{ + "comment": "test comment", + "_ref": ref, + "name": "my-simple-group", + "grid_primary": {'name': 'infoblox-test.example.com'} + }] + + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "grid_primary": {} + } + + 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_nsgroup_update_comment(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'default', + 'comment': 'updated comment', 'grid_primary': None} + + test_object = [ + { + "comment": "test comment", + "_ref": "nsgroup/ZG5zLm5ldHdvcmtfdmlldyQw:default/true", + "name": "default", + "grid_primary": {} + } + ] + + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "grid_primary": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.update_object.called_once_with(test_object)