is there any way to strip ansible output caused by with_items - ansible

I'm new to ansible.
I have a problem when using ansible 'with_items'.
For example, the following code:
- assert:
that:
- ...
with_items: "{{data_array_with_big_elements}}"
The running effect is
TASK [assert] ***************
ok: [10.250.15.160] => (item={......big.....body......here....}) => else_output.
What I want is to strip the item body to a short one.
Is there any idea to make it?

You can control the info displayed by loops using the loop_control attribute for your task.
- name: A looped task with controlled label
command: /bin/true
loop: "{{ some_list }}"
loop_control:
label: "Whatever you like, references to {{ item.some_attribute }} possible"
Check the above documentation link for more examples.

Related

Ansible Looping with conditional

I'm attempting to loop over a list ( I think) with a conditional which also is a list. According to the documentation the conditional should be re-evaluated with each loop. However, I'm finding this not the case.
Here are my two lists which are being set via a set_fact task:
image_id:
- 4bc0467496b6c7a60543069c570ef0e1be4565d25cb2bc7d524600a5fe0d3b8f
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
- e911c149c0ca46a11a8b6eb604439e972685ec25abfde07eb1cdb272a9c0d1a9
- eb40451959b6c5f4aebb2b687a589a58370faab9b15faa43c0aea8d711155b9e
container_id:
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
Then, I'm attempting to loop through the image_id list and remove any images. However, don't remove the items listed in the container_id list.
- name: remove images no longer used
containers.podman.podman_image:
name: "{{ item }}"
state: absent
loop: "{{ image_id }}"
when: container_id != image_id
I'm expecting all images to be remove except for the one listed in the container_id variable. Here is my output from the task above:
TASK [docker-host : remove images no longer used] *********************************************************************************************************************************************************
changed: [dtest05] => (item=4bc0467496b6c7a60543069c570ef0e1be4565d25cb2bc7d524600a5fe0d3b8f)
failed: [dtest05] (item=dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6) => changed=false
ansible_loop_var: item
item: dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
msg: |-
Failed to remove image with id dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6. Error: Image used by 099e729245fb339753cc7ad5e86480965faf2f638dbeb7f535336a532af34de0: image is in use by a container
changed: [dtest05] => (item=e911c149c0ca46a11a8b6eb604439e972685ec25abfde07eb1cdb272a9c0d1a9)
changed: [dtest05] => (item=eb40451959b6c5f4aebb2b687a589a58370faab9b15faa43c0aea8d711155b9e)
I was expecting the failed task to be skipped. If I re-run the task, I do see the task being skipped.
TASK [docker-host : Setting Facts for Image IDs] **********************************************************************************************************************************************************
ok: [dtest05]
TASK [docker-host : debug image_id] ***********************************************************************************************************************************************************************
ok: [dtest05] =>
image_id:
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
TASK [docker-host : debug container_id] *******************************************************************************************************************************************************************
ok: [dtest05] =>
container_id:
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
TASK [docker-host : remove images no longer used] *********************************************************************************************************************************************************
skipping: [dtest05] => (item=dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6)
Am I not understanding the documentation correctly? I'm thinking that my first run should not fail but have an skipped item.
The expression container_id != image_id doesn't make any sense. You're comparing the same two things for every iteration of the loop, and because the two variables are two unequal lists the comparison will always be false.
It looks like you're trying to check if item is contained in the container_id list, which would look something like:
- name: remove images no longer used
containers.podman.podman_image:
name: "{{ item }}"
state: absent
loop: "{{ image_id }}"
when: item not in container_id
Note that you can achieve the same thing with less work by running podman image prune -af (perhaps via command task).
Use the filter difference. For example, the playbook below
- hosts: localhost
vars:
image_id:
- 4bc046
- dbcefa
- e911c1
- eb4045
container_id:
- dbcefa
tasks:
- debug:
msg: "{{ item }}"
loop: "{{ image_id|difference(container_id) }}"
gives (abridged)
msg: 4bc046
msg: e911c1
msg: eb4045

Ansible use Find Module on Registered Variable

