Double with_items loop in ansible - ansible

I want to create a double loop in ansible.
I have one things like this :
userslist:
- name: user1
primary : user1-group
groups :
- group1
- group2
- name: user2
primary : user2-group
groups :
- group3
- group4
- name : Creating Secondary group
group :
name : "{{ item.groups }}"
state: present
with_items: "{{ userslist}}"
Is it possible for each users create each secondary group ?
I think for this i need to do a second with_items loop but i don't know how

There are two ways to make a nested (double) loop in Ansible.
with_nested. It allows you to have an inner iteration for object you iterate in the outer loop. Examples and explanation are provided in the official documentation: https://docs.ansible.com/ansible/2.5/plugins/lookup/nested.html
using with_items together with include_tasks. This is a complicated yet powerful construction. In theory there is no limit (except for the stack depth) on how nested this construction can be.
It requires to put inner code into a separate tasklist. I'll show it with outer.yaml and inner.yaml, outer perform loop over a list, and inner perform a loop over item (loop variable) of the outer loop.
outer.yaml:
- name: Loop over foo
include_tasks: inner.yaml
with_items: '{{ foo }}'
loop_control:
loop_var: inner_var_name
vars:
foo:
- [1, 2, 3]
- [a, b, c]
inner.yaml:
- name: Performing operation one
debug: msg="Action one for {{ item }}"
with_items: '{{ inner_var_name }}'
- name: Performing operation two
debug: msg="Action two for {{item}}"
with_items: '{{ inner_var_name }}'
The key advantage of this method is that inner.yaml can contain any number of statements, all of which will be processed in a loop from outer.yaml.
One important thing: all include things require a bit of caution with anything related to passing values (set_fact, register, etc) from included code. In is rather tricky and non-obvious, so my advice is never use variables set in include code outside of that inclusion.

I do this and it's work very well
---
- hosts: all
become: yes
vars:
userslist:
- name: user1
primary : user1-group
groups :
- group1
- group2
- name: user2
primary : user2-group
groups :
- group3
- group4
tasks:
- name: Creating Secondary group
group:
name="{{ item.1 }}"
with_subelements:
- '{{ userslist }}'
- groups

Related

Ansibe: concatenation of items from with_items

I'm trying to get a variable which will contain comma separated items from with_itmes loop as follow:
- hosts: localhost
connection: local
gather_facts: no
tasks:
- name: set_fact
set_fact:
foo: "{{ foo }}{{ item }},"
with_items:
- "one"
- "two"
- "three"
vars:
foo: ""
- name: Print the var
debug:
var: foo
It works as expected but what I'm getting at the end is trailing comma.
Is there any way to remove it?
There is a join filter that we can use with lists to concatenate list elements with a given character.
If we are passing the list directly to with_items or loop then we can use loop_control to "extend" some more loop information to get ansible_loop.allitems. Then this can be joined with the join filter.
Example:
- set_fact:
foo: "{{ ansible_loop.allitems|join(',') }}"
loop:
- one
- two
- three
loop_control:
extended: true
Otherwise a more straightforward way is to define a variable with list and use join filter on elements of that variable.
Example:
- set_fact:
foo: "{{ mylist|join(',') }}"
vars:
mylist:
- one
- two
- three
No clue if this is correct way to do but it does the job:
- name: Print the var
debug:
msg: "LIST: {{ foo | regex_replace(',$','') }}"

Ansible: Skip loop when list is undefined

Example playbook -
---
- hosts: localhost
vars:
lesson:
name: Physics
students:
- Bob
- Joe
tasks:
- name: Display student names
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: item | default("")
The above playbook works well to output the student names.
However, if the input changes (as per below) such that no student names have been defined, then an error occurs. Is there a simple way to have the playbook skip this task if the list is undefined as per the input below? I realize it would work if the input specifies students: [], but as this input is coming from simple users, they're not going to know this. Much Thanks!
vars:
lesson:
name: Physics
students:
Error: fatal: [localhost]: FAILED! =>
msg: 'Invalid data passed to ''loop'', it requires a list, got this instead: . Hint: If you passed a list/dict of just one element, try adding wantlist=True to your lookup invocation or use q/query instead of lookup.
Update - I've tried the below variations but still get the same error -
---
- hosts: localhost
vars:
lesson:
name: Physics
students:
tasks:
- name: Display student names variation 1
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: lesson.students is iterable
- name: Display student names variation 2
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: lesson.students is not none
- name: Display student names variation 3
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: ( item | default("") ) or ( item is not none )
The real problem is that loop requires a list, even if it is an empty list.
If your var is undefined/None/empty string, it exists but is not a list and your when condition will never get evaluated because loop will fire an error before it is ever reached.
You have to default your var to an empty list in such cases, which will lead to a 0 size loop equivalent to skipping the task.
Since your var is defined but None you need to use the second optional parameter to default so that empty/false values are replaced as well
Note: I used the short alias d to default in my below examples
- name: Display student names
debug:
msg: '{{ item }}'
loop: "{{ lesson.students | d([], true) }}"
A good practice here that would have nipped that error in the bud would be to have a coherent data declaration by either:
not declaring the key at all and use a simple default i.e.
# ... #
vars:
lesson:
name: Physics
# ... #
loop: "{{ lesson.students | d([]) }}"
declare an empty list for the key rather than a None value i.e.
# ... #
vars:
lesson:
name: Physics
students: []
# ... #
loop: "{{ lesson.students }}"
My first proposition is the safest in this case anyway and will work in for all the above vars declarations.
There is a difference between an undefined variable, and variable having None value.
When you set variable name, but leave the right hand side empty. The variable is defined, but it is set to NoneType.
So your when: condition should have additional check for NoneType:
- hosts: localhost
vars:
lesson:
name: Physics
students:
tasks:
- name: Display student names
debug:
msg: '{{ item }}'
loop: "{{ lesson.students }}"
when: ( item | default("") ) or ( item is not none )
This will give:
skipping: [localhost] => (item=None)

