Add cidr_merge filter (#36081)
This commit is contained in:
parent
bf53e441b1
commit
e2c1589201
3 changed files with 73 additions and 1 deletions
|
@ -462,6 +462,29 @@ Because of the size of IPv6 subnets, iteration over all of them to find the
|
||||||
correct one may take some time on slower computers, depending on the size
|
correct one may take some time on slower computers, depending on the size
|
||||||
difference between subnets.
|
difference between subnets.
|
||||||
|
|
||||||
|
Subnet Merging
|
||||||
|
^^^^^^^^^^^^^^
|
||||||
|
|
||||||
|
.. versionadded:: 2.6
|
||||||
|
|
||||||
|
The `cidr_merge` filter can be used to merge subnets or individual addresses
|
||||||
|
into their minimal representation, collapsing overlapping subnets and merging
|
||||||
|
adjacent ones wherever possible::
|
||||||
|
|
||||||
|
{{ ['192.168.0.0/17', '192.168.128.0/17', '192.168.128.1' ] | cidr_merge }}
|
||||||
|
# => ['192.168.0.0/16']
|
||||||
|
|
||||||
|
{{ ['192.168.0.0/24', '192.168.1.0/24', '192.168.3.0/24'] | cidr_merge }}
|
||||||
|
# => ['192.168.0.0/23', '192.168.3.0/24']
|
||||||
|
|
||||||
|
Changing the action from 'merge' to 'span' will instead return the smallest
|
||||||
|
subnet which contains all of the inputs::
|
||||||
|
|
||||||
|
{{ ['192.168.0.0/24', '192.168.3.0/24'] | cidr_merge('span') }}
|
||||||
|
# => '192.168.0.0/22'
|
||||||
|
|
||||||
|
{{ ['192.168.1.42', '192.168.42.1'] | cidr_merge('span') }}
|
||||||
|
# => '192.168.0.0/18'
|
||||||
|
|
||||||
MAC address filter
|
MAC address filter
|
||||||
^^^^^^^^^^^^^^^^^^
|
^^^^^^^^^^^^^^^^^^
|
||||||
|
|
|
@ -413,6 +413,38 @@ def _win_query(v):
|
||||||
|
|
||||||
|
|
||||||
# ---- IP address and network filters ----
|
# ---- IP address and network filters ----
|
||||||
|
|
||||||
|
# Returns a minified list of subnets or a single subnet that spans all of
|
||||||
|
# the inputs.
|
||||||
|
def cidr_merge(value, action='merge'):
|
||||||
|
if not hasattr(value, '__iter__'):
|
||||||
|
raise errors.AnsibleFilterError('cidr_merge: expected iterable, got ' + repr(value))
|
||||||
|
|
||||||
|
if action == 'merge':
|
||||||
|
try:
|
||||||
|
return [str(ip) for ip in netaddr.cidr_merge(value)]
|
||||||
|
except Exception as e:
|
||||||
|
raise errors.AnsibleFilterError('cidr_merge: error in netaddr:\n%s' % e)
|
||||||
|
|
||||||
|
elif action == 'span':
|
||||||
|
# spanning_cidr needs at least two values
|
||||||
|
if len(value) == 0:
|
||||||
|
return None
|
||||||
|
elif len(value) == 1:
|
||||||
|
try:
|
||||||
|
return str(netaddr.IPNetwork(value[0]))
|
||||||
|
except Exception as e:
|
||||||
|
raise errors.AnsibleFilterError('cidr_merge: error in netaddr:\n%s' % e)
|
||||||
|
else:
|
||||||
|
try:
|
||||||
|
return str(netaddr.spanning_cidr(value))
|
||||||
|
except Exception as e:
|
||||||
|
raise errors.AnsibleFilterError('cidr_merge: error in netaddr:\n%s' % e)
|
||||||
|
|
||||||
|
else:
|
||||||
|
raise errors.AnsibleFilterError("cidr_merge: invalid action '%s'" % action)
|
||||||
|
|
||||||
|
|
||||||
def ipaddr(value, query='', version=False, alias='ipaddr'):
|
def ipaddr(value, query='', version=False, alias='ipaddr'):
|
||||||
''' Check if string is an IP address or network and filter it '''
|
''' Check if string is an IP address or network and filter it '''
|
||||||
|
|
||||||
|
@ -1026,6 +1058,7 @@ class FilterModule(object):
|
||||||
''' IP address and network manipulation filters '''
|
''' IP address and network manipulation filters '''
|
||||||
filter_map = {
|
filter_map = {
|
||||||
# IP addresses and networks
|
# IP addresses and networks
|
||||||
|
'cidr_merge': cidr_merge,
|
||||||
'ipaddr': ipaddr,
|
'ipaddr': ipaddr,
|
||||||
'ipwrap': ipwrap,
|
'ipwrap': ipwrap,
|
||||||
'ip4_hex': ip4_hex,
|
'ip4_hex': ip4_hex,
|
||||||
|
|
|
@ -21,7 +21,7 @@ import pytest
|
||||||
|
|
||||||
from ansible.compat.tests import unittest
|
from ansible.compat.tests import unittest
|
||||||
from ansible.plugins.filter.ipaddr import (ipaddr, _netmask_query, nthhost, next_nth_usable,
|
from ansible.plugins.filter.ipaddr import (ipaddr, _netmask_query, nthhost, next_nth_usable,
|
||||||
previous_nth_usable, network_in_usable, network_in_network)
|
previous_nth_usable, network_in_usable, network_in_network, cidr_merge)
|
||||||
netaddr = pytest.importorskip('netaddr')
|
netaddr = pytest.importorskip('netaddr')
|
||||||
|
|
||||||
|
|
||||||
|
@ -457,3 +457,19 @@ class TestIpFilter(unittest.TestCase):
|
||||||
subnet = '1.12.1.0/24'
|
subnet = '1.12.1.0/24'
|
||||||
address = '1.12.2.0'
|
address = '1.12.2.0'
|
||||||
self.assertEqual(network_in_network(subnet, address), False)
|
self.assertEqual(network_in_network(subnet, address), False)
|
||||||
|
|
||||||
|
def test_cidr_merge(self):
|
||||||
|
self.assertEqual(cidr_merge([]), [])
|
||||||
|
self.assertEqual(cidr_merge([], 'span'), None)
|
||||||
|
subnets = ['1.12.1.0/24']
|
||||||
|
self.assertEqual(cidr_merge(subnets), subnets)
|
||||||
|
self.assertEqual(cidr_merge(subnets, 'span'), subnets[0])
|
||||||
|
subnets = ['1.12.1.0/25', '1.12.1.128/25']
|
||||||
|
self.assertEqual(cidr_merge(subnets), ['1.12.1.0/24'])
|
||||||
|
self.assertEqual(cidr_merge(subnets, 'span'), '1.12.1.0/24')
|
||||||
|
subnets = ['1.12.1.0/25', '1.12.1.128/25', '1.12.2.0/24']
|
||||||
|
self.assertEqual(cidr_merge(subnets), ['1.12.1.0/24', '1.12.2.0/24'])
|
||||||
|
self.assertEqual(cidr_merge(subnets, 'span'), '1.12.0.0/22')
|
||||||
|
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')
|
||||||
|
|
Loading…
Reference in a new issue