Ansible playbook with_subelements - ansible

Ansible playbook with_subelements error with 3 levels.
My Config looks like
---
Firstlevel:
- fl_number: fln1
fl_data: fld1
Secondlevel:
- sl_number: sln_f1_1
sl_data: sld_f1_1
Thirdlevel:
- tl_number: tln_s1_f1_1
tl_data: tld_s1_f1_1
- tl_number: tln_s2_f1_2
tl_data: tld_s2_f1_2
The Ansible playbook is
>cat test_threelevels.yml
---
- hosts: localhost
gather_facts: no
vars_files:
- ../vars/testConfig-var.yml
tasks:
- name: DebugWorks
debug: msg="{{ item.1.Thirdlevel }}"
with_subelements:
- Firstlevel
- Secondlevel
- name: DebugDoesNotWork
debug: msg=" Sub element Thirdlevel test"
with_subelements:
- Firstlevel
- Secondlevel
- Thirdlevel
When it is executed with
ansible-playbook -v test_threelevels.yml
the task "DebugWorks" works but the task "DebugDoesNotWork" dosent.
Output
TASK: [DebugDoesNotWork] ******************************************************
fatal: [localhost] => subelements lookup expects a list of two items, first a dict or a list, and second a string
FATAL: all hosts have already failed -- aborting
PLAY RECAP ********************************************************************
Need help in understanding if this is the right way to do and why it does not work.
Open to any suggestions.
Thanks.

The error description at least vaguely says what's meant. :)
Refer to the code to see exactly the error means here. terms is the list you pass.
if not isinstance(terms, list) or not 2 <= len(terms) <= 3:
In short: You can only go 2 levels, not 3.
The documentation does say clearly:
Optionally, you can add a third element to the subelements list, that
holds a dictionary of flags.

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

Execute ansible include_tasks until a certain condition is met (kind of while until loop)

I want to execute an include tasks list until a certain condition is met, I do not have a fixed loop but execution depends upon a condition.
A sample play below
Tasks list playbook
tasks.yml
---
- name: "inc test-var {{ test_var }}"
set_fact:
test_var: "{{ test_var | int + 1 }} "
parent playbook parent.yml
---
- hosts: all
gather_facts: no
tasks:
- set_fact:
test_var: '1'
req_var: '4'
- name: "Test multi run of task"
include_tasks: ./includes/tasks.yml
register: versions_result
until: test_var is version(req_var, '<')
retries: 5
here I am expecting parent.yml tasks to run multiple times but it only run once.
Could some one point out what I am doing wrong and how to run a task multiple times until a condition is met.
Cheers,
One way to include_tasks multiple times is to loop over the range of numbers till it reaches the required number. However as you expect the "parent" playbook will not be run multiple times, the tasks file will be.
Consider the below example:
Through my main playbook parent.yml, I would like to run tasks1.yml multiple times (as defined in set_fact).
tasks:
- set_fact:
num: 1
req_num: 4
- include_tasks: tasks1.yml
loop: "{{ range(num, req_num + 1)|list }}"
And in my tasks1.yml, I have a simple debug message:
- debug:
msg: "Run {{ item }}"
Includes tasks1.yml 4 times and gives below output when I run ansible-playbook parent.yml:
TASK [include_tasks] ******************************************************************************************************************************************************************
included: /home/user/tasks1.yml for localhost
included: /home/user/tasks1.yml for localhost
included: /home/user/tasks1.yml for localhost
included: /home/user/tasks1.yml for localhost
TASK [debug] **************************************************************************************************************************************************************************
ok: [localhost] =>
msg: Run 1
TASK [debug] **************************************************************************************************************************************************************************
ok: [localhost] =>
msg: Run 2
# ...goes till "Run 4"

Build variable name dynamically and access its content with Ansible

I have an ansible playbook, which I need to compare values returned from a task to a variable loaded from metadata file.
This metadata can be in any format, and I decided to go with YAML.
What I'm trying to achieve is to build a variable name from another variable + extra stuff and then lookup this value.
I've searched for answers over the web but I couldn't find any. There are also some similar questions here on SO, but they don't address exactly my issue.
Here's the code:
temp_task.yml
---
- name: Temp task
hosts: xenservers
gather_facts: no
vars_files:
- vars/xenservers_metadata.yml
tasks:
- command: ls /home # just a dummy task..
ignore_errors: yes
- set_fact: nic={{ inventory_hostname }}.network
- debug: msg={{ nic }}
- debug: msg={{ xen_perf.network }}
xenservers_metadata.yml
---
- xen:
network:
- xenbr0: "9b8be49c-....-....-...-..."
I'm trying to get the two debug messages print the same thing. One was constructed dynamically by {{ inventory_hostname }}.network while the other is explicit variable I loaded.
TASK [debug] ********************************************************************************************************************************************************
ok: [xen_perf] => {
"msg": "xen.network"
}
TASK [debug] ********************************************************************************************************************************************************
ok: [xen] => {
"msg": [
{
"xenbr0": "9b8be49c-....-....-...-..."
}
]
}
The first debug just prints the string. The second prints the actual data I need. How can I achieve the second data output by constructing the variable/attribute dynamically?
You don’t build the variable name dynamically in your example.
All variables (not facts) are stored in vars structure and you can access them this way:
- debug:
msg: "{{ vars[inventory_hostname].network }}"

