Ansible Compare registered variables and set fact - ansible

I'm rather newish to ansible and podman, and don't have a full grasp of python. So, I came up to a bit of a block on how to gather a list of container images on a system, and remove them. However only remove the image if no running container is using that image.
My initial tasks, which does work, looks like this:
- name: Gather facts about all container images
containers.podman.podman_image_info:
register: image_info
- name: remove images no longer used
containers.podman.podman_image:
name: "{{ item.Id }}"
state: absent
loop: "{{ image_info.images }}"
loop_control:
label: "{{ item.Id }}"
ignore_errors: true
However, This is not perfect, as the task will fail once it gets to an image that is in use, this is why ignore_errors: true is set. I also do not want the large block of error output (about 200+ lines) this provides when it does fail. Overall this does what I intended it to.
My next step in this "evolution" of code is how to remove the large error output without using no_log: true or anything similar, if possible. My thought is to compare a list of the image IDs I'm all ready gathering, to that of the running containers images. I updated the code to gather information on the containers on the system.
- name: Gather facts about all container images
containers.podman.podman_image_info:
register: image_info
- name: gather container info
containers.podman.podman_container_info:
register: container_info
- name: remove images no longer used
containers.podman.podman_image:
name: "{{ item.Id }}"
state: absent
loop: "{{ image_info.images }}"
loop_control:
label: "{{ item.Id }}"
ignore_errors: true
Then debugging the output of the container_info variable I do see the image ID, which ends up something like container_info.containers[0].Image as a variable I can use. My thought now is that I can use this to remove the image IDs that are in use from the list of images I have.
Here is my block, I'm not sure how I can, create this new image list with the info I have. I know I'll have to use set_facts for this, but is it possible to loop though two different lists in a task? Or am I overthinking this and a simpler way exists that I'm not aware of. All I really want to do is avoid needing to use ignore_errors in my code.
Update:
I have setting the facts down. which looks like the following in my playbook:
- name: Setting facts for image IDs
set_fact:
image_id: "{{ image_info.images | json_query('[].Id') }}"
- name: Setting fact for Container image ID
set_fact:
container_id: "{{ container_info.containers | json_query('[].Image') }}"
This ends up giving me the following info when debuged.
TASK [docker-host : debug image_id] ***********************************************************************************************************************************************************************
ok: [dtest05] =>
image_id:
- 4bc0467496b6c7a60543069c570ef0e1be4565d25cb2bc7d524600a5fe0d3b8f
- c223664c734cbbc7213a4312af596b37a5bf5e55f93526bcb34e527efc9c4d5b
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
- e911c149c0ca46a11a8b6eb604439e972685ec25abfde07eb1cdb272a9c0d1a9
- eb40451959b6c5f4aebb2b687a589a58370faab9b15faa43c0aea8d711155b9e
TASK [docker-host : debug container_id] *******************************************************************************************************************************************************************
ok: [dtest05] =>
container_id:
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
I think I just need to set another fact that will remove duplicates.
Update2: Created another fact to "merge" the two other facts. Full playbook looks like this:
- name: Gather facts about all container images
containers.podman.podman_image_info:
register: image_info
- name: gather container info
containers.podman.podman_container_info:
register: container_info
- name: Setting Facts for Image IDs
set_fact:
image_id: "{{ image_info.images | json_query('[].Id') }}"
container_id: "{{ container_info.containers | json_query('[].Image') }}"
- name: Merging facts
set_fact:
merged_ids: "{{ image_id }} + {{ container_id }}"
Then the merged_id variable outputs the following:
merged_ids:
- 4bc0467496b6c7a60543069c570ef0e1be4565d25cb2bc7d524600a5fe0d3b8f
- c223664c734cbbc7213a4312af596b37a5bf5e55f93526bcb34e527efc9c4d5b
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
- e911c149c0ca46a11a8b6eb604439e972685ec25abfde07eb1cdb272a9c0d1a9
- eb40451959b6c5f4aebb2b687a589a58370faab9b15faa43c0aea8d711155b9e
- dbcefaa52e7009f5d9b6179a256e70890d29148add0f77741ca4550ba2e2ffa6
I can see the duplicate IDs in the list. How do I remove the duplicate IDs from the merged_ids variable list?

There's a problem with your "Merging facts" tasks. You're not creating a merged list of values; you are in fact concatenating two strings. You want:
- name: Merging facts
set_fact:
merged_ids: "{{ image_id + container_id }}"
There is a unique filter you can use to unique-ify a list:
- name: Merging facts
set_fact:
merged_ids: "{{ image_id + container_id | unique }}"

Related

Ansible: Output of 'loop' with 'register' module

