Add cidr_merge filter (#36081)

This commit is contained in:
flowerysong 2018-05-23 15:35:23 -04:00 committed by Adam Miller
parent bf53e441b1
commit e2c1589201
3 changed files with 73 additions and 1 deletions

View file

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

View file

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

View file

@ -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')