Repository: ansible/ansible-modules-extras

Issue:
Cisco ASA_ACL "NameError: global name 'candidate' is not defined"
##### ISSUE TYPE
- Bug Report
##### COMPONENT NAME

asa_acl
##### ANSIBLE VERSION

```
root@osboxes:~/asa# ansible-playbook -i inventory acl.yml

PLAY [Test ASA] ****************************************************************

TASK [Create object to ACL-ANSIBLE] ********************************************
An exception occurred during task execution. To see the full traceback, use -vvv. The error was: NameError: global name 'candidate' is not defined
fatal: [asa]: FAILED! => {"changed": false, "failed": true, "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_JUM9ib/ansible_module_asa_acl.py\", line 202, in <module>\n    main()\n  File \"/tmp/ansible_JUM9ib/ansible_module_asa_acl.py\", line 184, in main\n    commands = candidate.difference(config)\nNameError: global name 'candidate' is not defined\n", "module_stdout": "", "msg": "MODULE FAILURE"}

PLAY RECAP *********************************************************************
asa                        : ok=0    changed=0    unreachable=0    failed=1

root@osboxes:~/asa#

```
##### CONFIGURATION
##### OS / ENVIRONMENT

Ubuntu
##### SUMMARY

Attempting to run asa_acl using ansible dev version results in above errors. asa_command and asa_config work with no issues. 
##### STEPS TO REPRODUCE

```

---

- name: Test ASA
  hosts: asa
  connection: local
  gather_facts: false

  tasks:

    - name: Create object to ACL-ANSIBLE
      asa_acl:
        lines:
          - "access-list ACL-ANSIBLE extended permit tcp any any eq 74"
        username: "ntc"
        host: "{{ inventory_hostname }}"
        authorize: true
        auth_pass: "\n"
```

```
ansible-playbook -i inventory acl.yml
```
##### EXPECTED RESULTS

ACL to be updated 
##### ACTUAL RESULTS

```
root@osboxes:~/asa# ansible-playbook -i inventory acl.yml -vvvv
Using /etc/ansible/ansible.cfg as config file
Loading callback plugin default of type stdout, v2.0 from /tmp/ansible/lib/ansible/plugins/callback/__init__.pyc

PLAYBOOK: acl.yml **************************************************************
1 plays in acl.yml

PLAY [Test ASA] ****************************************************************

TASK [Create object to ACL-ANSIBLE] ********************************************
task path: /root/asa/acl.yml:10
Using module file /tmp/ansible/lib/ansible/modules/extras/network/asa/asa_acl.py
<asa> ESTABLISH LOCAL CONNECTION FOR USER: root
<asa> EXEC /bin/sh -c '( umask 77 && mkdir -p "` echo $HOME/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872 `" && echo ansible-tmp-1473449999.57-34289925616872="` echo $HOME/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872 `" ) && sleep 0'
<asa> PUT /tmp/tmpu2hszs TO /root/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872/asa_acl.py
<asa> EXEC /bin/sh -c 'chmod u+x /root/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872/ /root/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872/asa_acl.py && sleep 0'
<asa> EXEC /bin/sh -c '/usr/bin/python /root/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872/asa_acl.py; rm -rf "/root/.ansible/tmp/ansible-tmp-1473449999.57-34289925616872/" > /dev/null 2>&1 && sleep 0'
An exception occurred during task execution. The full traceback is:
Traceback (most recent call last):
  File "/tmp/ansible_o9grpZ/ansible_module_asa_acl.py", line 202, in <module>
    main()
  File "/tmp/ansible_o9grpZ/ansible_module_asa_acl.py", line 184, in main
    commands = candidate.difference(config)
NameError: global name 'candidate' is not defined

fatal: [asa]: FAILED! => {
    "changed": false,
    "failed": true,
    "invocation": {
        "module_name": "asa_acl"
    },
    "module_stderr": "Traceback (most recent call last):\n  File \"/tmp/ansible_o9grpZ/ansible_module_asa_acl.py\", line 202, in <module>\n    main()\n  File \"/tmp/ansible_o9grpZ/ansible_module_asa_acl.py\", line 184, in main\n    commands = candidate.difference(config)\nNameError: global name 'candidate' is not defined\n",
    "module_stdout": "",
    "msg": "MODULE FAILURE"
}

PLAY RECAP *********************************************************************
asa                        : ok=0    changed=0    unreachable=0    failed=1


```



Discussion:
<!-- @itdependsnetworks: Thanks for filing this bug report! -->

@ogenstad ping, this issue is waiting for your response.
[click here for bot help](https://github.com/ansible/ansibullbot/blob/master/ISSUE_HELP.md)

<!--- boilerplate: issue_bug_new --->

It looks quite broken. My guess would be that this is due the restructuring of the networking modules in 2.2. If I had to guess I would point to [this commit](https://github.com/ansible/ansible-modules-extras/commit/10e8cdc93a56c9e20a86e99e3a3e471d6a32a1a2), perhaps @privateip knows what's going on?

The candidate is never assigned before being called. In the best of worlds this would have been caught in the CI tests. However I've seen in the chat rooms that the networking modules are currently broken in devel. It might be that this is something which will soon be fixed.

I haven't yet looked at the changes for 2.2, however looking at one of the other modules I see that they now have a function called get_candidate which is missing from the asa_acl one.

So I would first ask if this is some work in progress?


Resolving patch:
diff --git a/network/asa/asa_acl.py b/network/asa/asa_acl.py
--- a/network/asa/asa_acl.py
+++ b/network/asa/asa_acl.py
@@ -128,8 +128,10 @@
   type: list
   sample: ['...', '...']
 """
+import ansible.module_utils.asa
+
 from ansible.module_utils.netcfg import NetworkConfig
-from ansible.module_utils.asa import NetworkModule
+from ansible.module_utils.network import NetworkModule
 
 
 def get_config(module):
@@ -179,16 +181,22 @@ def main():
     match = module.params['match']
     replace = module.params['replace']
 
+    candidate = NetworkConfig(indent=1)
+    candidate.add(lines)
+
     module.filter = check_input_acl(lines, module)
+
     if not module.params['force']:
+        contents = get_config(module)
+        config = NetworkConfig(indent=1, contents=contents)
         commands = candidate.difference(config)
         commands = dumps(commands, 'commands').split('\n')
-        commands = [str(c) for c in commands if c]
     else:
         commands = str(candidate).split('\n')
 
     if commands:
         if not module.check_mode:
+            commands = [str(c) for c in commands if c]
             response = module.config(commands)
             result['responses'] = response
         result['changed'] = True


Test patch: