scrapli / scrapli_cfg

Network device Configuration Management with scrapli
https://scrapli.github.io/scrapli_cfg/
MIT License
32 stars 4 forks source link

Diff generation mismatch due to candidate config missing a trailing newline at the last line #21

Closed matman26 closed 3 years ago

matman26 commented 3 years ago

Describe the bug The diff functionality is not working properly for the last line of a given configuration input. Most precisely, line 100 of scrapli_cfg/platform/base/base_platform.py uses a join statement that doesn't properly append a \n to the last line of input.

rendered_config = "\n".join(line for line in rendered_config.splitlines() if line)

This is an expected behaviour for the join function:

Example: '.'.join(['ab', 'pq', 'rs']) -> 'ab.pq.rs'

One alternative to add a trailing newline at the end of the output would be to either append a \n after the list comprehension is executed or use a format statement instead.

my_list=['one', 'two', 'three']

# Appending a \n manually at the end of the string
print '\n'.join(my_list)+'\n'

# Using a Format statement
''.join(f"{row}\n" for row in my_list)

# Replace last item of the list for itself plus a \n
mylist[-1] = myslist[-1]+"\n"

To Reproduce We're trying out scrapli_cfg via the nornir_scrapli plugin. Here's the basic setup:

def get_diff_apply_cfg(task):
    templating_results = task.run(
            task = template_file,
            path = './templates',
            template = 'ntp_server.j2',
            ntp_server = "3.3.2.2",
            keep_trailing_newline = True
            )
    candidate_config = templating_results.result
    task.run(task=cfg_load_config, config=candidate_config)
    diff = task.run(task=cfg_diff_config,source="running")
    task.run(task=cfg_commit_config, source="running")
    return diff.scrapli_response.side_by_side_diff

def main():
    nr = InitNornir(config_file="config.yaml")
    diff = nr.run(task = get_diff_apply_cfg)
    print_result(diff)

Our Jinja2 template looks at the platform to determine which config block to apply, here's the snippet for IOSXE:

{% if host.platform == 'cisco_iosxe' -%}
ntp server {{ ntp_server }}
{%- endif %}

We're running our tests against an IOS XE on Cisco's Devnet Sandbox (IOS XE on CSR Recommended Code). When we try to add NTP configurations to a device which already has the same exact config on it, diff.scrapli_response.side_by_side_diff returns as if it detected a difference between the candidate config(for example, ntp server x.x.x.x) and the actual source config (ntp server x.x.x.x) as in the figure below:

image

This is due to the fact that even though our j2 template inserts a trailing newline, it seems to be removed the functions described under the Expect Behavior section.

Expected behavior The class method clean_config from /scrapli_cfg/platform/core/cisco_iosxe/sync_platform.py and class method _normalize_source_candidate_configs from scrapli_cfg/platform/core/cisco_iosxr/base_platform.py should return a string terminated by '\n' so that the candidate config would match loaded config during diff calculation in cfg_diff_config (otherwise the last line of the loaded config would not match the corresponding line in the source config).

OS:

carlmontanari commented 3 years ago

Hey @matman26

Branch in the linked pr should sort this. I re arranged some things so the pr is kinda big for this small fix but mostly was just organizational. If you an pull that and give it a shot and let me know if things look good I can get that merged.

I'll close this and ya can just chime in on the PR!

Thanks!

Carl