How can I do a loop on include_tasks or many tasks, and the condition is a variable when write ansible playbook

I have tried:
- name: set passed
set_fact:
i: 0
- name: test
include_tasks: test2.yml
until: i == 3
in test2.yml:
- name: set i
set_fact:
i: '{{ i|int + 1 }}'
But seems the "until" can't be used on include_tasks, only can be used on single task, but I need to loop a set of tasks.
Then I tried some thing like:
- name: test
include_tasks: test2.yml
loop: [1,2,3,4]
when: i != 3
But seems the "when" condition only verified once, so all the 4 loops run.
Is the a solution?
Thank you.
First, before making you able to write loops in Ansible, a word of advice. DO NOT WRITE PROGRAMS IN ANSIBLE. Every time you are trying to use loop with big task list, you bring more subtle complexity in your playbooks which will make you suffer later.
Most tasks you want to do with lists, can be done as series of operations on list. E.g. if you have foo: [1,2,3,4], and you want to create directories with those names and send those names to remote server, it's better to write like this:
- name: Creating dirs
file:
state: directory
name: '{{ item }}'
loop: '{{ foo }}'
when: item != 3
- name: Sending to remote
uri:
url: 'http://example.com/{{ item }}'
loop: '{{ foo }}'
when: item != 3
This code can be maintained and is relatively easy to debug. Now, we here is the thing you want to do with includes:
tasklist.yaml:
- file:
state: directory
name: '{{ item }}'
- uri:
url: 'http://example.com/{{ item }}'
outer tasklist:
- include_tasks: tasklist.yaml
loop: '{{ foo }}'
when: item != 3
when is evaluated on each run, but you need to put some non-invariant there, f.e. item (magic variable for loop to produce).

How to execute alternating roles in Ansible?

I need to somehow loop over a list of variables and execute both of the below roles once for each iteration, on each iteration passing a variable to the role. For example, given a variable list of 100-101, I need to execute in the order role1:100, role2:100, role1:101, role2:101. The variables 100-100 should be passed to the tasks inside the role.
---
- hosts: group1
tasks:
- include_role:
name: role1
- hosts: group2
tasks:
- include_role:
name: role2
I was looking at the below answer as a possible solution but I am not sure how to adapt it to my needs. Can the above scenario be accomplished in Ansible?
Ansible: How to iterate over a role with an array?
Loop over the Cartesian product of variables and role names:
vars:
roles_to_include:
- role1
- role2
values_to_pass:
- 100
- 101
tasks:
- include_role:
name: "{{ item.1 }}"
vars:
my_variable: "{{ item.0 }}"
loop: "{{ values_to_pass | product(roles_to_include) | list }}"

Ansible with_subelements

I am having a hard time understanding the logic of ansible with_subelements syntax, what exactly does with_subelements do? i took a look at ansible documentation on with_subelements here https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html#with-subelements and was not very helpful. I also saw a playbook with with_subelements example on a blog
---
- hosts: cent
vars:
users:
- name: jagadish
comments:
- 'Jagadish is Good'
- name: srini
comments:
- 'Srini is Bad'
tasks:
- name: User Creation
shell: useradd -c "{{ item.1 }}" "{{ item.0.name }}"
with_subelements:
- users
- comments
what do item.1 and item.0 refer to?
This is really bad example of how subelements lookup works. (And has old, unsupported, syntax as well).
Look at this one:
---
- hosts: localhost
gather_facts: no
vars:
families:
- surname: Smith
children:
- name: Mike
age: 4
- name: Kate
age: 7
- surname: Sanders
children:
- name: Pete
age: 12
- name: Sara
age: 17
tasks:
- name: List children
debug:
msg: "Family={{ item.0.surname }} Child={{ item.1.name }} Age={{ item.1.age }}"
with_subelements:
- "{{ families }}"
- children
Task List children is like a nested loop over families list (outer loop) and over children subelement in each family (inner loop).
So you should provide a list of dicts as first argument to subelements and name of subelement you want to iterate inside each outer item.
This way item.0 (family in my example) is an outer item and item.1 (child in my example) is an inner item.
In Ansible docs example subelements is used to loop over users (outer) and add several public keys (inner).

Resources