From 7a7ad7e81159bece8feb37cfc1db3a90f1813e94 Mon Sep 17 00:00:00 2001 From: Ashwini Mhatre Date: Thu, 20 Jan 2022 00:40:50 +0530 Subject: [PATCH] Add Nthhost filter plugin (#128) Add Nthhost filter plugin SUMMARY ISSUE TYPE New Module Pull Request COMPONENT NAME ADDITIONAL INFORMATION Reviewed-by: Nathaniel Case Reviewed-by: Nilashish Chakraborty Reviewed-by: None --- README.md | 1 + changelogs/fragments/add_nthhost_filter.yaml | 3 + docs/ansible.utils.nthhost_filter.rst | 151 +++++++++++++++++ plugins/filter/nthhost.py | 155 ++++++++++++++++++ plugins/plugin_utils/base/ipaddr_utils.py | 33 ++++ .../utils_ipaddr_filter/tasks/nthhost.yaml | 16 ++ tests/unit/plugins/filter/test_nthhost.py | 32 ++++ 7 files changed, 391 insertions(+) create mode 100644 changelogs/fragments/add_nthhost_filter.yaml create mode 100644 docs/ansible.utils.nthhost_filter.rst create mode 100644 plugins/filter/nthhost.py create mode 100644 tests/integration/targets/utils_ipaddr_filter/tasks/nthhost.yaml create mode 100644 tests/unit/plugins/filter/test_nthhost.py diff --git a/README.md b/README.md index 61631a1..26aa803 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,7 @@ Name | Description [ansible.utils.from_xml](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.from_xml_filter.rst)|Convert given XML string to native python dictionary. [ansible.utils.get_path](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.get_path_filter.rst)|Retrieve the value in a variable using a path [ansible.utils.index_of](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.index_of_filter.rst)|Find the indices of items in a list matching some criteria +[ansible.utils.nthhost](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.nthhost_filter.rst)|This filter returns the nth host within a network described by value. [ansible.utils.ipv4](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.ipv4_filter.rst)|To filter only Ipv4 addresses Ipv4 filter is used. [ansible.utils.param_list_compare](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.param_list_compare_filter.rst)|Generate the final param list combining/comparing base and provided parameters. [ansible.utils.to_paths](https://github.com/ansible-collections/ansible.utils/blob/main/docs/ansible.utils.to_paths_filter.rst)|Flatten a complex object into a dictionary of paths and values diff --git a/changelogs/fragments/add_nthhost_filter.yaml b/changelogs/fragments/add_nthhost_filter.yaml new file mode 100644 index 0000000..5218e86 --- /dev/null +++ b/changelogs/fragments/add_nthhost_filter.yaml @@ -0,0 +1,3 @@ +--- +minor_changes: + - Add nthhost filter plugin. diff --git a/docs/ansible.utils.nthhost_filter.rst b/docs/ansible.utils.nthhost_filter.rst new file mode 100644 index 0000000..50f034b --- /dev/null +++ b/docs/ansible.utils.nthhost_filter.rst @@ -0,0 +1,151 @@ +.. _ansible.utils.nthhost_filter: + + +********************* +ansible.utils.nthhost +********************* + +**This filter returns the nth host within a network described by value.** + + +Version added: 2.5.0 + +.. contents:: + :local: + :depth: 1 + + +Synopsis +-------- +- This filter returns the nth host within a network described by value. To return the nth ip from a network, use the filter nthhost. +- Nthhost also supports a negative value. + + + + +Parameters +---------- + +.. raw:: html + + + + + + + + + + + + + + + + + + + + +
ParameterChoices/DefaultsConfigurationComments
+
+ query + +
+ string +
+
+ + +
nth host
+
+
+ value + +
+ string + / required +
+
+ + +
The network address or range to test against.
+
+
+ + + + +Examples +-------- + +.. code-block:: yaml + + #### examples + - name: To return the nth ip from a network, use the filter nthhost. + debug: + msg: "{{ '10.0.0.0/8' | ansible.utils.nthhost(305) }}" + + - name: nthhost also supports a negative value. + debug: + msg: "{{ '10.0.0.0/8' | ansible.utils.nthhost(-1) }}" + + # TASK [To return the nth ip from a network, use the filter nthhost.] ***************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_nthhost.yaml:7 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "msg": "10.0.1.49" + # } + # + # TASK [nthhost also supports a negative value.] ************************************************************** + # task path: /Users/amhatre/ansible-collections/playbooks/test_nthhost.yaml:11 + # Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils + # ok: [localhost] => { + # "msg": "10.255.255.255" + # } + + + +Return Values +------------- +Common return values are documented `here `_, the following are the fields unique to this filter: + +.. raw:: html + + + + + + + + + + + + +
KeyReturnedDescription
+
+ data + +
+ string +
+
+
Returns nth host from network
+
+
+

+ + +Status +------ + + +Authors +~~~~~~~ + +- Ashwini Mhatre (@amhatre) + + +.. hint:: + Configuration entries for each entry type have a low to high priority order. For example, a variable that is lower in the list will override a variable that is higher up. diff --git a/plugins/filter/nthhost.py b/plugins/filter/nthhost.py new file mode 100644 index 0000000..6d13a1e --- /dev/null +++ b/plugins/filter/nthhost.py @@ -0,0 +1,155 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +filter plugin file for ipaddr filters: nthhost +""" +from __future__ import absolute_import, division, print_function +from functools import partial +from ansible_collections.ansible.utils.plugins.plugin_utils.base.ipaddr_utils import ( + ipaddr, + _need_netaddr, +) +from ansible.errors import AnsibleFilterError +from ansible_collections.ansible.utils.plugins.module_utils.common.argspec_validate import ( + AnsibleArgSpecValidator, +) + +__metaclass__ = type + + +try: + from jinja2.filters import pass_environment +except ImportError: + from jinja2.filters import environmentfilter as pass_environment + +try: + import netaddr + + HAS_NETADDR = True +except ImportError: + # in this case, we'll make the filters return error messages (see bottom) + HAS_NETADDR = False +else: + + class mac_linux(netaddr.mac_unix): + pass + + mac_linux.word_fmt = "%.2x" + +DOCUMENTATION = """ + name: nthhost + author: Ashwini Mhatre (@amhatre) + version_added: "2.5.0" + short_description: This filter returns the nth host within a network described by value. + description: + - This filter returns the nth host within a network described by value. To return the nth ip from a network, use the filter nthhost. + - Nthhost also supports a negative value. + options: + value: + description: The network address or range to test against. + type: str + required: True + query: + description: nth host + type: str + notes: +""" + +EXAMPLES = r""" +#### examples +- name: To return the nth ip from a network, use the filter nthhost. + debug: + msg: "{{ '10.0.0.0/8' | ansible.utils.nthhost(305) }}" + +- name: nthhost also supports a negative value. + debug: + msg: "{{ '10.0.0.0/8' | ansible.utils.nthhost(-1) }}" + +# TASK [To return the nth ip from a network, use the filter nthhost.] ***************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_nthhost.yaml:7 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "msg": "10.0.1.49" +# } +# +# TASK [nthhost also supports a negative value.] ************************************************************** +# task path: /Users/amhatre/ansible-collections/playbooks/test_nthhost.yaml:11 +# Loading collection ansible.utils from /Users/amhatre/ansible-collections/collections/ansible_collections/ansible/utils +# ok: [localhost] => { +# "msg": "10.255.255.255" +# } + + +""" + +RETURN = """ + data: + type: str + description: + - Returns nth host from network + +""" + + +@pass_environment +def _nthhost(*args, **kwargs): + """This filter returns whether an address or a network passed as argument is in a network.""" + keys = ["value", "query"] + data = dict(zip(keys, args[1:])) + data.update(kwargs) + aav = AnsibleArgSpecValidator( + data=data, schema=DOCUMENTATION, name="nthhost" + ) + valid, errors, updated_data = aav.validate() + if not valid: + raise AnsibleFilterError(errors) + return nthhost(**updated_data) + + +def nthhost(value, query=""): + """ Returns the nth host within a network described by value. """ + try: + vtype = ipaddr(value, "type") + if vtype == "address": + v = ipaddr(value, "cidr") + elif vtype == "network": + v = ipaddr(value, "subnet") + + value = netaddr.IPNetwork(v) + except Exception: + return False + + if not query: + return False + + try: + nth = int(query) + if value.size > nth: + return str(value[nth]) + + except ValueError: + return False + + return False + + +class FilterModule(object): + """IP address and network manipulation filters + """ + + filter_map = { + # IP addresses and networks + "nthhost": _nthhost + } + + def filters(self): + """ ipaddr filter """ + if HAS_NETADDR: + return self.filter_map + else: + return dict( + (f, partial(_need_netaddr, f)) for f in self.filter_map + ) diff --git a/plugins/plugin_utils/base/ipaddr_utils.py b/plugins/plugin_utils/base/ipaddr_utils.py index 0b83537..d527943 100644 --- a/plugins/plugin_utils/base/ipaddr_utils.py +++ b/plugins/plugin_utils/base/ipaddr_utils.py @@ -604,3 +604,36 @@ def _need_netaddr(f_name, *args, **kwargs): "The %s filter requires python's netaddr be " "installed on the ansible controller" % f_name ) + + +def _address_normalizer(value): + """ + Used to validate an address or network type and return it in a consistent format. + This is being used for future use cases not currently available such as an address range. + :param value: The string representation of an address or network. + :return: The address or network in the normalized form. + """ + try: + vtype = ipaddr(value, "type") + if vtype == "address" or vtype == "network": + v = ipaddr(value, "subnet") + else: + return False + except Exception: + return False + + return v + + +def _range_checker(ip_check, first, last): + """ + Tests whether an ip address is within the bounds of the first and last address. + :param ip_check: The ip to test if it is within first and last. + :param first: The first IP in the range to test against. + :param last: The last IP in the range to test against. + :return: bool + """ + if first <= ip_check <= last: + return True + else: + return False diff --git a/tests/integration/targets/utils_ipaddr_filter/tasks/nthhost.yaml b/tests/integration/targets/utils_ipaddr_filter/tasks/nthhost.yaml new file mode 100644 index 0000000..09fc90e --- /dev/null +++ b/tests/integration/targets/utils_ipaddr_filter/tasks/nthhost.yaml @@ -0,0 +1,16 @@ +--- +- name: nthhost filter test1 + ansible.builtin.set_fact: + result1: "{{ '10.0.0.0/8'|ansible.utils.nthhost(305) }}" + +- name: Assert result for nthhost. + assert: + that: "{{ result1 == '10.0.1.49' }}" + +- name: nthhost filter test2 + ansible.builtin.set_fact: + result1: "{{ '10.0.0.0/8'|ansible.utils.nthhost(-1) }}" + +- name: Assert result for nthhost + assert: + that: "{{ result1 == '10.255.255.255' }}" diff --git a/tests/unit/plugins/filter/test_nthhost.py b/tests/unit/plugins/filter/test_nthhost.py new file mode 100644 index 0000000..d7f7bbd --- /dev/null +++ b/tests/unit/plugins/filter/test_nthhost.py @@ -0,0 +1,32 @@ +# -*- coding: utf-8 -*- +# Copyright 2021 Red Hat +# GNU General Public License v3.0+ +# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) + +""" +Unit test file for nthhost filter plugin +""" + +from __future__ import absolute_import, division, print_function + +__metaclass__ = type + +import unittest +from ansible_collections.ansible.utils.plugins.filter.nthhost import _nthhost + + +class Test_nthhost(unittest.TestCase): + def setUp(self): + pass + + def test_nthhost_filter_1(self): + """nthhost filter""" + args = ["", "10.0.0.0/8", "305"] + result = _nthhost(*args) + self.assertEqual(result, "10.0.1.49") + + def test_nthhost_filter_2(self): + """nthhost filter""" + args = ["", "10.0.0.0/8", "-1"] + result = _nthhost(*args) + self.assertEqual(result, "10.255.255.255")