Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement Context Diff option in eos_config module #485

Open
wants to merge 15 commits into
base: main
Choose a base branch
from
Open
3 changes: 3 additions & 0 deletions changelogs/fragments/off_box_diff_enhancement.yaml
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
---
minor_changes:
- eos_config - Implement off-box-diff optional feature.
79 changes: 79 additions & 0 deletions docs/arista.eos.eos_config_module.rst
Original file line number Diff line number Diff line change
Expand Up @@ -132,6 +132,58 @@ Parameters
<div>The ordered set of commands to push on to the command stack if a change needs to be made. This allows the playbook designer the opportunity to perform configuration commands prior to pushing any changes without affecting how the set of commands are matched against the system.</div>
</td>
</tr>
<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>context_diff</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">dictionary</span>
</div>
</td>
<td>
</td>
<td>
<div>Specify the off-box diff options</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>context_lines</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">integer</span>
</div>
</td>
<td>
</td>
<td>
<div>Specify The number of context lines, by default it includes all lines.</div>
</td>
</tr>
<tr>
<td class="elbow-placeholder"></td>
<td colspan="1">
<div class="ansibleOptionAnchor" id="parameter-"></div>
<b>enable</b>
<a class="ansibleOptionLink" href="#parameter-" title="Permalink to this option"></a>
<div style="font-size: small">
<span style="color: purple">boolean</span>
</div>
</td>
<td>
<ul style="margin: 0; padding: 0"><b>Choices:</b>
<li>no</li>
<li>yes</li>
</ul>
</td>
<td>
<div>Enable off box diff</div>
</td>
</tr>

<tr>
<td colspan="2">
<div class="ansibleOptionAnchor" id="parameter-"></div>
Expand Down Expand Up @@ -397,6 +449,16 @@ Examples
filename: backup.cfg
dir_path: /home/user

- name: Get the full context diff
arista.eos.eos_config:
src: candidate.cfg
backup: true
context_diff:
enable: true
backup_options:
filename: backup.cfg
dir_path: /home/user



