Repository: ansible/ansible

Issue:
FEAT: ios_config module save: option
<!---
Verify first that your issue/request is not already reported on GitHub.
Also test if the latest release, and master branch are affected too.
-->

##### ISSUE TYPE
<!--- Pick one below and delete the rest: -->
 - Feature Idea

##### COMPONENT NAME
<!--- Name of the module/plugin/task/feature -->
ios_config
##### ANSIBLE VERSION
<!--- Paste verbatim output from “ansible --version” between quotes below -->
```
ansible 2.3.0.0
  config file = /usr/local/rancid/ansible/ntc_stuff/ansible.cfg
  configured module search path = Default w/o overrides
  python version = 2.7.5 (default, Nov  6 2016, 00:28:07) [GCC 4.8.5 20150623 (Red Hat 4.8.5-11)]
```

##### CONFIGURATION
<!---
Mention any settings you have changed/added/removed in ansible.cfg
(or using the ANSIBLE_* environment variables).
-->

##### OS / ENVIRONMENT
<!---
Mention the OS you are running Ansible from, and the OS you are
managing, or say “N/A” for anything that is not platform-specific.
-->
N/A
##### SUMMARY
<!--- Explain the problem briefly -->
Currently, when using the save: yes option to the ios_config module, the running config is written to the startup config regardless of whether any changes were needed to be made. I'm requesting that this option be changed or have another choice added where the running config is only written to the startup config when the ios_config task actually produces a change to the running config. This would preserve idempotency for the ios_config module when the save: option is used.
##### STEPS TO REPRODUCE
<!---
For bugs, show exactly how to reproduce the problem, using a minimal test-case.
For new features, show how the feature would be used.
-->

<!--- Paste example playbooks or commands between quotes below -->
```yaml
  tasks:
  - name: configure logging
    ios_config:
      src: "configs/logging.cfg"
      provider: "{{ ios_provider }}"
      save: if_changed
```

<!--- You can also paste gist.github.com links for larger files -->

##### EXPECTED RESULTS
<!--- What did you expect to happen when running the steps above? -->
I'd like the above to save the config on that device if and only if the task produced a change.
##### ACTUAL RESULTS
<!--- What actually happened? If possible run with extra verbosity (-vvvv) -->
<!--- Paste verbatim command output between quotes below -->
```
With the current save: yes option, the save is executed even if no config changes were needed.

See: https://github.com/ansible/ansible/issues/22727
```



Discussion:
cc @privateip @rcarrillocruz
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->
In [this](https://github.com/ansible/ansible/issues/22727) issue it was mentioned that `verify /md5 nvram:startup-config` could be used to verify if the startup config has been changed or not. Even without setting `save: yes` the command `verify /md5 system:running-config` could/should be used to check if the running config has been changed? Another option for both running and startup config is to grab the output from `show run` or `show start` before and after the operation and `diff` to see if its the same or not.

Resolving patch:
diff --git a/lib/ansible/modules/network/eos/eos_config.py b/lib/ansible/modules/network/eos/eos_config.py
--- a/lib/ansible/modules/network/eos/eos_config.py
+++ b/lib/ansible/modules/network/eos/eos_config.py
@@ -331,8 +331,10 @@ def main():
 
     if module.params['save']:
         if not module.check_mode:
-            run_commands(module, ['copy running-config startup-config'])
-        result['changed'] = True
+            response = run_commands(module, ['show running-config diffs'])
+            if len(response[0]):
+                run_commands(module, ['copy running-config startup-config'])
+                result['changed'] = True
 
     module.exit_json(**result)
 
diff --git a/lib/ansible/modules/network/ios/ios_config.py b/lib/ansible/modules/network/ios/ios_config.py
--- a/lib/ansible/modules/network/ios/ios_config.py
+++ b/lib/ansible/modules/network/ios/ios_config.py
@@ -382,8 +382,10 @@ def main():
 
     if module.params['save']:
         if not module.check_mode:
-            run_commands(module, ['copy running-config startup-config\r'])
-        result['changed'] = True
+            response = run_commands(module, ['show archive config differences'])
+            if response[0].find('!No changes were found') < 0:
+                run_commands(module, ['copy running-config startup-config\r'])
+                result['changed'] = True
 
     module.exit_json(**result)
 


Test patch:
diff --git a/test/units/modules/network/ios/test_ios_config.py b/test/units/modules/network/ios/test_ios_config.py
--- a/test/units/modules/network/ios/test_ios_config.py
+++ b/test/units/modules/network/ios/test_ios_config.py
@@ -69,9 +69,10 @@ def test_ios_config_backup(self):
         self.assertIn('__backup__', result)
 
     def test_ios_config_save(self):
+        self.run_commands.return_value = "Hostname foo"
         set_module_args(dict(save=True))
         self.execute_module(changed=True)
-        self.assertEqual(self.run_commands.call_count, 1)
+        self.assertEqual(self.run_commands.call_count, 2)
         self.assertEqual(self.get_config.call_count, 0)
         self.assertEqual(self.load_config.call_count, 0)
         args = self.run_commands.call_args[0][1]