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.
Related
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.
I'm using the Ansible slack module to send notifications, which include #mentions for notifiable people. I'm having trouble with multi-word display names, though single-word display names work fine.
For example:
ok: [localhost] => {
"msg": "message: `SANDPIT` started ELMS and LACM MapGuide\nFYI <#alice> <#bob>"
}
But then <#Genghis Khan> comes to work alongside Alice and Bob. And he, because he's Ghengis Khan, of course chooses a multi-word display name. Given the following message:
ok: [localhost] => {
"msg": "message: `SANDPIT` started ELMS and LACM MapGuide\nFYI <#Ghenkis Khan> <#bob>"
}
#bob is recognised, <#Ghenkis Khan> isn't.
I've also tried user ids as recommended at https://api.slack.com/changelog/2017-09-the-one-about-usernames but they don't work any better, so I've reverted to display names. But for completeness here is an example of the message content, with valid user ids but an invalid result:
TASK [slack : debug]
***********************************************************
ok: [localhost] => {
"msg": "message: `SANDPIT` started ELMS and LACM MapGuide\nFYI <#U12345DK> <#U12345TT>"
}
As mentioned, Slack is not buying these user ids either.
Here's the Ansible task that sends the message to Slack. All tokens etc are working as expected, and this is all happening in the same workspace.
- name: Send message via Slack
slack:
token: 'valid token'
color: 'valid color'
msg: '{{ message }}'
link_names: 1
# problems with notifications should not fail the pipeline
ignore_errors: yes
According to the official documentation of the msg property of the slack plugin all angle brackets and ampersands need to be escaped using HTML notation:
Message to send. Note that the module does not handle escaping
characters. Plain-text angle brackets and ampersands should be
converted to HTML entities (e.g. & to &) before sending. See
Slack's documentation (https://api.slack.com/docs/message-formatting)
for more.
So you want to try something like this:
TASK [slack : debug]
***********************************************************
ok: [localhost] => {
"msg": "message: `SANDPIT` started ELMS and LACM MapGuide\nFYI <#U12345DK> <#U12345TT>"
}
I am now experiencing a typical problem in ansible V 2.1.0. In the case below,
[DEV:children]
DEV8
[DEV8]
thehost ansible_ssh_host=10.2.131.26 ansible_ssh_user=someuser1
Now, the when I run
{{hostvars[inventory_hostname].group_names, it outputs
TASK [debug] ************************************************************
ok: [thehost] => {
"msg": [
"DEV",
"DEV8"
]
}
Now, for other group of machines
[PRODCTE:children]
CTE3
[CTE3]
thehost1 ansible_ssh_host=10.2.131.30 ansible_ssh_user=someuser2
output:
TASK [debug] *******************************************************************
ok: [thehost] => {
"msg": [
"CTE3",
"PRODCTE"
]
}
PROBLEM:
[PROD]
PRODA
[PRODA]
PROD1
[PROD1]
thehost2 ansible_ssh_host=10.2.3.33 ansible_ssh_user=someuser3
output:
TASK [debug] *******************************************************************
ok: [thehost] => {
"msg": [
"PROD",
"PROD1"
"PRODA"
]
}
Now, If ansible code is to execute alphabetically, then consistency cannot be achieved. The output always has to be consistent. I mean, if group_names[0] or group_names[1] shows me different values for different groups based alphabetically, the playbooks cannot be standardized.
Anyways, even if go with this behavior, I am trying to understand on what factors does ansible outputs these values?
If alphabetically, then how was PROD1 chosen over PRODA? Does ansible considers numerics to be priority than alphabets here?
Why should it be parent->children?
I guess it is supposed to be alphabetically sorted. From code:
results['group_names'] = sorted([ g.name for g in self.get_groups() if g.name != 'all'])
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 }}?
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.