How do I reference a nested key in Ansible? - ansible

I have a task in ansible that greps a list of emails in a loop. I am trying to get the "stdout_lines" output of this.
vars:
emails:
- test#email.com
tasks:
- name: search
register: found
shell: "grep -i {{item}} ~/file"
with_items: "{{emails}}"
debug:
msg: "{{found.results}}"
The above displays the following:
"msg": {
"changed": true,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "item",
"changed": true,
"cmd": "grep -i test#email.com ~/file",
"delta": "0:00:00.003060",
"end": "2020-10-19 12:52:29.930458",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "grep -i test#email.com ~/x",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "test#email.com",
"rc": 0,
"start": "2020-10-19 12:52:29.927398",
"stderr": "",
"stderr_lines": [],
"stdout": "test#email.com line 1\ntest#email.com line2",
"stdout_lines": [
"test#email.com line 1",
"test#email.com line 2"
]
}
]
}
And when I try
debug:
msg: "{{found.results.stdout_lines}}"
I get the error
fatal: [127.0.0.1]: FAILED! => {"msg": "The task includes an option
with an undefined variable. The error was: 'list object' has no
attribute 'stdout_lines'
I should also note that I run into issues when I have more than one email in the list, but one problem at at time.

When you run an Ansible module in a loop, like you have done here with the shell module, the results key is a list (docs).
When you use register with a loop, the data structure placed in the variable will contain a results attribute that is a list of all responses from the module. This differs from the data structure returned when using register without a loop.
Since found.results is a list of objects it doesn't have a stdout_lines key itself, rather each of its items do. Depending on what you need you could access the key on the first item:
found.results[0].stdout_lines
You could loop over each item again and do something with each:
- name: Do something with output
example:
var: "{{ item }}"
loop: found.results
You could run the map filter over it, for example to concatenate the lines in each stdout_lines item:
found.results | map(attribute='stdout_lines') | join('\n')

Related

Limit ansible output from shell command results list

Team,
how can I avoid this huge output and just get the value I want from every item in the results list?
I just want the item value that is hostname to be shown and only the line from stdout for every host.
- name: "Mount count on GPU Nodes"
shell: "mount | grep -Ec '/dev/sd.*\\<csi' | awk '{ print $0,\"mounts found on $HOSTNAME\"($0>64? \" that are more than 64.\" : \".\") }'"
register: mounts_count
changed_when: false
failed_when:
delegate_to: "{{ item }}"
with_items: "{{ groups['kube-gpu-node'] }}"
- name: Check if csi related mounts are present on gpu nodes
assert:
that:
- item.stdout is search('64')
fail_msg: " mounts are present on this node"
success_msg: "mounts are not present on this node"
loop: "{{ mounts_count.results }}"
loop_control:
label: "{{ item.item }}"
ignore_errors: yes
sample output
TASK [team-services-pre-install-checks : Check if csi related mounts are present on gpu nodes] ***
Wednesday 18 December 2019 22:47:17 +0000 (0:00:07.012) 0:00:09.110 ****
failed: [localhost] (item=node1) => {
"ansible_loop_var": "item",
"assertion": "item.stdout is search('0')",
"changed": false,
"evaluated_to": false,
"item": {
"ansible_loop_var": "item",
"changed": false,
"cmd": "mount | grep -Ec '/dev/sd.*\\<csi' | awk '{ print $0,\"mounts found on hostname\"($0>64? \" that are more than 64.\" : \".\") }'",
"delta": "0:00:00.102618",
"end": "2019-12-18 22:47:12.566399",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "mount | grep -Ec '/dev/sd.*\\<csi' | awk '{ print $0,\"mounts found on hostname\"($0>64? \" that are more than 64.\" : \".\") }'",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "node1",
"rc": 0,
"start": "2019-12-18 22:47:12.463781",
"stderr": "",
"stderr_lines": [],
"stdout": "1 mounts found on hostname.",
"stdout_lines": [
"1 mounts found on hostname."
]
},
"msg": " mounts are present on this node"
}
ok: [localhost] => (item=node2) => {
"ansible_loop_var": "item",
"changed": false,
"item": {
"ansible_loop_var": "item",
"changed": false,
"cmd": "mount | grep -Ec '/dev/sd.*\\<csi' | awk '{ print $0,\"mounts found on hostname\"($0>64? \" that are more than 64.\" : \".\") }'",
"delta": "0:00:00.109244",
"end": "2019-12-18 22:47:14.303305",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": "mount | grep -Ec '/dev/sd.*\\<csi' | awk '{ print $0,\"mounts found on hostname\"($0>64? \" that are more than 64.\" : \".\") }'",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"item": "node2",
"rc": 0,
"start": "2019-12-18 22:47:14.194061",
"stderr": "",
"stderr_lines": [],
"stdout": "0 mounts found on hostname.",
"stdout_lines": [
"0 mounts found on hostname."
]
},
"msg": "mounts are not present on this node"
}
expected output: below is just a manual sample i made but any thing that shows hostname and stdout_line is good enough.
"item": "node1"
0 mounts found on hostname.
"item": "node2"
0 mounts found on hostname.
Ansible isn't really designed to produce "nice" output on the console. If you want information produced in a specific format, you are better off generating a file from a template.
That said, the assert module is designed primarily as a debugging tool and generates verbose output. I think you could get the behavior you want using the fail module instead:
- name: Check if csi related mounts are present on gpu nodes
fail:
msg: " mounts are present on this node"
when: item.stdout is not search('64')
loop: "{{ mounts_count.results }}"
loop_control:
label: "{{ item.item }}"
ignore_errors: yes
This will simply "skip" items for which the when condition is false.

Print out the task on which a host has failed at the end of a playbook?

I am running the deployment concurrently on a number of hosts. As can be expected, the output moves quickly during runtime and it is hard to track at what state each task ends. When I get to the end of the playbook I can see which host have failed which is great. However, I need to scroll through pages and pages of output in order to find out on which task did a certain host fail.
Is there a way to have a print out at the end of the playbook saying for example:
"Host 1 failed on Task 1/cmd"
Don't know if this fits your issue exactly, but you can help yourself with a little exception handling like this:
---
- hosts: localhost
any_errors_fatal: true
tasks:
- block:
- name: "Fail a command"
shell: |
rm someNonExistentFile
rescue:
- debug:
msg: "{{ ansible_failed_result }}"
#- fail:
# msg: "Playbook run failed. Aborting..."
# uncomment this failed section to actually fail a deployment after writing the error message
The variable ansible_failed_result contains something like this:
TASK [debug] ************************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": {
"changed": true,
"cmd": "rm someNonExistentFile\n",
"delta": "0:00:00.036509",
"end": "2019-10-31 12:06:09.579806",
"failed": true,
"invocation": {
"module_args": {
"_raw_params": "rm someNonExistentFile\n",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"stdin_add_newline": true,
"strip_empty_ends": true,
"warn": true
}
},
"msg": "non-zero return code",
"rc": 1,
"start": "2019-10-31 12:06:09.543297",
"stderr": "rm: cannot remove ‘someNonExistentFile’: No such file or directory",
"stderr_lines": [
"rm: cannot remove ‘someNonExistentFile’: No such file or directory"
],
"stdout": "",
"stdout_lines": [],
"warnings": [
"Consider using the file module with state=absent rather than running 'rm'. If you need to use command because file is insufficient you can add 'warn: false' to this command task or set 'command_warnings=False' in ansible.cfg to get rid of this message."
]
}
}
I mostly use stderr when applicable. Otherwise I use "{{ ansible_failed_result | to_nice_json }}".
hth

