diff --git a/lib/ansible/module_utils/net_tools/__init__.py b/lib/ansible/module_utils/net_tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/module_utils/net_tools/nios/__init__.py b/lib/ansible/module_utils/net_tools/nios/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/module_utils/net_tools/nios/api.py b/lib/ansible/module_utils/net_tools/nios/api.py new file mode 100644 index 0000000000..3429ffc00f --- /dev/null +++ b/lib/ansible/module_utils/net_tools/nios/api.py @@ -0,0 +1,265 @@ +# This code is part of Ansible, but is an independent component. +# This particular file snippet, and this file snippet only, is BSD licensed. +# Modules you write using this snippet, which is embedded dynamically by Ansible +# still belong to the author of the module, and may assign their own license +# to the complete work. +# +# (c) 2018 Red Hat Inc. +# +# Redistribution and use in source and binary forms, with or without modification, +# are permitted provided that the following conditions are met: +# +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above copyright notice, +# this list of conditions and the following disclaimer in the documentation +# and/or other materials provided with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND +# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED +# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. +# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, +# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, +# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT +# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE +# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# +from functools import partial + +from ansible.module_utils.six import iteritems + +try: + from infoblox_client.connector import Connector + from infoblox_client.exceptions import InfobloxException + HAS_INFOBLOX_CLIENT = True +except ImportError: + HAS_INFOBLOX_CLIENT = False + + +nios_provider_spec = { + 'host': dict(), + 'username': dict(), + 'password': dict(no_log=True), + 'ssl_verify': dict(type='bool', default=False), + 'http_request_timeout': dict(type='int', default=10), + 'http_pool_connections': dict(type='int', default=10), + 'http_pool_maxsize': dict(type='int', default=10), + 'max_retries': dict(type='int', default=3), + 'wapi_version': dict(default='1.4'), +} + + +def get_provider_spec(): + return {'provider': dict(type='dict', options=nios_provider_spec)} + + +def get_connector(module): + if not HAS_INFOBLOX_CLIENT: + module.fail_json(msg='infoblox-client is required but does not appear ' + 'to be installed. It can be installed using the ' + 'command `pip install infoblox-client`') + return Connector(module.params['provider']) + + +class WapiBase(object): + ''' Base class for implementing Infoblox WAPI API ''' + + def __init__(self, module): + self.module = module + self.connector = get_connector(module) + + def __getattr__(self, name): + try: + return self.__dict__[name] + except KeyError: + if name.startswith('_'): + raise AttributeError("'%s' object has no attribute '%s'" % (self.__class__.__name__, name)) + return partial(self._invoke_method, name) + + def _invoke_method(self, name, *args, **kwargs): + try: + method = getattr(self.connector, name) + return method(*args, **kwargs) + except InfobloxException as exc: + self.module.fail_json( + msg=exc.response['text'], + type=exc.response['Error'].split(':')[0], + code=exc.response.get('code'), + action=name + ) + + def run(self, ib_obj_type, ib_spec): + raise NotImplementedError + + +class Wapi(WapiBase): + ''' Implements WapiBase for executing a NIOS module ''' + + def run(self, ib_obj_type, ib_spec): + ''' Runs the module and performans configuration tasks + + :args ib_obj_type: the WAPI object type to operate against + :args ib_spec: the specification for the WAPI object as a dict + + :returns: a results dict + ''' + state = self.module.params['state'] + if state not in ('present', 'absent'): + self.module.fail_json(msg='state must be one of `present`, `absent`, got `%s`' % state) + + result = {'changed': False} + + obj_filter = dict([(k, self.module.params[k]) for k, v in iteritems(ib_spec) if v.get('ib_req')]) + ib_obj = self.get_object(ib_obj_type, obj_filter.copy(), return_fields=ib_spec.keys()) + + if ib_obj: + current_object = ib_obj[0] + if 'extattrs' in current_object: + current_object['extattrs'] = self.flatten_extattrs(current_object['extattrs']) + ref = current_object.pop('_ref') + else: + current_object = obj_filter + ref = None + + proposed_object = {} + for key, value in iteritems(ib_spec): + if self.module.params[key] is not None: + if 'transform' in value: + proposed_object[key] = value['transform'](self.module) + else: + proposed_object[key] = self.module.params[key] + + modified = not self.compare_objects(current_object, proposed_object) + + if 'extattrs' in proposed_object: + proposed_object['extattrs'] = self.normalize_extattrs(proposed_object['extattrs']) + + if state == 'present': + if ref is None: + if not self.module.check_mode: + self.create_object(ib_obj_type, proposed_object) + result['changed'] = True + elif modified: + if 'network_view' in proposed_object: + self.check_if_network_view_exists(proposed_object['network_view']) + proposed_object.pop('network_view') + elif 'view' in proposed_object: + self.check_if_dns_view_exists(proposed_object['view']) + if not self.module.check_mode: + res = self.update_object(ref, proposed_object) + result['changed'] = True + + elif state == 'absent': + if ref is not None: + if not self.module.check_mode: + self.delete_object(ref) + result['changed'] = True + + return result + + def check_if_dns_view_exists(self, name, fail_on_missing=True): + ''' Checks if the specified DNS view is already configured + + :args name: the name of the DNS view to check + :args fail_on_missing: fail the module if the DNS view does not exist + + :returns: True if the network_view exists and False if the DNS view + does not exist and fail_on_missing is False + ''' + res = self.get_object('view', {'name': name}) is not None + if not res and fail_on_missing: + self.module.fail_json(msg='DNS view %s does not exist, please create ' + 'it using nios_dns_view first' % name) + return res + + def check_if_network_view_exists(self, name, fail_on_missing=True): + ''' Checks if the specified network_view is already configured + + :args name: the name of the network view to check + :args fail_on_missing: fail the module if the network_view does not exist + + :returns: True if the network_view exists and False if the network_view + does not exist and fail_on_missing is False + ''' + res = self.get_object('networkview', {'name': name}) is not None + if not res and fail_on_missing: + self.module.fail_json(msg='Network view %s does not exist, please create ' + 'it using nios_network_view first' % name) + return res + + def normalize_extattrs(self, value): + ''' Normalize extattrs field to expected format + + The module accepts extattrs as key/value pairs. This method will + transform the key/value pairs into a structure suitable for + sending across WAPI in the format of: + + extattrs: { + key: { + value: + } + } + ''' + return dict([(k, {'value': v}) for k, v in iteritems(value)]) + + def flatten_extattrs(self, value): + ''' Flatten the key/value struct for extattrs + + WAPI returns extattrs field as a dict in form of: + + extattrs: { + key: { + value: + } + } + + This method will flatten the structure to: + + extattrs: { + key: value + } + + ''' + return dict([(k, v['value']) for k, v in iteritems(value)]) + + def issubset(self, item, objects): + ''' Checks if item is a subset of objects + + :args item: the subset item to validate + :args objects: superset list of objects to validate against + + :returns: True if item is a subset of one entry in objects otherwise + this method will return None + ''' + for obj in objects: + if isinstance(item, dict): + if all(entry in obj.items() for entry in item.items()): + return True + else: + if item in obj: + return True + + def compare_objects(self, current_object, proposed_object): + + for key, proposed_item in iteritems(proposed_object): + current_item = current_object.get(key) + + # if proposed has a key that current doesn't then the objects are + # not equal and False will be immediately returned + if current_item is None: + return False + + elif isinstance(proposed_item, list): + for subitem in proposed_item: + if not self.issubset(subitem, current_item): + return False + + elif isinstance(proposed_item, dict): + return self.compare_objects(current_item, proposed_item) + + else: + if current_item != proposed_item: + return False + + return True diff --git a/lib/ansible/modules/net_tools/nios/__init__.py b/lib/ansible/modules/net_tools/nios/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/lib/ansible/modules/net_tools/nios/nios_dns_view.py b/lib/ansible/modules/net_tools/nios/nios_dns_view.py new file mode 100644 index 0000000000..154d43e296 --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_dns_view.py @@ -0,0 +1,131 @@ +#!/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_dns_view +version_added: "2.5" +author: "Peter Sprygada (@privateip)" +short_description: Configure Infoblox NIOS DNS views +description: + - Adds and/or removes instances of DNS view objects from + Infoblox NIOS servers. This module manages NIOS C(view) objects + using the Infoblox WAPI interface over REST. +requirements: + - infoblox_client +extends_documentation_fragment: nios +options: + name: + description: + - Specifies the name of the DNS view to add and/or remove from the + system configuration based on the setting of the C(state) argument. + required: true + aliases: + - view + network_view: + description: + - Specifies the name of the network view to assign the configured + DNS view to. The network view must already be configured on the + target system. + required: true + default: default + 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. + required: false + default: present + choices: + - present + - absent +''' + +EXAMPLES = ''' +- name: configure a new dns view instance + nios_dns_view: + name: ansible-dns + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: update the comment for dns view + nios_dns_view: + name: ansible-dns + comment: this is an example comment + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: remove the dns view instance + nios_dns_view: + name: ansible-dns + state: absent + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin +''' + +RETURN = ''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi + + +def main(): + ''' Main entry point for module execution + ''' + ib_spec = dict( + name=dict(required=True, aliases=['view'], ib_req=True), + network_view=dict(default='default', ib_req=True), + + 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(get_provider_spec()) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + wapi = Wapi(module) + result = wapi.run('view', ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/net_tools/nios/nios_host_record.py b/lib/ansible/modules/net_tools/nios/nios_host_record.py new file mode 100644 index 0000000000..00e741cf14 --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_host_record.py @@ -0,0 +1,219 @@ +#!/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_host_record +version_added: "2.5" +author: "Peter Sprygada (@privateip)" +short_description: Configure Infoblox NIOS host records +description: + - Adds and/or removes instances of host record objects from + Infoblox NIOS servers. This module manages NIOS C(record:host) objects + using the Infoblox WAPI interface over REST. +requirements: + - infoblox_client +extends_documentation_fragment: nios +options: + name: + description: + - Specifies the fully qualified hostname to add or remove from + the system + required: true + default: null + view: + description: + - Sets the DNS view to associate this host record with. The DNS + view must already be configured on the system + required: true + default: default + aliases: + - dns_view + ipv4addrs: + description: + - Configures the IPv4 addresses for this host record. This argument + accepts a list of values (see suboptions) + required: false + default: null + aliases: + - ipv4 + suboptions: + ipv4addr: + description: + - Configures the IPv4 address for the host record + required: true + default: null + aliases: + - address + mac: + description: + - Configures the hardware MAC address for the host record + required: false + ipv6addrs: + description: + - Configures the IPv6 addresses for the host record. This argument + accepts a list of values (see options) + required: false + default: null + aliases: + - ipv6 + suboptions: + ipv6addr: + description: + - Configures the IPv6 address for the host record + required: true + default: null + aliases: + - address + ttl: + description: + - Configures the TTL to be associated with this host record + required: false + default: null + 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. + required: false + default: present + choices: + - present + - absent +''' + +EXAMPLES = ''' +- name: configure an ipv4 host record + nios_host_record: + name: host.ansible.com + ipv4: + address: 192.168.10.1 + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin +- name: add a comment to an existing host record + nios_host_record: + name: host.ansible.com + ipv4: + address: 192.168.10.1 + comment: this is a test comment + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: remove a host record from the system + nios_host_record: + name: host.ansible.com + state: absent + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin +''' + +RETURN = ''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi + + +def ipaddr(module, key, filtered_keys=None): + ''' Transforms the input value into a struct supported by WAPI + + This function will transform the input from the playbook into a struct + that is valid for WAPI in the form of: + + { + ipv4addr: , + mac: + } + + This function does not validate the values are properly formatted or in + the acceptable range, that is left to WAPI. + ''' + filtered_keys = filtered_keys or list() + objects = list() + for item in module.params[key]: + objects.append(dict([(k, v) for k, v in iteritems(item) if v is not None and k not in filtered_keys])) + return objects + + +def ipv4addrs(module): + return ipaddr(module, 'ipv4addrs', filtered_keys=['address']) + + +def ipv6addrs(module): + return ipaddr(module, 'ipv6addrs', filtered_keys=['address']) + + +def main(): + ''' Main entry point for module execution + ''' + ipv4addr_spec = dict( + ipv4addr=dict(required=True, aliases=['address'], ib_req=True), + mac=dict() + ) + + ipv6addr_spec = dict( + ipv6addr=dict(required=True, aliases=['address'], ib_req=True) + ) + + ib_spec = dict( + name=dict(required=True, ib_req=True), + view=dict(default='default', aliases=['dns_view'], ib_req=True), + + ipv4addrs=dict(type='list', aliases=['ipv4'], elements='dict', options=ipv4addr_spec, transform=ipv4addrs), + ipv6addrs=dict(type='list', aliases=['ipv6'], elements='dict', options=ipv6addr_spec, transform=ipv6addrs), + + 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(get_provider_spec()) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + wapi = Wapi(module) + result = wapi.run('record:host', ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/net_tools/nios/nios_network.py b/lib/ansible/modules/net_tools/nios/nios_network.py new file mode 100644 index 0000000000..a7e9f48f4d --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_network.py @@ -0,0 +1,215 @@ +#!/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_network +version_added: "2.5" +author: "Peter Sprygada (@privateip)" +short_description: Configure Infoblox NIOS network object +description: + - Adds and/or removes instances of network objects from + Infoblox NIOS servers. This module manages NIOS C(network) objects + using the Infoblox WAPI interface over REST. +requirements: + - infoblox_client +extends_documentation_fragment: nios +options: + network: + description: + - Specifies the network to add or remove from the system. The value + should use CIDR notation. + required: true + default: null + aliases: + - name + - cidr + network_view: + description: + - Configures the name of the network view to associate with this + configured instance. + required: true + default: default + options: + description: + - Configures the set of DHCP options to be included as part of + the configured network instance. This argument accepts a list + of values (see suboptions). When configuring suboptions at + least one of C(name) or C(num) must be specified. + required: false + default: null + suboptions: + name: + description: + - The name of the DHCP option to configure + required: false + default: null + num: + description: + - The number of the DHCP option to configure + required: false + value: + description: + - The value of the DHCP option specified by C(name) + required: true + default: null + use_option: + description: + - Only applies to a subset of options (see NIOS API documentation) + required: false + type: bool + default: true + vendor_class: + description: + - The name of the space this DHCP option is associated to + required: false + default: DHCP + 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 + default: null + 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 + default: null + 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. + required: false + default: present + choices: + - present + - absent +''' + +EXAMPLES = ''' +- name: configure a network + nios_network: + network: 192.168.10.0/24 + comment: this is a test comment + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: set dhcp options for a network + nios_network: + network: 192.168.10.0/24 + comment: this is a test comment + options: + - name: domain-name + value: ansible.com + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: remove a network + nios_network: + network: 192.168.10.0/24 + state: absent + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin +''' + +RETURN = ''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.six import iteritems +from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi + + +def options(module): + ''' Transforms the module argument into a valid WAPI struct + + This function will transform the options argument into a structure that + is a valid WAPI structure in the format of: + + { + name: , + num: , + value: , + use_option: , + vendor_class: + } + + It will remove any options that are set to None since WAPI will error on + that condition. It will also verify that either `name` or `num` is + set in the structure but does not validate the values are equal. + + The remainder of the value validation is performed by WAPI + ''' + options = list() + for item in module.params['options']: + opt = dict([(k, v) for k, v in iteritems(item) if v is not None]) + if 'name' not in opt and 'num' not in opt: + module.fail_json(msg='one of `name` or `num` is required for option value') + options.append(opt) + return options + + +def main(): + ''' Main entry point for module execution + ''' + option_spec = dict( + # one of name or num is required; enforced by the function options() + name=dict(), + num=dict(type='int'), + + value=dict(required=True), + + use_option=dict(type='bool', default=True), + vendor_class=dict(default='DHCP') + ) + + ib_spec = dict( + network=dict(required=True, aliases=['name', 'cidr'], ib_req=True), + network_view=dict(default='default', ib_req=True), + + options=dict(type='list', elements='dict', options=option_spec, transform=options), + + 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(get_provider_spec()) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + wapi = Wapi(module) + result = wapi.run('network', ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/net_tools/nios/nios_network_view.py b/lib/ansible/modules/net_tools/nios/nios_network_view.py new file mode 100644 index 0000000000..3efcf5e466 --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_network_view.py @@ -0,0 +1,125 @@ +#!/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_network_view +version_added: "2.5" +author: "Peter Sprygada (@privateip)" +short_description: Configure Infoblox NIOS network views +description: + - Adds and/or removes instances of network view objects from + Infoblox NIOS servers. This module manages NIOS C(networkview) objects + using the Infoblox WAPI interface over REST. +requirements: + - infoblox_client +extends_documentation_fragment: nios +options: + name: + description: + - Specifies the name of the network view to either add or remove + from the configuration. + required: true + aliases: + - network_view + 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 + default: null + 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 + default: null + 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. + required: false + default: present + choices: + - present + - absent +''' + +EXAMPLES = ''' +- name: configure a new network view + nios_network_view: + name: ansible + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: update the comment for network view + nios_network_view: + name: ansible + comment: this is an example comment + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: remove the network view + nios_network_view: + name: ansible + state: absent + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin +''' + +RETURN = ''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi + + +def main(): + ''' Main entry point for module execution + ''' + ib_spec = dict( + name=dict(required=True, aliases=['network_view'], ib_req=True), + + 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(get_provider_spec()) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + wapi = Wapi(module) + result = wapi.run('networkview', ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/modules/net_tools/nios/nios_zone.py b/lib/ansible/modules/net_tools/nios/nios_zone.py new file mode 100644 index 0000000000..62840f64fc --- /dev/null +++ b/lib/ansible/modules/net_tools/nios/nios_zone.py @@ -0,0 +1,162 @@ +#!/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_zone +version_added: "2.5" +author: "Peter Sprygada (@privateip)" +short_description: Configure Infoblox NIOS DNS zones +description: + - Adds and/or removes instances of DNS zone objects from + Infoblox NIOS servers. This module manages NIOS C(zone_auth) objects + using the Infoblox WAPI interface over REST. +requirements: + - infoblox_client +extends_documentation_fragment: nios +options: + fqdn: + description: + - Specifies the qualified domain name to either add or remove from + the NIOS instance based on the configured C(state) value. + required: true + default: null + aliases: + - name + view: + description: + - Configures the DNS view name for the configured resource. The + specified DNS zone must already exist on the running NIOS instance + prior to configuring zones. + required: true + default: default + aliases: + - dns_view + grid_primary: + description: + - Configures the grid primary servers for this zone. + required: false + suboptions: + name: + description: + - The name of the grid primary server + required: true + default: null + grid_secondaries: + description: + - Configures the grid secondary servers for this zone. + required: false + suboptions: + name: + description: + - The name of the grid secondary server + required: true + 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. + required: false + default: present + choices: + - present + - absent +''' + +EXAMPLES = ''' +- name: configure a zone on the system + nios_zone: + name: ansible.com + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: update the comment and ext attributes for an existing zone + nios_zone: + name: ansible.com + comment: this is an example comment + extattrs: + Site: west-dc + state: present + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin + +- name: remove the dns zone + nios_zone: + name: ansible.com + state: absent + provider: + host: "{{ inventory_hostname_short }}" + username: admin + password: admin +''' + +RETURN = ''' # ''' + +from ansible.module_utils.basic import AnsibleModule +from ansible.module_utils.net_tools.nios.api import get_provider_spec, Wapi + + +def main(): + ''' Main entry point for module execution + ''' + grid_spec = dict( + name=dict(required=True), + ) + + ib_spec = dict( + fqdn=dict(required=True, aliases=['name'], ib_req=True), + view=dict(default='default', aliases=['dns_view'], ib_req=True), + + grid_primary=dict(type='list', elements='dict', options=grid_spec), + grid_secondaries=dict(type='list', elements='dict', options=grid_spec), + + 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(get_provider_spec()) + + module = AnsibleModule(argument_spec=argument_spec, + supports_check_mode=True) + + wapi = Wapi(module) + result = wapi.run('zone_auth', ib_spec) + + module.exit_json(**result) + + +if __name__ == '__main__': + main() diff --git a/lib/ansible/utils/module_docs_fragments/nios.py b/lib/ansible/utils/module_docs_fragments/nios.py new file mode 100644 index 0000000000..0496702f38 --- /dev/null +++ b/lib/ansible/utils/module_docs_fragments/nios.py @@ -0,0 +1,65 @@ +# +# (c) 2015, Peter Sprygada +# +# 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 . + + +class ModuleDocFragment(object): + + # Standard files documentation fragment + DOCUMENTATION = """ +options: + provider: + description: + - A dict object containing connection details. + default: null + suboptions: + host: + description: + - Specifies the DNS host name or address for connecting to the remote + instance of NIOS WAPI over REST + required: true + username: + description: + - Configures the username to use to authenticate the connection to + the remote instance of NIOS. + password: + description: + - Specifies the password to use to authenticate the connection to + the remote instance of NIOS. + default: null + ssl_verify: + description: + - Boolean value to enable or disable verifying SSL certificates + required: false + default: false + http_request_timeout: + description: + - The amount of time before to wait before receiving a response + required: false + default: 10 + max_retries: + description: + - Configures the number of attempted retries before the connection + is declared usable + required: false + default: 3 + wapi_version: + description: + - Specifies the version of WAPI to use + required: false + default: 1.4 +""" diff --git a/test/units/module_utils/net_tools/__init__.py b/test/units/module_utils/net_tools/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/units/module_utils/net_tools/nios/__init__.py b/test/units/module_utils/net_tools/nios/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/test/units/module_utils/net_tools/nios/test_api.py b/test/units/module_utils/net_tools/nios/test_api.py new file mode 100644 index 0000000000..7e65046a2d --- /dev/null +++ b/test/units/module_utils/net_tools/nios/test_api.py @@ -0,0 +1,230 @@ +# (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 + +import sys +import copy + +from ansible.compat.tests import unittest +from ansible.compat.tests.mock import patch, MagicMock, Mock +from ansible.module_utils.net_tools.nios import api + + +class TestNiosApi(unittest.TestCase): + + def setUp(self): + super(TestNiosApi, self).setUp() + + self.module = MagicMock(name='AnsibleModule') + self.module.check_mode = False + self.module.params = {'provider': None} + + self.mock_connector = patch('ansible.module_utils.net_tools.nios.api.get_connector') + self.mock_connector.start() + + def tearDown(self): + super(TestNiosApi, self).tearDown() + + self.mock_connector.stop() + + def test_get_provider_spec(self): + provider_options = ['host', 'username', 'password', 'ssl_verify', + 'http_request_timeout', 'http_pool_connections', + 'http_pool_maxsize', 'max_retries', 'wapi_version'] + res = api.get_provider_spec() + self.assertIsNotNone(res) + self.assertIn('provider', res) + self.assertIn('options', res['provider']) + returned_options = res['provider']['options'] + self.assertEqual(sorted(provider_options), sorted(returned_options.keys())) + + def test_wapi_base(self): + wapi = api.WapiBase(self.module) + + with self.assertRaises(NotImplementedError): + wapi.run(None, None) + + def _get_wapi(self, test_object): + wapi = api.Wapi(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 test_wapi_no_change(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'default', + 'comment': 'test comment', 'extattrs': None} + + test_object = [ + { + "comment": "test comment", + "_ref": "networkview/ZG5zLm5ldHdvcmtfdmlldyQw:default/true", + "name": "default", + "extattrs": {} + } + ] + + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertFalse(res['changed']) + + def test_wapi_change(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'default', + 'comment': 'updated comment', 'extattrs': None} + + test_object = [ + { + "comment": "test comment", + "_ref": "networkview/ZG5zLm5ldHdvcmtfdmlldyQw:default/true", + "name": "default", + "extattrs": {} + } + ] + + test_spec = { + "name": {"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_wapi_extattrs_change(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'default', + 'comment': 'test comment', 'extattrs': {'Site': 'update'}} + + ref = "networkview/ZG5zLm5ldHdvcmtfdmlldyQw:default/true" + + test_object = [{ + "comment": "test comment", + "_ref": ref, + "name": "default", + "extattrs": {'Site': {'value': 'test'}} + }] + + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "extattrs": {} + } + + kwargs = copy.deepcopy(test_object[0]) + kwargs['extattrs']['Site']['value'] = 'update' + del kwargs['_ref'] + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.update_object.assert_called_once_with(ref, kwargs) + + def test_wapi_extattrs_nochange(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'default', + 'comment': 'test comment', 'extattrs': {'Site': 'test'}} + + test_object = [{ + "comment": "test comment", + "_ref": "networkview/ZG5zLm5ldHdvcmtfdmlldyQw:default/true", + "name": "default", + "extattrs": {'Site': {'value': 'test'}} + }] + + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "extattrs": {} + } + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertFalse(res['changed']) + + def test_wapi_create(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'ansible', + 'comment': None, 'extattrs': None} + + test_object = None + + test_spec = { + "name": {"ib_req": True}, + "comment": {}, + "extattrs": {} + } + + 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': 'ansible'}) + + def test_wapi_delete(self): + self.module.params = {'provider': None, 'state': 'absent', 'name': 'ansible', + 'comment': None, 'extattrs': None} + + ref = "networkview/ZG5zLm5ldHdvcmtfdmlldyQw:ansible/false" + + test_object = [{ + "comment": "test comment", + "_ref": ref, + "name": "ansible", + "extattrs": {'Site': {'value': 'test'}} + }] + + test_spec = { + "name": {"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_wapi_strip_network_view(self): + self.module.params = {'provider': None, 'state': 'present', 'name': 'ansible', + 'comment': 'updated comment', 'extattrs': None, + 'network_view': 'default'} + + test_object = [{ + "comment": "test comment", + "_ref": "view/ZG5zLm5ldHdvcmtfdmlldyQw:ansible/true", + "name": "ansible", + "extattrs": {}, + "network_view": "default" + }] + + test_spec = { + "name": {"ib_req": True}, + "network_view": {"ib_req": True}, + "comment": {}, + "extattrs": {} + } + + kwargs = test_object[0].copy() + ref = kwargs.pop('_ref') + kwargs['comment'] = 'updated comment' + del kwargs['network_view'] + del kwargs['extattrs'] + + wapi = self._get_wapi(test_object) + res = wapi.run('testobject', test_spec) + + self.assertTrue(res['changed']) + wapi.update_object.assert_called_once_with(ref, kwargs)