How to show invocation module arguments using official stdout_callback plugins? - ansible

I've been using the human_log.py callback plugin (https://github.com/n0ts/ansible-human_log or https://gist.github.com/cliffano/9868180) for some time now. When a task runs, it shows information useful for debugging:
TASK [server : Install nginx] ************************************************
ok: [192.168.1.2]
{ '_ansible_no_log': False,
'_ansible_parsed': True,
'cache_update_time': 1548736000,
'cache_updated': True,
'changed': False,
'invocation': { 'module_args': { 'allow_unauthenticated': False,
'autoclean': False,
'autoremove': False,
'cache_valid_time': 0,
'deb': None,
'default_release': None,
'dpkg_options': 'force-confdef,force-confold',
'force': False,
'force_apt_get': False,
'install_recommends': None,
'name': 'nginx-extras',
'only_upgrade': False,
'package': ['nginx-extras'],
'purge': False,
'state': 'present',
'update-cache': True,
'update_cache': True,
'upgrade': None}}}
I recently became aware of the existence of the Ansible stdout_callback option. For example, in ansible.cfg:
[defaults]
stdout_callback = debug # or json, or yaml, etc.
# Alternative method: stdout_callback can be set as an environmental variable instead.
# For example: ANSIBLE_STDOUT_CALLBACK=debug ansible-playbook tasks.yml
With stdout_callback = debug set, the output is:
TASK [server : Install nginx] ****************
ok: [192.168.1.2] => {
"cache_update_time": 1548738000,
"cache_updated": true,
"changed": false
}
It's nice to have an official way for showing more information in the output, but the official plugins have one glaring omission: invocation and module_args are missing. The output does not show the arguments that the module was called with.
Is it possible to show the module arguments in the output without using third-party plugins (such as human_log.py)?
(Reference: the full list of official stdout_callback plugins is here: https://docs.ansible.com/ansible/2.7/plugins/callback.html#plugin-list. Note: the plugins only show their effect when ansible-playbook is run using the verbose flag [-v])

I think this is not the location to request such a function.
You should head over to the official pages and make a request over there.
https://github.com/ansible/ansible/issues
Interesting plugin tho!

Executing ansible-playbook with -vvv verbosity shows invocation parameters even with default stdout callback.

Related

Ansible WinRM: Unknown failure when polling update result - attempting to cancel task: pop from empty list

Linux Ubuntu 18
Ansile 2.9.27
Python 3.6
pywinrm 0.4.2
Remote host: Microsoft Windows 10 Enterprise
How to fix the error pop from empty list ?
Ansible playbook code:
- win_updates:
category_names:
- CriticalUpdates
reboot: no
reboot_timeout: 1000
Ansible console log showing Warning:
<1.2.3.4> Running win_updates - round 1
<1.2.3.4> Starting update task
Using module file /somewhere/collections/ansible_collections/ansible/windows/plugins/modules/win_updates.ps1
Pipelining is enabled.
EXEC (via pipeline wrapper)
<1.2.3.4> Starting polling for update results
EXEC (via pipeline wrapper)
[WARNING]: Unknown failure when polling update result - attempting to cancel task: pop from empty list
EXEC (via pipeline wrapper)
EXEC (via pipeline wrapper)
Error printed to console:
IndexError: pop from empty list
fatal: [1.2.3.4]: FAILED! => {
"changed": false,
"failed_update_count": 0,
"filtered_updates": {},
"found_update_count": 0,
"installed_update_count": 0,
"invocation": {
"module_args": {
"accept_list": null,
"category_names": [
"CriticalUpdates"
],
"log_path": null,
"reboot": false,
"reboot_timeout": 1000,
"reject_list": null,
"server_selection": "default",
"skip_optional": false,
"state": "installed",
"use_scheduled_task": false
}
},
"msg": "pop from empty list",
"updates": {}
}
Potential workaround
The code fails in win_updates.py --> offset = int(lines.pop(-1)) , when lines is empty. Is lines = stdout.splitlines() expected to return something always? Otherwise, we can just ignore the pop(-1) when it's empty.

Parsing value from non-trivial JSON using Ansibles uri module

I have this (in the example shown I reduced it by removing many lines) non-trivial JSON retrieved from a Spark server:
{
"spark.worker.cleanup.enabled": true,
"spark.worker.ui.retainedDrivers": 50,
"spark.worker.cleanup.appDataTtl": 7200,
"fusion.spark.worker.webui.port": 8082,
"fusion.spark.worker.memory": "4g",
"fusion.spark.worker.port": 8769,
"spark.worker.timeout": 30
}
I try to read fusion.spark.worker.memory but fail to do so. In my debug statements I can see that the information is there:
msg: "Spark memory: {{spark_worker_cfg.json}} shows this:
ok: [process1] => {
"msg": "Spark memory: {u'spark.worker.ui.retainedDrivers': 50, u'spark.worker.cleanup.enabled': True, u'fusion.spark.worker.port': 8769, u'spark.worker.cleanup.appDataTtl': 7200, u'spark.worker.timeout': 30, u'fusion.spark.worker.memory': u'4g', u'fusion.spark.worker.webui.port': 8082}"
}
The dump using var: spark_worker_cfg shows this:
ok: [process1] => {
"spark_worker_cfg": {
"changed": false,
"connection": "close",
"content_length": "279",
"content_type": "application/json",
"cookies": {},
"cookies_string": "",
"failed": false,
"fusion_request_id": "Pj2zeWThLw",
"json": {
"fusion.spark.worker.memory": "4g",
"fusion.spark.worker.port": 8769,
"fusion.spark.worker.webui.port": 8082,
"spark.worker.cleanup.appDataTtl": 7200,
"spark.worker.cleanup.enabled": true,
"spark.worker.timeout": 30,
"spark.worker.ui.retainedDrivers": 50
},
"msg": "OK (279 bytes)",
"redirected": false,
"server": "Jetty(9.4.12.v20180830)",
"status": 200,
"url": "http://localhost:8765/api/v1/configurations?prefix=spark.worker"
}
}
I can't access the value using {{spark_worker_cfg.json.fusion.spark.worker.memory}}, my problem seems to be caused by the names containing dots:
The task includes an option with an undefined variable. The error was:
'dict object' has no attribute 'fusion'
I have had a look at two SO posts (1 and 2) that look like duplicates of my question but could not derive from them how to solve my current issue.
The keys in the 'json' element of the data structure, contain literal dots, rather than represent a structure. This will causes issues, because Ansible will not know to treat them as literal if dotted notation is used. Therefore, use square bracket notation to reference them, rather than dotted:
- debug:
msg: "{{ spark_worker_cfg['json']['fusion.spark.worker.memory'] }}"
(At first glance this looked like an issue with a JSON encoded string that needed decoding, which could have been handled:"{{ spark_worker_cfg.json | from_json }}")
You could use the json_query filter to get your results. https://docs.ansible.com/ansible/latest/user_guide/playbooks_filters.html
msg="{{ spark_worker_cfg.json | json_query('fusion.spark.worker.memory') }}
edit:
In response to your comment, the fact that we get an empty string returned leads me to believe that the query isn't correct. It can be frustrating to find the exact query while using the json_query filter so I usually use a jsonpath tool beforehand. I've linking one in my comment below but I, personally, use the jsonUtils addon in intelliJ to find my path (which still needs adjustment because the paths are handled a bit differently between the two).
If your json looked like this:
{
value: "theValue"
}
then
json_query('value')
would work.
The path you're passing to json_query isn't correct for what you're trying to do.
If your top level object was named fusion_spark_worker_memory (without the periods), then your query should work. The dots are throwing things off, I believe. There may be a way to escape those in the query...
edit 2: clockworknet for the win! He beat me to it both times. :bow:

Building custom module Ansible

I am trying to build a custom module for our private cloud Infrastructure.
I followed this doc http://docs.ansible.com/ansible/latest/dev_guide/developing_modules_general.html
I created a my_module.py module file.
When I hit ansible-playbook playbook/my_module.yml
Response:
PLAY [Create, Update, Delete VM] *********************************************************************************************************
TASK [Gathering Facts] ************************************************************************************************************************
ok: [localhost]
TASK [VM create] *************************************************************************************************************************
changed: [localhost]
TASK [dump test output] ***********************************************************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"failed": false,
"message": "goodbye",
"original_message": "pcp_vm_ansible"
}
}
PLAY RECAP ************************************************************************************************************************************
localhost : ok=3 changed=1 unreachable=0 failed=0
Which means it is working fine as expected.
Module.py
from ansible.module_utils.basic import AnsibleModule
def run_module():
module_args = dict(
name=dict(type='str', required=True),
new=dict(type='bool', required=False, default=False)
)
result = dict(
changed=False,
original_message='',
message=''
)
module = AnsibleModule(
argument_spec=module_args,
supports_check_mode=True
)
if module.check_mode:
return result
result['original_message'] = module.params['name']
result['message'] = 'goodbye'
if module.params['new']:
result['changed'] = True
if module.params['name'] == 'fail me':
module.fail_json(msg='You requested this to fail', **result)
module.exit_json(**result)
def main():
print("================== Main Called =======================")
run_module()
if __name__ == '__main__':
main()
I am trying to print logs to visualize my input data using print() or even logging.
print("================== Main Called =======================")
But nothing is getting printed to console.
As per Conventions, Best Practices, and Pitfalls, "Modules must output valid JSON only. The top level return type must be a hash (dictionary) although they can be nested. Lists or simple scalar values are not supported, though they can be trivially contained inside a dictionary."
Effectively, the core runtime only communicates with the module via JSON and the core runtime controls stdout so the standard print statements from the module are suppressed. If you want or need more information out of the execution runtime then I suggest a Callback Plugin.