I'm trying to get the output of my Ansible script using register module but the loop I'm using is probably causing some issue.
Whats a default way of using register module with loop?
Code:
---
- name:
hosts:
gather_facts:
tasks:
- name: Execute file
ansible.builtin.shell:
environment:
"setting environment"
register: output
loop:
"value"
- debug:
vars: output.std_lines
Whats a default way of using register module with loop?
It is just registering the result.
The only difference will be, that a single task will provide you just with an dictionary result (or output in your example) and registering in a loop will provide with a list result.results (or output.results in your example). To access the .stdout_lines you will need to loop over the result set .results too.
You may have a look into the following example playbook which will show some aspects of Registering variables, Loops, data structures, dicts and lists and type debugging.
---
- hosts: localhost
become: false
gather_facts: false
tasks:
- name: Create STDOUT output (single)
command: 'echo "1"'
register: result
- name: Show full result (single)
debug:
var: result
- name: Show '.stdout' (single)
debug:
msg: "The result in '.stdout': {{ result.stdout }} is of type {{ result.stdout | type_debug }}"
- name: Create STDOUT output (loop)
command: 'echo "{{ item }}"'
register: result
loop: [1, 2, 3]
loop_control:
label: "{{ item }}"
- name: Show full result (loop)
debug:
var: result
- name: Show '.stdout' (loop)
debug:
msg: "The result in '.stdout': {{ item.stdout }} is of type {{ item.stdout | type_debug }}"
loop: "{{ result.results }}"
loop_control:
label: "{{ item.item }}"
By running it and going through the output you can get familiar with the differences in your tasks.
Further Q&A
Register Variables in Loop in an Ansible Playbook
Ansible: loop, register, and stdout
Register variables in loop in Ansible playbook
... and many more here on SO.

A method for listing Ansible modules used by playbooks

I'm creating a requirements.yml in an Ansible project, and I want to identify all of the modules that need to be installed from ansible-galaxy that are used by project playbooks. ansible-doc --list --playbook-dir foo seems like the right tool for the job, but it lists all locally available modules, not just the ones which are actually used in the foo directory. ansible-galaxy list doesn't account for any which are needed but not installed.
Is there a way to do this where I don't end up writing a shell script to sed|awk|grep the info I want?
The best approach I've been able to come up with so far is to ansible-playbook --syntax-check each of the playbooks. This will throw errors such as
ERROR! the role 'foo' was not found ...
ERROR! couldn't resolve module/action 'bar'. This often indicates a misspelling, missing collection, or incorrect module path.
but this is not ideal because it exits as soon as any error occurs. I have to fix each one and run the syntax check again.
FWIW, as a concept, the playbook below lists the modules used in a role
- hosts: localhost
vars:
keywords:
- always
- become
- block
- loop
- loop_control
- name
- notify
- register
- tags
- vars
- when
tasks:
- name: The variable my_role_path is mandatory
assert:
that: my_role_path|d('')|length > 0
- name: Find tasks files
find:
path: "{{ my_role_path }}/tasks"
patterns: '*.yml,*.yaml'
recurse: true
register: result
- name: Create list of tasks
set_fact:
lft: "{{ lft|d([]) + lookup('file', item)|from_yaml }}"
loop: "{{ result.files|map(attribute='path')|list }}"
- name: Get list of keys
set_fact:
lfk: "{{ lfk|d([]) + item.keys()|list }}"
loop: "{{ lft }}"
- name: Get list of keys from block/rescue/always
set_fact:
lfk: "{{ lfk|d([]) + item.keys()|list }}"
loop: "{{ lft|json_query('[].[block, rescue, always]')|flatten }}"
- name: Display list of modules
debug:
var: lfk|unique|sort|difference(keywords)
For example, analyze the role systemd
shell> ansible-playbook pb.yml -e my_role_path=roles/ansible-role-systemd
...
lfk|unique|sort|difference(keywords):
- command
- file
- include_tasks
- meta
- systemd
- template
Complete the list of the keywords.
Use the tasks below to analyze a playbook
tasks:
- name: The variable my_playbook_path is mandatory
assert:
that: my_playbook_path|d('')|length > 0
- name: Create list of tasks
set_fact:
lft: "{{ _playbook|map(attribute='tasks')|flatten }}"
vars:
_playbook: "{{ lookup('file', my_playbook_path)|from_yaml }}"
- name: Get list of keys
set_fact:
lfk: "{{ lfk|d([]) + item.keys()|list }}"
loop: "{{ lft }}"
- name: Get list of keys from block/rescue/always
set_fact:
lfk: "{{ lfk|d([]) + item.keys()|list }}"
loop: "{{ lft|json_query('[].[block, rescue, always]')|flatten }}"
- name: Display list of modules
debug:
var: lfk|unique|sort|difference(keywords)
For example, analyzing the first playbook gives
lfk|unique|sort|difference(keywords):
- assert
- debug
- find
- set_fact

