I want the playbook to run once with each item in the list and not all items in the list at once.
Ansible version: 2.6.1
Tasks.yaml:
---
- name: Task 1
debug:
msg: "Message 1: {{ item }}"
with_items: "{{ messages }}"
- name: Task 2
debug:
msg: "Message 2: {{ item }}"
with_items: "{{ messages }}"
Playbook:
- hosts: localhost
gather_facts: no
tasks:
- import_tasks: Tasks.yml
vars:
messages:
- 1
- 2
This is my expected result:
Task 1 with Item 1
Task 2 with Item 1
Task 3 with Item 1
Task 1 with Item 2
Task 2 with Item 2
Task 3 with Item 2
Task 1 with Item 3
Task 2 with Item 3
Task 3 with Item 3
But when I execute the playbook, then it is like this:
Task 1 with Item 1
Task 1 with Item 2
Task 2 with Item 1
Task 2 with Item 2
Task 3 with Item 1
Task 3 with Item 2
...
I tried both import and include - both have the same result.
Your playbook.yml should implement a loop (notice you cannot loop with import_tasks; it will raise an error):
- hosts: localhost
connection: local
gather_facts: no
vars:
messages:
- 1
- 2
- 3
tasks:
- include_tasks: Tasks.yml
loop: "{{ messages }}"
And Tasks.yml should look like this (no loops inside):
---
- name: Task 1
debug:
msg: "Message 1: {{ item }}"
- name: Task 2
debug:
msg: "Message 2: {{ item }}"
Related
Real scenario is,
I have n hosts in a inventory group and playbook has to run a specific *command for the specific inventory hostname(done with ansible when condition statement), but whenever the condition met and I need to register a variable for the above *command result.
so this variable creation should be done dynamically and these created variable should be appended into a list and then at end of the same playbook by passing the list to a loop I have to check the job async_status.
So could some one help me here?
tasks:
-name:
command:
when: invenory_hostname == x
async: 360
poll:0
regsiter: "here dynamic variable"
-name:
command:
when: invenory_hostname == x
async: 360
poll:0
regsiter: "here dynamic variable"
-name:
command:
when: invenory_hostname == x
async: 360
poll:0
regsiter: "here dynamic variable" #his will continue based on the requirments
-name: collect the job ids
async_status:
jid:{item}
with_items:"list which has all the dynamically registered variables"
If you can write this as a loop instead of a series of independent tasks this becomes much easier. E.g:
tasks:
- command: "{{ item }}"
register: results
loop:
- "command1 ..."
- "command2 ..."
- name: show command output
debug:
msg: "{{ item.stdout }}"
loop: "{{ results.results }}"
The documentation on "Registering variables with a loop" discusses what the structure of results would look like after this task executes.
If you really need to write independent tasks instead, you could use the
vars lookup to find the results from all the tasks like this:
tasks:
- name: task 1
command: echo task1
register: task_result_1
- name: task 2
command: echo task2
register: task_result_2
- name: task 3
command: echo task3
register: task_result_3
- name: show results
debug:
msg: "{{ item }}"
loop: "{{ q('vars', *q('varnames', '^task_result_')) }}"
loop_control:
label: "{{ item.cmd }}"
You've updated the question to show that you're using async tasks, so
that changes things a bit. In this example, we use an until loop
that waits for each job to complete before checking the status of the
next job. The gather results task won't exit until all the async
tasks have completed.
Here's the solution using a loop:
- hosts: localhost
gather_facts: false
tasks:
- name: run tasks
command: "{{ item }}"
async: 360
poll: 0
register: task_results
loop:
- sleep 1
- sleep 5
- sleep 10
- name: gather results
async_status:
jid: "{{ item.ansible_job_id }}"
register: status
until: status.finished
loop: "{{ task_results.results }}"
- debug:
var: status
And the same thing using individual tasks:
- hosts: localhost
gather_facts: false
tasks:
- name: task 1
command: sleep 1
async: 360
poll: 0
register: task_result_1
- name: task 2
command: sleep 5
async: 360
poll: 0
register: task_result_2
- name: task 3
command: sleep 10
async: 360
poll: 0
register: task_result_3
- name: gather results
async_status:
jid: "{{ item.ansible_job_id }}"
register: status
until: status.finished
loop: "{{ q('vars', *q('varnames', '^task_result_')) }}"
- debug:
var: status
The documentation for import_tasks mentions
Any loops, conditionals and most other keywords will be applied to the included tasks, not to this statement itself.
This is exactly what I want. Unfortunately, when I attempt to make import_tasks work with a loop
- import_tasks: msg.yml
with_items:
- 1
- 2
- 3
I get the message
ERROR! You cannot use loops on 'import_tasks' statements. You should use 'include_tasks' instead.
I don't want the include_tasks behaviour, as this applies the loop to the included file, and duplicates the tasks. I specifically want to run the first task for each loop variable (as one task with the standard with_items output), then the second, and so on. How can I get this behaviour?
Specifically, consider the following:
Suppose I have the following files:
playbook.yml
---
- hosts: 192.168.33.100
gather_facts: no
tasks:
- include_tasks: msg.yml
with_items:
- 1
- 2
msg.yml
---
- name: Message 1
debug:
msg: "Message 1: {{ item }}"
- name: Message 2
debug:
msg: "Message 2: {{ item }}"
I would like the printed messages to be
Message 1: 1
Message 1: 2
Message 2: 1
Message 2: 2
However, with import_tasks I get an error, and with include_tasks I get
Message 1: 1
Message 2: 1
Message 1: 2
Message 2: 2
You can add a with_items loop taking a list to every task in the imported file, and call import_tasks with a variable which you pass to the inner with_items loop. This moves the handling of the loops to the imported file, and requires duplication of the loop on all tasks.
Given your example, this would change the files to:
playbook.yml
---
- hosts: 192.168.33.100
gather_facts: no
tasks:
- import_tasks: msg.yml
vars:
messages:
- 1
- 2
msg.yml
---
- name: Message 1
debug:
msg: "Message 1: {{ item }}"
with_items:
- "{{ messages }}"
- name: Message 2
debug:
msg: "Message 2: {{ item }}"
with_items:
- "{{ messages }}"
It is not possible. include/import statements operate with task files as a whole.
So with loops you'll have:
Task 1 with Item 1
Task 2 with Item 1
Task 3 with Item 1
Task 1 with Item 2
Task 2 with Item 2
Task 3 with Item 2
Task 1 with Item 3
Task 2 with Item 3
Task 3 with Item 3
I need to create a certain number of systemd unit files based on name and included variable with number of files to create, like:
app-name#1.service
app-name#2.service
app-name#3.service
script-name#1.service
script-name#2.service
script-name#3.service
script-name#4.service
It works with a nested loop using range function, but I not understand how to use loop variable (item.1.workers) into range() parameters.
- hosts: localhost
vars:
apps:
- name: app-name
workers: 3
- name: script-name
workers: 5
connection: local
tasks:
- name: test
debug:
msg: "{{ item.1.name }}#{{ item.0 }}.service"
loop: "{{ range(1, 3) | product(apps) | list }}"
Let's create the lists of workers in the first task and loop with subelements in the second task
- set_fact:
apps1: "{{ apps1|default([]) +
[{'name': item.name,
'workers': range(1, item.workers + 1)|list}] }}"
loop: "{{ apps }}"
- debug:
msg: "{{ item.0.name }}#{{ item.1 }}.service"
loop: "{{ apps1|subelements('workers') }}"
gives
msg: app-name#1.service
msg: app-name#2.service
msg: app-name#3.service
msg: script-name#1.service
msg: script-name#2.service
msg: script-name#3.service
msg: script-name#4.service
msg: script-name#5.service
I have a dynamic inventory which returns me my hosts addresses.
But sometimes, I would like to apply some configuration to a limited number of hosts.
A sample with N hosts but only 5 are echoed:
- name: "Run silly shell script"
shell: |
echo {{ item }}
exit 0
with_items: "{{ hosts | only(5) }}"
to get the first X elements from a list, use: list_var[:X].
full example as PB below:
---
- hosts: localhost
gather_facts: false
vars:
list_var:
- 1
- 2
- 3
- 4
- 5
- 6
tasks:
- name: print full list
debug:
var: list_var
- name: print list of first 3 elements
debug:
var: list_var[:3]
hope it helps.
Is it possible to skip some items in Ansible with_items loop operator, on a conditional, without generating an additional step?
Just for example:
- name: test task
command: touch "{{ item.item }}"
with_items:
- { item: "1" }
- { item: "2", when: "test_var is defined" }
- { item: "3" }
in this task I want to create file 2 only when test_var is defined.
The other answer is close but will skip all items != 2. I don't think that's what you want. here's what I would do:
- hosts: localhost
tasks:
- debug: msg="touch {{item.id}}"
with_items:
- { id: 1 }
- { id: 2 , create: "{{ test_var is defined }}" }
- { id: 3 }
when: item.create | default(True) | bool
The when: conditional on the task is evaluated for each item. So in this case, you can just do:
...
with_items:
- 1
- 2
- 3
when: item != 2 and test_var is defined
I had a similar problem, and what I did was:
...
with_items:
- 1
- 2
- 3
when: (item != 2) or (item == 2 and test_var is defined)
Which is simpler and cleaner.
I recently ran into this problem and none of the answers I found were exactly what I was looking for. I wanted a way to selectively include a with_item based on another variable.
Here is what I came up with:
- name: Check if file exists
stat:
path: "/{{item}}"
with_items:
- "foo"
- "bar"
- "baz"
- "{% if some_variable == 'special' %}bazinga{% endif %}"
register: file_stat
- name: List files
shell: echo "{{item.item | basename}}"
with_items:
- "{{file_stat.results}}"
when:
- item.stat | default(false) and item.stat.exists
When the above plays are run, the list of items in file_stat will only include bazinga if some_variable == 'special'
What you want is that file 1 and file 3 always gets created but file 2 is created only when test_var is defined. If you use ansible's when condition it works on complete task and not on individual items like this :
- name: test task
command: touch "{{ item.item }}"
with_items:
- { item: "1" }
- { item: "2" }
- { item: "3" }
when: test_var is defined
This task will check the condition for all three line items 1,2 and 3.
However you can achieve this by two simple tasks :
- name: test task
command: touch "{{ item }}"
with_items:
- 1
- 3
- name: test task
command: touch "{{ item }}"
with_items:
- 2
when: test_var is defined