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.
+ + +
+ context_diff + +
+ dictionary +
+ + + + +
Specify the off-box diff options
+ + + + + +
+ context_lines + +
+ integer +
+ + + + +
Specify The number of context lines, by default it includes all lines.
+ + + + + +
+ enable + +
+ boolean +
+ + + + + +
Enable off box diff
+ + +
@@ -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 @@ -445,6 +507,23 @@ Common return values are documented `here ['hostname switch01', 'interface Ethernet1', 'no shutdown'] + + +
+ context_diff + +
+ string +
+ + when user opt for off-box-diff through context_diff option. + +
The diff between candidate and target config.
+
+
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)'
+ +
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")