Return Values
Expand Down Expand Up @@ -445,6 +507,23 @@ Common return values are documented `here <https://docs.ansible.com/ansible/late
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">[&#x27;hostname switch01&#x27;, &#x27;interface Ethernet1&#x27;, &#x27;no shutdown&#x27;]</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
<b>context_diff</b>
<a class="ansibleOptionLink" href="#return-" title="Permalink to this return value"></a>
<div style="font-size: small">
<span style="color: purple">string</span>
</div>
</td>
<td>when user opt for off-box-diff through context_diff option.</td>
<td>
<div>The diff between candidate and target config.</div>
<br/>
<div style="font-size: smaller"><b>Sample:</b></div>
<div style="font-size: smaller; color: blue; word-wrap: break-word; word-break: break-all;">&#x27; --- +++ @@ -1,7 +1,7 @@ ! Command: show running-config -! device: arista (vEOS, EOS-4.24.6M) +! device: candidate-arista-11 (vEOS, EOS-4.24.6M)&#x27;</div>
</td>
</tr>
<tr>
<td colspan="1">
<div class="ansibleOptionAnchor" id="return-"></div>
Expand Down
10 changes: 10 additions & 0 deletions plugins/module_utils/network/eos/utils/utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@

__metaclass__ = type

import difflib


def get_interface_number(name):
digits = ""
Expand Down Expand Up @@ -82,3 +84,11 @@ def numerical_sort(string_int_list):
as_int_list.append(int(vlan))
as_int_list.sort()
return list(set(as_int_list))


def unified_diff(content1, content2, count):
"""
Provide the unified diff in context to number of lines specified with count
"""
unified_diff = difflib.unified_diff(content1, content2, n=count, lineterm="\n")
return "\n".join(unified_diff)
57 changes: 52 additions & 5 deletions plugins/modules/eos_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,6 +140,16 @@
false, the command is issued without the all keyword
type: bool
default: false
context_diff:
rohitthakur2590 marked this conversation as resolved.
Show resolved Hide resolved
description: Specify the off-box diff options
type: dict
suboptions:
enable:
description: Enable off box diff
type: bool
context_lines:
description: Specify The number of context lines, by default it includes all lines.
type: int
save_when:
description:
- When changes are made to the device running-configuration, the changes are not
Expand Down Expand Up @@ -272,6 +282,16 @@
backup_options:
filename: backup.cfg
dir_path: /home/user

- name: Get the full context diff
arista.eos.eos_config:
src: candidate.cfg
backup: true
context_diff:
enable: true
backup_options:
filename: backup.cfg
dir_path: /home/user
"""

RETURN = """
Expand Down Expand Up @@ -310,6 +330,17 @@
returned: when backup is true
type: str
sample: "22:28:34"
context_diff:
description: The diff between candidate and target config.
returned: when user opt for off-box-diff through context_diff option.
type: str
sample: '''
---
+++
@@ -1,7 +1,7 @@
! Command: show running-config
-! device: arista (vEOS, EOS-4.24.6M)
+! device: candidate-arista-11 (vEOS, EOS-4.24.6M)'''
"""
from ansible.module_utils._text import to_text
from ansible.module_utils.basic import AnsibleModule
Expand All @@ -326,6 +357,7 @@
load_config,
run_commands,
)
from ansible_collections.arista.eos.plugins.module_utils.network.eos.utils.utils import unified_diff


def get_candidate(module):
Expand Down Expand Up @@ -375,6 +407,13 @@ def main():
parents=dict(type="list", elements="str"),
before=dict(type="list", elements="str"),
after=dict(type="list", elements="str"),
context_diff=dict(
type="dict",
options=dict(
enable=dict(type="bool"),
context_lines=dict(type="int"),
),
),
match=dict(
default="line",
choices=["line", "strict", "exact", "none"],
Expand Down Expand Up @@ -453,7 +492,6 @@ def main():

candidate = get_candidate(module)
running = get_running_config(module, contents, flags=flags)

try:
response = connection.get_diff(
candidate=candidate,
Expand Down Expand Up @@ -487,12 +525,22 @@ def main():
replace=replace,
commit=commit,
)

result["changed"] = True

if module.params["diff_against"] == "session":
if "diff" in response:
result["diff"] = {"prepared": response["diff"]}
context_diff = module.params.get("context_diff")
if context_diff and context_diff.get("enable"):
if context_diff.get("context_lines"):
count = context_diff.get("context_lines")
else:
count = max(len(candidate), len(running))
result["context_diff"] = unified_diff(
candidate.split("\n"),
running.split("\n"),
count,
)
else:
result["diff"] = {"prepared": response["diff"]}
else:
result["changed"] = False

Expand Down Expand Up @@ -618,7 +666,6 @@ def main():
result["warnings"].append(msg)
else:
result["warnings"] = msg

module.exit_json(**result)


Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
! Command: show running-config
!
interface Ethernet2
description "This is candidate config sample"
shutdown
no switchport
!
end
16 changes: 16 additions & 0 deletions tests/integration/targets/eos_config/tests/cli/backup.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,22 @@
that:
- result.changed == true

- name: get the context diff with backup config
become: true
register: result
arista.eos.eos_config:
src: defaults/candidate.j2
backup: true
context_diff:
enable: true
context_lines: 10
backup_options:
filename: running_backup.cfg

- ansible.builtin.assert:
that:
- result.context_diff is defined

- name: check if the backup file-2 exist
ansible.builtin.find:
paths: "{{ role_path }}/backup/backup.cfg"
Expand Down
7 changes: 7 additions & 0 deletions tests/unit/modules/network/eos/fixtures/eos_candidate.cfg
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
hostname switch02
!
interface Ethernet1
description test interface
no shutdown
!
ip routing
36 changes: 36 additions & 0 deletions tests/unit/modules/network/eos/test_eos_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,42 @@ def test_eos_config_src_replace(self):
result["commands"],
)

def test_eos_config_context_diff(self):
src = load_fixture("eos_candidate.cfg")
args = dict(
src=src,
backup=True,
context_diff=dict(enable=True),
backup_options=dict(
filename="backup.cfg",
dir_path="./",
),
)
set_module_args(args)
self.conn.get_diff = MagicMock(
return_value=self.cliconf_obj.get_diff(src, self.running_config),
)
result = self.execute_module(changed=True)
self.assertIn("context_diff", result)

def test_eos_config_context_diff_lines(self):
src = load_fixture("eos_candidate.cfg")
args = dict(
src=src,
backup=True,
context_diff=dict(enable=True, context_lines=10),
backup_options=dict(
filename="backup.cfg",
dir_path="./",
),
)
set_module_args(args)
self.conn.get_diff = MagicMock(
return_value=self.cliconf_obj.get_diff(src, self.running_config),
)
result = self.execute_module(changed=True)
self.assertIn("context_diff", result)

def test_eos_config_lines_block(self):
lines = ["hostname switch01", "ip domain-name eng.ansible.com"]
args = dict(lines=lines, replace="block")
Expand Down