How to manipulate this result in Ansible

I have a problem for manipulating data in Ansible.
My main goal is to purge ADRCI log.
In a first time, i'm listing the databases that are launched with users Oracle and Grid with the shell module
- name: Generate List of databases started with user oracle
shell: "ps -ef | grep smon | grep -v grep | grep oracle | awk '{print $NF}' | awk -F '_' '{print $NF}'"
register: list_oracle_bases
Then i want to get the homes to purge with an other shell command :
- name: Get databases homes
register: get_homes
environment:
ORACLE_SID: "{{ item }}"
ORAENV_ASK: "NO"
become: true
become_user: oracle
become_method: su
shell: ". oraenv 1>/dev/null 2>&- && adrci exec='show homes' | grep -v 'ADR Homes:'"
with_items:
- "{{ list_oracle_bases.stdout_lines }}"
And here comes my problem i've got this result for {{ get_homes }}
ok: [host1] => {
"msg": {
"changed": true,
"msg": "All items completed",
"results": [
{
"_ansible_ignore_errors": null,
"_ansible_item_label": "ANSIBLE1",
"_ansible_item_result": true,
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": ". oraenv 1>/dev/null 2>&- && adrci exec='show homes' | grep -v 'ADR Homes:'",
"delta": "0:00:00.028970",
"end": "2019-03-18 11:29:29.708709",
"failed": false,
"invocation": {
"module_args": {
"_raw_params": ". oraenv 1>/dev/null 2>&- && adrci exec='show homes' | grep -v 'ADR Homes:'",
"_uses_shell": true,
"argv": null,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"warn": true
}
},
"item": "ANSIBLE1",
"rc": 0,
"start": "2019-03-18 11:29:29.679739",
"stderr": "",
"stderr_lines": [],
"stdout": "diag/rdbms/ansible1/ANSIBLE1",
"stdout_lines": [
"diag/rdbms/ansible1/ANSIBLE1"
]
}
]
}
}
I tried to get get_homes.results.stdout_lines but i've got the following error when i want to display it in a debug statement.
FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'stdout_lines'\n\nThe error appears to have been in '/tmp/sylvain/roles/test/tasks/query.yml': line 59, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
What is the correct way to get that stdout_lines ("diag/rdbms/ansible1/ANSIBLE1") in a variable.
Regards
Sylvain
results is an array, because you have created it in a loop (with_items). The correct syntax is:
get_homes.results[0].stdout_lines
for the first and so on for the rest.
In order to get the list of home directories you have to filter the results array by the attribute you need. And in the end you have to flatten the array of arrays. Here is a mcve:
---
- name: filter
hosts: localhost
connection: local
tasks:
- shell: |-
echo ~{{ item }}
with_items:
- man
- lp
- mail
register: get_homes
ignore_errors: true
changed_when: false
- debug: var=get_homes
- set_fact:
homes: >-
{{ get_homes.results | map(attribute='stdout_lines') | list | flatten }}
- debug: var=homes

