Iterate a list within a dictionary in ansible - loops

I have a variable structured like this. I have successfully used this with with_dict with a single key in the accessible_from
vars:
mysql_dbs:
db1:
user: db1_user
pass: "password"
accessible_from: localhost
db2:
user: db2_user
pass: "password2"
accessible_from: '%'
This is applied using the mysql_db ansible module, like this:
- name: Configure mysql users
mysql_user: name={{ item.value.user }} password={{ item.value.pass }} host={{ item.value.accessible_from | default('localhost')}} priv={{ item.key }}.*:ALL state=present
with_dict: "{{ mysql_dbs }}"
I would like accessible_from to have the ability to be a list. It doesn't matter if it has to be a list, but a single key/value pair is not enough :) So for example:
vars:
mysql_dbs:
db1:
user: db1_user
pass: "password"
accessible_from:
- server1
- server2
- localhost
db2:
user: db2_user
pass: "password"
accessible_from:
- '%'
So - the aim is to create all the DBs and users in one play. I've tried playing around with with_subelements, without success. Is it actually possible to do this? Or is it necessary to restructure the data, or rewrite the play? I'll do that if I have to, but I was wondering if there was another way round it.

First: You may refactor your mysql_dbs into list (because in with_subelements you can't refer items' keys), like:
mysql_dbs:
- name: db1
user: db1_user
pass: "password"
accessible_from:
- server1
- server2
- localhost
- name: db2
user: db2_user
pass: "password2"
accessible_from:
- '%'
And user with_subelements:
- mysql_user: name={{ item[0].user }} password={{ item[0].pass }} host={{ item[1] }} priv={{ item[0].name }}.*:ALL state=present
with_subelements:
- "{{ mysql_dbs }}"
- accessible_from
But this will fail if accessible_from is undefined for any db. You may use skip_missing, but this will skip entire db. So you can't omit accessible_from in this case.
Second: You may use helper set_fact to form a list with key and value, also defaulting accessible_from to localhost. This will work without refactoring your data:
- set_fact:
db_name: "{{ item.key }}"
db_params: "{{ dict(accessible_from=['localhost']) | combine(item.value) }}"
with_dict: "{{ mysql_dbs }}"
register: mysql_dbs_fact
loop_control:
label: "{{ item.key }}"
- debug:
msg: "mysql_user: name={{ item[0].db_params.user }} password={{ item[0].db_params.pass }} host={{ item[1] }} priv={{ item[0].db_name }}.*:ALL state=present"
with_subelements:
- "{{ mysql_dbs_fact.results | map(attribute='ansible_facts') | list }}"
- db_params.accessible_from
loop_control:
label: "{{ item[0].db_name }}->{{ item[1] }}"

Try this:
vars:
mysql_dbs:
db1:
user: db1_user
pass: "password"
accessible_from:
- acc_from: server1
- acc_from: server2
- acc_from: localhost
db2:
user: db2_user
pass: "password"
accessible_from:
- acc_from: '%'
tasks:
- name: Configure mysql users
debug: msg="{{ item.0.user }} password={{ item.0.pass }} host={{ item.1.acc_from }} priv={{ item.0 }}.*:ALL state=present"
with_subelements:
- "{{ mysql_dbs }}"
- accessible_from

Related

Ansible cannot invoke variable for hostname in playbook

