content: "{{ all_hosts_list.stdout_lines|join('\n') }}"
content: "{{ all_hosts_list.stdout_lines|join('\n') }}"
This line worked better for me:
content: "{{ output.stdout_lines | join('\n') }}\n"
It adds a final LF which is otherwise omitted.
The join function "concatenates a list into a string" using the character provided in the parameter as the separator between list items. The output first exists as a list, without line terminators; the join() converts the list to a single string with LFs at the end of each former list item. Except for the final \n, which I added above.
Docs: https://docs.ansible.com/ansible/latest/playbook_guide/playbooks_filters.html
Save to file formatting is basically unreadable
help bulding - stdout_lines to dict list
ansible - Get exactly data from "stdout" or "stdout_lines" with exact words of output - Stack Overflow
ansible output printing unwanted things. how to format and display only specific data's - Stack Overflow
Try [this][1] option. Youβll love it.
To use it, edit your ansible.cfg file (either globally, in /etc/ansible/ansible.cfg, or locally in your playbook/project), and add the following lines under the [defaults] section:
Copy# Use the YAML callback plugin.
callback_result_format = yaml
What I found to work best so far for getting CLI-like output in Ansible, and which should work out of the box (at least for me on Fedora 34, Ansible 2.9), is setting the unixy callback for condensed Ansible output
Copystdout_callback = unixy
bin_ansible_callbacks = True
in your ansible.cfg. Given the tasks
Copy tasks:
- name: uptime
shell: uptime
- name: volumes
shell: "df -h"
the output in the terminal will look like
Copy- all on hosts: all -
uptime...
host1 done | stdout: 08:20:09 up 33 min, 1 user, load average: 0.55, 0.27, 0.26
host2 done | stdout: 08:20:09 up 1 day, 1:39, 1 user, load average: 0.18, 0.17, 0.17
volumes...
host1 done | stdout: Filesystem Size Used Avail Use% Mounted on
/dev/root 7.2G 1.5G 5.4G 21% /overlay/pivot
devtmpfs 212M 0 212M 0% /dev
none 217M 0 217M 0% /overlay/pivot/overlay
none 217M 137M 80M 64% /overlay/rwdata
overlay 217M 137M 80M 64% /
tmpfs 217M 0 217M 0% /dev/shm
tmpfs 217M 25M 192M 12% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 217M 0 217M 0% /sys/fs/cgroup
/dev/mmcblk0p1 253M 53M 200M 21% /boot
tmpfs 44M 0 44M 0% /run/user/1000
host2 done | stdout: Filesystem Size Used Avail Use% Mounted on
/dev/root 7.2G 1.5G 5.4G 22% /overlay/pivot
devtmpfs 212M 0 212M 0% /dev
none 217M 0 217M 0% /overlay/pivot/overlay
none 217M 103M 114M 48% /overlay/rwdata
overlay 217M 103M 114M 48% /
tmpfs 217M 0 217M 0% /dev/shm
tmpfs 217M 5.8M 211M 3% /run
tmpfs 5.0M 4.0K 5.0M 1% /run/lock
tmpfs 217M 0 217M 0% /sys/fs/cgroup
/dev/mmcblk0p1 253M 53M 200M 21% /boot
tmpfs 44M 0 44M 0% /run/user/1000
- Play recap -
host1 : ok=1 changed=1 unreachable=0 failed=0 rescued=0 ignored=0
host2 : ok=1 changed=1 unreachable=0 failed=0 rescued=0 ignored=0
You should be able to list all available callback plugins using ansible-doc -t callback -l and their respective documentation using ansible-doc -t callback <plugin name>
Source Documentation
- Callback plugins
- Index of all Callback Plugins
Ansible defaults to a machine-readable JSON output, not suitable for human reading. But there are other βcallbackβ modules available, some of which can format the stream output.
- The misleadingly-named
debugmodule is more suitable for human viewing. - Recently, the
yamlmodule formats the stream output as a easy-to-read YAML document.
So, using the ANSIBLE_STDOUT_CALLBACK environment variable:
$ ANSIBLE_STDOUT_CALLBACK=yaml ansible-playbook ansible/deploy.yaml
will change the formatting of stream output:
[β¦]
TASK [Django: Collect media fixture files] ******************************************************************************
ok: [lorem]
TASK [Django: Create superuser] ******************************************************************************
fatal: [lorem]: FAILED! => changed=false
cmd: |-
python3 -m django createsuperuser
--noinput
--username "admin"
--email "[email protected]
msg: |-
stderr: |-
CommandError: You must use --full_name with --noinput.
path: "/var/local/dolor/virtualenv/rectory/venv.py3.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/games"
syspath:
- /tmp/ansible_django_manage_payload_uj9f3le8/ansible_django_manage_payload.zip
- /usr/lib/python37.zip
- /usr/lib/python3.7
- /usr/lib/python3.7/lib-dynload
- /usr/local/lib/python3.7/dist-packages
- /usr/lib/python3/dist-packages
1. Create config file
To get Ansible playbook output nicely formatted as pretty JSON, create a config file:
- either
ansible.cfg, in the same directory as the Ansible playbook file - or in the home directory, beginning with a dot,
~/.ansible.cfg - or in
/etc/ansible/ansible.cfg
Source: https://docs.ansible.com/ansible/latest/reference_appendices/config.html
Bonus: debug also gives pretty stdout, stderr and msg.
File contents:
[defaults]
stdout_callback=debug
2. Run the playbook
Run the playbook in exactly the same way as before.
ansible-playbook foo.yaml
Comparison
Before:
TASK [Django: Collect media fixture files] ******************************************************************************
ok: [lorem]
TASK [Django: Create superuser] ******************************************************************************
fatal: [lorem]: FAILED! => {"changed": false, "cmd": "python3 -m django createsuperuser\n --noinput\n --username \"admin\"\n --email \"[email protected]\"", "msg": "\n:stderr: CommandError: You must use --full_name with --noinput.\n", "path": "/var/local/dolor/virtualenv/rectory/venv.py3.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/games", "syspath": ["/tmp/ansible_django_manage_payload_uj9f3le8/ansible_django_manage_payload.zip", "/usr/lib/python37.zip", "/usr/lib/python3.7", "/usr/lib/python3.7/lib-dynload", "/usr/local/lib/python3.7/dist-packages", "/usr/lib/python3/dist-packages"]}
After:
TASK [Django: Collect media fixture files] ******************************************************************************
ok: [lorem]
TASK [Django: Create superuser] ******************************************************************************
fatal: [lorem]: FAILED! => {
"changed": false,
"cmd": "python3 -m django createsuperuser\n --noinput\n --username \"admin\"\n --email \"[email protected]\"",
"msg": "\n:stderr: CommandError: You must use --full_name with --noinput.\n",
"path": "/var/local/dolor/virtualenv/rectory/venv.py3.7/bin:/usr/local/bin:/usr/bin:/bin:/usr/games",
"syspath": [
"/tmp/ansible_django_manage_payload_uj9f3le8/ansible_django_manage_payload.zip",
"/usr/lib/python37.zip",
"/usr/lib/python3.7",
"/usr/lib/python3.7/lib-dynload",
"/usr/local/lib/python3.7/dist-packages",
"/usr/lib/python3/dist-packages"
]
}
STDERR:
CommandError: You must use --full_name with --noinput.
MSG:
CommandError: You must use --full_name with --noinput.
Hello everyone,
I am not sure if there is anything I can do here, but I figured someone in the past has ran into this issue and might have figured out a way to fix it. I have the output of "sh ip route" command and it comes out after running my play looking like this: https://gyazo.com/ed74cb7f5bb144b18f25eb11cbccd08f
Is there anything I can do to make it look more like the native output as seen here:
https://gyazo.com/dd380dc855005d275622634232b1493e
here is my code:
---
- name: Show Cisco
hosts: devices
connection: network_cli
become: yes
become_method: enable
tasks:
- name: Show commands being sent for Cisco Devices
ios_command:
commands:
- "show version"
register: show_version
- debug: var=show_version
- name: save to file
copy:
content: "{{ show_version.stdout_lines }}"
dest: "./script_results/shver.json"
- name: Show commands being sent for Cisco Devices
ios_command:
commands:
- "show ip interface brief"
register: show_ipinterface
- debug: var=show_ipinterface
- name: save to file
copy:
content: "{{ show_ipinterface.stdout_lines }}"
dest: "./script_results/shipinterface.json"
- name: Show commands being sent for Cisco Devices
ios_command:
commands:
- "show ip route"
register: show_iproute
- debug: var=show_iproute
- name: save to file
copy:
content: "{{ show_iproute.stdout_lines }}"
dest: "./script_results/shiproute.json"Thank you
There isn't a way to do what you want natively in Ansible. You can do this as a workaround:
ansible-playbook ... | sed 's/\\n/\n/g'
If you want more human friendly output define:
ANSIBLE_STDOUT_CALLBACK=debug
This will make ansible use the debug output module (previously named human_log) which despite its unfortunate name is less verbose and much easier to read by humans.
If you get an error that this module is not available, upgrade Ansible or add this module locally if you cannot upgrade ansible, it will work with over versions of ansible like 2.0 or probably even 1.9.
Another option to configure this is to add stdout_callback = debug to your ansible.cfg
I'm trying to build a playbook that would look at the interface configuration of a Cisco switch and give me a pass/fail if certain configurations are not found. I can totally get the expected result if I focus on a singular interface but I want to look at all the interfaces on the actual device. Currently using the cisco ios_command to register the output of 'show run | section interface'
I need help/guidance on turning the stdout_lines output into a dict list format so I can iterate over it. I'm just having a heck of a time trying to figure out how to do it. Ideally the 'interface #/#' would be the key and everything else would be a the value list.
Current stdout_lines output
"stdout_lines": [
[
"interface Ethernet0/0",
" no switchport",
" ip address 192.168.40.50 255.255.255.0",
" access-session port-control auto",
"interface Ethernet0/1",
"interface Ethernet0/2",
" switchport access vlan 20",
" switchport mode access",
" switchport nonegotiate",
" access-session closed",
" access-session port-control auto",
" no lldp transmit",
" spanning-tree portfast edge",
" ip dhcp snooping limit rate 20",
"interface Ethernet0/3"
]
]expected output
"interface_dict": {
"Ethernet0/0": {
" no switchport",
" ip address 192.168.40.50 255.255.255.0",
" access-session port-control auto"
},
"Ethernet0/1": {
},
"Ethernet0/2": {
" switchport access vlan 20",
" switchport mode access",
" switchport nonegotiate",
" access-session closed",
" access-session port-control auto",
" no lldp transmit",
" spanning-tree portfast edge",
" ip dhcp snooping limit rate 100"
},
"interface Ethernet0/3": {
}
}----------------------------------------------------------------------------------------------------------------------------------------------
Thanks for the feedback! It took a while to workout all the bugs but here is the play i came up with along with the ttp parser.
The play gets the running config and uses the parser to put the interface confguration into structure data. The set facts can be edited for whatever data you want by modifying the selectattr [which references the parse template].
Play:
tasks:
- name: 'Gather facts'
ios_facts:
gather_subset:
- config
- name: 'Parse config text'
ansible.utils.cli_parse:
text: "{{ ansible_net_config }}"
parser:
name: ansible.utils.ttp
template_path: "./awd4416.ttp"
register: parse_output
- name: 'Filter registered result'
set_fact:
filtered_output:
- "{{ parse_output.parsed[0][0].interfaces | dict2items |
selectattr('value.mode', 'defined') |
selectattr('value.mode', '==' , 'access' ) |
selectattr('value.voice_vlan', 'undefined' ) |
list | items2dict }}"
- name: 'Filtered output'
debug:
msg:
- "{{ filtered_output[0] }}"TTP parse template:
<group name="interfaces.{{ interface }}" >
interface {{ interface | _start_ }}
description {{ description | ORPHRASE }}
switchport access vlan {{ vlan | to_int }}
switchport mode {{ mode }}
switchport nonegotiate {{ nonegotiate | set(true) }}
switchport voice vlan {{ voice_vlan | to_int }}
spanning-tree portfast {{ spanning-tree_portfast | set(true) }}
ip dhcp snooping limit rate {{ ip_dhcp_snooping_limit_rate | to_int }}
</group>Parsed interface configs:
ok: [GECKO-L2] => {
"changed": false,
"parsed": [
[
{
"interfaces": {
"Ethernet0/0": {},
"Ethernet0/1": {
"description": "Test",
"ip_dhcp_snooping_limit_rate": 100,
"mode": "access",
"nonegotiate": true,
"vlan": 20
},
"Ethernet0/2": {
"description": "Test",
"ip_dhcp_snooping_limit_rate": 100,
"mode": "access",
"nonegotiate": true,
"vlan": 20,
"voice_vlan": 21
},
"Ethernet0/3": {
"description": "Test",
"ip_dhcp_snooping_limit_rate": 100,
"mode": "access",
"nonegotiate": true,
"vlan": 20
}
}
}
]
]
}Filtered output showing only the access interfaces without a voice vlan:
ok: [GECKO-L2] => {
"msg": [
{
"Ethernet0/1": {
"description": "Test",
"ip_dhcp_snooping_limit_rate": 100,
"mode": "access",
"nonegotiate": true,
"vlan": 20
},
"Ethernet0/3": {
"description": "Test",
"ip_dhcp_snooping_limit_rate": 100,
"mode": "access",
"nonegotiate": true,
"vlan": 20
}
}
]
}You may have a look into debug β Print statements during execution, Using Variables and Return Values.
Copy---
- hosts: localhost
become: true
gather_facts: false
vars:
RESULT:
STDOUT_LINES:
- "# name admin@shrrah.esquimail.com"
- "zimbraIsDelegatedAdminAccount: FALSE"
- ""
- "# name prueba5@prueba5.com"
- ""
- "# name prueba7@prueba7.com"
- "zimbraIsDelegatedAdminAccount: TRUE"
- ""
- "# name prueba9@prueba9.com"
tasks:
- name: Show STDOUT_LINES
debug:
msg: "{{ RESULT.STDOUT_LINES }}"
resulting into an output only of
CopyTASK [Show STDOUT_LINES] *****************
ok: [localhost] =>
msg:
- '# name admin@shrrah.esquimail.com'
- 'zimbraIsDelegatedAdminAccount: FALSE'
- ''
- '# name prueba5@prueba5.com'
- ''
- '# name prueba7@prueba7.com'
- 'zimbraIsDelegatedAdminAccount: TRUE'
- ''
- '# name prueba9@prueba9.com'
and if Ansible Callback plugin is configured to YAML instead of JSON.
To get lines containing certain strings only you may Loop over the list based on a Condition
Copy - name: Show lines with TRUE only
debug:
msg: "{{ item }}"
when: "'TRUE' in item"
loop: "{{ RESULT.STDOUT_LINES }}"
resulting into an output of
CopyTASK [Show lines with TRUE only] *******************************
ok: [localhost] => (item=zimbraIsDelegatedAdminAccount: TRUE) =>
msg: 'zimbraIsDelegatedAdminAccount: TRUE'
Further Documenation
- Index of all Callback Plugins
If you like to have the line before included, you could use an approach like
Copy - name: Show lines with TRUE and line before
debug:
msg: "{{ RESULT.STDOUT_LINES[ansible_loop.index0 - 1] }}\n{{ item }}"
when: "'TRUE' in item"
loop: "{{ RESULT.STDOUT_LINES }}"
loop_control:
extended: true
label: "{{ ansible_loop.index0 }}"
resulting into an output of
CopyTASK [Show lines with TRUE and line before] *************************************************************************************************************************************
ok: [localhost] => (item=6) =>
msg: |-
# name prueba7@prueba7.com
zimbraIsDelegatedAdminAccount: TRUE
Further Documentation
- Extended loop variables
Since you are using the shell module, you could use also an approach like
Copy- name: DELEGATED ADMIN ACCOUNTS - check, get and send to the file domain.list
shell:
cmd: /opt/zimbra/bin/zmprov -l gaaa -v zimbraIsDelegatedAdminAccount | grep -B 1 TRUE
and gather only result lines which are true an the line before.
Further Q&A
grepa file, but show several surrounding lines?
Regarding
... send it to the
file.txt
you may have a look into
- Ansible - Save registered variable to file
- Ansible: Save registered variables to file
- ...
Create a dictionary
Copy - set_fact:
info: "{{ info|d({})|combine({_key: _val}) }}"
loop: "{{ stdout.split('#')[1:] }}"
vars:
_list: "{{ item.split('\n')|map('trim') }}"
_key: "{{ _list.0.split(' ')|last }}"
_val: "{{ _list[1:]|select()|map('from_yaml')|combine }}"
gives
Copy info:
admin@shrrah.esquimail.com:
zimbraIsDelegatedAdminAccount: false
prueba5@prueba5.com: {}
prueba7@prueba7.com:
zimbraIsDelegatedAdminAccount: true
prueba9@prueba9.com: {}
Then, the template is trivial. Either print all items
Copy - copy:
content: |-
{% for k,v in info.items() %}
{{ k }}
{{ v|to_nice_yaml }}
{% endfor %}
dest: file.txt
gives
Copyshell> cat file.txt
admin@shrrah.esquimail.com
zimbraIsDelegatedAdminAccount: false
prueba5@prueba5.com
{}
prueba7@prueba7.com
zimbraIsDelegatedAdminAccount: true
prueba9@prueba9.com
{}
, or explicitly select item(s)
Copy - copy:
content: |-
prueba7@prueba7.com
{{ info['prueba7@prueba7.com']|to_nice_yaml }}
dest: file.txt
gives
Copyshell> cat file.txt
prueba7@prueba7.com
zimbraIsDelegatedAdminAccount: true
Note
Additional attributes will be parsed too, e.g.
Copy stdout_lines: [
"# name admin@shrrah.esquimail.com",
"zimbraIsDelegatedAdminAccount: FALSE",
"",
"# name prueba5@prueba5.com",
"",
"# name prueba7@prueba7.com",
"zimbraIsDelegatedAdminAccount: TRUE",
"zimbraIsDelegatedRootAccount: TRUE",
"",
"# name prueba9@prueba9.com"
]
will give
Copy info:
admin@shrrah.esquimail.com:
zimbraIsDelegatedAdminAccount: false
prueba5@prueba5.com: {}
prueba7@prueba7.com:
zimbraIsDelegatedAdminAccount: true
zimbraIsDelegatedRootAccount: true
prueba9@prueba9.com: {}
and consequently
Copyshell> cat file.txt
prueba7@prueba7.com
zimbraIsDelegatedAdminAccount: true
zimbraIsDelegatedRootAccount: true
Hey,
I am curious if there is any way to print out the exact output of a command, without formatting it to JSON or something else, which is standard when you use the debug module.
I want to create a playbook/role that will generate a QR code using the qrencode command with the ansiutf8 format, and then I want to print it.
If I use the debug function, it creates a JSON output which then garbles the output based on the terminal size, see:
garbled QR codeCurrent code:
- name: generate QR code
local_action:
module: command
cmd: qrencode -o - -t asciii BLABLA
register: qrcode
- name: show QR code
local_action:
module: debug
msg: "{{ qrcode.stdout }}"Is there any nice/proper way of doing this?
Thanks!
Edit: added current code for the example shown