Getting Memory Error in Ansible when using command task in playbook

- hosts: all
ignore_errors: yes
tasks:
- name: Install BKUP
command: yes | var/tocopy/Client/install
error message:
Traceback (most recent call last): File
"/tmp/ansible_HXcBpN/ansible_modlib.zip/ansible/module_utils/basic.py",
line 2817, in run_command
stdout += self._read_from_pipes(rpipes, rfds, cmd.stdout) MemoryError
fatal: []: FAILED! => {
"changed": false,
"cmd": "yes '|' var/tocopy/Client/install",
"invocation": {
"module_args": {
"_raw_params": "yes | var/tocopy/Client/install",
"_uses_shell": false,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"stdin": null,
"warn": true
}
},
"msg": "",
"rc": 257 } ...ignoring META: ran handlers META: ran handlers
The play
command: yes | var/tocopy/Client/install
never terminates, when your script var/tocopy/Client/install does not terminate, because yes as the man page states
yes - output a string repeatedly until killed
never gets killed. The memory error is a subsequent error, because the output gets somewhere buffered and eats up all your memory.
So use an other command that terminates like
command: echo y | var/tocopy/Client/install
If you need to input the string y to your script better use the expect module.

Ansible: How to select specific field using register

I am using a package specific command for which there is no existing module. The output (stdout) of the command is either 0 or 1. I'm trying assign the value of stdout to a variable for later use in the playbook. However, I cannot seem to isolate the stdout, which is clearly being captured.
ok: [hostname1] => {
"dmstate": {
"changed": true,
"msg": "All items completed",
"results": [
{
"_ansible_item_result": true,
"_ansible_no_log": false,
"_ansible_parsed": true,
"changed": true,
"cmd": "/usr/bin/ssh -q hostname1 \"/opt/REDACTED/bin/dmctl -s localhost:8426/dmbroker get DomainManager::hostname1-ampm1::state\"",
"delta": "0:00:01.808716",
"end": "2017-09-06 13:28:04.853221",
"invocation": {
"module_args": {
"_raw_params": "/usr/bin/ssh -q hostname1 \"/opt/REDACTED/bin/dmctl -s localhost:8426/dmbroker get DomainManager::hostname1-ampm1::state\"",
"_uses_shell": true,
"chdir": null,
"creates": null,
"executable": null,
"removes": null,
"warn": true
}
},
"item": "hostname1-ampm1",
"rc": 0,
"start": "2017-09-06 13:28:03.044505",
"stderr": "",
"stderr_lines": [],
"stdout": "0",
"stdout_lines": [
"0"
]
}
]
}
}
What I tried was using set_fact: to isolate and search the dmstate.results, but I cannot figure out the correct syntax.
tasks:
- name: check status
shell: '/usr/bin/ssh -q {{ inventory_hostname }} "/opt/REDACTED/bin/dmctl -s localhost:8426/dmbroker get DomainManager::{{ item }}::state" '
register: dmstate
with_items: "{{ myprocess }}"
- name: find stdout value
set_fact: stdout_value="{{ item.stdout }}"
when: item.dmstate.results.stdout == "stdout"
with_items: dmstate.results
- name: show value of stdout
debug: var=stdout_value
The results of this are here:
fatal: [hostname1]: FAILED! => {"failed": true, "msg": "The conditional check 'item.dmstate.results.stdout == \"stdout\"' failed. The error was: error while evaluating conditional (item.dmstate.results.stdout == \"stdout\"): 'AnsibleUnsafeText' object has no attribute 'dmstate'\n\n
Once you are using with_items, item contains the inner value, so item.dmstate.results.stdout has no sense, you have to use item.stdout (like you do in the set_fact).
Even with this fix, your item will be skipped every time because you are testing the stdout value against the "stdout" string, but you say that stdout is only "0" or "1".
I think you could simply remove the when clause.
Another problem: each item will overwrite the value of stdout_value, so at the end you will only get the last value.

Resources