I need to add new users to multiple Ubuntu servers. Unfortunately, the password and username are not consistent. Every machine has its own username and the password cannot be the same. For example, host-1 will have a user account host-1_username with password host-1_password and host-2 will have a user account host-2_username with password host-2_password, and so on.
I would like to do that by Ansible. I have a list.yaml file:
---
list:
- hostname: host-1
username: host-1_username
password: host-1_password
- hostname: host-2
username: host-2_username
password: host-2_password
- hostname: host-3
username: host-3_username
password: host-3_password
Here is my Ansible playbook:
- name: Crate new user
vars_files:
- list.yml
hosts: "{{ item.hostname }}"
remote_user: root
become: true
tasks:
- name: Create new user
ansible.builtin.user:
name: "{{ item.username }}"
groups: sudo
password: "{{ item.password | password_hash('sha512') }}"
shell: /bin/bash
- name: Modify sshd_config
ansible.builtin.lineinfile:
dest: /etc/ssh/sshd_config
line: 'AllowUsers {{ item.username }}'
loop: "{{ list }}"
But looks like Ansible cannot invoke the variable to add into hosts column:
ERROR! couldn't resolve module/action 'hosts'. This often indicates a misspelling, missing collection, or incorrect module path.
I am very new to Ansible, any help is appreciated!
Given the data
shell> cat list.yml
users_list:
- hostname: host-1
username: host-1_username
password: host-1_password
- hostname: host-2
username: host-2_username
password: host-2_password
- hostname: host-3
username: host-3_username
password: host-3_password
Create an inventory file, e.g.
shell> cat hosts
host-1
host-2
host-3
Convert the data to dictionaries, e.g.
- hosts: all
gather_facts: false
vars_files:
- list.yml
tasks:
- set_fact:
users_dict: "{{ users_list|items2dict(key_name='hostname', value_name='username') }}"
psswd_dict: "{{ users_list|items2dict(key_name='hostname', value_name='password') }}"
run_once: true
gives
users_dict:
host-1: host-1_username
host-2: host-2_username
host-3: host-3_username
and
psswd_dict:
host-1: host-1_password
host-2: host-2_password
host-3: host-3_password
Use the dictionaries to select the hosts' specific users and passwords, e.g.
- debug:
msg: "Create user: {{ users_dict[inventory_hostname] }}
password: {{ psswd_dict[inventory_hostname] }}"
gives
TASK [debug] ***************************************************************
ok: [host-1] =>
msg: 'Create user: host-1_username password: host-1_password'
ok: [host-2] =>
msg: 'Create user: host-2_username password: host-2_password'
ok: [host-3] =>
msg: 'Create user: host-3_username password: host-3_password'
You can omit the inventory file and create a playbook completely driven by the data. Create dynamic group my_group in the first play and use it in the second one. The playbook below gives the same results
- name: Create dynamic group of the hosts from users_list
hosts: localhost
gather_facts: false
vars_files:
- list.yml
tasks:
- add_host:
name: "{{ item.hostname }}"
groups: my_group
loop: "{{ users_list }}"
- name: Create users
hosts: my_group
gather_facts: false
vars_files:
- list.yml
tasks:
- set_fact:
users_dict: "{{ users_list|items2dict(key_name='hostname', value_name='username') }}"
psswd_dict: "{{ users_list|items2dict(key_name='hostname', value_name='password') }}"
run_once: true
- debug:
var: users_dict
run_once: true
- debug:
var: psswd_dict
run_once: true
- debug:
msg: "Create user: {{ users_dict[inventory_hostname] }}
password: {{ psswd_dict[inventory_hostname] }}"

concatenate variables in ansible to pass them as helm flags for deployments

The goal is to pass variables from ansible to helm --set key=value. The following output structure for ansible is available
apps:
- name: proxy
properties:
- key: proxy.externalIP
value: 192.168.178.1
- key: proxy.service.Type
value: LoadBalancer
- name: proxylived
properties:
- key: proxylived.externalIP
value: 192.168.178.1
- key: proxylived.port
value: 31443
The ansible role should execute the following commands
$ helm install proxy . --set proxy.externalIP=192.168.178.1 --set proxy.service.Type=LoadBalancer
$ helm install proxylived . --set proxy.externalIP=192.168.178.1 --set proxylived.port=31443
My problem is, I don't know how to iterate over the objects. I tried the following:
main.yml
---
- name: deploy applications
include_tasks: apps.yml
loop: "{{ apps }}"
loop_control:
loop_var: app
apps.yml
---
- name: deploy application {{ app.name }}
ansible.builtin.command:
argv:
- /usr/bin/helm
- install
- {{ app.name }}
- {{ how to pass here a list of the key value attributes? }}
In a nutshell and not thoroughly tested:
apps.yml
---
- name: create the list of values to set
set_fact:
kvs: "{{ kvs | default([]) + ['--set', item.key ~ '=' ~ item.value] }}"
loop: "{{ app.properties }}"
- name: deploy application {{ app.name }}
vars:
base_cmd:
- "/usr/bin/helm"
- "install"
- "{{ app.name }}"
- "."
ansible.builtin.command:
argv: "{{ base_cmd + kvs }}"

Looping multiple values for a variable Ansible

I am trying to set multiple values for 2 variables that I am using in the following code:
- name: Create the subnets
os_subnet:
cloud: "{{ item.cloud }}"
state: present
validate_certs: no
no_gateway_ip: yes
enable_dhcp: yes
name: "{{ item.subnet }}"
network_name: "{{ item.network }}"
cidr: "{{ item.cidr }}"
allocation_pool_start: "{{ item.allocation_pool_start }}"
allocation_pool_end: "{{ item.allocation_pool_end }}"
host_routes:
- destination: "{{ item.destination | default(omit) }}"
nexthop: "{{ item.nexthop | default(omit) }}"
with_items:
- "{{ subnets }}"
tags: create_subnets
In my environment, some subnets have a different number of host_routes or none. My variable file with one variable for destination+nexthop looks now like:
--- #
subnets:
- { cloud: tenant1, network: nw, subnet: nw_subnet, cidr: 172.10.10.224/27, allocation_pool_start: 172.10.10.228, allocation_pool_end: 172.10.10.254, destination: 10.10.10.0/24, nexthop: 172.10.114.125 }
And this works. But if I am having more than one value for destination+nexthop, how shall I proceed?
I have tried yet to duplicate the same line and change just in the end the destination+nexthop, but didn't worked. If I am trying add in the same list one the same row another destination+nexthop, it will take just the last value.
Define host_routes as list in the subnets variable:
subnets:
- allocation_pool_end: "172.10.10.254"
allocation_pool_start: "172.10.10.228"
cidr: 172.10.10.224/27
cloud: tenant1
host_routes:
- destination: 10.10.10.0/24
nexthop: "172.10.114.125"
- destination: YYY.YYY.YYY.YYY/ZZ
nexthop: XXX.XXX.XXX.XXX
network: nw
subnet: nw_subnet
- allocation_pool_end: "172.10.30.254"
allocation_pool_start: "172.10.30.228"
cidr: 172.10.30.224/27
cloud: tenant2
host_routes:
- destination: YYY.YYY.YYY.YYY/ZZ
nexthop: XXX.XXX.XXX.XXX
network: nw
subnet: nw_subnet2
Now you can use it in your task
- name: Ensure subnets are present
os_subnet:
cloud: "{{ item.cloud }}"
state: present
validate_certs: no
no_gateway_ip: yes
enable_dhcp: yes
name: "{{ item.subnet }}"
network_name: "{{ item.network }}"
cidr: "{{ item.cidr }}"
allocation_pool_start: "{{ item.allocation_pool_start }}"
allocation_pool_end: "{{ item.allocation_pool_end }}"
host_routes: "{{ item.host_routes }}"
with_items:
- "{{ subnets }}"
tags: create_subnets