My goal is to create a registered variable that I can use to search through and register other variables based on it. Mostly it is a find module that is searching for files which then, based upon the filename, should be registered in other variables. Here is an example of what I have but it is not filtering out the variables properly:
- name: Register all files
find:
paths: ./files/
patterns: '*.pkg,*.dmg'
file_type: file
register: installers
- name: Find Slack Installers
find:
paths: "{{ item }}"
patterns: "*slack*"
loop: "{{ installers.files|map(attribute='path')|map('basename')|list }}"
register: slack_installers
- debug:
msg: "{{ slack_installers }}"
This then outputs the following:
TASK [Find Slack Installers] *******************************************************************************************************************************
ok: [localhost] => (item=slack.19.5.dmg)
ok: [localhost] => (item=slack.19.6.pkg)
ok: [localhost] => (item=box.1.2.3.dmg)
(ignore the versions of things, these are just fake names/versions but I'm just testing the find functionality)
As you can see the "Find Slack Installers" task is just registering everything within the "installers" variable rather than actually finding only things with the slack pattern.
EDIT FOR extended question:
set_fact:
"{{ item.item|basename|lower }}_installers": "{{ installers.files|map(attribute='path')|map('basename')|list|select('search', '{{ item.item|basename|lower }}') }}"
loop: "{{ selected_installers.results }}"
loop_control:
label: "{{ item.item|basename }}"
when: item.user_input == 'y' and
item.item == ( item.item|basename )
Using the find module twice in a row does not make sense.
The find module is to find files in a folder and its subfolders, but not to filter a list (even if file names are in this list), as you tried here. Have a look at the examples for find in the Ansible docs.
However, you have two possibilities.
Using find with using the search word slack in pattern:
Add the search word slack directly as pattern in the find module: *slack*.pkg,*slack*.dmg.
- name: Register all files
find:
paths: ./files/
patterns: '*slack*.pkg,*slack*.dmg'
file_type: file
register: installers
- debug:
msg: "{{ installers.files|map(attribute='path')|map('basename')|list }}"
Result:
TASK [Register all files] **************
ok: [localhost]
TASK [debug] ***************************
ok: [localhost] => {
"msg": [
"slack.19.5.dmg",
"slack.19.6.pkg"
]
}
Using find to search only for the packages, then filtering your list:
Using select('regex', 'slack') you can filter the list by specific words in regex syntax, in your case specifying slack is enough.
- name: Register all files
find:
paths: ./files/
patterns: '*.pkg,*.dmg'
file_type: file
register: installers
- debug:
msg: "{{ installers.files|map(attribute='path')|map('basename')|list|select('regex', 'slack') }}"
Result:
TASK [Register all files] **************
ok: [localhost]
TASK [debug] ***************************
ok: [localhost] => {
"msg": [
"slack.19.5.dmg",
"slack.19.6.pkg"
]
}
EDIT: Extended Question
Nested jinja expressions are not allowed and not required.
If you want to specify a variable or a filter expression, just write it without using another jinja expression with {{ and }}.
Try the following select:
select('search', item.item|basename|lower )
Tip
If you use several operators and are not sure which operator binds stronger, you can also use parentheses like in mathematics.
The parenthesis should not be necessary at this point, but it does not interfere.
select('search', (item.item|basename|lower) )
But if you want to get e.g. the second element (index=1) of your list, then the term looks like this:
( installers.files|map(attribute='path')|map('basename')|list )[1]
At this point, the parentheses are important so that the inner expression is evaluated first, and then the corresponding element can be accessed.

Search for a list of strings in ansible register variable

I am newbie to ansible. I have a task where in I have to register the output of a first task and execute second task when some list of variables are present in the output of first task. It looks like below
- name: execute first task
command: /tmp/somescript.sh
register: output
- fail:
msg: when {{ item }} present in. output.stdout or output.stdlines
with_items:
- a
- b
- c
You didn't test the output yet as you posted on your code.
It should be something like this:
- fail:
msg: 'Fail looking for {{ item }} in output'
when: item not in output.stdout or item not in output.stdlines
with_items:
- a
- b
- c
As #ilias-sp said, take a look here: https://docs.ansible.com/ansible/latest/modules/fail_module.html

Ansible check if variables are set

I want to automate the installation process of our software for our client. Therefore I wrote an Ansible playbook which has a task which should check if all the mandatory variables are set:
- name: Check environment variables.
hosts: all
vars_files:
- required_vars.yml
tasks:
- fail: msg="Variable '{{ item }}' is not defined"
when: item not in hostvars[inventory_hostname]
with_items:
- required_vars
The required_vars.yml looks like this:
required_vars:
- APPHOME: /home/foo/bar
- TMPDIR: /home/foo/bar/tmp
When I execute the playbook via ansible-playbook -i inventory/dev.yml playbook.yml I get the following error:
TASK [Gathering Facts] *************************************************************************************************************************************************************************************************************************ok: [localhost]
TASK [fail] ************************************************************************************************************************************************************************************************************************************failed:
[localhost] (item=required_vars) => {"changed": false, "failed": true, "item": "required_vars", "msg": "Variable 'required_vars' is not defined"}
It is obvious that I am doing something wrong, but I cannot point to the error. Can you help me please?
Edit: the accepted answer helped me out. Thank you.
But I have two more questions:
The executed task says:
TASK [fail]
skipping: [some_ip] => (item=/root)
skipping: [some_ip] => (item=TMPDIR: /home/foo/bar/tmp)
It is getting skipped because all variables are set, correct?
I think I figured out how to print the correct message, if the variable is not set:
- name: Check environment variables.
hosts: all
vars_files:
- required_vars.yml
tasks:
- fail:
msg: "Variable '{{ item }}' is not defined"
with_items: "{{ required_vars }}"
when: item is undefined
Correct? Or is there a better solution?
Two problems here:
You want to iterate over the value of required_vars variable value, so you need to provide it as an argument to with_items: "{{ required_vars }}":
with_items: "{{ required_vars }}"
Currently you are providing a list of a single element with a statically defined string required_vars.
You need to change the data type of the elements in your required_vars list to strings:
required_vars:
- "APPHOME: /home/foo/bar"
- "TMPDIR: /home/foo/bar/tmp"
Currently (because of : followed by space) you defined dictionaries, so for example in the first iteration item will have a value of { "APPHOME": "/home/foo/bar" }, which will then always fail on the when condition.
Bonus problem:
you defined a message in the form "Variable '{{ item }}' is not defined";
Ansible reports Variable 'required_vars' is not defined;
the above is not an error, as you think ("I get the following error"), but a correct result of the fail module with the message you defined yourself.
Since you have only one value for 'with_items' I think it should look like this:
with_items: "{{ required_vars }}"
On one line and with the brackets and quotation marks. Once you have more then one item, you can use the list like you did:
with_items:
- "{{ one }}"
- "{{ two }}"

Ansible - iterate over a list of dictionaries

I built the following list but I don't succeed to iterate over it.
Should I use with_items? with_elements? or something else?
My goal is to iterate over all the hosts in the inventory, get their name and their IP, and finally print it.
- set_fact:
list_of_hosts: |
{% set myList = [] %}
{% for host in groups['all'] %}
{% set ignored = myList.extend([{'server_name': host, 'server_ip': hostvars[host].ansible_eth0.ipv4.address }]) %}
{% endfor %}
{{ myList }}
- debug: msg="{{ item.server_name }}"
with_items: "{{ list_of_hosts }}"
Here is my list when I debug it:
TASK [common : debug] ************************************************************************************************
ok: [my1stServer] => {
"msg": " [{'server_ip': u'192.168.0.1', 'server_name': u'my1stServer'}, {'server_ip': u'192.168.0.2', 'server_name': u'my2ndServer'}]\n"
}
And here is the error but it is not really relevant :
fatal: [my1stServer]: FAILED! => {"failed": true, "msg": "the field 'args' has an invalid value, which appears to include a variable that is undefined. The error was: 'ansible.vars.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'server_name'\n\nThe error appears to have been in 'hosts.yml': line 19, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- debug: msg=\"{{ item.server_name }}\"\n ^ here\nWe could be wrong, but this one looks like it might be an issue with\nmissing quotes. Always quote template expression brackets when they\nstart a value. For instance:\n\n with_items:\n - {{ foo }}\n\nShould be written as:\n\n with_items:\n - \"{{ foo }}\"\n"}
Please forgive me for bluntness, but the proposed implementation makes it an effort to understand what the idea actually is. which is simple: to print some variables anyway present in hostvars[host] for a list of hosts picked by various criteria.
If we keep implementation close to that idea, implementation is simpler.
So what I'd do to create a list of hosts picked by group membership, or possibly 'hand picked' is to actually do what I just wrote :).
Consider this task list:
# this task creates an empty list
- name: create my_cool_list
set_fact:
my_cool_list: []
# this task adds to the list all hosts in groups we're iterating over
- name: update my cool list with whole groups
set_fact: '{{my_cool_list + groups[curr_grp]}}'
with_items:
- grp1
- grp2
loop_control:
loop_var: curr_grp
# this task adds to the list all hosts we're iterating over
- name: update my cool list with specific hosts
set_fact: '{{my_cool_list + [curr_node]}}'
with_items:
- node001
- node101
loop_control:
loop_var: curr_node
# now we can iterate over the list, accessing specific fields on each host
- name: go over my cool list and print ansible_init_mgr
debug:
msg: 'hostvars["{{curr_host}}"].ansible_init_mgr: {{hostvars[curr_host].ansible_init_mgr}}'
with_items: '{{my_cool_list|default([], true)|list}}'
Furthermore, you can add safety when: by validating the key you're accessing is defined..
And, to print a selection of variables about each host, you should use jinja filter map('extract',...):
- name: print some vars from each host
debug:
msg: {'server_name': '{{hostvars[curr_node]|selectattr("ansible_hostname")}}', 'ip_address': '{{hostvars[curr_node]|selectattr("ansible_eth0.ipv4.address")}}'}
with_items: '{{my_cool_list|default([], true)|list}}'
loop_control:
loop_var: curr_node
IF you want to increase readability though, you better write a filter plugin, which would do the above stuff, and hide iteration ugliness in readable way, so you can have:
Either (For generic approach, i.e. without renaming attributes)
- name: print some vars from each host
debug:
msg: '{{my_cool_list|multi_select_in_dict(["ansible_hostname", "ansible_eth0.ipv4.address")}}'
Or specific approach (so that you are using specific hard coded remapping of attributes...)
- name: print some vars from each host
debug:
msg: '{{my_cool_list|my_cool_filter}}'

Resources