Is there a way to filter ansible vars based on condition

I have this vars in the playbook and multiple tasks, I just want to filter the items from vars list based on the prompt input. Right now I have to exclude that item using when from multiple tasks. Please see following example:
vars_prompt:
- name: rmetcd
prompt: "remove etcd: YES OR NO?"
private: no
vars:
containers:
- "etcd"
- "mysql"
- "postgres"
folders:
- "etcd"
- "mysql"
- "postgres"
tasks:
- name: remove container
shell: "docker rm -f {{ item }}"
with_items: "{{ containers }}"
when:
- '"etcd" not in item'
- name: remove folder
file:
path: "{{ item }}"
state: absent
with_items: "{{ folders }}"
when:
- '"etcd" not in item'
when: rmetcd == "NO"
I would take the problem in reverse order: rather than filtering my list for each task to potentially take out an element, I would define my list with default elements and add the additional one if user answered yes.
Note: your two lists are identical, I only kept one in the following example:
---
- hosts: localhost
gather_facts: false
vars_prompt:
- name: rmetcd
prompt: "Remove etcd [yes/no]?"
private: no
vars:
_default_services:
- mysql
- postgres
services: "{{ _default_services + (rmetcd | bool | ternary(['etcd'], [])) }}"
tasks:
- name: task1
debug:
msg: "remove container {{ item }}"
loop: "{{ services }}"
- name: taks2
debug:
msg: "remove folder {{ item }}"
loop: "{{ services }}"
The key points:
I defined a "private" variable _default_services. This is the list of services that will always be included.
I calculated the variable services which is an addition of two lists: _default_services and the additional value to add depending on user input. For this last one:
I used rmetcd containing the value (which should be "yes" or "no")
I applied the bool filter to cast the value to boolean
and I used the ternary filter to select a single element list if true (['etcd']) and an empty list if false ([]).

Ansible - create a list of unused disks

I am trying to create a list of unused disks from gather facts
-
key | search ("sd")
with_dict: "{{ ansible_devices }}"
But its only displaying one disk, like if the server have two unused disks sdb and sdc , its only displaying sdb. How can I modify my code to include all unused disks.
the way you have it, set_fact gets equal to the "current" item from the iteration, probably this is why you see only 1 disk in the final result. You need to set the disks as a list of elements and append to that list the current item.key. you can use this syntax:
set_fact:
disks: "{{ disks|default([]) + ['/dev/{{item.key}}'] }}"
to understand how many results you have in the loops of the with_dict clause, you can try a debug task:
- name: Print disk result
debug:
msg: "/dev/{{item.key}}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
(indentation may need fixes, i just copied from your code)
hope it helps
thanks for the reply, I used the mentioned code for printing the disk result, it was displaying multiple disks, but when I used set_fact, and the msg to display the variable, its showing like this:-
"disks": [
"/dev/sdc",
"/dev/{{item.key}}"
]
- name: Print disk result
set_fact:
disks: "{{ disks|default([]) + ['/dev/{{item.key}}'] }}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
- debug: var=disks
If anyone is ever looking for the answer, this works for me:
- name: Print disk result
set_fact:
disks: "{{ disks|default([]) + ['/dev/' + item.key] }}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | regex_search ("sd")
with_dict: "{{ ansible_devices }}"
- name: debug
debug:
msg: "{{disks}}"
It returns:
ok: [localhost] => {
"msg": [
"/dev/sdb"
]
}

loop control with digital_ocean ansible module

Thanks to other stackoverflow users, I have managed to pull some data out of a variable registered by the digital_ocean ansible module. I attempted to use loop_control to print only part of the huge variable that is registered. Here is an extract from the role:
- name: Add droplet
digital_ocean: >
{ some parameters }
with_dict: "{{ droplets_up }}"
register: my_droplet
- debug: msg="Droplet IP is {{ item.droplet.ip_address }}"
with_items: "{{ my_droplet.results }}"
loop_control:
label: "{{ item }}"
I'm obviously doing it wrong here, as it prints the whole variable as well as the debug message. I don't quite understand loop_control at this point, but does anyone know if it's possible to use it in this manner with this module?
debug action has result['_ansible_verbose_always'] = True, so it will always print full item, no matter what your label is (although label: "{{item}}" doesn't change anything, try label: "{{ item.droplet.ip_address }}").
If you just need to list all your IP addresses, use map filter and single debug statement:
- name: Print droplets IP
debug:
msg: "{{ my_droplet.results | map(attribute='droplet.ip_address') | list }}"

Resources