Is there a way to stop a loop when the conditional is true the first time? In the example below, I want the value of student to be betty. But it currently will be set to brad. If I add "and student is undefined" to the when condition then it will work, but is there a better way? Thanks!
---
- hosts: localhost
vars:
marks:
- name: bob
grade: a
- name: betty
grade: c
- name: jenny
grade: d
- name: brad
grade: c
tasks:
- set_fact:
student: '{{ item.name }}'
loop: '{{ marks }}'
when: item.grade == 'c'
- debug:
var: student
Given the list
marks:
- {grade: a, name: bob}
- {grade: c, name: betty}
- {grade: d, name: jenny}
- {grade: c, name: brad}
Q: " ... when the conditional is true the first time ... I want the value of student to be betty."
A: Use filters instead of a loop. There are more options:
Select the first item that meets the condition
student: "{{ marks|
selectattr('grade', 'eq', 'c')|
map(attribute='name')|
first }}"
gives
student: betty
The filter json_query gives the same result
student: "{{ marks|
json_query('[?grade==`c`].name')|
first }}"
Example of how to find when the conditional is true for the first time in a loop
For example, let's simulate the use case of iterating a command until it succeeds. The task below iterates the list [1,2,3,4,5] and the command test the item is greater than 3. The loop will skip the rest of the list after the condition is met
- command: '[ "{{ item }}" -gt "3" ]'
loop: "{{ range(1, 5 + 1)|list }}"
register: result
ignore_errors: true
when: not condition
vars:
condition: "{{ (result|default({'rc': 1})).rc == 0 }}"
- debug:
msg: "{{ result.results|
json_query('[?rc==`0`].item')|
first }}"
gives (selected relevant part)
failed: [localhost] (item=3) => changed=true
...
item: 3
msg: non-zero return code
rc: 1
...
changed: [localhost] => (item=4)
skipping: [localhost] => (item=5)
...ignoring
TASK [debug] ****
ok: [localhost] =>
msg: '4'
See details in the thread Looping a task until it succeeds.
Related
Let me introduce my problem. I have some list of dictionary in my Ansible code:
my_example_list = [
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
I need execute command which will iterate over this list and it should look something like:
- name: 'Example'
shell: 'Here is my {{ item.key }} and here is {{ item.value }}'
What I've do or try to do:
I was trying to do that with with_items but i'm not able to point into value of particular key.
I've also try to filter values using | first and | last but it's not worked in my case.
What I want to achieve:
Creating loop which will iterate via that list and inject separated key and value into command.
I was asked to show how I was trying to resolve my issue:
Here is some code:
# Showing last component failing
- name: "Try to show last component of my list"
debug:
msg: "{{ my_example_list[1] | last }}"
# When i'm trying to show first component of my list i get "key1"
- name: "Try to show first component of my list"
debug:
msg: "{{ my_example_list[1] | first }}"
# This shows me my list of dict
- name: "Trying use with_items"
debug:
msg: "{{ item }}"
with_items: "{{ my_example_list }}"
# But when i'm trying point to key and value for example
- name: "Trying use with_items point to key and value"
debug:
msg: "Here is my {{ item.key }} which keep {{ item.value }}"
with_items: "{{ my_example_list }}"
# It's failing.
Sorry it's not maybe solution with using loop. I'm just stack with that issue over few days... And as first step I want to know how correctly point to pair keys and values.
It also works well:
- name: Correct solution
debug:
msg: "This is my {{ item.key }} and my value {{ item.value }}"
with_dict: "{{ my_example_list }}"
Thanks #U880D for help! I'm not able to add some plus for your solution because I'm new joiner. Appreciate your answer! :)
Your data structure and naming seems to be unfavorable. There is no need to number the key name and therefore it should be avoided. Furthermore counting list elements in Python starts at 0 not 1.
The following minimal example playbook
---
- hosts: localhost
become: false
gather_facts: false
vars:
example_list: |
[
{
"key1" : "value_of_first_key"
},
{
"key2": "value_of_second_key"
},
{
"key3": "value_of_third_key"
}
]
tasks:
- name: Example loop
debug:
msg: "{{ item }} is of type {{ item | type_debug }}"
loop: "{{ example_list }}"
- name: Example loop
debug:
msg: "{{ item.values() }}"
loop: "{{ example_list }}"
will result into an output of
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg: '{u''key1'': u''value_of_first_key''} is of type dict'
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg: '{u''key2'': u''value_of_second_key''} is of type dict'
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg: '{u''key3'': u''value_of_third_key''} is of type dict'
TASK [Example loop] ******************************************
ok: [localhost] => (item={u'key1': u'value_of_first_key'}) =>
msg:
- value_of_first_key
ok: [localhost] => (item={u'key2': u'value_of_second_key'}) =>
msg:
- value_of_second_key
ok: [localhost] => (item={u'key3': u'value_of_third_key'}) =>
msg:
- value_of_third_key
Further Readings
How to work with lists and dictionaries in Ansible
Extended loop variables
This is the code that I am trying to run
- name: Read and Register Contents of .bash_profile
shell: grep -E 'AB_AG_HOME|AB_AG_LOCAL_ROOT|AB_AG_LOCAL_DIR|AB_AG_CONFIG_DIR|AB_AG_LOG_DIR' /home/username/.bash_profile
register: output
- debug:
msg: "{{ output.stdout_lines }}"
- name: Append AG environment variables in .bash_profile
shell: cat /home/{{ admin_user }}/tmp_bash.profile >> /home/{{ admin_user }}/.bash_profile
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
with_items: "{{ output.stdout_lines }}"
- name: Delete the temporary tmp_bash.profile"
file:
path: /home/{{ abinitio_admin_user }}/tmp_bash.profile
state: absent
when I run this code, all the values are repeated 4 times.
Is there anything that is missing?
By looping over the stdout_lines via
with_items: "{{ output.stdout_lines }}"`
the condition
when: "'AB_AG_HOME' and 'AB_AG_LOCAL_ROOT' and 'AB_AG_LOCAL_DIR' and 'AB_AG_CONFIG_DIR' and 'AB_AG_LOG_DIR' not in item"
would only be true if all strings are in one line (item) together.
You may have a look into the following minimal example
---
- hosts: localhost
become: false
gather_facts: false
vars:
result:
stdout_lines:
- "A1"
- "A2"
- "A3"
- "A4"
- "A5"
tasks:
- name: Debug one-line conditional
debug:
msg: "Not in stdout_lines"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in result.stdout_lines"
Whereby the first given example get skipped because of the condition, the second example
- name: Debug loop conditional
debug:
msg: "Not in {{ result.stdout_lines[ansible_loop.index0] }}"
when: "'A1' and 'A2' and 'A3' and 'A4' and 'A5' not in item"
loop: "{{ result.stdout_lines }}"
loop_control:
extended: true
will result into an output of
TASK [Debug loop conditional] ******
ok: [localhost] => (item=A1) =>
msg: Not in A1
ok: [localhost] => (item=A2) =>
msg: Not in A2
ok: [localhost] => (item=A3) =>
msg: Not in A3
ok: [localhost] => (item=A4) =>
msg: Not in A4
For further debugging you could also use the assert module – Asserts given expressions are true.
I Have 2 dictionary:
- Test1:
1: pass
2: fail
3: pass
- Test2:
1.1.1.1: val1
2.2.2.2: val2
3.3.3.3: val3
Condition is when Test1.value contians fail
- name: test
debug:
msg: "{{item.1.value}} {{item.1.key}} {{item.0.key}} {{item.0.value}}"
with_together:
- "{{Test1}}"
- "{{Test2}}"
when: item.0.value == "fail"
This is not working as expected unable to get both key and value of 2 dict in one loop
In when statement you must to use item.0 or item.1 to evaluate the condition. And I recommend you use a list in with_together loop and if you are using a variable you have to use braces {{ variable }} .
Try as below:
- name: test
debug:
msg: "{{item.1 }}"
with_together:
- "{{ Test1.values() | list }}"
- "{{ Test2.values() | list }}"
when: item.0 == "fail"
You'll get
TASK [test] *******************************************************************************************************************************************************************************************************
skipping: [127.0.0.1] => (item=['pass', 'val1'])
ok: [127.0.0.1] => (item=['fail', 'val2']) => {
"msg": "val2"
}
skipping: [127.0.0.1] => (item=['pass', 'val3'])
I achieved this by :
converting dict to list using filter -> |list
since
both dict of same size I was able to get data of both dict in single loop:
- name: test
debug:
msg: "{{item.0}} {{item.1}} {{item.2}} {{item.3}}"
with_together:
- "{{ Test1.values() | list }}"
- "{{ Test2.values() | list }}"
- "{{ Test1.keys() | list }}"
- "{{ Test2.keys() | list }}"
when: item.0 == "fail"
I am trying to assign a random number between 7000 and 7005 to a variable which is not present in a list.
the list has 7000, 7001, 7004 and 7002.
- name: Set fact
set_fact:
val: "{{ 7004 | random(start=7000) }}"
until: val not in list
The above playbook tried to assign 3 times and failed. It did not assign the value 7003.
TASK [xxx] ******************************
task path: /tmp/awx_164677__l9__xmu/project/roles/xxxxx/tasks/main.yml:26
FAILED - RETRYING: Set fact (3 retries left).
FAILED - RETRYING: Set fact (2 retries left).
FAILED - RETRYING: Set fact (1 retries left).
fatal: [xxxprod]: FAILED! => {"ansible_facts": {"val": "7000"}, "attempts": 3, "changed": false}
How to increase the retry value from 3 to some other value?
Note: the above list was updated by this playbook, only the last value did not get updated.
Thanks.
retries playbook keyword can be used to specify the number of retries before giving up in a until loop. This setting is only used in combination with until Keyword.
- name: Set fact
set_fact:
val: "{{ 7004 | random(start=7000) }}"
until: val not in list
retries: 5
Here is the refereance for you to look more Retries Playbook Keyword
Make the random choice from the missing items only. The playbook below solves the general problem of randomly completing a sequence
- hosts: localhost
vars:
vals: [7000, 7003]
vals_all: [7000, 7001, 7002, 7003, 7004]
vals_missing: "{{ vals_all|difference(vals)|length }}"
tasks:
- set_fact:
vals2: "{{ vals }}"
- set_fact:
vals2: "{{ vals2 + [vals_all|difference(vals2)|random] }}"
with_sequence: end="{{ vals_missing }}"
- debug:
var: vals2
gives
vals2:
- 7000
- 7003
- 7002
- 7001
- 7004
If you want to iterate the random generation of the missing items the playbook below
- hosts: localhost
vars:
vals: [7000, 7003]
vals_all: [7000, 7001, 7002, 7003, 7004]
vals_missing: "{{ vals_all|difference(vals)|length }}"
tasks:
- set_fact:
vals2: "{{ vals }}"
- debug:
var: val
with_sequence: end="{{ vals_missing }}"
vars:
val: "{{ [vals_all|difference(vals2)|random] }}"
vals2: "{{ vals2 + [vals] }}"
gives
ok: [localhost] => (item=1) =>
ansible_loop_var: item
item: '1'
val:
- 7002
ok: [localhost] => (item=2) =>
ansible_loop_var: item
item: '2'
val:
- 7004
ok: [localhost] => (item=3) =>
ansible_loop_var: item
item: '3'
val:
- 7001
Change the variable vals: [7000, 7001, 7004, 7002] to solve your problem. (But, it's trivial because only a single item is missing.)
I have a playbook where I execute several tasks. Each task can be executed if it meets the WHEN condition. I would like to save some data into a list so I can use it later in the process.
Here is an over simplified example to illustrate my need:
- Set GlobalVar = []
- task A
when task_A_enabled
register custom_value_A into GlobalVar
- task B
when task_B_enabled
register custom_value_B into GlobalVar
- task C
do something with GlobalVar
I hope it's clear enough to help me figure out how to do that. Thank you.
An option would be to use block
For example the play below
- hosts: localhost
gather_facts: no
vars:
GlobalVar: []
task_a: true
task_b: false
tasks:
- name: task A
block:
- debug:
msg: Task A is enabled
- set_fact:
GlobalVar: "{{ GlobalVar + [ 'A' ] }}"
when: task_a
- name: task B
block:
- debug:
msg: Task B is enabled
- set_fact:
GlobalVar: "{{ GlobalVar + [ 'B' ] }}"
when: task_b
- name: task C
debug:
var: GlobalVar
gives (abridged):
ok: [localhost] => {
"msg": "Task A is enabled"
}
...
ok: [localhost] => {
"GlobalVar": [
"A"
]
}
You can use the module set_fact to do the variable assignment and use blocks to group a task and the variable assignment step so you can check conditions once:
---
- hosts: "all"
vars:
GlobalVar: []
tasks:
- block:
- set_fact:
GlobalVar: "{{ GlobalVar + [1, 2] }}"
- debug:
msg: "{{GlobalVar}}"
when: true
- block:
- set_fact:
GlobalVar: "{{ GlobalVar + [3, 4] }}"
- debug:
msg: "{{GlobalVar}}"
when: false
- block:
- set_fact:
GlobalVar: "{{ GlobalVar + [5, 6] }}"
- debug:
msg: "{{GlobalVar}}"
when: true