While developing an Ansible module that uses ansible.netcommon / network_cli connection plugin, I need to debug what is going on with the connection flow and internal module, actions & plugin code. The particular combination of AnsiballZ zip file and local execution with Ansible network_cli connection type seems to add some extra complexity to navigate when trying to debug this.
This guide gives some helpful instructions. Yet, due to how AnsiballZ deals with the internal zipped module code paths seems to cause problems for the debugger when it tries to find the code files being executed.
I have instrumented my network_cli plugin code using debugpy like so:
def run_commands(module, commands, check_rc=True):
# BEGIN DEBUG INSTRUMENTATION
import debugpy
debugpy.listen(("0.0.0.0", 5678))
debugpy.wait_for_client()
debugpy.breakpoint()
# END DEBUG INSTRUMENTATION
connection = get_connection(module)
try:
return connection.run_commands(commands=commands, check_rc=check_rc)
except ConnectionError as exc:
module.fail_json(msg=to_text(exc))
.vscode/launch.json:
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Remote Attach",
"type": "debugpy",
"request": "attach",
"connect": {
"host": "127.0.0.1",
"port": 5678
},
"pathMappings": [
{
"localRoot": "${workspaceFolder}",
"remoteRoot": "${workspaceFolder}" // does not work
// "remoteRoot": "." // also did not work
// Maybe a hack like this could work... but the tmp dir is random at runtime...
// "remoteRoot": "/tmp/ansible_lyraphase.opnsense.facts_payload_XXXXXXXX/ansible_lyraphase.opnsense.facts_payload.zip/.../path/to/local/python/code"
}
]
}
]
}
When I try each of the following, I run into a different roadblock:
-
Running the
debugpy-instrumented module via a playbook seems to work, but paths are given within a random temporary directory path, and within theAnsiballzpayload.zip. Thus, VSCode debugger fails to find the local python file path to debug. For example:Could not load source '/tmp/ansible_lyraphase.opnsense.facts_payload_sc4ozorn/ansible_lyraphase.opnsense.facts_payload.zip/ansible/module_utils/connection.py': Unable to retrieve source for /tmp/ansible_lyraphase.opnsense.facts_payload_sc4ozorn/ansible_lyraphase.opnsense.facts_payload.zip/ansible/module_utils/connection.py. -
Unpacking the module and running it manually (as in the previously mentioned guide) results in an error when the temporary Ansible connection socket is not found:
$ python3 AnsiballZ_opnsense_facts.py explode Module expanded into: /home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir $ python3 AnsiballZ_opnsense_facts.py execute {"failed": true, "msg": "socket path /home/exampleuser/.ansible/pc/a6f4bd23f0 does not exist or cannot be found. See Troubleshooting socket path issues in the Network Debug and Troubleshooting Guide", "exception": " File \"/home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py\", line 217, in get_capabilities\n capabilities = Connection(module._socket_path).get_capabilities()\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir/ansible/module_utils/connection.py\", line 177, in __rpc__\n response = self._exec_jsonrpc(name, *args, **kwargs)\n ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^\n File \"/home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir/ansible/module_utils/connection.py\", line 124, in _exec_jsonrpc\n raise ConnectionError(\n", "invocation": {"module_args": {"gather_subset": ["default"], "gather_network_resources": null, "opnsense_shell_option": null, "passwords": null}}}
Expand for stacktrace with newlines rendered
File "/home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir/ansible_collections/ansible/netcommon/plugins/module_utils/network/common/network.py", line 217, in get_capabilities
capabilities = Connection(module._socket_path).get_capabilities()
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir/ansible/module_utils/connection.py", line 177, in __rpc__
response = self._exec_jsonrpc(name, *args, **kwargs)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
File "/home/exampleuser/src/pub/ansible/lyraphase-ansible-playbooks/tmp/debug_dir/ansible/module_utils/connection.py", line 124, in _exec_jsonrpc
raise ConnectionError(
", "invocation": {"module_args": {"gather_subset": ["default"], "gather_network_resources": null, "opnsense_shell_option": null, "passwords": null}}}
The first case seems to work, as the VSCode debugger can connect to debugpy server port 5678, and the breakpoint, variables, and call stack are visible in the VSCode debug UI. Yet, the python code file cannot be found to show the currently executing line to step through.
The second case (explode + execute) fails because it expects Ansible to have setup a socket file (via task_executor.py + ansible_connection_cli_stub.py perhaps?)
Searching through Ansible’s source code, I found mention of an environment variable that seems related to this: _ANSIBALLZ_DEBUGPY_CONFIG
_ANSIBALLZ_DEBUGPY_CONFIG: name: Configure the AnsiballZ remote debugging extension for debugpy description: - Enables and configures the AnsiballZ remote debugging extension for debugpy. - This is for internal use only.
How can this be used to configure debugpy for debugging of AnsiballZ-packed code?
