# Buggy code
def map_obj_to_commands(updates, module):
    commands = list()
    state = module.params["state"]  # FIXME NOT USED

    for update in updates:
        want, have = update

        def needs_update(want, have, x):
            return want.get(x) and (want.get(x) != have.get(x))

        if want["state"] == "absent":
            commands.append("no vrf definition %s" % want["name"])
            continue

        if not have.get("state"):
            commands.extend(
                [
                    "vrf definition %s" % want["name"],
                    "address-family ipv4",
                    "exit",
                    "address-family ipv6",
                    "exit",
                ]
            )

        if needs_update(want, have, "description"):
            cmd = "description %s" % want["description"]
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "rd"):
            cmd = "rd %s" % want["rd"]
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_import"):
            for route in want["route_import"]:
                cmd = "route-target import %s" % route
                add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_export"):
            for route in want["route_export"]:
                cmd = "route-target export %s" % route
                add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_both"):
            for route in want["route_both"]:
                cmd = "route-target both %s" % route
                add_command_to_vrf(want["name"], cmd, commands)

        if want["interfaces"] is not None:
            # handle the deletes
            for intf in set(have.get("interfaces", [])).difference(want["interfaces"]):
                commands.extend(
                    ["interface %s" % intf, "no vrf forwarding %s" % want["name"]]
                )

            # handle the adds
            for intf in set(want["interfaces"]).difference(have.get("interfaces", [])):
                cfg = get_config(module)
                configobj = NetworkConfig(indent=1, contents=cfg)
                children = configobj["interface %s" % intf].children
                intf_config = "\n".join(children)

                commands.extend(
                    ["interface %s" % intf, "vrf forwarding %s" % want["name"]]
                )

                match = re.search("ip address .+", intf_config, re.M)
                if match:
                    commands.append(match.group())

    return commands