Digging down an Ansible fact

I have a playbook
---
- hosts: all
gather_facts: True
tasks:
- action: debug msg="time = {{ ansible_date_time }}"
Which returns the full json representation for each machine.
How do I further filter that within the playbook such that I only get the iso8601_basic_short part
[root#pjux playbooks]# ansible --version
ansible 2.1.1.0
config file = /etc/ansible/ansible.cfg
configured module search path = Default w/o overrides
TASK [debug] *******************************************************************
ok: [10.99.97.222] => {
"msg": "time = {u'weekday_number': u'2', u'iso8601_basic_short': u'20160906T182117', u'tz': u'BST', u'weeknumber': u'36', u'hour': u'18', u'year': u'2016', u'minute': u'21', u'tz_offset': u'+0100', u'month': u'09', u'epoch': u'1473182477', u'iso8601_micro': u'2016-09-06T17:21:17.761900Z', u'weekday': u'Tuesday', u'time': u'18:21:17', u'date': u'2016-09-06', u'iso8601': u'2016-09-06T17:21:17Z', u'day': u'06', u'iso8601_basic': u'20160906T182117761843', u'second': u'17'}"
}
ok: [10.99.97.216] => {
"msg": "time = {u'weekday_number': u'2', u'iso8601_basic_short': u'20160906T182117', u'tz': u'BST', u'weeknumber': u'36', u'hour': u'18', u'year': u'2016', u'minute': u'21', u'tz_offset': u'+0100', u'month': u'09', u'epoch': u'1473182477', u'iso8601_micro': u'2016-09-06T17:21:17.938563Z', u'weekday': u'Tuesday', u'time': u'18:21:17', u'date': u'2016-09-06', u'iso8601': u'2016-09-06T17:21:17Z', u'day': u'06', u'iso8601_basic': u'20160906T182117938491', u'second': u'17'}"
}
Have you tried {{ ansible_date_time.iso8601_basic_short }}?

Debugging Ansible modules

I tried to write an Ansible module. My module is buggy. When I run it from a playbook I get the following unreadable output:
$ ansible-playbook lacp.yml
PLAY [xxxxxxxx] ****************************************************************
TASK [Test that my module works] ***********************************************
fatal: [xxxxxxxx]: FAILED! => {"changed": false, "failed": true, "module_stderr": "couldn't set locale correctly\ncouldn't set locale correctly\ncouldn't set locale correctly\nTraceback (most recent call last):\n File \"/tmp/ansible_vHkWq8/ansible_module_lacp.py\", line 27, in <module>\n main()\n File \"/tmp/ansible_vHkWq8/ansible_module_lacp.py\", line 14, in main\n m = re.match('^key: ([0-9]+) ', dladm.readline())\nAttributeError: 'Popen' object has no attribute 'readline'\ndladm: insufficient privileges\n", "module_stdout": "", "msg": "MODULE FAILURE", "parsed": false}
NO MORE HOSTS LEFT *************************************************************
[WARNING]: Could not create retry file 'lacp.retry'. [Errno 2] No such file or directory: ''
PLAY RECAP *********************************************************************
xxxxxxxx : ok=0 changed=0 unreachable=0 failed=1
How to stop Ansible quoting error messages with JSON? Or is there another way to debug Ansible modules?
How to stop Ansible quoting error messages with JSON?
You can use human_log.py plugin to force Ansible to interpret and print newline characters in its output.
You put the file into /path/to/callback_plugins/ directory and add the following to the ansible.cfg:
[defaults]
callback_plugins = /path/to/callback_plugins/
The detailed instructions are in the Human-Readable Ansible Playbook Log Output Using Callback Plugin blog post.
You can look into this and this callback plugins (for Ansible 2.x).
You will need to modify them a bit, because they don't convert module_stderr out of the box.
Also you may want to execute playbook with ANSIBLE_KEEP_REMOTE_FILES=1, then ssh to the remote box and debug your module in-place, then save to ansible library.
Debugging Ansible modules will quickly become next to impossible and very, very time consuming without following the recommended approach.
The recommended approach is to build your Ansible stuff using very small steps. That way you can more easily guess what is wrong as you add stuff to what you know and have verified to work.
So when you state that the module is buggy, you have gone to far. You will be searching for a needle in the haystack that Ansible without question is.
Refactoring is not really a practical option. You basically start fresh, recreating your code step by step.
I hope you noticed that Ansible doesn't even bother to format error output in a human readable way. In a way on error Ansible outputs the same message: something went wrong.
Let's say I have this Ansible task
- name: Mymodule
mymodule:
something: "something"
My module also is simple enough
#!/usr/bin/python
from ansible.module_utils.basic import *
def somefunction(data):
has_changed = False
meta = { "something": "something"}
return (has_changed, meta)
def main():
fields = {
"something": {"required": True, "type": "str"},
"state": {
"default": "perform",
"choices": ["perform"],
"type": "str"
},
}
choice_map = {
"perform2": somefunction,
}
module = AnsibleModule(argument_spec=fields)
has_changed, result = choice_map.get(module.params['state'])(module.params)
module.exit_json(changed=has_changed, meta=result)
if __name__ == '__main__':
main()
Ansible will produce the following error message
TASK [backup : Mymodule]
******************************************************* fatal: [myapp]: FAILED! => {"changed": false, "module_stderr": "Shared
connection to 127.0.0.1 closed.\r\n", "module_stdout": "Traceback
(most recent call last):\r\n File
\"/home/vagrant/.ansible/tmp/ansible-tmp-1570188887.99-191548982937437/AnsiballZ_mymodule.py\",
line 114, in \r\n _ansiballz_main()\r\n File
\"/home/vagrant/.ansible/tmp/ansible-tmp-1570188887.99-191548982937437/AnsiballZ_mymodule.py\",
line 106, in _ansiballz_main\r\n invoke_module(zipped_mod,
temp_path, ANSIBALLZ_PARAMS)\r\n File
\"/home/vagrant/.ansible/tmp/ansible-tmp-1570188887.99-191548982937437/AnsiballZ_mymodule.py\",
line 49, in invoke_module\r\n imp.load_module('main', mod,
module, MOD_DESC)\r\n File
\"/tmp/ansible_mymodule_payload_XTaVPp/main.py\", line 29, in
\r\n File
\"/tmp/ansible_mymodule_payload_XTaVPp/main.py\", line 25, in
main\r\nTypeError: 'NoneType' object is not callable\r\n", "msg":
"MODULE FAILURE\nSee stdout/stderr for the exact error", "rc": 1}
The "message" we should focus on is
TypeError: 'NoneType' object is not callable
It is caused by the wrong action perform2. It should be perform. A simple typo.
choice_map = {
"perform2": somefunction,
}
The typo is in module file mymodule.py on line 21. The files and lines 114, 106, 49, 29, 25 might be useful in someway but how these files are useful is not clear at all.
This is just a very simple example to illustrate the point of the haystack. Ansible does not format the error message in human readable way. Reporting on the problem file and line number is also not an exact science. And the error message is not useful. The error message should be that my choice_map is referencing a non-existing action. It could list the available choices.
IMHO this is a common problem with Ansible. A typing mistake can take a hour to fix.
The only way to workaround this limitation is to build up provision step by step. Baby steps.

Resources