Add iosxr_acl_interfaces RM (#66936)

Signed-off-by: NilashishC <nilashishchakraborty8@gmail.com>
pull/67563/head
Nilashish Chakraborty 4 years ago committed by GitHub
parent 493dda94e9
commit e632d93371
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -0,0 +1,88 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The arg spec for the iosxr_acl_interfaces module
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
class Acl_interfacesArgs(object): # pylint: disable=R0903
"""The arg spec for the iosxr_acl_interfaces module
"""
def __init__(self, **kwargs):
pass
argument_spec = {
'running_config': {
'type': 'str'
},
'config': {
'elements': 'dict',
'options': {
'access_groups': {
'elements': 'dict',
'options': {
'acls': {
'elements': 'dict',
'options': {
'direction': {
'choices': ['in', 'out'],
'type': 'str',
'required': True
},
'name': {
'type': 'str',
'required': True
}
},
'type': 'list'
},
'afi': {
'choices': ['ipv4', 'ipv6'],
'type': 'str',
'required': True
}
},
'type': 'list'
},
'name': {
'type': 'str',
'required': True
}
},
'type': 'list'
},
'state': {
'choices': [
'merged', 'replaced', 'overridden', 'deleted', 'gathered',
'parsed', 'rendered'
],
'default':
'merged',
'type':
'str'
}
} # pylint: disable=C0301

@ -0,0 +1,318 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The iosxr_acl_interfaces class
It is in this file where the current configuration (as dict)
is compared to the provided configuration (as dict) and the command set
necessary to bring the current configuration to it's desired end-state is
created
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
from ansible.module_utils.network.common.cfg.base import ConfigBase
from ansible.module_utils.network.iosxr.facts.facts import Facts
from ansible.module_utils.network.iosxr.utils.utils import normalize_interface, diff_list_of_dicts, pad_commands
from ansible.module_utils.network.common.utils \
import (
to_list,
dict_diff,
search_obj_in_list,
remove_empties
)
class Acl_interfaces(ConfigBase):
"""
The iosxr_acl_interfaces class
"""
gather_subset = [
'!all',
'!min',
]
gather_network_resources = [
'acl_interfaces',
]
def __init__(self, module):
super(Acl_interfaces, self).__init__(module)
def get_acl_interfaces_facts(self, data=None):
""" Get the 'facts' (the current configuration)
:rtype: A dictionary
:returns: The current configuration as a dictionary
"""
facts, _warnings = Facts(self._module).get_facts(
self.gather_subset, self.gather_network_resources, data=data)
acl_interfaces_facts = facts['ansible_network_resources'].get(
'acl_interfaces')
if not acl_interfaces_facts:
return []
return acl_interfaces_facts
def execute_module(self):
""" Execute the module
:rtype: A dictionary
:returns: The result from module execution
"""
result = {'changed': False}
warnings = list()
commands = list()
if self.state in self.ACTION_STATES:
existing_acl_interfaces_facts = self.get_acl_interfaces_facts()
else:
existing_acl_interfaces_facts = []
if self.state in self.ACTION_STATES or self.state == "rendered":
commands.extend(self.set_config(existing_acl_interfaces_facts))
if commands and self.state in self.ACTION_STATES:
if not self._module.check_mode:
self._connection.edit_config(commands)
result["changed"] = True
if self.state in self.ACTION_STATES:
result["commands"] = commands
if self.state in self.ACTION_STATES or self.state == "gathered":
changed_acl_interfaces_facts = self.get_acl_interfaces_facts()
elif self.state == "rendered":
result["rendered"] = commands
elif self.state == "parsed":
running_config = self._module.params["running_config"]
if not running_config:
self._module.fail_json(msg="value of running_config parameter must not be empty for state parsed")
result["parsed"] = self.get_acl_interfaces_facts(
data=running_config)
if self.state in self.ACTION_STATES:
result["before"] = existing_acl_interfaces_facts
if result["changed"]:
result["after"] = changed_acl_interfaces_facts
elif self.state == "gathered":
result["gathered"] = changed_acl_interfaces_facts
result["warnings"] = warnings
return result
def set_config(self, existing_acl_interfaces_facts):
""" Collect the configuration from the args passed to the module,
collect the current configuration (as a dict from facts)
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
want = self._module.params['config']
if want:
for item in want:
item['name'] = normalize_interface(item['name'])
if 'members' in want and want['members']:
for item in want['members']:
item.update({
'member':
normalize_interface(item['member']),
'mode':
item['mode']
})
have = existing_acl_interfaces_facts
resp = self.set_state(want, have)
return to_list(resp)
def set_state(self, want, have):
""" Select the appropriate function based on the state provided
:param want: the desired configuration as a dictionary
:param have: the current configuration as a dictionary
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
state = self._module.params['state']
commands = []
if state in ('overridden', 'merged', 'replaced', 'rendered') and not want:
self._module.fail_json(msg='value of config parameter must not be empty for state {0}'.format(state))
if state == 'overridden':
commands.extend(self._state_overridden(want, have))
elif state == 'deleted':
if not want:
for intf in have:
commands.extend(self._state_deleted({}, intf))
else:
for item in want:
obj_in_have = search_obj_in_list(item['name'], have) or {}
commands.extend(
self._state_deleted(remove_empties(item), obj_in_have))
else:
# Instead of passing entire want and have
# list of dictionaries to the respective
# _state_* methods we are passing the want
# and have dictionaries per interface
for item in want:
name = item['name']
obj_in_have = search_obj_in_list(name, have) or {}
if state == 'merged' or state == 'rendered':
commands.extend(self._state_merged(item, obj_in_have))
elif state == 'replaced':
commands.extend(self._state_replaced(item, obj_in_have))
return commands
def _state_replaced(self, want, have):
""" The command generator when state is replaced
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
want = remove_empties(want)
delete_commands = []
for have_afi in have.get('access_groups', []):
want_afi = search_obj_in_list(have_afi['afi'],
want.get('access_groups', []),
key='afi') or {}
afi = have_afi.get('afi')
for acl in have_afi.get('acls', []):
if acl not in want_afi.get('acls', []):
delete_commands.extend(
self._compute_commands(afi, [acl], remove=True))
if delete_commands:
pad_commands(delete_commands, want['name'])
commands.extend(delete_commands)
merged_commands = self._state_merged(want, have)
if merged_commands and delete_commands:
del merged_commands[0]
commands.extend(merged_commands)
return commands
def _state_overridden(self, want, have):
""" The command generator when state is overridden
:rtype: A list
:returns: the commands necessary to migrate the current configuration
to the desired configuration
"""
commands = []
for have_intf in have:
want_intf = search_obj_in_list(have_intf['name'], want) or {}
if not want_intf:
commands.extend(self._state_deleted(want_intf, have_intf))
for want_intf in want:
have_intf = search_obj_in_list(want_intf['name'], have) or {}
commands.extend(self._state_replaced(want_intf, have_intf))
return commands
def _state_merged(self, want, have):
""" The command generator when state is merged
:rtype: A list
:returns: the commands necessary to merge the provided into
the current configuration
"""
commands = []
want = remove_empties(want)
for want_afi in want.get('access_groups', []):
have_afi = search_obj_in_list(want_afi['afi'],
have.get('access_groups', []),
key='afi') or {}
delta = diff_list_of_dicts(want_afi['acls'],
have_afi.get('acls', []),
key='direction')
commands.extend(self._compute_commands(want_afi['afi'], delta))
if commands:
pad_commands(commands, want['name'])
return commands
def _state_deleted(self, want, have):
""" The command generator when state is deleted
:rtype: A list
:returns: the commands necessary to remove the current configuration
of the provided objects
"""
commands = []
# This handles deletion for both empty/no config
# and just interface name provided.
if 'access_groups' not in want:
for x in have.get('access_groups', []):
afi = x.get('afi')
for have_acl in x.get('acls', []):
commands.extend(
self._compute_commands(afi, [have_acl], remove=True))
else:
for want_afi in want['access_groups']:
have_afi = search_obj_in_list(want_afi['afi'],
have.get('access_groups', []),
key='afi') or {}
afi = have_afi.get('afi')
# If only the AFI has be specified, we
# delete all the access-groups for that AFI
if 'acls' not in want_afi:
for have_acl in have_afi.get('acls', []):
commands.extend(
self._compute_commands(afi, [have_acl],
remove=True))
# If one or more acl has been explicitly specified, we
# delete that and leave the rest untouched
else:
for acl in want_afi['acls']:
if acl in have_afi.get('acls', []):
commands.extend(
self._compute_commands(afi, [acl],
remove=True))
if commands:
pad_commands(commands, have['name'])
return commands
def _compute_commands(self, afi, delta, remove=False):
updates = []
map_dir = {'in': 'ingress', 'out': 'egress'}
for x in delta:
cmd = "{0} access-group {1} {2}".format(afi, x['name'],
map_dir[x['direction']])
if remove:
cmd = "no " + cmd
updates.append(cmd)
return updates

@ -0,0 +1,104 @@
#
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
"""
The iosxr acl_interfaces fact class
It is in this file the configuration is collected from the device
for a given resource, parsed, and the facts tree is populated
based on the configuration.
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
import re
from copy import deepcopy
from ansible.module_utils.six import iteritems
from ansible.module_utils.network.common import utils
from ansible.module_utils.network.iosxr.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
class Acl_interfacesFacts(object):
""" The iosxr acl_interfaces fact class
"""
def __init__(self, module, subspec='config', options='options'):
self._module = module
self.argument_spec = Acl_interfacesArgs.argument_spec
spec = deepcopy(self.argument_spec)
if subspec:
if options:
facts_argument_spec = spec[subspec][options]
else:
facts_argument_spec = spec[subspec]
else:
facts_argument_spec = spec
self.generated_spec = utils.generate_dict(facts_argument_spec)
def populate_facts(self, connection, ansible_facts, data=None):
""" Populate the facts for acl_interfaces
:param connection: the device connection
:param ansible_facts: Facts dictionary
:param data: previously collected conf
:rtype: dictionary
:returns: facts
"""
if not data:
data = connection.get_config(flags='interface')
interfaces = data.split('interface ')
objs = []
for interface in interfaces:
obj = self.render_config(self.generated_spec, interface)
if obj:
objs.append(obj)
ansible_facts['ansible_network_resources'].pop('acl_interfaces', None)
facts = {}
facts['acl_interfaces'] = []
params = utils.validate_config(self.argument_spec, {'config': objs})
for cfg in params['config']:
facts['acl_interfaces'].append(utils.remove_empties(cfg))
ansible_facts['ansible_network_resources'].update(facts)
return ansible_facts
def render_config(self, spec, conf):
"""
Render config as dictionary structure and delete keys
from spec for null values
:param spec: The facts tree, generated from the argspec
:param conf: The configuration
:rtype: dictionary
:returns: The generated config
"""
config = deepcopy(spec)
config['access_groups'] = []
map_dir = {'ingress': 'in', 'egress': 'out'}
match = re.search(r'(?:preconfigure)*(?:\s*)(\S+)', conf, re.M)
if match:
config['name'] = match.group(1)
acls = {'ipv4': [], 'ipv6': []}
for item in conf.split('\n'):
item = item.strip()
if item.startswith('ipv4 access-group'):
acls['ipv4'].append(item)
elif item.startswith('ipv6 access-group'):
acls['ipv6'].append(item)
for key, value in iteritems(acls):
if value:
entry = {'afi': key, 'acls': []}
for item in value:
entry['acls'].append({'name': item.split()[2], 'direction': map_dir[item.split()[3]]})
config['access_groups'].append(entry)
config['access_groups'] = sorted(config['access_groups'], key=lambda i: i['afi'])
return utils.remove_empties(config)

@ -23,7 +23,7 @@ from ansible.module_utils.network.iosxr.facts.interfaces.interfaces import Inter
from ansible.module_utils.network.iosxr.facts.lag_interfaces.lag_interfaces import Lag_interfacesFacts
from ansible.module_utils.network.iosxr.facts.l2_interfaces.l2_interfaces import L2_InterfacesFacts
from ansible.module_utils.network.iosxr.facts.l3_interfaces.l3_interfaces import L3_InterfacesFacts
from ansible.module_utils.network.iosxr.facts.acl_interfaces.acl_interfaces import Acl_interfacesFacts
FACT_LEGACY_SUBSETS = dict(
default=Default,
@ -39,7 +39,8 @@ FACT_RESOURCE_SUBSETS = dict(
interfaces=InterfacesFacts,
l2_interfaces=L2_InterfacesFacts,
lag_interfaces=Lag_interfacesFacts,
l3_interfaces=L3_InterfacesFacts
l3_interfaces=L3_InterfacesFacts,
acl_interfaces=Acl_interfacesFacts
)

@ -98,7 +98,10 @@ def filter_dict_having_none_value(want, have):
test_dict.update({'ipv4': test_key_dict})
# Checks if want doesn't have secondary IP but have has secondary IP set
elif have.get('ipv4'):
if [True for each_have in have.get('ipv4') if 'secondary' in each_have]:
if [
True for each_have in have.get('ipv4')
if 'secondary' in each_have
]:
test_dict.update({'ipv4': {'secondary': True}})
if k == 'l2protocol':
if want[k] != have.get('l2protocol') and have.get('l2protocol'):
@ -167,7 +170,7 @@ def pad_commands(commands, interface):
commands.insert(0, 'interface {0}'.format(interface))
def diff_list_of_dicts(w, h):
def diff_list_of_dicts(w, h, key='member'):
"""
Returns a list containing diff between
two list of dictionaries
@ -179,11 +182,11 @@ def diff_list_of_dicts(w, h):
diff = []
for w_item in w:
h_item = search_obj_in_list(w_item['member'], h, key='member') or {}
h_item = search_obj_in_list(w_item[key], h, key=key) or {}
d = dict_diff(h_item, w_item)
if d:
if 'member' not in d.keys():
d['member'] = w_item['member']
if key not in d.keys():
d[key] = w_item[key]
diff.append(d)
return diff
@ -196,7 +199,9 @@ def validate_ipv4(value, module):
module.fail_json(msg='address format is <ipv4 address>/<mask>, got invalid format {0}'.format(value))
if not is_masklen(address[1]):
module.fail_json(msg='invalid value for mask: {0}, mask should be in range 0-32'.format(address[1]))
module.fail_json(
msg='invalid value for mask: {0}, mask should be in range 0-32'
.format(address[1]))
def validate_ipv6(value, module):

@ -0,0 +1,743 @@
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright 2019 Red Hat
# GNU General Public License v3.0+
# (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
#############################################
# WARNING #
#############################################
#
# This file is auto generated by the resource
# module builder playbook.
#
# Do not edit this file manually.
#
# Changes to this file will be over written
# by the resource module builder.
#
# Changes should be made in the model used to
# generate this file or in the resource module
# builder template.
#
#############################################
"""
The module file for iosxr_acl_interfaces
"""
from __future__ import absolute_import, division, print_function
__metaclass__ = type
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'network'
}
DOCUMENTATION = """
---
module: iosxr_acl_interfaces
version_added: "2.10"
short_description: Manage Access Control Lists (ACLs) configuration for interfaces in IOS-XR.
description:
- This module manages adding and removing Access Control Lists (ACLs) from interfaces on devices running IOS-XR software.
author: Nilashish Chakraborty (@NilashishC)
options:
config:
description: A dictionary of ACL options for interfaces.
type: list
elements: dict
suboptions:
name:
description:
- Name/Identifier for the interface
type: str
required: True
access_groups:
type: list
elements: dict
description:
- Specifies ACLs attached to the interfaces.
suboptions:
afi:
description:
- Specifies the AFI for the ACL(s) to be configured on this interface.
type: str
choices: ['ipv4', 'ipv6']
required: True
acls:
type: list
description:
- Specifies the ACLs for the provided AFI.
elements: dict
suboptions:
name:
description:
- Specifies the name of the IPv4/IPv6 ACL for the interface.
type: str
required: True
direction:
description:
- Specifies the direction of packets that the ACL will be applied on.
type: str
choices: ['in', 'out']
required: True
running_config:
description:
- The module, by default, will connect to the remote device and
retrieve the current running-config to use as a base for comparing
against the contents of source. There are times when it is not
desirable to have the task get the current running-config for
every task in a playbook. The I(running_config) argument allows the
implementer to pass in the configuration to use as the base
config for comparison. This value of this option should be the
output received from device by executing command
B(show running-config interface).
type: str
state:
description:
- The state the configuration should be left in.
type: str
choices:
- merged
- replaced
- overridden
- deleted
- gathered
- parsed
- rendered
default: merged
"""
EXAMPLES = """
# Using merged
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:22:32.911 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# !
- name: Merge the provided configuration with the existing running configuration
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
- afi: ipv6
acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: out
state: merged
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:27:49.378 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
# Using merged to update interface ACL configuration
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:27:49.378 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
- name: Update acl_interfaces configuration using merged
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_2
direction: out
- name: acl_1
direction: in
state: merged
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:27:49.378 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# !
#
# Using replaced
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
- name: Replace device configurations of listed interface with provided configurations
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
acls:
- name: acl6_3
direction: in
state: replaced
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv6 access-group acl6_3 ingress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
# Using overridden
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
- name: Overridde all interface ACL configuration with provided configuration
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_2
direction: in
- afi: ipv6
acls:
- name: acl6_3
direction: out
state: overridden
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_2 ingress
# ipv6 access-group acl6_3 egress
# !
#
# Using 'deleted' to delete all ACL attributes of a single interface
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
- name: Delete all ACL attributes of GigabitEthernet0/0/0/1
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/1
state: deleted
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# !
#
# Using 'deleted' to delete a single attached ACL from an interface
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
- name: Delete a single ACL attached to GigabitEthernet0/0/0/0
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_2
direction: out
state: deleted
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# !
#
# Using 'deleted' to delete all ACLs of a particular AFI from an interface
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
state: deleted
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# !
#
# Using 'deleted' to remove all ACLs attached to all the interfaces in the device
# Before state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
#
- name: Delete all ACL interfaces configuration from the device
iosxr_acl_interfaces:
state: deleted
# After state:
# -------------
#
# RP/0/RP0/CPU0:ios#sh running-config interface
# Wed Jan 15 12:34:56.689 UTC
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# !
#
# Using parsed
# parsed.cfg
# ------------
#
# interface MgmtEth0/RP0/CPU0/0
# ipv4 address dhcp
# !
# interface GigabitEthernet0/0/0/0
# shutdown
# ipv4 access-group acl_1 ingress
# ipv4 access-group acl_2 egress
# ipv6 access-group acl6_1 ingress
# ipv6 access-group acl6_2 egress
# !
# interface GigabitEthernet0/0/0/1
# shutdown
# ipv4 access-group acl_1 egress
# !
# - name: Convert ACL interfaces config to argspec without connecting to the appliance
# iosxr_acl_interfaces:
# running_config: "{{ lookup('file', './parsed.cfg') }}"
# state: parsed
# Task Output (redacted)
# -----------------------
# "parsed": [
# {
# "name": "MgmtEth0/RP0/CPU0/0"
# },
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "in",
# "name": "acl_1"
# },
# {
# "direction": "out",
# "name": "acl_2"
# }
# ],
# "afi": "ipv4"
# },
# {
# "acls": [
# {
# "direction": "in",
# "name": "acl6_1"
# },
# {
# "direction": "out",
# "name": "acl6_2"
# }
# ],
# "afi": "ipv6"
# }
# ],
# "name": "GigabitEthernet0/0/0/0"
# },
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "out",
# "name": "acl_1"
# }
# ],
# "afi": "ipv4"
# }
# ],
# "name": "GigabitEthernet0/0/0/1"
# }
# ]
# }
# Using gathered
- name: Gather ACL interfaces facts using gathered state
iosxr_acl_interfaces:
state: gathered
# Task Output (redacted)
# -----------------------
#
# "gathered": [
# {
# "name": "MgmtEth0/RP0/CPU0/0"
# },
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "in",
# "name": "acl_1"
# },
# {
# "direction": "out",
# "name": "acl_2"
# }
# ],
# "afi": "ipv4"
# }
# "name": "GigabitEthernet0/0/0/0"
# },
# {
# "access_groups": [
# {
# "acls": [
# {
# "direction": "in",
# "name": "acl6_1"
# }
# ],
# "afi": "ipv6"
# }
# "name": "GigabitEthernet0/0/0/1"
# }
# ]
# Using rendered
- name: Render platform specific commands from task input using rendered state
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
state: rendered
# Task Output (redacted)
# -----------------------
# "rendered": [
# "interface GigabitEthernet0/0/0/0",
# "ipv4 access-group acl_1 ingress",
# "ipv4 access-group acl_2 egress"
# ]
"""
RETURN = """
before:
description: The configuration prior to the model invocation.
returned: always
type: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
after:
description: The resulting configuration model invocation.
returned: when changed
type: list
sample: >
The configuration returned will always be in the same format
of the parameters above.
commands:
description: The set of commands pushed to the remote device.
returned: always
type: list
sample:
- "interface GigabitEthernet0/0/0/1"
- "ipv4 access-group acl_1 ingress"
- "ipv4 access-group acl_2 egress"
- "ipv6 access-group acl6_1 ingress"
- "interface GigabitEthernet0/0/0/2"
- "no ipv4 access-group acl_3 ingress"
- "ipv4 access-group acl_4 egress"
"""
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.network.iosxr.argspec.acl_interfaces.acl_interfaces import Acl_interfacesArgs
from ansible.module_utils.network.iosxr.config.acl_interfaces.acl_interfaces import Acl_interfaces
def main():
"""
Main entry point for module execution
:returns: the result form module invocation
"""
required_if = [('state', 'merged', ('config',)),
('state', 'replaced', ('config',)),
('state', 'overridden', ('config',)),
('state', 'rendered', ('config',)),
('state', 'parsed', ('running_config',))]
module = AnsibleModule(argument_spec=Acl_interfacesArgs.argument_spec, required_if=required_if,
supports_check_mode=True)
result = Acl_interfaces(module).execute_module()
module.exit_json(**result)
if __name__ == '__main__':
main()

@ -0,0 +1,3 @@
---
testcase: "[^_].*"
test_items: []

@ -0,0 +1,20 @@
---
- name: Collect all cli test cases
find:
paths: "{{ role_path }}/tests/cli"
patterns: "{{ testcase }}.yaml"
use_regex: true
register: test_cases
delegate_to: localhost
- name: Set test_items
set_fact: test_items="{{ test_cases.files | map(attribute='path') | list }}"
delegate_to: localhost
- name: Run test case (connection=network_cli)
include: "{{ test_case_to_run }}"
vars:
ansible_connection: network_cli
with_items: "{{ test_items }}"
loop_control:
loop_var: test_case_to_run

@ -0,0 +1,2 @@
---
- { include: cli.yaml, tags: ['cli'] }

@ -0,0 +1,31 @@
---
- name: Populate the device with ACLs
iosxr_config:
lines: |
ipv4 access-list acl_1
10 permit ipv4 any any
ipv4 access-list acl_2
10 permit ipv4 any any
ipv4 access-list acl_3
10 permit ipv4 any any
ipv6 access-list acl6_1
10 permit ipv6 any any
ipv6 access-list acl6_2
10 permit ipv6 any any
ipv6 access-list acl6_3
10 permit ipv6 any any
- name: Setup ACL interfaces configuration for GigabitEthernet0/0/0/0
iosxr_config:
lines: |
ipv4 access-group acl_1 ingress
ipv4 access-group acl_2 egress
ipv6 access-group acl6_1 ingress
ipv6 access-group acl6_2 egress
parents: interface GigabitEthernet0/0/0/0
- name: Setup ACL interfaces configuration for GigabitEthernet0/0/0/1
iosxr_config:
lines: ipv4 access-group acl_1 egress
parents: interface GigabitEthernet0/0/0/1

@ -0,0 +1,34 @@
---
- name: Remove/Default Resources
cli_config:
config: "{{ lines }}"
vars:
lines: |
default interface GigabitEthernet0/0/0/0
default interface GigabitEthernet0/0/0/1
no ipv4 access-list acl_1
no ipv4 access-list acl_2
no ipv6 access-list acl6_1
no ipv6 access-list acl6_2
no ipv6 access-list acl6_3
- name: Initialize interfaces
iosxr_config:
lines: shutdown
parents: "{{ item }}"
loop:
- interface GigabitEthernet0/0/0/0
- interface GigabitEthernet0/0/0/1
# To make sure our assertions are not affected by
# spill overs from previous tests
- name: Remove unwanted interfaces from config
iosxr_config:
lines:
- "no interface GigabitEthernet{{ item }}"
loop:
- 0/0/0/2
- 0/0/0/3
- 0/0/0/4
- 0/0/0/5
ignore_errors: yes

@ -0,0 +1,85 @@
---
- debug:
msg: "Start iosxr_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Delete ACL attributes of GigabitEthernet0/0/0/1
iosxr_acl_interfaces: &deleted
config:
- name: GigabitEthernet0/0/0/1
state: deleted
register: result
- assert:
that:
- "'interface GigabitEthernet0/0/0/1' in result.commands"
- "'no ipv4 access-group acl_1 egress' in result.commands"
- "result.commands|length == 2"
- name: Delete ACL attributes of GigabitEthernet0/0/0/1 (IDEMPOTENT)
iosxr_acl_interfaces: *deleted
register: result
- assert:
that:
- "result.changed == False"
- "result.commands|length == 0"
- name: Delete a single ACL attached to GigabitEthernet0/0/0/0
iosxr_acl_interfaces: &deleted_1
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_2
direction: out
state: deleted
register: result
- assert:
that:
- "'interface GigabitEthernet0/0/0/0' in result.commands"
- "'no ipv4 access-group acl_2 egress' in result.commands"
- "result.commands|length == 2"
- name: Delete a single ACL attached to GigabitEthernet0/0/0/0 (IDEMPOTENT)
iosxr_acl_interfaces: *deleted_1
register: result
- assert:
that:
- "result.changed == False"
- "result.commands|length == 0"
- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0
iosxr_acl_interfaces: &deleted_2
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
state: deleted
register: result
- assert:
that:
- "'interface GigabitEthernet0/0/0/0' in result.commands"
- "'no ipv6 access-group acl6_1 ingress' in result.commands"
- "'no ipv6 access-group acl6_2 egress' in result.commands"
- "result.commands|length == 3"
- name: Delete all IPv6 ACLs attached to GigabitEthernet0/0/0/0 (IDEMPOTENT)
iosxr_acl_interfaces: *deleted_2
register: result
- assert:
that:
- "result.changed == False"
- "result.commands|length == 0"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,46 @@
---
- debug:
msg: "Start iosxr_acl_interfaces deleted integration tests ansible_connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Delete all ACL interfaces configuration from the device
iosxr_acl_interfaces: &deleted_3
state: deleted
register: result
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that the correct set of commands were generated
assert:
that:
- "{{ deleted['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that the after dicts were correctly generated
assert:
that:
- "{{ deleted['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Delete ACL attributes of all interfaces (IDEMPOTENT)
iosxr_acl_interfaces: *deleted_3
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result.changed == false"
- "result.commands|length == 0"
- name: Assert that the before dicts were correctly generated
assert:
that:
- "{{ deleted['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,58 @@
---
- debug:
msg: "START iosxr_acl_interfaces empty_config integration tests on connection={{ ansible_connection }}"
- name: Merged with empty config should give appropriate error message
iosxr_acl_interfaces:
config:
state: merged
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state merged'
- name: Replaced with empty config should give appropriate error message
iosxr_acl_interfaces:
config:
state: replaced
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state replaced'
- name: Overridden with empty config should give appropriate error message
iosxr_acl_interfaces:
config:
state: overridden
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state overridden'
- name: Rendered with empty config should give appropriate error message
iosxr_acl_interfaces:
config:
state: rendered
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of config parameter must not be empty for state rendered'
- name: Parsed with empty config should give appropriate error message
iosxr_acl_interfaces:
running_config:
state: parsed
register: result
ignore_errors: True
- assert:
that:
- result.msg == 'value of running_config parameter must not be empty for state parsed'

@ -0,0 +1,14 @@
interface MgmtEth0/0/CPU0/0
ipv4 address dhcp
!
interface GigabitEthernet0/0/0/0
shutdown
ipv4 access-group acl_1 ingress
ipv4 access-group acl_2 egress
ipv6 access-group acl6_1 ingress
ipv6 access-group acl6_2 egress
!
interface GigabitEthernet0/0/0/1
shutdown
ipv4 access-group acl_1 egress
!

@ -0,0 +1,57 @@
---
- debug:
msg: "START iosxr_acl_interfaces gathered integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- name: Populate the device with ACLs
iosxr_config:
lines: |
ipv4 access-list acl_1
10 permit ipv4 any any
ipv4 access-list acl_2
10 permit ipv4 any any
ipv6 access-list acl6_1
10 permit ipv6 any any
ipv6 access-list acl6_2
10 permit ipv6 any any
- block:
- name: Merge the provided configuration with the existing running configuration
iosxr_acl_interfaces: &merged
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
- afi: ipv6
acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: out
state: merged
register: result
- name: Gather ACL interfaces facts using gathered state
iosxr_acl_interfaces:
state: gathered
register: result
- name: Assert that facts were correctly generated
assert:
that: "{{ merged['after'] | symmetric_difference(result['gathered']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,115 @@
---
- debug:
msg: "START iosxr_acl_interfaces merged integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- name: Populate the device with ACLs
iosxr_config:
lines: |
ipv4 access-list acl_1
10 permit ipv4 any any
ipv4 access-list acl_2
10 permit ipv4 any any
ipv6 access-list acl6_1
10 permit ipv6 any any
ipv6 access-list acl6_2
10 permit ipv6 any any
- block:
- name: Merge the provided configuration with the existing running configuration
iosxr_acl_interfaces: &merged
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
- afi: ipv6
acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: out
state: merged
register: result
- name: Assert that before dicts were correctly generated
assert:
that: "{{ merged['before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that after dicts was correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Merge the provided configuration with the existing running configuration (IDEMPOTENT)
iosxr_acl_interfaces: *merged
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
- name: Assert that before dicts were correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Update acl_interfaces configuration using merged
iosxr_acl_interfaces: &merged_update
config:
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_2
direction: out
- name: acl_1
direction: in
state: merged
register: result
- name: Assert that before dicts were correctly generated
assert:
that: "{{ merged['update_before'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ merged['update_commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that after dicts was correctly generated
assert:
that:
- "{{ merged['update_after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Update acl_interfaces configuration using merged (IDEMPOTENT)
iosxr_acl_interfaces: *merged_update
register: result
- name: Assert that the previous task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,64 @@
---
- debug:
msg: "START iosxr_acl_interfaces overridden integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Overridde all interface ACL configuration with provided configuration
iosxr_acl_interfaces: &overridden
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
acls:
- name: acl6_3
direction: in
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_2
direction: in
- afi: ipv6
acls:
- name: acl6_3
direction: out
state: overridden
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ overridden['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Overridde all interface LACP configuration with provided configuration (IDEMPOTENT)
iosxr_acl_interfaces: *overridden
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
- name: Assert that before dict is correctly generated
assert:
that:
- "{{ overridden['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,14 @@
---
- debug:
msg: "START iosxr_acl_interfaces parsed integration tests on connection={{ ansible_connection }}"
- name: Parse externally provided ACL interfaces config to agnostic model
iosxr_acl_interfaces:
running_config: "{{ lookup('file', './fixtures/parsed.cfg') }}"
state: parsed
register: result
- name: Assert that config was correctly parsed
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['parsed']) |length == 0 }}"

@ -0,0 +1,35 @@
---
- debug:
msg: "START iosxr_acl_interfaces rendered integration tests on connection={{ ansible_connection }}"
- name: Render platform specific commands from task input using rendered state
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
- afi: ipv6
acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: out
state: rendered
register: result
- name: Assert that correct set of commands were rendered
assert:
that:
- "{{ merged['commands'] | symmetric_difference(result['rendered']) |length == 0 }}"

@ -0,0 +1,53 @@
---
- debug:
msg: "START iosxr_acl_interfaces replaced integration tests on connection={{ ansible_connection }}"
- include_tasks: _remove_config.yaml
- include_tasks: _populate.yaml
- block:
- name: Replace device configurations of listed interface with provided configurations
iosxr_acl_interfaces: &replaced
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
acls:
- name: acl6_3
direction: in
state: replaced
register: result
- name: Assert that correct set of commands were generated
assert:
that:
- "{{ replaced['commands'] | symmetric_difference(result['commands']) |length == 0 }}"
- name: Assert that before dicts are correctly generated
assert:
that:
- "{{ merged['after'] | symmetric_difference(result['before']) |length == 0 }}"
- name: Assert that after dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Replace device configurations of listed interfaces with provided configurarions (IDEMPOTENT)
iosxr_acl_interfaces: *replaced
register: result
- name: Assert that task was idempotent
assert:
that:
- "result['changed'] == false"
- "result.commands|length == 0"
- name: Assert that before dict is correctly generated
assert:
that:
- "{{ replaced['after'] | symmetric_difference(result['before']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,102 @@
---
- debug:
msg: "START iosxr_acl_interfaces round trip integration tests on connection={{ ansible_connection }}"
- block:
- include_tasks: _remove_config.yaml
- name: Populate the device with ACLs
iosxr_config:
lines: |
ipv4 access-list acl_1
10 permit ipv4 any any
ipv4 access-list acl_2
10 permit ipv4 any any
ipv4 access-list acl_3
10 permit ipv4 any any
ipv6 access-list acl6_1
10 permit ipv6 any any
ipv6 access-list acl6_2
10 permit ipv6 any any
ipv6 access-list acl6_3
10 permit ipv6 any any
- name: Apply the provided configuration (base config)
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
- afi: ipv6
acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: out
state: merged
- name: Gather interfaces facts
iosxr_facts:
gather_subset:
- "!all"
- "!min"
gather_network_resources:
- acl_interfaces
- name: Apply the provided configuration (config to be reverted)
iosxr_acl_interfaces:
config:
- name: GigabitEthernet0/0/0/1
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
- afi: ipv6
acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv4
acls:
- name: acl_1
direction: out
state: overridden
register: result
- name: Assert that changes were applied
assert:
that: "{{ round_trip['after'] | symmetric_difference(result['after']) |length == 0 }}"
- name: Revert back to base config using facts round trip
iosxr_acl_interfaces:
config: "{{ ansible_facts['network_resources']['acl_interfaces'] }}"
state: overridden
register: revert
- name: Assert that config was reverted
assert:
that: "{{ merged['after'] | symmetric_difference(revert['after']) |length == 0 }}"
always:
- include_tasks: _remove_config.yaml

@ -0,0 +1,205 @@
---
merged:
before:
- name: MgmtEth0/0/CPU0/0
- name: GigabitEthernet0/0/0/0
- name: GigabitEthernet0/0/0/1
commands:
- interface GigabitEthernet0/0/0/0
- ipv4 access-group acl_1 ingress
- ipv4 access-group acl_2 egress
- ipv6 access-group acl6_1 ingress
- ipv6 access-group acl6_2 egress
- interface GigabitEthernet0/0/0/1
- ipv4 access-group acl_1 egress
update_commands:
- interface GigabitEthernet0/0/0/1
- ipv4 access-group acl_2 egress
- ipv4 access-group acl_1 ingress
after:
- name: MgmtEth0/0/CPU0/0
- name: GigabitEthernet0/0/0/0
access_groups:
- acls:
- name: acl_1
direction: in
- name: acl_2
direction: out
afi: ipv4
- acls:
- name: acl6_1
direction: in
- name: acl6_2
direction: out
afi: ipv6
- name: GigabitEthernet0/0/0/1
access_groups:
- acls:
- name: acl_1
direction: out
afi: ipv4
update_before:
- name: MgmtEth0/0/CPU0/0
- access_groups:
- acls:
- direction: in
name: acl_1
- direction: out
name: acl_2
afi: ipv4
- acls:
- direction: in
name: acl6_1
- direction: out
name: acl6_2
afi: ipv6
name: GigabitEthernet0/0/0/0
- access_groups:
- acls:
- direction: out
name: acl_1
afi: ipv4
name: GigabitEthernet0/0/0/1
update_after:
- name: MgmtEth0/0/CPU0/0
- access_groups:
- acls:
- direction: in
name: acl_1
- direction: out
name: acl_2
afi: ipv4
- acls:
- direction: in
name: acl6_1
- direction: out
name: acl6_2
afi: ipv6
name: GigabitEthernet0/0/0/0
- access_groups:
- acls:
- direction: in
name: acl_1
- direction: out
name: acl_2
afi: ipv4
name: GigabitEthernet0/0/0/1
replaced:
commands:
- interface GigabitEthernet0/0/0/0
- no ipv4 access-group acl_1 ingress
- no ipv4 access-group acl_2 egress
- no ipv6 access-group acl6_1 ingress
- no ipv6 access-group acl6_2 egress
- ipv6 access-group acl6_3 ingress
after:
- name: MgmtEth0/0/CPU0/0
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
acls:
- name: acl6_3
direction: in
- name: GigabitEthernet0/0/0/1
access_groups:
- acls:
- name: acl_1
direction: out
afi: ipv4
overridden:
commands:
- interface GigabitEthernet0/0/0/0
- no ipv4 access-group acl_1 ingress
- no ipv4 access-group acl_2 egress
- no ipv6 access-group acl6_1 ingress
- no ipv6 access-group acl6_2 egress
- ipv6 access-group acl6_3 ingress
- interface GigabitEthernet0/0/0/1
- no ipv4 access-group acl_1 egress
- ipv4 access-group acl_2 ingress
- ipv6 access-group acl6_3 egress
after:
- name: MgmtEth0/0/CPU0/0
- name: GigabitEthernet0/0/0/0
access_groups:
- afi: ipv6
acls:
- name: acl6_3
direction: in
- name: GigabitEthernet0/0/0/1
access_groups:
- acls:
- name: acl_2
direction: in
afi: ipv4
- acls:
- name: acl6_3
direction: out
afi: ipv6
deleted:
commands:
- interface GigabitEthernet0/0/0/0
- no ipv4 access-group acl_1 ingress
- no ipv4 access-group acl_2 egress
- no ipv6 access-group acl6_1 ingress
- no ipv6 access-group acl6_2 egress
- interface GigabitEthernet0/0/0/1
- no ipv4 access-group acl_1 egress
after:
- name: MgmtEth0/0/CPU0/0
- name: GigabitEthernet0/0/0/0
- name: GigabitEthernet0/0/0/1
round_trip:
after:
- name: MgmtEth0/0/CPU0/0
- access_groups:
- acls:
- direction: out
name: acl_1
afi: ipv4
name: GigabitEthernet0/0/0/0
- access_groups:
- acls:
- direction: in
name: acl_1
- direction: out
name: acl_2
afi: ipv4
- acls:
- direction: in
name: acl6_1
- direction: out
name: acl6_2
afi: ipv6
name: GigabitEthernet0/0/0/1
Loading…
Cancel
Save