# Traceback (TypeError)
ansible-playbook 2.5.4
config file = None
configured module search path = [u'/home/e600790/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/lib/python2.7/dist-packages/ansible
executable location = /usr/local/bin/ansible-playbook
python version = 2.7.13 (default, Nov 24 2017, 17:33:09) [GCC 6.3.0 20170516]
No config file found; using defaults
setting up inventory plugins
Set default localhost to localhost
Parsed /media/sf_Shared/ansible/static_route/inventory inventory source with ini plugin
Loading callback plugin default of type stdout, v2.0 from /usr/local/lib/python2.7/dist-packages/ansible/plugins/callback/default.pyc

PLAYBOOK: swenet-vrf.yml ****************************************************************************************************************************************************************************
1 plays in swenet-vrf.yml

PLAY [vrf] ******************************************************************************************************************************************************************************************
META: ran handlers

TASK [vrf definition] ***************************************************************************************************************************************************************************
task path: /media/sf_Shared/git/prod-network/swenet-vrf.yml:43
<ix-test-pe01.net.dcinf.se> attempting to start connection
<ix-test-pe01.net.dcinf.se> using connection plugin network_cli
<ix-test-pe02.net.dcinf.se> attempting to start connection
<ix-test-pe02.net.dcinf.se> using connection plugin network_cli
<ix-test-pe01.net.dcinf.se> local domain socket does not exist, starting it
<ix-test-pe01.net.dcinf.se> control socket path is /home/e600790/.ansible/pc/97af846ba3
<ix-test-pe01.net.dcinf.se> <ix-test-pe01.net.dcinf.se> ESTABLISH CONNECTION FOR USER: e600790 on PORT 22 TO ix-test-pe01.net.dcinf.se
<ix-test-pe01.net.dcinf.se> <ix-test-pe01.net.dcinf.se> ssh connection done, setting terminal
<ix-test-pe01.net.dcinf.se> <ix-test-pe01.net.dcinf.se> loaded terminal plugin for network_os ios
<ix-test-pe01.net.dcinf.se> <ix-test-pe01.net.dcinf.se> loaded cliconf plugin for network_os ios
<ix-test-pe01.net.dcinf.se> <ix-test-pe01.net.dcinf.se> firing event: on_open_shell()
<ix-test-pe01.net.dcinf.se> <ix-test-pe01.net.dcinf.se> ssh connection has completed successfully
<ix-test-pe01.net.dcinf.se> connection to remote device started successfully
<ix-test-pe01.net.dcinf.se> local domain socket listeners started successfully
<ix-test-pe01.net.dcinf.se>
<ix-test-pe01.net.dcinf.se> local domain socket path is /home/e600790/.ansible/pc/97af846ba3
<ix-test-pe02.net.dcinf.se> local domain socket does not exist, starting it
<ix-test-pe02.net.dcinf.se> control socket path is /home/e600790/.ansible/pc/c420ed9159
<ix-test-pe02.net.dcinf.se> <ix-test-pe02.net.dcinf.se> ESTABLISH CONNECTION FOR USER: e600790 on PORT 22 TO ix-test-pe02.net.dcinf.se
<ix-test-pe02.net.dcinf.se> <ix-test-pe02.net.dcinf.se> ssh connection done, setting terminal
<ix-test-pe02.net.dcinf.se> <ix-test-pe02.net.dcinf.se> loaded terminal plugin for network_os ios
<ix-test-pe02.net.dcinf.se> <ix-test-pe02.net.dcinf.se> loaded cliconf plugin for network_os ios
<ix-test-pe02.net.dcinf.se> <ix-test-pe02.net.dcinf.se> firing event: on_open_shell()
<ix-test-pe02.net.dcinf.se> <ix-test-pe02.net.dcinf.se> ssh connection has completed successfully
<ix-test-pe02.net.dcinf.se> connection to remote device started successfully
<ix-test-pe02.net.dcinf.se> local domain socket listeners started successfully
<ix-test-pe02.net.dcinf.se>
<ix-test-pe02.net.dcinf.se> local domain socket path is /home/e600790/.ansible/pc/c420ed9159
Using module file /home/e600790/.ansible/plugins/modules/devel_ios_vrf.py
<ix-test-pe02.net.dcinf.se> ESTABLISH LOCAL CONNECTION FOR USER: e600790
<ix-test-pe02.net.dcinf.se> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358 `" &amp;&amp; echo ansible-tmp-1529046216.44-94701435360358="` echo /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358 `" ) &amp;&amp; sleep 0'
Using module file /home/e600790/.ansible/plugins/modules/devel_ios_vrf.py
<ix-test-pe01.net.dcinf.se> ESTABLISH LOCAL CONNECTION FOR USER: e600790
<ix-test-pe01.net.dcinf.se> EXEC /bin/sh -c '( umask 77 &amp;&amp; mkdir -p "` echo /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068 `" &amp;&amp; echo ansible-tmp-1529046216.44-237853136648068="` echo /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068 `" ) &amp;&amp; sleep 0'
<ix-test-pe02.net.dcinf.se> PUT /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/tmpttIp1v TO /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358/devel_ios_vrf.py
<ix-test-pe02.net.dcinf.se> EXEC /bin/sh -c 'chmod u+x /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358/ /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358/devel_ios_vrf.py &amp;&amp; sleep 0'
<ix-test-pe01.net.dcinf.se> PUT /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/tmp1K9JSV TO /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068/devel_ios_vrf.py
<ix-test-pe01.net.dcinf.se> EXEC /bin/sh -c 'chmod u+x /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068/ /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068/devel_ios_vrf.py &amp;&amp; sleep 0'
<ix-test-pe02.net.dcinf.se> EXEC /bin/sh -c '/usr/bin/python /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358/devel_ios_vrf.py &amp;&amp; sleep 0'
<ix-test-pe01.net.dcinf.se> EXEC /bin/sh -c '/usr/bin/python /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068/devel_ios_vrf.py &amp;&amp; sleep 0'
<ix-test-pe02.net.dcinf.se> EXEC /bin/sh -c 'rm -f -r /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-94701435360358/ > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
<ix-test-pe01.net.dcinf.se> EXEC /bin/sh -c 'rm -f -r /home/e600790/.ansible/tmp/ansible-local-1868dyi_qh/ansible-tmp-1529046216.44-237853136648068/ > /dev/null 2>&amp;1 &amp;&amp; sleep 0'
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_Aug5Jg/ansible_module_devel_ios_vrf.py", line 520, in <module>
main()
File "/tmp/ansible_Aug5Jg/ansible_module_devel_ios_vrf.py", line 497, in main
commands = map_obj_to_commands(update_objects(want, have), module)
File "/tmp/ansible_Aug5Jg/ansible_module_devel_ios_vrf.py", line 428, in update_objects
elif value != item[key]:
TypeError: 'NoneType' object has no attribute '__getitem__'

fatal: [ix-test-pe02.net.dcinf.se]: FAILED! => {
"changed": false,
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_Aug5Jg/ansible_module_devel_ios_vrf.py\", line 520, in <module>\n    main()\n  File \"/tmp/ansible_Aug5Jg/ansible_module_devel_ios_vrf.py\", line 497, in main\n    commands = map_obj_to_commands(update_objects(want, have), module)\n  File \"/tmp/ansible_Aug5Jg/ansible_module_devel_ios_vrf.py\", line 428, in update_objects\n    elif value != item[key]:\nTypeError: 'NoneType' object has no attribute '__getitem__'\n",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 1
}
The full traceback is:
Traceback (most recent call last):
File "/tmp/ansible_52bkKf/ansible_module_devel_ios_vrf.py", line 520, in <module>
main()
File "/tmp/ansible_52bkKf/ansible_module_devel_ios_vrf.py", line 497, in main
commands = map_obj_to_commands(update_objects(want, have), module)
File "/tmp/ansible_52bkKf/ansible_module_devel_ios_vrf.py", line 428, in update_objects
elif value != item[key]:
TypeError: 'NoneType' object has no attribute '__getitem__'

fatal: [ix-test-pe01.net.dcinf.se]: FAILED! => {
"changed": false,
"module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_52bkKf/ansible_module_devel_ios_vrf.py\", line 520, in <module>\n    main()\n  File \"/tmp/ansible_52bkKf/ansible_module_devel_ios_vrf.py\", line 497, in main\n    commands = map_obj_to_commands(update_objects(want, have), module)\n  File \"/tmp/ansible_52bkKf/ansible_module_devel_ios_vrf.py\", line 428, in update_objects\n    elif value != item[key]:\nTypeError: 'NoneType' object has no attribute '__getitem__'\n",
"module_stdout": "",
"msg": "MODULE FAILURE",
"rc": 1
}

NO MORE HOSTS LEFT **********************************************************************************************************************************************************************************
to retry, use: --limit @/media/sf_Shared/git/prod-network/swenet-vrf.retry

PLAY RECAP ******************************************************************************************************************************************************************************************
ix-test-pe01.net.dcinf.se  : ok=0    changed=0    unreachable=0    failed=1
ix-test-pe02.net.dcinf.se  : ok=0    changed=0    unreachable=0    failed=1

# Fixed code
def map_obj_to_commands(updates, module):
    commands = list()

    for update in updates:
        want, have = update

        def needs_update(want, have, x):
            if isinstance(want.get(x), list) and isinstance(have.get(x), list):
                return (
                    want.get(x)
                    and (want.get(x) != have.get(x))
                    and not all(elem in have.get(x) for elem in want.get(x))
                )
            return want.get(x) and (want.get(x) != have.get(x))

        if want["state"] == "absent":
            commands.append("no vrf definition %s" % want["name"])
            continue

        if not have.get("state"):
            commands.extend(
                [
                    "vrf definition %s" % want["name"],
                    "address-family ipv4",
                    "exit",
                    "address-family ipv6",
                    "exit",
                ]
            )

        if needs_update(want, have, "description"):
            cmd = "description %s" % want["description"]
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "rd"):
            cmd = "rd %s" % want["rd"]
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_import"):
            for route in want["route_import"]:
                cmd = "route-target import %s" % route
                add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_export"):
            for route in want["route_export"]:
                cmd = "route-target export %s" % route
                add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_import_ipv4"):
            cmd = "address-family ipv4"
            add_command_to_vrf(want["name"], cmd, commands)
            for route in want["route_import_ipv4"]:
                cmd = "route-target import %s" % route
                add_command_to_vrf(want["name"], cmd, commands)
            cmd = "exit-address-family"
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_export_ipv4"):
            cmd = "address-family ipv4"
            add_command_to_vrf(want["name"], cmd, commands)
            for route in want["route_export_ipv4"]:
                cmd = "route-target export %s" % route
                add_command_to_vrf(want["name"], cmd, commands)
            cmd = "exit-address-family"
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_import_ipv6"):
            cmd = "address-family ipv6"
            add_command_to_vrf(want["name"], cmd, commands)
            for route in want["route_import_ipv6"]:
                cmd = "route-target import %s" % route
                add_command_to_vrf(want["name"], cmd, commands)
            cmd = "exit-address-family"
            add_command_to_vrf(want["name"], cmd, commands)

        if needs_update(want, have, "route_export_ipv6"):
            cmd = "address-family ipv6"
            add_command_to_vrf(want["name"], cmd, commands)
            for route in want["route_export_ipv6"]:
                cmd = "route-target export %s" % route
                add_command_to_vrf(want["name"], cmd, commands)
            cmd = "exit-address-family"
            add_command_to_vrf(want["name"], cmd, commands)

        if want["interfaces"] is not None:
            # handle the deletes
            for intf in set(have.get("interfaces", [])).difference(want["interfaces"]):
                commands.extend(
                    ["interface %s" % intf, "no vrf forwarding %s" % want["name"]]
                )

            # handle the adds
            for intf in set(want["interfaces"]).difference(have.get("interfaces", [])):
                cfg = get_config(module)
                configobj = NetworkConfig(indent=1, contents=cfg)
                children = configobj["interface %s" % intf].children
                intf_config = "\n".join(children)

                commands.extend(
                    ["interface %s" % intf, "vrf forwarding %s" % want["name"]]
                )

                match = re.search("ip address .+", intf_config, re.M)
                if match:
                    commands.append(match.group())

    return commands