Add cidr_merge filter (#36081)

pull/40633/head
flowerysong 6 years ago committed by Adam Miller
parent bf53e441b1
commit e2c1589201

@ -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
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
^^^^^^^^^^^^^^^^^^

@ -413,6 +413,38 @@ def _win_query(v):
# ---- 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'):
''' 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 '''
filter_map = {
# IP addresses and networks
'cidr_merge': cidr_merge,
'ipaddr': ipaddr,
'ipwrap': ipwrap,
'ip4_hex': ip4_hex,

@ -21,7 +21,7 @@ import pytest
from ansible.compat.tests import unittest
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')
@ -457,3 +457,19 @@ class TestIpFilter(unittest.TestCase):
subnet = '1.12.1.0/24'
address = '1.12.2.0'
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…
Cancel
Save