Repository: ansible/ansible

Issue:
iosxr_system: not idempotent & AttributeError: 'str' object has no attribute 'get'
##### ISSUE TYPE
 - Bug Report

##### COMPONENT NAME
iosxr_system

##### ANSIBLE VERSION
```

```

##### SUMMARY
`iosxr_system` isn't idempotent.

This also exposes another issues 
```
The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_YEHiix/ansible_module_iosxr_system.py", line 269, in <module>
    main()
  File "/tmp/ansible_YEHiix/ansible_module_iosxr_system.py", line 262, in main
    if response.get('diff') and module._diff:
AttributeError: 'str' object has no attribute 'get'
```

Which I've fixed locally (by copying **ios**_system)
```
diff --git a/lib/ansible/modules/network/iosxr/iosxr_system.py b/lib/ansible/modules/network/iosxr/iosxr_system.py
index c04ec9f..1f208d6 100644
--- a/lib/ansible/modules/network/iosxr/iosxr_system.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_system.py
@@ -257,10 +257,8 @@ def main():
     result['commands'] = commands
 
     if commands:
-        commit = not module.check_mode
-        response = load_config(module, commands, commit=commit)
-        if response.get('diff') and module._diff:
-            result['diff'] = {'prepared': response.get('diff')}
+        if not module.check_mode:
+            load_config(module, commands)
         result['changed'] = True
 
     module.exit_json(**result)

```


##### STEPS TO REPRODUCE
```yaml
- name: setup
  iosxr_config:
    lines:
      - no ip name-server 1.1.1.1
      - no ip name-server 2.2.2.2
      - no ip name-server 3.3.3.3
    match: none
    provider: "{{ cli }}"

- name: configure name_servers
  iosxr_system:
    name_servers:
      - 1.1.1.1
      - 2.2.2.2
      - 3.3.3.3
    provider: "{{ cli }}"
  register: result

- assert:
    that:
      - result.changed == true
      - result.commands|length == 3
      - "'domain name-server 1.1.1.1' in result.commands"
      - "'domain name-server 2.2.2.2' in result.commands"
      - "'domain name-server 3.3.3.3' in result.commands"

- name: verify name_servers
  iosxr_system:
    name_servers:
      - 1.1.1.1
      - 2.2.2.2
      - 3.3.3.3
    provider: "{{ cli }}"
  register: result

- assert:
    that:
      - result.changed == false
```


##### EXPECTED RESULTS

##### ACTUAL RESULTS
```yaml
TASK [iosxr_system : configure name_servers] *****************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml:13
<iosxr01> wrong context, sending exit to device
Using module file /home/johnb/git/ansible-inc/ansible-workspace-2/lib/ansible/modules/network/iosxr/iosxr_system.py
<iosxr01> ESTABLISH LOCAL CONNECTION FOR USER: johnb
<iosxr01> EXEC /bin/sh -c 'echo ~ && sleep 0'
<iosxr01> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546 `" && echo ansible-tmp-1488810673.15-259741987236546="` echo /home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546 `" ) && sleep 0'
<iosxr01> PUT /tmp/tmpQNkAEZ TO /home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546/iosxr_system.py
<iosxr01> EXEC /bin/sh -c 'chmod u+x /home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546/ /home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546/iosxr_system.py && sleep 0'
<iosxr01> EXEC /bin/sh -c 'python /home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546/iosxr_system.py; rm -rf "/home/johnb/.ansible/tmp/ansible-tmp-1488810673.15-259741987236546/" > /dev/null 2>&1 && sleep 0'
changed: [iosxr01] => {
    "changed": true, 
    "commands": [
        "domain name-server 2.2.2.2", 
        "domain name-server 3.3.3.3", 
        "domain name-server 1.1.1.1"
    ], 
    "invocation": {
        "module_args": {
            "domain_name": null, 
            "domain_search": null, 
            "host": null, 
            "hostname": null, 
            "lookup_enabled": null, 
            "lookup_source": null, 
            "name_servers": [
                "1.1.1.1", 
                "2.2.2.2", 
                "3.3.3.3"
            ], 
            "password": null, 
            "port": null, 
            "provider": {
                "host": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "port": null, 
                "ssh_keyfile": null, 
                "timeout": null, 
                "transport": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "username": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
            }, 
            "ssh_keyfile": null, 
            "state": "present", 
            "timeout": null, 
            "username": null
        }
    }
}

TASK [iosxr_system : assert] *********************************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml:22
ok: [iosxr01] => {
    "changed": false, 
    "msg": "All assertions passed"
}

