mirror of
https://github.com/ansible-collections/community.general.git
synced 2024-09-14 20:13:21 +02:00
nmcli: suggest new routes4 and routes6 format (#4328)
* suggest new routes4 and routes6 format * make new options instead of modifying exiting one * fix docs and some small errors * fixing docs
This commit is contained in:
parent
bbe231e261
commit
feb0fffd58
3 changed files with 217 additions and 12 deletions
|
@ -0,0 +1,4 @@
|
|||
---
|
||||
bugfixes:
|
||||
- nmcli - fix returning "changed" when routes parameters set, also suggest new routes4 and routes6 format
|
||||
(https://github.com/ansible-collections/community.general/issues/4131).
|
|
@ -90,11 +90,53 @@ options:
|
|||
version_added: 3.2.0
|
||||
routes4:
|
||||
description:
|
||||
- The list of ipv4 routes.
|
||||
- Use the format '192.0.3.0/24 192.0.2.1'
|
||||
- The list of IPv4 routes.
|
||||
- Use the format C(192.0.3.0/24 192.0.2.1).
|
||||
- To specify more complex routes, use the I(routes4_extended) option.
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 2.0.0
|
||||
routes4_extended:
|
||||
description:
|
||||
- The list of IPv4 routes.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
ip:
|
||||
description:
|
||||
- IP or prefix of route.
|
||||
- Use the format C(192.0.3.0/24).
|
||||
type: str
|
||||
required: true
|
||||
next_hop:
|
||||
description:
|
||||
- Use the format C(192.0.2.1).
|
||||
type: str
|
||||
metric:
|
||||
description:
|
||||
- Route metric.
|
||||
type: int
|
||||
table:
|
||||
description:
|
||||
- The table to add this route to.
|
||||
- The default depends on C(ipv4.route-table).
|
||||
type: int
|
||||
cwnd:
|
||||
description:
|
||||
- The clamp for congestion window.
|
||||
type: int
|
||||
mtu:
|
||||
description:
|
||||
- If non-zero, only transmit packets of the specified size or smaller.
|
||||
type: int
|
||||
onlink:
|
||||
description:
|
||||
- Pretend that the nexthop is directly attached to this link, even if it does not match any interface prefix.
|
||||
type: bool
|
||||
tos:
|
||||
description:
|
||||
- The Type Of Service.
|
||||
type: int
|
||||
route_metric4:
|
||||
description:
|
||||
- Set metric level of ipv4 routes configured on interface.
|
||||
|
@ -165,9 +207,47 @@ options:
|
|||
description:
|
||||
- The list of IPv6 routes.
|
||||
- Use the format C(fd12:3456:789a:1::/64 2001:dead:beef::1).
|
||||
- To specify more complex routes, use the I(routes6_extended) option.
|
||||
type: list
|
||||
elements: str
|
||||
version_added: 4.4.0
|
||||
routes6_extended:
|
||||
description:
|
||||
- The list of IPv6 routes but with parameters.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
ip:
|
||||
description:
|
||||
- IP or prefix of route.
|
||||
- Use the format C(fd12:3456:789a:1::/64).
|
||||
type: str
|
||||
required: true
|
||||
next_hop:
|
||||
description:
|
||||
- Use the format C(2001:dead:beef::1).
|
||||
type: str
|
||||
metric:
|
||||
description:
|
||||
- Route metric.
|
||||
type: int
|
||||
table:
|
||||
description:
|
||||
- The table to add this route to.
|
||||
- The default depends on C(ipv6.route-table).
|
||||
type: int
|
||||
cwnd:
|
||||
description:
|
||||
- The clamp for congestion window.
|
||||
type: int
|
||||
mtu:
|
||||
description:
|
||||
- If non-zero, only transmit packets of the specified size or smaller.
|
||||
type: int
|
||||
onlink:
|
||||
description:
|
||||
- Pretend that the nexthop is directly attached to this link, even if it does not match any interface prefix.
|
||||
type: bool
|
||||
route_metric6:
|
||||
description:
|
||||
- Set metric level of IPv6 routes configured on interface.
|
||||
|
@ -1260,6 +1340,7 @@ class Nmcli(object):
|
|||
self.gw4 = module.params['gw4']
|
||||
self.gw4_ignore_auto = module.params['gw4_ignore_auto']
|
||||
self.routes4 = module.params['routes4']
|
||||
self.routes4_extended = module.params['routes4_extended']
|
||||
self.route_metric4 = module.params['route_metric4']
|
||||
self.routing_rules4 = module.params['routing_rules4']
|
||||
self.never_default4 = module.params['never_default4']
|
||||
|
@ -1272,6 +1353,7 @@ class Nmcli(object):
|
|||
self.gw6 = module.params['gw6']
|
||||
self.gw6_ignore_auto = module.params['gw6_ignore_auto']
|
||||
self.routes6 = module.params['routes6']
|
||||
self.routes6_extended = module.params['routes6_extended']
|
||||
self.route_metric6 = module.params['route_metric6']
|
||||
self.dns6 = module.params['dns6']
|
||||
self.dns6_search = module.params['dns6_search']
|
||||
|
@ -1371,7 +1453,7 @@ class Nmcli(object):
|
|||
'ipv4.ignore-auto-dns': self.dns4_ignore_auto,
|
||||
'ipv4.gateway': self.gw4,
|
||||
'ipv4.ignore-auto-routes': self.gw4_ignore_auto,
|
||||
'ipv4.routes': self.routes4,
|
||||
'ipv4.routes': self.enforce_routes_format(self.routes4, self.routes4_extended),
|
||||
'ipv4.route-metric': self.route_metric4,
|
||||
'ipv4.routing-rules': self.routing_rules4,
|
||||
'ipv4.never-default': self.never_default4,
|
||||
|
@ -1383,7 +1465,7 @@ class Nmcli(object):
|
|||
'ipv6.ignore-auto-dns': self.dns6_ignore_auto,
|
||||
'ipv6.gateway': self.gw6,
|
||||
'ipv6.ignore-auto-routes': self.gw6_ignore_auto,
|
||||
'ipv6.routes': self.routes6,
|
||||
'ipv6.routes': self.enforce_routes_format(self.routes6, self.routes6_extended),
|
||||
'ipv6.route-metric': self.route_metric6,
|
||||
'ipv6.method': self.ipv6_method,
|
||||
'ipv6.ip6-privacy': self.ip_privacy6,
|
||||
|
@ -1614,6 +1696,29 @@ class Nmcli(object):
|
|||
return None
|
||||
return [address if '/' in address else address + '/128' for address in ip6_addresses]
|
||||
|
||||
def enforce_routes_format(self, routes, routes_extended):
|
||||
if routes is not None:
|
||||
return routes
|
||||
elif routes_extended is not None:
|
||||
return [self.route_to_string(route) for route in routes_extended]
|
||||
else:
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def route_to_string(route):
|
||||
result_str = ''
|
||||
result_str += route['ip']
|
||||
if route.get('next_hop') is not None:
|
||||
result_str += ' ' + route['next_hop']
|
||||
if route.get('metric') is not None:
|
||||
result_str += ' ' + str(route['metric'])
|
||||
|
||||
for attribute, value in sorted(route.items()):
|
||||
if attribute not in ('ip', 'next_hop', 'metric') and value is not None:
|
||||
result_str += ' {0}={1}'.format(attribute, str(value).lower())
|
||||
|
||||
return result_str
|
||||
|
||||
@staticmethod
|
||||
def bool_to_string(boolean):
|
||||
if boolean:
|
||||
|
@ -1657,6 +1762,20 @@ class Nmcli(object):
|
|||
return list
|
||||
return str
|
||||
|
||||
def get_route_params(self, raw_values):
|
||||
routes_params = []
|
||||
for raw_value in raw_values:
|
||||
route_params = {}
|
||||
for parameter, value in re.findall(r'([\w-]*)\s?=\s?([^\s,}]*)', raw_value):
|
||||
if parameter == 'nh':
|
||||
route_params['next_hop'] = value
|
||||
elif parameter == 'mt':
|
||||
route_params['metric'] = value
|
||||
else:
|
||||
route_params[parameter] = value
|
||||
routes_params.append(route_params)
|
||||
return [self.route_to_string(route_params) for route_params in routes_params]
|
||||
|
||||
def list_connection_info(self):
|
||||
cmd = [self.nmcli_bin, '--fields', 'name', '--terse', 'con', 'show']
|
||||
(rc, out, err) = self.execute_command(cmd)
|
||||
|
@ -1852,13 +1971,7 @@ class Nmcli(object):
|
|||
if key in conn_info:
|
||||
current_value = conn_info[key]
|
||||
if key in ('ipv4.routes', 'ipv6.routes') and current_value is not None:
|
||||
# ipv4.routes and ipv6.routes do not have same options and show_connection() format
|
||||
# options: ['10.11.0.0/24 10.10.0.2', '10.12.0.0/24 10.10.0.2 200']
|
||||
# show_connection(): ['{ ip = 10.11.0.0/24, nh = 10.10.0.2 }', '{ ip = 10.12.0.0/24, nh = 10.10.0.2, mt = 200 }']
|
||||
# Need to convert in order to compare both
|
||||
current_value = [re.sub(r'^{\s*ip\s*=\s*([^, ]+),\s*nh\s*=\s*([^} ]+),\s*mt\s*=\s*([^} ]+)\s*}', r'\1 \2 \3',
|
||||
route) for route in current_value]
|
||||
current_value = [re.sub(r'^{\s*ip\s*=\s*([^, ]+),\s*nh\s*=\s*([^} ]+)\s*}', r'\1 \2', route) for route in current_value]
|
||||
current_value = self.get_route_params(current_value)
|
||||
if key == self.mac_setting:
|
||||
# MAC addresses are case insensitive, nmcli always reports them in uppercase
|
||||
value = value.upper()
|
||||
|
@ -1942,6 +2055,18 @@ def main():
|
|||
gw4=dict(type='str'),
|
||||
gw4_ignore_auto=dict(type='bool', default=False),
|
||||
routes4=dict(type='list', elements='str'),
|
||||
routes4_extended=dict(type='list',
|
||||
elements='dict',
|
||||
options=dict(
|
||||
ip=dict(type='str', required=True),
|
||||
next_hop=dict(type='str'),
|
||||
metric=dict(type='int'),
|
||||
table=dict(type='int'),
|
||||
tos=dict(type='int'),
|
||||
cwnd=dict(type='int'),
|
||||
mtu=dict(type='int'),
|
||||
onlink=dict(type='bool')
|
||||
)),
|
||||
route_metric4=dict(type='int'),
|
||||
routing_rules4=dict(type='list', elements='str'),
|
||||
never_default4=dict(type='bool', default=False),
|
||||
|
@ -1958,6 +2083,17 @@ def main():
|
|||
dns6_search=dict(type='list', elements='str'),
|
||||
dns6_ignore_auto=dict(type='bool', default=False),
|
||||
routes6=dict(type='list', elements='str'),
|
||||
routes6_extended=dict(type='list',
|
||||
elements='dict',
|
||||
options=dict(
|
||||
ip=dict(type='str', required=True),
|
||||
next_hop=dict(type='str'),
|
||||
metric=dict(type='int'),
|
||||
table=dict(type='int'),
|
||||
cwnd=dict(type='int'),
|
||||
mtu=dict(type='int'),
|
||||
onlink=dict(type='bool')
|
||||
)),
|
||||
route_metric6=dict(type='int'),
|
||||
method6=dict(type='str', choices=['ignore', 'auto', 'dhcp', 'link-local', 'manual', 'shared', 'disabled']),
|
||||
ip_privacy6=dict(type='str', choices=['disabled', 'prefer-public-addr', 'prefer-temp-addr', 'unknown']),
|
||||
|
@ -2014,7 +2150,9 @@ def main():
|
|||
gsm=dict(type='dict'),
|
||||
wireguard=dict(type='dict'),
|
||||
),
|
||||
mutually_exclusive=[['never_default4', 'gw4']],
|
||||
mutually_exclusive=[['never_default4', 'gw4'],
|
||||
['routes4_extended', 'routes4'],
|
||||
['routes6_extended', 'routes6']],
|
||||
required_if=[("type", "wifi", [("ssid")])],
|
||||
supports_check_mode=True,
|
||||
)
|
||||
|
|
|
@ -169,6 +169,17 @@ TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE = [
|
|||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
{
|
||||
'type': 'ethernet',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'ifname': 'ethernet_non_existant',
|
||||
'ip6': '2001:beef:cafe:10::1/64',
|
||||
'routes6_extended': [{'ip': 'fd2e:446f:d85d:5::/64',
|
||||
'next_hop': '2001:beef:cafe:10::2'}],
|
||||
'method6': 'manual',
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE_SHOW_OUTPUT = """\
|
||||
|
@ -197,6 +208,14 @@ TESTCASE_ETHERNET_MOD_IPV4_INT_WITH_ROUTE_AND_METRIC = [
|
|||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
{
|
||||
'type': 'ethernet',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'routes4_extended': [{'ip': '192.168.200.0/24', 'next_hop': '192.168.1.1'}],
|
||||
'route_metric4': 10,
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_ETHERNET_MOD_IPV4_INT_WITH_ROUTE_AND_METRIC_SHOW_OUTPUT = """\
|
||||
|
@ -218,6 +237,14 @@ TESTCASE_ETHERNET_MOD_IPV6_INT_WITH_ROUTE_AND_METRIC = [
|
|||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
{
|
||||
'type': 'ethernet',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'routes6_extended': [{'ip': 'fd2e:446f:d85d:5::/64', 'next_hop': '2001:beef:cafe:10::2'}],
|
||||
'route_metric6': 10,
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_ETHERNET_MOD_IPV6_INT_WITH_ROUTE_AND_METRIC_SHOW_OUTPUT = """\
|
||||
|
@ -241,6 +268,17 @@ TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_MULTIPLE_ROUTES = [
|
|||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
{
|
||||
'type': 'ethernet',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'ifname': 'ethernet_non_existant',
|
||||
'ip6': '2001:beef:cafe:10::1/64',
|
||||
'routes6_extended': [{'ip': 'fd2e:446f:d85d:5::/64', 'next_hop': '2001:beef:cafe:10::2'},
|
||||
{'ip': 'fd2e:8890:abcd:25::/64', 'next_hop': '2001:beef:cafe:10::5'}],
|
||||
'method6': 'manual',
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_MULTIPLE_ROUTES_SHOW_OUTPUT = """\
|
||||
|
@ -273,6 +311,18 @@ TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE_AND_METRIC = [
|
|||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
{
|
||||
'type': 'ethernet',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'ifname': 'ethernet_non_existant',
|
||||
'method4': 'disabled',
|
||||
'ip6': '2001:beef:cafe:10::1/64',
|
||||
'routes6_extended': [{'ip': 'fd2e:446f:d85d:5::/64', 'next_hop': '2001:beef:cafe:10::2'}],
|
||||
'route_metric6': 5,
|
||||
'method6': 'manual',
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_ROUTE_AND_METRIC_SHOW_OUTPUT = """\
|
||||
|
@ -305,6 +355,19 @@ TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_MULTIPLE_ROUTES_AND_METRIC = [
|
|||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
{
|
||||
'type': 'ethernet',
|
||||
'conn_name': 'non_existent_nw_device',
|
||||
'ifname': 'ethernet_non_existant',
|
||||
'method4': 'disabled',
|
||||
'ip6': '2001:beef:cafe:10::1/64',
|
||||
'routes6_extended': [{'ip': 'fd2e:446f:d85d:5::/64', 'next_hop': '2001:beef:cafe:10::2'},
|
||||
{'ip': 'fd2e:8890:abcd:25::/64', 'next_hop': '2001:beef:cafe:10::5'}],
|
||||
'route_metric6': 5,
|
||||
'method6': 'manual',
|
||||
'state': 'present',
|
||||
'_ansible_check_mode': False,
|
||||
},
|
||||
]
|
||||
|
||||
TESTCASE_ETHERNET_ADD_IPV6_INT_WITH_MULTIPLE_ROUTES_AND_METRIC_SHOW_OUTPUT = """\
|
||||
|
|
Loading…
Reference in a new issue