diff --git a/napalm_base/indent_differ.py b/napalm_base/indent_differ.py new file mode 100644 index 00000000..8c0ad118 --- /dev/null +++ b/napalm_base/indent_differ.py @@ -0,0 +1,193 @@ +from collections import OrderedDict + +from napalm_base.utils import py23_compat + +import re + + +def parse_indented_config(config, current_indent=0, previous_indent=0, nested=False): + """ + This methid basically reads a configuration that conforms to a very poor industry standard + and returns a nested structure that behaves like a dict. For example: + + {'enable password whatever': {}, + 'interface GigabitEthernet1': { + 'description "bleh"': {}, + 'fake nested': { + 'nested nested configuration': {}}, + 'switchport mode trunk': {}}, + 'interface GigabitEthernet2': { + 'no ip address': {}}, + 'interface GigabitEthernet3': { + 'negotiation auto': {}, + 'no ip address': {}, + 'shutdown': {}}, + 'interface Loopback0': { + 'description "blah"': {}}} + """ + parsed = IndentedConfig() + while True: + if not config: + break + line = config.pop(0) + last = line.lstrip() + leading_spaces = len(line) - len(last) + + # print("current_indent:{}, previous:{}, leading:{} - {}".format( + # current_indent, previous_indent, leading_spaces, line)) + + if leading_spaces > current_indent: + parsed[last] = parse_indented_config(config, leading_spaces, current_indent, True) + elif leading_spaces < current_indent: + config.insert(0, line) + break + else: + if not nested: + parsed[last] = parse_indented_config(config, leading_spaces, current_indent, True) + else: + config.insert(0, line) + break + + return parsed + + +def _can_have_multiple(command): + """ + This method returns true if a command can have multiple instances of itself. + For example; interface Fa0, interface Fa1, neighor 1.1.1.1, neighbor 1.1.1.2, etc. + + It is important this is up to date or the diff might fail to detect changes properly. + """ + EXACT_MATCHES = [ + "interface", + "router", + "access-list", + "policy-map", + "ip prefix", + "ipv6 prefix", + "neighbor", + "ip address", + "ipv6 address", + "snmp-server enable traps", + "vlan", + ] + return any([command.startswith(e) for e in EXACT_MATCHES]) + + +def _expand(d, action, indent): + """ + Returns a list of (action, subcommand) + """ + result = [] + for k, v in d.items(): + k = "{}{}".format(" " * indent * 2, k) + result.append((action, k)) + result += _expand(v, action, indent+1) + return result + + +def merge(running, candidate, negators, indent=0): + """ + This method reads a running and a candidate config and returns a list of + (action, command) that are needed to converge. + """ + result = [] + for command, subcommands in candidate.items(): + if any([command.startswith(n) for n in negators]): + ncmd = " ".join(command.split(" ")[1:]) + remove = running.find(ncmd) + for r in remove: + result.append(("remove", "{}{}".format(" " * indent * 2, r))) + result += _expand(running[r], "remove", indent+1) + elif command in running: + r = merge(running[command], subcommands, negators, indent+1) + if r: + result.append(("change", command)) + result += r + elif command not in running: + result.append(("add", "{}{}".format(" " * indent * 2, command))) + result += _expand(subcommands, "add", indent+1) + + remove = running.find(command) + for r in remove: + result.append(("remove", "{}{}".format(" " * indent * 2, r))) + result += _expand(running[r], "remove", indent+1) + + return result + + +class IndentedConfig(object): + + def __init__(self, config=None, comments="!", negators=["no", "default"]): + self.config = config if config is not None else "" + + if self.config: + # let's get rid of empty lines and comments + lines_no_blanks = [line for line in self.config.splitlines() + if line.strip() and not line.startswith(comments)] + self.parsed = parse_indented_config(lines_no_blanks) + else: + self.parsed = OrderedDict() + self.negators = negators + + def items(self): + return self.parsed.items() + + def keys(self): + return self.parsed.keys() + + def values(self): + return self.parsed.values() + + def __setitem__(self, item, value): + self.parsed.__setitem__(item, value) + + def __getitem__(self, item): + return self.parsed.__getitem__(item) + + def __contains__(self, key): + return key in self.parsed + + def to_dict(self): + result = {} + for k, v in self.items(): + if v: + result[k] = v.to_dict() + else: + result[k] = OrderedDict() + return result + + def find(self, command): + """ + Find commands in self that look like command. + + This method relies on _can_have_multiple. When working properly it lets you do things like: + + 1. If you do `switchport mode trunk` and try to find it with _can_have_multiple + returning False, you would be able to match on `switchport mode access` as well, + which will allow you to realize you are changing one by the other. + 2. Similarly as above, you can run `switchport trunk vlan 1,2,3,5` and realize you + are changing the existing command `switchport trunk vlan 1,2`. + 3. You can also do `no neighbor 1.1.1.1` and match if _can_have_multiple is True, all + the commands you may have like `neighbor 1.1.1.1 remote-as 12345`, + `neighbor 1.1.1.1 route-map blah in` while not matching at all other neighbors. + """ + if not _can_have_multiple(command): + cmd = " ".join(command.split(" ")[0:-1]) + command = cmd if cmd else command + regex = re.compile("^{}.*".format(command)) + return [c for c in self.keys() if regex.match(c)] + + def diff(self, candidate): + """ + Returns the diff of the configuration when applying the candidate on top of self. + """ + if isinstance(candidate, py23_compat.string_types): + candidate = IndentedConfig(candidate) + result = merge(self, candidate, self.negators) + m = { + "remove": "-", + "add": "+", + "change": " ", + } + return "\n".join(["{} {}".format(m[r[0]], r[1]) for r in result]) diff --git a/test/unit/test_indent_differ.py b/test/unit/test_indent_differ.py new file mode 100644 index 00000000..08ae0426 --- /dev/null +++ b/test/unit/test_indent_differ.py @@ -0,0 +1,82 @@ +from napalm_base import get_network_driver +from napalm_base import indent_differ + +from glob import glob +import pytest + +import os + + +BASE_PATH = os.path.dirname(__file__) + + +driver = get_network_driver("mock") + + +test_cases_differ = [x.split('/')[-1] for x in glob('{}/test_indent_differ/*'.format(BASE_PATH))] + + +class Test_Indent_differ(object): + """Test Mock Driver.""" + + def test_parser(self): + candidate = ''' +enable password whatever + +interface Loopback0 + description "blah" +interface GigabitEthernet1 + description "bleh" + + fake nested + nested nested configuration + + switchport mode trunk + +interface GigabitEthernet2 + no ip address + +interface GigabitEthernet3 + no ip address + shutdown + + negotiation auto''' + + parsed = indent_differ.IndentedConfig(candidate) + + expected = {'enable password whatever': {}, + 'interface GigabitEthernet1': { + 'description "bleh"': {}, + 'fake nested': { + 'nested nested configuration': {}}, + 'switchport mode trunk': {}}, + 'interface GigabitEthernet2': { + 'no ip address': {}}, + 'interface GigabitEthernet3': { + 'negotiation auto': {}, + 'no ip address': {}, + 'shutdown': {}}, + 'interface Loopback0': { + 'description "blah"': {}}} + + assert parsed.to_dict() == expected + + @pytest.mark.parametrize("case", test_cases_differ) + def test_basic(self, case): + path = os.path.join(BASE_PATH, "test_indent_differ", case) + optional_args = { + "path": path, + "profile": ["mock"], + } + + with driver("blah", "bleh", "blih", optional_args=optional_args) as d: + running = d.cli(["show running config"])["show running config"] + + with open(os.path.join(path, "candidate.txt"), "r") as f: + candidate = f.read() + + with open(os.path.join(path, "diff.txt"), "r") as f: + expected = f.read() + + diff = indent_differ.IndentedConfig(running).diff(candidate) + assert diff.strip() == expected.strip(), diff diff --git a/test/unit/test_indent_differ/test_case_1/candidate.txt b/test/unit/test_indent_differ/test_case_1/candidate.txt new file mode 100644 index 00000000..b6da3a22 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_1/candidate.txt @@ -0,0 +1,17 @@ +enable password whatever +default logging + +no interface Loopback0 + +interface GigabitEthernet1 + description "bleh" + switchport mode trunk + switchport trunk vlan 1,2,3,5 + +interface GigabitEthernet2 + no ip address + +no interface GigabitEthernet666 + +router bgp 65000 + no neighbor 1.1.1.1 diff --git a/test/unit/test_indent_differ/test_case_1/cli.1.show_running_config.0 b/test/unit/test_indent_differ/test_case_1/cli.1.show_running_config.0 new file mode 100644 index 00000000..eb2e31b9 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_1/cli.1.show_running_config.0 @@ -0,0 +1,139 @@ +version 15.4 +service timestamps debug datetime msec +service timestamps log datetime msec +no platform punt-keepalive disable-kernel-core +platform console virtual +! +hostname vagrant_box +! +boot-start-marker +boot-end-marker +! +! +enable secret 5 $1$nc08$bizeEFbgCBKjZP4nurNCd. +enable password vagrant +! +aaa new-model +! +! +aaa authorization exec default local +! +! +logging blah +logging bleh +! +! +! +aaa session-id common +! +! +! +! +! +! +! +! +! + + +ip domain name acme.com + +! +! +! +! +! +! +! +! +! +! +subscriber templating +! +multilink bundle-name authenticated +! +! +! +license udi pid CSR1000V sn 9EKBH55S58T +archive + path bootflash:archive + write-memory +spanning-tree extend system-id +! +username vagrant privilege 15 password 0 vagrant +! +redundancy + mode none +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + description something + ip address 1.1.1.1 255.255.255.255 +! +interface GigabitEthernet1 + ip address dhcp + negotiation auto + no mop enabled + switchport mode access + switchport trunk vlan 1,2,3,4,5 +! +interface GigabitEthernet2 + description blah + ip address 172.20.0.1 255.255.255.0 + shutdown + negotiation auto +! +interface GigabitEthernet3 + no ip address + shutdown + negotiation auto +! +router bgp 65000 + bgp log-neighbor-changes + neighbor 1.1.1.1 remote-as 12345 + neighbor 1.1.1.1 local-as 54321 + neighbor 2.2.2.2 local-as 54321 +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +! +no ip http server +no ip http secure-server +ip ssh version 2 +ip scp server enable +! +! +! +! +! +! +control-plane +! +! +line con 0 + stopbits 1 +line vty 0 4 + password vagrant +! +! +end diff --git a/test/unit/test_indent_differ/test_case_1/diff.txt b/test/unit/test_indent_differ/test_case_1/diff.txt new file mode 100644 index 00000000..6b87e2a0 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_1/diff.txt @@ -0,0 +1,18 @@ ++ enable password whatever +- enable password vagrant +- logging blah +- logging bleh +- interface Loopback0 +- description something +- ip address 1.1.1.1 255.255.255.255 + interface GigabitEthernet1 ++ description "bleh" ++ switchport mode trunk +- switchport mode access ++ switchport trunk vlan 1,2,3,5 +- switchport trunk vlan 1,2,3,4,5 + interface GigabitEthernet2 +- ip address 172.20.0.1 255.255.255.0 + router bgp 65000 +- neighbor 1.1.1.1 remote-as 12345 +- neighbor 1.1.1.1 local-as 54321 diff --git a/test/unit/test_indent_differ/test_case_2/candidate.txt b/test/unit/test_indent_differ/test_case_2/candidate.txt new file mode 100644 index 00000000..c23e36cc --- /dev/null +++ b/test/unit/test_indent_differ/test_case_2/candidate.txt @@ -0,0 +1,7 @@ +no router bgp 65000 +router bgp 65001 + bgp log-neighbor-changes + neighbor 1.1.1.1 remote-as 12345 + neighbor 1.1.1.1 local-as 54321 + neighbor 2.2.2.2 local-as 54321 + diff --git a/test/unit/test_indent_differ/test_case_2/cli.1.show_running_config.0 b/test/unit/test_indent_differ/test_case_2/cli.1.show_running_config.0 new file mode 100644 index 00000000..eb2e31b9 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_2/cli.1.show_running_config.0 @@ -0,0 +1,139 @@ +version 15.4 +service timestamps debug datetime msec +service timestamps log datetime msec +no platform punt-keepalive disable-kernel-core +platform console virtual +! +hostname vagrant_box +! +boot-start-marker +boot-end-marker +! +! +enable secret 5 $1$nc08$bizeEFbgCBKjZP4nurNCd. +enable password vagrant +! +aaa new-model +! +! +aaa authorization exec default local +! +! +logging blah +logging bleh +! +! +! +aaa session-id common +! +! +! +! +! +! +! +! +! + + +ip domain name acme.com + +! +! +! +! +! +! +! +! +! +! +subscriber templating +! +multilink bundle-name authenticated +! +! +! +license udi pid CSR1000V sn 9EKBH55S58T +archive + path bootflash:archive + write-memory +spanning-tree extend system-id +! +username vagrant privilege 15 password 0 vagrant +! +redundancy + mode none +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + description something + ip address 1.1.1.1 255.255.255.255 +! +interface GigabitEthernet1 + ip address dhcp + negotiation auto + no mop enabled + switchport mode access + switchport trunk vlan 1,2,3,4,5 +! +interface GigabitEthernet2 + description blah + ip address 172.20.0.1 255.255.255.0 + shutdown + negotiation auto +! +interface GigabitEthernet3 + no ip address + shutdown + negotiation auto +! +router bgp 65000 + bgp log-neighbor-changes + neighbor 1.1.1.1 remote-as 12345 + neighbor 1.1.1.1 local-as 54321 + neighbor 2.2.2.2 local-as 54321 +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +! +no ip http server +no ip http secure-server +ip ssh version 2 +ip scp server enable +! +! +! +! +! +! +control-plane +! +! +line con 0 + stopbits 1 +line vty 0 4 + password vagrant +! +! +end diff --git a/test/unit/test_indent_differ/test_case_2/diff.txt b/test/unit/test_indent_differ/test_case_2/diff.txt new file mode 100644 index 00000000..85cc4610 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_2/diff.txt @@ -0,0 +1,10 @@ +- router bgp 65000 +- bgp log-neighbor-changes +- neighbor 1.1.1.1 remote-as 12345 +- neighbor 1.1.1.1 local-as 54321 +- neighbor 2.2.2.2 local-as 54321 ++ router bgp 65001 ++ bgp log-neighbor-changes ++ neighbor 1.1.1.1 remote-as 12345 ++ neighbor 1.1.1.1 local-as 54321 ++ neighbor 2.2.2.2 local-as 54321 diff --git a/test/unit/test_indent_differ/test_case_3/candidate.txt b/test/unit/test_indent_differ/test_case_3/candidate.txt new file mode 100644 index 00000000..867dceb7 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_3/candidate.txt @@ -0,0 +1,6 @@ +ip access-list extended TEST-ACL + permit tcp any any eq www + permit tcp any any eq 443 + permit tcp any any eq 22 + permit udp any any eq snmp + deny ip any any diff --git a/test/unit/test_indent_differ/test_case_3/cli.1.show_running_config.0 b/test/unit/test_indent_differ/test_case_3/cli.1.show_running_config.0 new file mode 100644 index 00000000..eb2e31b9 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_3/cli.1.show_running_config.0 @@ -0,0 +1,139 @@ +version 15.4 +service timestamps debug datetime msec +service timestamps log datetime msec +no platform punt-keepalive disable-kernel-core +platform console virtual +! +hostname vagrant_box +! +boot-start-marker +boot-end-marker +! +! +enable secret 5 $1$nc08$bizeEFbgCBKjZP4nurNCd. +enable password vagrant +! +aaa new-model +! +! +aaa authorization exec default local +! +! +logging blah +logging bleh +! +! +! +aaa session-id common +! +! +! +! +! +! +! +! +! + + +ip domain name acme.com + +! +! +! +! +! +! +! +! +! +! +subscriber templating +! +multilink bundle-name authenticated +! +! +! +license udi pid CSR1000V sn 9EKBH55S58T +archive + path bootflash:archive + write-memory +spanning-tree extend system-id +! +username vagrant privilege 15 password 0 vagrant +! +redundancy + mode none +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface Loopback0 + description something + ip address 1.1.1.1 255.255.255.255 +! +interface GigabitEthernet1 + ip address dhcp + negotiation auto + no mop enabled + switchport mode access + switchport trunk vlan 1,2,3,4,5 +! +interface GigabitEthernet2 + description blah + ip address 172.20.0.1 255.255.255.0 + shutdown + negotiation auto +! +interface GigabitEthernet3 + no ip address + shutdown + negotiation auto +! +router bgp 65000 + bgp log-neighbor-changes + neighbor 1.1.1.1 remote-as 12345 + neighbor 1.1.1.1 local-as 54321 + neighbor 2.2.2.2 local-as 54321 +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +! +no ip http server +no ip http secure-server +ip ssh version 2 +ip scp server enable +! +! +! +! +! +! +control-plane +! +! +line con 0 + stopbits 1 +line vty 0 4 + password vagrant +! +! +end diff --git a/test/unit/test_indent_differ/test_case_3/diff.txt b/test/unit/test_indent_differ/test_case_3/diff.txt new file mode 100644 index 00000000..c6b2d4b2 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_3/diff.txt @@ -0,0 +1,6 @@ ++ ip access-list extended TEST-ACL ++ permit tcp any any eq www ++ permit tcp any any eq 443 ++ permit tcp any any eq 22 ++ permit udp any any eq snmp ++ deny ip any any diff --git a/test/unit/test_indent_differ/test_case_vlan/candidate.txt b/test/unit/test_indent_differ/test_case_vlan/candidate.txt new file mode 100644 index 00000000..1809b3fe --- /dev/null +++ b/test/unit/test_indent_differ/test_case_vlan/candidate.txt @@ -0,0 +1,10 @@ +no vlan 4 +vlan 111 + name network-management + +no interface Vlan4 +interface Vlan111 + description *** Management VLAN *** + ip address 172.16.1.224 255.255.255.0 + no ip route-cache + ipv6 address 2001:DB8:BEEF::224/64 diff --git a/test/unit/test_indent_differ/test_case_vlan/cli.1.show_running_config.0 b/test/unit/test_indent_differ/test_case_vlan/cli.1.show_running_config.0 new file mode 100644 index 00000000..77839454 --- /dev/null +++ b/test/unit/test_indent_differ/test_case_vlan/cli.1.show_running_config.0 @@ -0,0 +1,95 @@ +! +version 15.2 +no service pad +service timestamps debug datetime msec +service timestamps log datetime msec +service password-encryption +! +hostname sw02 +! +boot-start-marker +boot-end-marker +! +! +vlan internal allocation policy ascending +! +vlan 4 + name network-management +! +vlan 100 + name access +! +vlan 101 + name voip +! +! +! +! +interface Port-channel1 + description Link to sw01 + switchport mode trunk +! +interface GigabitEthernet0/1 + description Notebook 01 + switchport access vlan 100 + switchport mode access + no logging event link-status + spanning-tree portfast +! +interface GigabitEthernet0/2 + description IP-Phone 01 + switchport access vlan 101 + switchport mode access + no logging event link-status + spanning-tree portfast +! +interface GigabitEthernet0/3 + no logging event link-status + shutdown +! +interface GigabitEthernet0/4 + no logging event link-status + shutdown +! +interface GigabitEthernet0/5 + no logging event link-status + shutdown +! +interface GigabitEthernet0/6 + no logging event link-status + shutdown +! +interface GigabitEthernet0/7 + no logging event link-status + shutdown +! +interface GigabitEthernet0/8 + no logging event link-status + shutdown +! +interface GigabitEthernet0/9 + description Link to sw02 + switchport mode trunk + channel-group 1 mode active +! +interface GigabitEthernet0/10 + description Link to sw02 + switchport mode trunk + channel-group 1 mode active +! +interface Vlan1 + no ip address + no ip route-cache + shutdown +! +interface Vlan4 + description *** Management VLAN *** + ip address 172.16.1.224 255.255.255.0 + no ip route-cache + ipv6 address 2001:DB8:BEEF::224/64 +! +! +line con 0 +line vty 0 4 +! +end diff --git a/test/unit/test_indent_differ/test_case_vlan/diff.txt b/test/unit/test_indent_differ/test_case_vlan/diff.txt new file mode 100644 index 00000000..960febbb --- /dev/null +++ b/test/unit/test_indent_differ/test_case_vlan/diff.txt @@ -0,0 +1,14 @@ +- vlan 4 +- name network-management ++ vlan 111 ++ name network-management +- interface Vlan4 +- description *** Management VLAN *** +- ip address 172.16.1.224 255.255.255.0 +- no ip route-cache +- ipv6 address 2001:DB8:BEEF::224/64 ++ interface Vlan111 ++ description *** Management VLAN *** ++ ip address 172.16.1.224 255.255.255.0 ++ no ip route-cache ++ ipv6 address 2001:DB8:BEEF::224/64 \ No newline at end of file diff --git a/test/unit/test_indent_differ/test_snmp_server/candidate.txt b/test/unit/test_indent_differ/test_snmp_server/candidate.txt new file mode 100644 index 00000000..d1e9450b --- /dev/null +++ b/test/unit/test_indent_differ/test_snmp_server/candidate.txt @@ -0,0 +1,5 @@ +snmp-server location DEMO_ENV +snmp-server contact NAPALM-NOC_DEMO +snmp-server community napalm RO +snmp-server community public RO 10 +snmp-server enable traps entity-sensor diff --git a/test/unit/test_indent_differ/test_snmp_server/cli.1.show_running_config.0 b/test/unit/test_indent_differ/test_snmp_server/cli.1.show_running_config.0 new file mode 100644 index 00000000..0847608b --- /dev/null +++ b/test/unit/test_indent_differ/test_snmp_server/cli.1.show_running_config.0 @@ -0,0 +1,169 @@ +version 16.4 +service config +service timestamps debug datetime msec +service timestamps log datetime msec +no platform punt-keepalive disable-kernel-core +platform console auto +! +hostname R1 +! +boot-start-marker +boot-end-marker +! +! +enable secret 5 $1$eDEi$pZFFuuwLSgnnhBmg2XdrD0 +! +aaa new-model +! +! +aaa authentication login LOCALDB local +aaa authorization exec LOCALAUTHZ local +aaa authorization network LOCALAUTHZ local +! +! +! +! +! +aaa session-id common +! +! +! +! +! +! +! +! +! + + + +! +! +! +! +! +! +! +! +! +! +subscriber templating +! +! +! +multilink bundle-name authenticated +! +! +! +! +! +! + + +! +! +! +! +! +! +! +license udi pid CSR1000V sn 9N6NA9WRBAW +diagnostic bootup level minimal +! +spanning-tree extend system-id +! +! +username vagrant privilege 15 secret 5 $1$hoD9$fiXAskKgXkVfP9bJ0d4Oy1 +! +redundancy +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +! +interface GigabitEthernet1 + ip address dhcp + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet2 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +interface GigabitEthernet3 + no ip address + shutdown + negotiation auto + no mop enabled + no mop sysid +! +! +virtual-service csr_mgmt +! +ip forward-protocol nd +no ip http server +no ip http secure-server +! +! +! +access-list 10 permit 10.10.10.12 +! +! +snmp-server community public RO +snmp-server location VAGRANT +snmp-server contact NAPALM-NOC +snmp-server enable traps entity +! +! +! +! +control-plane +! + ! + ! + ! + ! +! +! +! +! +! +line con 0 + stopbits 1 +line vty 0 4 + authorization exec LOCALAUTHZ + login authentication LOCALDB +! +ntp server pool.ntp.org +! +! +! +! +! +end diff --git a/test/unit/test_indent_differ/test_snmp_server/diff.txt b/test/unit/test_indent_differ/test_snmp_server/diff.txt new file mode 100644 index 00000000..f451463b --- /dev/null +++ b/test/unit/test_indent_differ/test_snmp_server/diff.txt @@ -0,0 +1,8 @@ ++ snmp-server location DEMO_ENV +- snmp-server location VAGRANT ++ snmp-server contact NAPALM-NOC_DEMO +- snmp-server contact NAPALM-NOC ++ snmp-server community napalm RO ++ snmp-server community public RO 10 +- snmp-server community public RO ++ snmp-server enable traps entity-sensor