Explanation of behaviour of Ansible Playbook variables defined in vars

I am fairly new to Ansible and today while writing a new playbook I came across some behaviour I can't explain. I have been through the documentation and done my best Google-Fu, but I can't seem to find the answer.
The behaviour relates to variables defined in a playbook's "var" section and their lack of expansion into proper values when using other variables or arithmetic.
Take an example playbook with "static" values in vars:
--- # Test playbook vars
- hosts: 'all'
connection: ssh
gather_facts: false
vars:
- max_size_bytes: 10240
tasks:
- name: debug!
debug:
msg: |
"max_size_bytes: {{max_size_bytes}}"
"vars: {{vars['max_size_bytes']}}"
This outputs:
# ansible-playbook -i host1, test_vars.yml
PLAY [all] *************************************************************************************************************************
TASK [debug!] **********************************************************************************************************************
ok: [host1] => {}
MSG:
"max_size_bytes: 10240"
"vars: 10240"
Which is exactly what I would expect.
However, let's say I want to calculate that 10240 number dynamically:
--- # Test playbook vars
- hosts: 'all'
connection: ssh
gather_facts: false
vars:
- max_size_bytes: "{{10 * 1024}}"
tasks:
- name: debug!
debug:
msg: |
"max_size_bytes: {{max_size_bytes}}"
"vars: {{vars['max_size_bytes']}}"
And the new result is:
# ansible-playbook -i host1, test_vars.yml
PLAY [all] *************************************************************************************************************************
TASK [debug!] **********************************************************************************************************************
ok: [host1] => {}
MSG:
"max_size_bytes: 10240"
"vars: {{10 * 1024}}"
I get a similar output if I try to use another variable within the assignment. I came across this issue when I wanted to allow a playbook user to quick change some settings, without requiring them to calculate anything. For example:
vars:
- max_size_in_megabytes: 100 #change me if required
- max_size_bytes: "{{max_size_in_megabytes * 1024 * 1024}}"
But this didn't work as I expected, as above.
In some places the variable is expanded correctly and gives the result I would expect (i.e. the calculated value). At other times, it seems the variable is not expanded and is treated as a string as per the output for vars['max_size_bytes'].
What is the reason for this behaviour? Variable expansion and calculated values seem to work elsewhere in a playbook - why not for pre-defined variables?
If this behaviour is considered normal, what is the proper way of creating global/reusable variables that may need to be calculated on the fly?
UPDATE
I realise there may be some confusion as to what my issue actually is. That's my fault.
To try and explain better, take a look at another example:
--- # Test playbook vars
- hosts: 'localhost'
connection: local
gather_facts: false
vars:
- multiplier: 10 #change me as required
- some_value: 300
- max_size_dynamic: "{{10 * 20}}"
- max_size_static: 200
tasks:
- set_fact:
is_bigger_dynamic: "{{some_value > max_size_dynamic}}"
is_bigger_static: "{{some_value > max_size_static}}"
- name: debug!
debug:
msg: |
multiplier: {{multiplier}}
some_value {{some_value}}
Is {{some_value}} bigger than {{max_size_static}}?
is_bigger_static: {{is_bigger_static}} <-- hooray
Is {{some_value}} bigger than {{max_size_dynamic}}?
is_bigger_dynamic: {{is_bigger_dynamic}} <-- woops!
When running this playbook, you can see that the value of the conditional clauses in the set_fact task differs based on how a variable is created in vars.
Output:
PLAY [localhost] **********************************************************************************************************************
TASK [set_fact] ***********************************************************************************************************************
ok: [localhost]
TASK [debug!] *************************************************************************************************************************
ok: [localhost] => {}
MSG:
multiplier: 10
some_value 300
Is 300 bigger than 200?
is_bigger_static: True <-- hooray
Is 300 bigger than 200?
is_bigger_dynamic: False <-- woops!
The conditionals are checking the same expression: 300 > 200, but if the value 200 is derived from another expression in vars the condition is wrong.
I suspect in the case of {{some_value > max_size_dynamic}} the variable isn't being expanded (much like using vars['name\] as mentioned in the comments). So it looks like the conditional ends up being {{some_value > "{{10 * 20}}"}}.
Is this expected behaviour with set_fact? Is there anything I can do to allow expressions in vars which can be further used in set_fact tasks?
This is running the latest version of Ansible on MacOS High Sierra:
# ansible --version
ansible 2.5.0
config file = /Users/xxx/.ansible.cfg
configured module search path = [u'/Users/xxx/.ansible/plugins/modules', u'/usr/share/ansible/plugins/modules']
ansible python module location = /usr/local/Cellar/ansible/2.5.0/libexec/lib/python2.7/site-packages/ansible
executable location = /usr/local/bin/ansible
python version = 2.7.14 (default, Apr 9 2018, 16:44:39) [GCC 4.2.1 Compatible Apple LLVM 9.1.0 (clang-902.0.39.1)]
Your update to the question is completely different to original post.
And the issue with update part is this: Why a string in always "greater than" a number?
Just do the type casting:
- set_fact:
is_bigger_dynamic: "{{some_value > max_size_dynamic | int }}"
is_bigger_static: "{{some_value > max_size_static }}"
In your case some_value and max_size_static are numbers, but max_size_dynamic is a string (you can't make simple integer outside of {{...}} in ansible – only strings/booleans/lists/dictionaries).

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 }}"

Resources