From 5928ec04cae570adc6a16433380685db773b8679 Mon Sep 17 00:00:00 2001 From: Fran Fitzpatrick Date: Wed, 11 Jul 2018 12:52:31 -0500 Subject: [PATCH] Adds an ipmath filter (#41985) --- .../user_guide/playbooks_filters_ipaddr.rst | 42 ++++++++++++++----- lib/ansible/plugins/filter/ipaddr.py | 18 ++++++++ test/units/plugins/filter/test_ipaddr.py | 31 +++++++++++++- 3 files changed, 79 insertions(+), 12 deletions(-) diff --git a/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst b/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst index ceec8c3ed9..a1b08feb80 100644 --- a/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst +++ b/docs/docsite/rst/user_guide/playbooks_filters_ipaddr.rst @@ -295,15 +295,15 @@ Converting subnet masks to CIDR notation Given a subnet in the form of network address and subnet mask, it can be converted into CIDR notation using ``ipaddr()``. This can be useful for converting Ansible facts gathered about network configuration from subnet masks into CIDR format:: ansible_default_ipv4: { - address: "192.168.0.11", - alias: "eth0", - broadcast: "192.168.0.255", - gateway: "192.168.0.1", - interface: "eth0", - macaddress: "fa:16:3e:c4:bd:89", - mtu: 1500, - netmask: "255.255.255.0", - network: "192.168.0.0", + address: "192.168.0.11", + alias: "eth0", + broadcast: "192.168.0.255", + gateway: "192.168.0.1", + interface: "eth0", + macaddress: "fa:16:3e:c4:bd:89", + mtu: 1500, + netmask: "255.255.255.0", + network: "192.168.0.0", type: "ether" } @@ -379,6 +379,28 @@ be automatically converted to a router address (with ``::1/48`` host address):: .. _6to4: https://en.wikipedia.org/wiki/6to4 +IP Math +^^^^^^^ + +.. versionadded:: 2.7 + +``ipmath()`` filter can be used to do simple IP math/arithmetic. + +Here are a few simple examples:: + + # {{ '192.168.1.5' | ipmath(5) }} + 192.168.1.10 + + # {{ '192.168.0.5' | ipmath(-10) }} + 192.167.255.251 + + # {{ '2001::1' | ipmath(10) }} + 2001::b + + # {{ '2001::5' | ipmath(-10) }} + 2000:ffff:ffff:ffff:ffff:ffff:ffff:fffb + + Subnet manipulation ^^^^^^^^^^^^^^^^^^^ @@ -527,5 +549,3 @@ convert it between various formats. Examples:: Have a question? Stop by the google group! `irc.freenode.net `_ #ansible IRC chat channel - - diff --git a/lib/ansible/plugins/filter/ipaddr.py b/lib/ansible/plugins/filter/ipaddr.py index bdca960521..fe3c3dbdf7 100644 --- a/lib/ansible/plugins/filter/ipaddr.py +++ b/lib/ansible/plugins/filter/ipaddr.py @@ -670,6 +670,23 @@ def ipaddr(value, query='', version=False, alias='ipaddr'): return False +def ipmath(value, amount): + try: + ip = netaddr.IPAddress(value) + except netaddr.AddrFormatError: + msg = 'You must pass a valid IP address; {0} is invalid'.format(value) + raise errors.AnsibleFilterError(msg) + + if not isinstance(amount, int): + msg = ( + 'You must pass an integer for arithmetic; ' + '{0} is not a valid integer' + ).format(amount) + raise errors.AnsibleFilterError(msg) + + return str(ip + amount) + + def ipwrap(value, query=''): try: if isinstance(value, (list, tuple, types.GeneratorType)): @@ -1060,6 +1077,7 @@ class FilterModule(object): # IP addresses and networks 'cidr_merge': cidr_merge, 'ipaddr': ipaddr, + 'ipmath': ipmath, 'ipwrap': ipwrap, 'ip4_hex': ip4_hex, 'ipv4': ipv4, diff --git a/test/units/plugins/filter/test_ipaddr.py b/test/units/plugins/filter/test_ipaddr.py index 785d57e371..cd375397cc 100644 --- a/test/units/plugins/filter/test_ipaddr.py +++ b/test/units/plugins/filter/test_ipaddr.py @@ -20,8 +20,10 @@ __metaclass__ = type import pytest from ansible.compat.tests import unittest +from ansible.errors import AnsibleFilterError from ansible.plugins.filter.ipaddr import (ipaddr, _netmask_query, nthhost, next_nth_usable, - previous_nth_usable, network_in_usable, network_in_network, cidr_merge) + previous_nth_usable, network_in_usable, network_in_network, + cidr_merge, ipmath) netaddr = pytest.importorskip('netaddr') @@ -473,3 +475,30 @@ class TestIpFilter(unittest.TestCase): subnets = ['1.12.1.1', '1.12.1.255'] self.assertEqual(cidr_merge(subnets), ['1.12.1.1/32', '1.12.1.255/32']) self.assertEqual(cidr_merge(subnets, 'span'), '1.12.1.0/24') + + def test_ipmath(self): + self.assertEqual(ipmath('192.168.1.5', 5), '192.168.1.10') + self.assertEqual(ipmath('192.168.1.5', -5), '192.168.1.0') + self.assertEqual(ipmath('192.168.0.5', -10), '192.167.255.251') + + self.assertEqual(ipmath('2001::1', 8), '2001::9') + self.assertEqual(ipmath('2001::1', 9), '2001::a') + self.assertEqual(ipmath('2001::1', 10), '2001::b') + self.assertEqual(ipmath('2001::5', -3), '2001::2') + self.assertEqual( + ipmath('2001::5', -10), + '2000:ffff:ffff:ffff:ffff:ffff:ffff:fffb' + ) + + expected = 'You must pass a valid IP address; invalid_ip is invalid' + with self.assertRaises(AnsibleFilterError) as exc: + ipmath('invalid_ip', 8) + self.assertEqual(exc.exception.message, expected) + + expected = ( + 'You must pass an integer for arithmetic; ' + 'some_number is not a valid integer' + ) + with self.assertRaises(AnsibleFilterError) as exc: + ipmath('1.2.3.4', 'some_number') + self.assertEqual(exc.exception.message, expected)