Ansible with_subelements - ansible

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).

Related

Checking if a value exists in a list of dictionaries

I have a dictionary with a list of dictionaries. How do I do a lookup to check if a certain name exists in key 'firstname'?
---
- hosts: localhost
vars:
Persons:
- firstname: Bob
- firstname: Sarah
name: Sarah
tasks:
- debug: msg="{{ name in Persons }}" - ????
Found the answer on another post -
- debug: msg="{{ name in Persons | map(attribute='firstname') | list }}"

set path when file exists in Ansible yml code

I'm trying to set a var only when a file exists, here is one of my attempts
---
- hosts: all
tasks:
- stat:
path: '{{ srch_path_new }}/bin/run'
register: result
- vars: srch_path="{{ srch_path_new }}"
when: result.stat.exists
This also didn't work
- vars: srch_path:"{{ srch_path_new }}"
The task you are looking for is called set_fact: and is the mechanism ansible uses to declare arbitrary "host variables", sometimes called "hostvars", or (also confusingly) "facts"
The syntax would be:
- set_fact:
srch_path: "{{ srch_path_new }}"
when: result.stat.exists
Also, while vars: is a legal keyword on a Task, its syntax is the same as set_fact: (or the vars: on the playbook): a yaml dictionary, not a key:value pair as you had. For example:
- debug:
msg: hello, {{ friend }}
vars:
friend: Jane Doe
and be aware that vars: on a task exist only for that task

Double with_items loop in 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

loop using with_sequence over with_subelement in ansible

I am trying to loop over a subelement variables using the a loop like with_sequence,
For the moment I have :
---
- hosts: corosync
gather_facts: no
vars:
host_list:
- node_one
- node_two
list_services:
- group: ALPHA
services:
- name: DHCP
directory: /etc/dhcp
- name: DNS
directory : /etc/dns
- group: BETA
services:
- name: SSH
directory: /etc/ssh
- name: FTP
directory: /ztc/ftp
tasks:
- name: create group-services
debug:
msg: "the service name is {{ item.0.group}}-{{ item.1.name}} , directory is {{ item.1.directory }}"
with_subelements:
- "{{ list_services }}"
- services
Since I have 2 nodes in my cluster
node_one
node_two
I want to deplucate each service like below :
{{ item.0.group}}-{{host_id}}-{{ item.1.name}}
with {{ host_id }} a list that equal ['0','1'] since I have 2 nodes
and the with_subelement function loop over the {{ host_id }} twice since we have two nodes, what gives :
ALPHA-0-DHCP
ALPHA-0-DNS
ALPHA-1-DHCP
ALPHA-1-DNS
BETA-0-SSH
BETA-0-FTP
BETA-1-SSH
BETA-1-FTP
I want to use something like with_sequence function beside with_subelement like
with_sequence: start=0, end={{ groups['host_list']|length}}
Any suggestions please
The loop declaration introduced in Ansible 2.5 makes it pretty straightforward ー you just need to combine the two patterns replacing legacy with_sequence and legacy with_subelements:
- name: create group-services
debug:
msg: "{{item.1.0.group}}-{{item.0}}-{{item.1.1.name}}"
loop: "{{ range(0, host_list|length) | product(list_services|subelements('services')) | list }}"

Ansible: How to create uid's within certain range

I am currently working on a host where i have installed ansible. I have created 2 application accounts with groups with nologin and within that groups i want to add users, so that every department has their own ansible directory.
My vars look like below:
---
- hosts: localhost
become: yes
vars:
ansible_groupuser:
- name: "ansible-dictators"
ansible_groupuser_uid: "3000"
ansible_users:
- idia
- josefs
- donaldt
- kimjongu
- name: "ansible-druglords"
ansible_groupuser_uid: "3001"
ansible_users:
- pabloe
- javierg
- frankl
- rossu
Now i have 2 plays. 1 to create the Groupuser:
# This creates the groupuser
- name: Play 1 Create central ansible user and group per department
user:
name: "{{ item.name }}"
shell: "/sbin/nologin"
home: "/home/{{ item.name }}"
comment: "{{ item.name }} Group Account"
uid: "{{ item.ansible_groupuser_uid }}"
append: "yes"
with_items:
- "{{ansible_groupuser}}"
And 1 to create the "normal" users:
- name: Play 2 Create users
user:
name: "{{ item.1 }}"
shell: "/bin/bash"
home: "/home/{{ item.1 }}"
comment: "{{ item.1 }}"
groups: "{{ item.0.name }}"
append: "yes"
with_subelements:
- "{{ ansible_groupuser }}"
- ansible_users
If i run this play it creates the groupuser ansible-dictators on 3000 and ansible-druglords on 3001. idia gets 3002, josefs gets 3003 etc. It gets kinda messy, when i want to add a 3th groupuser like ansible-rockstars, it starts counting at the first available uid, 3010. What i want is to place the groupusers and the common users in 2 different ranges (2000 and 3000 for example)
When i do a with_together on the first play, like below, it works:
- name: Play1 Create central ansible user and group per department
user:
name: "{{ item.0.name }}"
shell: "/sbin/nologin"
home: "/home/{{ item.0.name }}"
comment: "{{ item.0.name }} Group Account"
uid: "{{ item.1 }}"
append: "yes"
with_together:
- "{{ansible_groupuser}}"
- "{{ range(3000,3020)|list }}"
when: item.0 != None
But when i do a with_together on the second play, it doesnt work:
- name: Create users
user:
name: "{{ item.1 }}"
shell: "/bin/bash"
home: "/home/{{ item.1 }}"
comment: "{{ item.1 }}"
groups: "{{ item.0.name }}"
append: "yes"
uid: "{{ item.2 }}"
with_together:
- "{{ ansible_groupuser }}"
- ansible_users
- "{{ range(2000,2020)|list }}"
Anyone got a suggestion how to make the second play work with a uid in a certain range? Or another suggestion how to get the uid's in different groups? To give the groupusers an uid in the vars is no problem. But i am expecting a lot of "common" users to add (+50) and i dont want to specify a uid for all of those users.
Hope it makes sense. Thanks in advance.
I think range(...) approach has a flaw: if you delete some user from your list in the future, IDs for subsequent entries will change and you can end up with messed file permissions on your system.
You can patch user module to support --firstuid/--lastuid arguments of the underlying adduser command, so you can set different range for uid generation.
But I'd suggest you to define "static" uids for top-level users in your vars file (from some predefined range, say: 3000..30xx) – this way you can safely add/remove top-level user/groups in the future.
And leave "common" users to get their ids automatically, so adding/deleting them will not mess your ids. If you like them to be from some specific range, you can modify system-wide /etc/adduser.conf with FIRST_UID=5000/LAST_UID=6000.

Resources