Loop through list and variable in Ansible

I'm going to grant privileges on two MySQL databases to user from to different IP's using Ansible. What I've got now:
Vars:
#users
root_user: 'root'
root_password: 'root'
prosody_user: 'prosody'
prosody_password: 'prosody'
#databases
oauth_db: "oauth"
#hosts
prosody_hosts: ['10.0.1.4', '10.0.1.5']
Task:
- name: add or update mysql user prosody
mysql_user:
name: "{{ prosody_user }}"
host: "{{ item.host }}"
password: "{{ prosody_password }}"
login_user: "{{ root_user }}"
login_password: "{{ root_password }}"
check_implicit_admin: yes
append_privs: yes
priv: "{{ item.database }}.*:ALL,GRANT"
with_items:
- { host: "{{ prosody_hosts[0] }}", database: "{{ oauth_db }}" }
- { host: "{{ prosody_hosts[1] }}", database: "{{ oauth_db }}" }
- { host: "{{ prosody_hosts[0] }}", database: "{{ prosody_db }}" }
- { host: "{{ prosody_hosts[1] }}", database: "{{ prosody_db }}" }
Direct calling of array elements doesn't look very nice. I just want loop through prosody_hosts array in with_item directive, сonsidering that database is not an array.
Goal is to to get something like this:
...
with_items
- { host: "{{ prosody_hosts }}", database: "{{ oauth_db }}" }
- { host: "{{ prosody_hosts }}", database: "{{ prosody_db }}" }
Thanks in advance!
What you need is nested loops.
See this Ansible documentation.
Basically you'd end up with something similar like this. Put your databases in a list called 'databases' like you did with hosts. This will execute the task for every host and every database.
I haven't tested this but it should get pretty close.
- name: add or update mysql user prosody
mysql_user:
name: "{{ prosody_user }}"
host: "{{ item[0] }}"
password: "{{ prosody_password }}"
login_user: "{{ root_user }}"
login_password: "{{ root_password }}"
check_implicit_admin: yes
append_privs: yes
priv: "{{ item[1] }}.*:ALL,GRANT"
with_nested:
- "{{ prosody_hosts }}"
- "{{ databases }}"

Ansible looping over sub-lists

I have a yaml for the creation of a user.
users:
username:
uid: 12345
gid: 6789
secggroups:
- group1
- group3
gecos: user_for_xyz
home: /home/username
I also have a file with just the usernames called users_list. The playbook to create users is as follows:
---
\- name: create users
user: name="{{ item }}" uid={{ users[item]['uid'] }} group={{ users[item][gid] }} comment="{{ users[item]['gecos'] }}" home={{ users[item]['home' }} expires=0
with_items:
\- users_list
How can I loop through the groups to be added to user?
Your playbook is on the right track.
Try this for the users variable:
users:
- username: someusername
uid: 12345
gid: 6789
groups:
- group1
- group3
gecos: "Some user"
home: /home/someusername
- username: someusername
... etc ...
And this for the user play
- name: User creation
user:
name:"{{item.username}}"
groups: "{{item.groups | join(',')}}"
comment: "{{item.name}}"
uid: "{{item.uid}}"
with_items: users
Note that I modified your syntax to not use inline YAML.
Also you may find this users role helpful.
You can solve this using subelements like this:
- name: User creation
user:
name:"{{item.0.username}}"
groups: "{{item.1 }}"
with_subelements:
- "{{ users }}"
- groups
here is a sample to debug, like a directory tree:
vars:
test:
- name: Testing subelements loop
dir: dir0
subdir:
- subdir0
- subdir1
- subdir2
tasks:
- name: Subelements loop sample
debug:
msg: "{{ item.0.dir }}/{{ item.1 }}"
with_subelements:
- "{{ test }}"
- subdir
you can find more here: http://docs.ansible.com/ansible/playbooks_loops.html#standard-loops

Resources