diff --git a/changelogs/fragments/off_box_diff_enhancement.yaml b/changelogs/fragments/off_box_diff_enhancement.yaml
new file mode 100644
index 000000000..e42f846aa
--- /dev/null
+++ b/changelogs/fragments/off_box_diff_enhancement.yaml
@@ -0,0 +1,3 @@
+---
+minor_changes:
+ - eos_config - Implement off-box-diff optional feature.
diff --git a/docs/arista.eos.eos_config_module.rst b/docs/arista.eos.eos_config_module.rst
index 100a809fe..39285b781 100644
--- a/docs/arista.eos.eos_config_module.rst
+++ b/docs/arista.eos.eos_config_module.rst
@@ -132,6 +132,58 @@ Parameters
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.
+
diff --git a/plugins/module_utils/network/eos/utils/utils.py b/plugins/module_utils/network/eos/utils/utils.py
index 260aa1996..109ea7fb1 100644
--- a/plugins/module_utils/network/eos/utils/utils.py
+++ b/plugins/module_utils/network/eos/utils/utils.py
@@ -10,6 +10,8 @@
__metaclass__ = type
+import difflib
+
def get_interface_number(name):
digits = ""
@@ -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)
diff --git a/plugins/modules/eos_config.py b/plugins/modules/eos_config.py
index 5aa8e7ceb..4b79a3712 100644
--- a/plugins/modules/eos_config.py
+++ b/plugins/modules/eos_config.py
@@ -140,6 +140,16 @@
false, the command is issued without the all keyword
type: bool
default: false
+ context_diff:
+ 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
@@ -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 = """
@@ -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
@@ -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):
@@ -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"],
@@ -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,
@@ -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
@@ -618,7 +666,6 @@ def main():
result["warnings"].append(msg)
else:
result["warnings"] = msg
-
module.exit_json(**result)
diff --git a/tests/integration/targets/eos_config/templates/defaults/candidate.j2 b/tests/integration/targets/eos_config/templates/defaults/candidate.j2
new file mode 100644
index 000000000..a8ac1b2f2
--- /dev/null
+++ b/tests/integration/targets/eos_config/templates/defaults/candidate.j2
@@ -0,0 +1,8 @@
+! Command: show running-config
+!
+interface Ethernet2
+ description "This is candidate config sample"
+ shutdown
+ no switchport
+!
+end
diff --git a/tests/integration/targets/eos_config/tests/cli/backup.yaml b/tests/integration/targets/eos_config/tests/cli/backup.yaml
index 11d36af97..8fa491684 100644
--- a/tests/integration/targets/eos_config/tests/cli/backup.yaml
+++ b/tests/integration/targets/eos_config/tests/cli/backup.yaml
@@ -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"
diff --git a/tests/unit/modules/network/eos/fixtures/eos_candidate.cfg b/tests/unit/modules/network/eos/fixtures/eos_candidate.cfg
new file mode 100644
index 000000000..87266dcbd
--- /dev/null
+++ b/tests/unit/modules/network/eos/fixtures/eos_candidate.cfg
@@ -0,0 +1,7 @@
+hostname switch02
+!
+interface Ethernet1
+ description test interface
+ no shutdown
+!
+ip routing
diff --git a/tests/unit/modules/network/eos/test_eos_config.py b/tests/unit/modules/network/eos/test_eos_config.py
index 3a28710ee..959c5c2f9 100644
--- a/tests/unit/modules/network/eos/test_eos_config.py
+++ b/tests/unit/modules/network/eos/test_eos_config.py
@@ -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")
|