TASK [iosxr_system : verify name_servers] ********************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml:30
Using module file /home/johnb/git/ansible-inc/ansible-workspace-2/lib/ansible/modules/network/iosxr/iosxr_system.py
<iosxr01> ESTABLISH LOCAL CONNECTION FOR USER: johnb
<iosxr01> EXEC /bin/sh -c 'echo ~ && sleep 0'
<iosxr01> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo /home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701 `" && echo ansible-tmp-1488810676.6-196583747287701="` echo /home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701 `" ) && sleep 0'
<iosxr01> PUT /tmp/tmpYqMOS9 TO /home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701/iosxr_system.py
<iosxr01> EXEC /bin/sh -c 'chmod u+x /home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701/ /home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701/iosxr_system.py && sleep 0'
<iosxr01> EXEC /bin/sh -c 'python /home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701/iosxr_system.py; rm -rf "/home/johnb/.ansible/tmp/ansible-tmp-1488810676.6-196583747287701/" > /dev/null 2>&1 && sleep 0'
changed: [iosxr01] => {
    "changed": true, 
    "commands": [
        "domain name-server 2.2.2.2", 
        "domain name-server 3.3.3.3", 
        "domain name-server 1.1.1.1"
    ], 
    "invocation": {
        "module_args": {
            "domain_name": null, 
            "domain_search": null, 
            "host": null, 
            "hostname": null, 
            "lookup_enabled": null, 
            "lookup_source": null, 
            "name_servers": [
                "1.1.1.1", 
                "2.2.2.2", 
                "3.3.3.3"
            ], 
            "password": null, 
            "port": null, 
            "provider": {
                "host": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "password": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "port": null, 
                "ssh_keyfile": null, 
                "timeout": null, 
                "transport": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER", 
                "username": "VALUE_SPECIFIED_IN_NO_LOG_PARAMETER"
            }, 
            "ssh_keyfile": null, 
            "state": "present", 
            "timeout": null, 
            "username": null
        }
    }
}

TASK [iosxr_system : assert] *********************************************************************************************************************************************************************************************************************
task path: /home/johnb/git/ansible-inc/ansible-workspace-2/test/integration/targets/iosxr_system/tests/cli/set_name_servers.yaml:39
fatal: [iosxr01]: FAILED! => {
    "assertion": "result.changed == false", 
    "changed": false, 
    "evaluated_to": false, 
   
```



Discussion:
cc @rcarrillocruz @privateip @Qalthos
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)
<!--- boilerplate: notify --->

Resolving patch:
diff --git a/lib/ansible/module_utils/iosxr.py b/lib/ansible/module_utils/iosxr.py
--- a/lib/ansible/module_utils/iosxr.py
+++ b/lib/ansible/module_utils/iosxr.py
@@ -27,7 +27,7 @@
 # USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 #
 from ansible.module_utils.basic import env_fallback
-from ansible.module_utils.network_common import to_list
+from ansible.module_utils.network_common import to_list, ComplexList
 from ansible.module_utils.connection import exec_command
 
 _DEVICE_CONFIGS = {}
@@ -64,9 +64,20 @@ def get_config(module, flags=[]):
         _DEVICE_CONFIGS[cmd] = cfg
         return cfg
 
+
+def to_commands(module, commands):
+    spec = {
+        'command': dict(key=True),
+        'prompt': dict(),
+        'answer': dict()
+    }
+    transform = ComplexList(spec, module)
+    return transform(commands)
+
+
 def run_commands(module, commands, check_rc=True):
     responses = list()
-
+    commands = to_commands(module, to_list(commands))
     for cmd in to_list(commands):
         rc, out, err = exec_command(module, cmd)
         if check_rc and rc != 0:
@@ -75,6 +86,7 @@ def run_commands(module, commands, check_rc=True):
     return responses
 
 def load_config(module, commands, commit=False, replace=False, comment=None):
+
     rc, out, err = exec_command(module, 'configure terminal')
     if rc != 0:
         module.fail_json(msg='unable to enter configuration mode', err=err)
@@ -82,7 +94,7 @@ def load_config(module, commands, commit=False, replace=False, comment=None):
     failed = False
     for command in to_list(commands):
         if command == 'end':
-            pass
+            continue
 
         rc, out, err = exec_command(module, command)
         if rc != 0:
diff --git a/lib/ansible/modules/network/iosxr/iosxr_system.py b/lib/ansible/modules/network/iosxr/iosxr_system.py
--- a/lib/ansible/modules/network/iosxr/iosxr_system.py
+++ b/lib/ansible/modules/network/iosxr/iosxr_system.py
@@ -258,10 +258,8 @@ def main():
     result['commands'] = commands
 
     if commands:
-        commit = not module.check_mode
-        response = load_config(module, commands, commit=commit)
-        if response.get('diff') and module._diff:
-            result['diff'] = {'prepared': response.get('diff')}
+        if not module.check_mode:
+            load_config(module, commands, commit=True)
         result['changed'] = True
 
     module.exit_json(**result)


Test patch: