How to read a complex include variables in Ansible - loops

I have a variable file that has multiple variables "name" & "path" listing under an IP address like below.
10.0.0.12
- name: exe_folder
path: /tmp/exe
- name: log_folder
path: /tmp/log
- name: src_folder
path: /tmp/src
10.0.0.13
- name: test_folder
path: /tmp/exe1
- name: out_folder
path: /tmp/log1
- name: com_folder
path: /tmp/src1
etc ....
I can loop on the name and path successfully in my playbook as below.
- name: Load repository
include_vars:
file="{{ playbook_dir }}/vars/list.yml"
name=user1
- debug:
msg: "{{ item.name + ':' + item.path }}"
loop: "{{ user1[inventory_hostname] }}"
- set_fact:
allinonecmd: "{{ allinonecmd | default('') + 'ls -ltr ' + item.path + ' '}}"
loop: "{{ user1[inventory_hostname] }}"
My requirement is to have a new variable "mycode" under each IP however just have a single mention of it and I should be able to print it in the loop like above playbook does.
Thus i need my variable file to have mycode specified onetime for each IP. I'm not sure what changes I need to make to my variable file and playbook to accomodate this requirement.
10.0.0.12
- name: exe_folder
path: /tmp/exe
mycode: "56.12"
- name: log_folder
path: /tmp/log
- name: src_folder
path: /tmp/src
10.0.0.13
- name: test_folder
path: /tmp/exe1
mycode: "76.88"
- name: out_folder
path: /tmp/log1
- name: com_folder
path: /tmp/src1
etc ....
The playbook fails after I make the above changes and try to print the mycode variable.
- debug:
msg: "{{ item.name + ':' + item.path + item.mycode }}"
loop: "{{ user1[inventory_hostname] }}"
Error Output:
fatal: [10.0.0.12]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'dict object' has no attribute 'mycode'
Note: I do not wish to specify mycode multiple times under an IP as it looks like a dirty solution.

I think that for this use case, you can use default() filter, so if this element is not on the list, you can recall a default one or omit it.
vars:
my_default_value: "56.12"
- debug:
msg: "{{ item.name + ':' + item.path + (item.mycode | default(my_default_value) }}" loop: "{{ user1[inventory_hostname] }}"
Even you can make a combination with default(value) and default(omit) to fit your needs.

Related

Construct a Distinguished Name with Ansible

In my ansible playbook I have a variable defining the OU DN I'd like to create.
my_ou: "OU=win10,OU=workstations,OU=mycompany"
domain_root: "DC=example,DC=com"
I'd like to convert my_ou to a list and create each OU. The problem is that the loop does not know the path for each OU because it is an aggregation of the previous elements in the loop.
If I split my_ou on ',' and reverse the loop, I can print each OU in order of creation. I can set the name by using regex_replace. However, I don't know how to construct the path.
For example:
- name: create required OU's
win_domain_ou:
name: "{{ my_ou | regex_replace('^OU=') }}"
path: ???? should be all items to the right of the current {{ item }},{{ domain_root }}
state: present
loop: "{{ my_ou.split(',') | reverse | list }}"
So, to be clear, the path for OU=win10 should be OU=workstations,OU=mycompany,DC=example,DC=com
The OU=workstations path should be OU=mycompany,DC=example,DC=com
And so forth.
Thanks!
Declare the list of elements
elements: "{{ my_ou.split(',') + domain_root.split(',') }}"
gives
elements:
- OU=win10
- OU=workstations
- OU=mycompany
- DC=example
- DC=com
and use extended loop variables. For example,
- debug:
msg: "item: {{ item }} path: {{ elements[idx|int:]|join(',') }}"
loop: "{{ my_ou.split(',')|reverse }}"
loop_control:
extended: true
vars:
idx: "{{ my_ou.split(',')|length - ansible_loop.index0 }}"
gives (abridged)
msg: 'item: OU=mycompany path: DC=example,DC=com'
msg: 'item: OU=workstations path: OU=mycompany,DC=example,DC=com'
msg: 'item: OU=win10 path: OU=workstations,OU=mycompany,DC=example,DC=com'
Example of a complete playbook for testing
- hosts: localhost
vars:
my_ou: "OU=win10,OU=workstations,OU=mycompany"
domain_root: "DC=example,DC=com"
elements: "{{ my_ou.split(',') + domain_root.split(',') }}"
tasks:
- debug:
msg: "item: {{ item }} path: {{ elements[idx|int:]|join(',') }}"
loop: "{{ my_ou.split(',')|reverse }}"
loop_control:
extended: true
vars:
idx: "{{ my_ou.split(',')|length - ansible_loop.index0 }}"

ansible and sub loops

Hope I have framed the title correctly. I am trying to loop over the first play using with_sequence and then use the output of the first play as the input of another, but the issue is the second play also uses a sequence. I can't get my around how I could achieve that.
create root directory
file:
path: dir{{ item }}
with_sequence: 1-6
register: outdirs
mount using above directory
mount:
state: mounted
src: xxxx{{ item }}
path: *outdirs.dir1*
fstype: nfs
with_sequence: 21-26
Thanks
Use the results from first task to loop on the next one and add your offset:
- name: Create root directory
file:
path: "dir{{ item }}"
state: directory
with_sequence: 1-6
register: outdirs
- name: Mount root dir
vars:
nfs_offset: 20
nfs_number: "{{ item.item | int + nfs_offset | int }}"
mount:
state: mounted
src: "xxxx{{ nfs_number }}"
path: "{{ item.path }}"
fstype: nfs
loop: "{{ outdirs.results }}"

Ansible: How to loop through 2 var dicts only when hostname corresponds

Im trying to loop to a set of variables depending on the hostname. I cant get the conditional right.
Say i have 2 set of vars:
databases_eu:
- name: database_eu_1
port: 5432
login: {{ vault_user_database_eu_1 }}
password: {{ vault_pw_database_eu_1 }}
- name: database_eu_2
port: 5433
login: {{ vault_user_database_eu_2 }}
password: {{ vault_pw_database_eu_2 }}
databases_us:
- name: database_us_1
port: 5432
login: {{ vault_user_database_us_1 }}
password: {{ vault_pw_database_us_1 }}
- name: database_us_2
port: 5433
login: {{ vault_user_database_us_2 }}
password: {{ vault_pw_database_us_2 }}
And i want to loop through it depending on the hostname, how would i do that?
I thought something like below, but im missing the when statement:
- name: copy configs
ansible.builtin.template:
src: config.yml.j2
dest: /opt/db/configs/{{ item.name }}.yml
owner: root
group: root
mode: '0644'
loop:
- "{{ databases_eu | inventory_hostname == 'eu.db.example.com' }}"
- "{{ databases_us | inventory_hostname == 'us.db.example.com' }}"
Use the lookup plugin vars to get the value of a variable. See
shell> ansible-doc -t lookup vars
There are many options on how to reference the data. For example, given the inventory
shell> cat hosts
eu.db.example.com
us.db.example.com
Create a dictionary of data assigned to the hosts, e.g.
databases_host:
eu.db.example.com: databases_eu
us.db.example.com: databases_us
Then the playbook
- hosts: all
tasks:
- debug:
msg: "Configure {{ item.name }}"
loop: "{{ lookup('vars', databases_host[inventory_hostname]) }}"
loop_control:
label: "{{ item.name }}"
gives
ok: [eu.db.example.com] => (item=database_eu_1) =>
msg: Configure database_eu_1
ok: [us.db.example.com] => (item=database_us_1) =>
msg: Configure database_us_1
ok: [us.db.example.com] => (item=database_us_2) =>
msg: Configure database_us_2
ok: [eu.db.example.com] => (item=database_eu_2) =>
msg: Configure database_eu_2
The next option is to put the name of the dictionaries to a host-specific variable, e.g.
shell> cat hosts
eu.db.example.com my_db=databases_eu
us.db.example.com my_db=databases_us
Then the playbook below gives the same result
- hosts: all
tasks:
- debug:
msg: "Configure {{ item.name }}"
loop: "{{ lookup('vars', my_db) }}"
loop_control:
label: "{{ item.name }}"
The next option is to create the name of the dictionary dynamically, e.g.
- hosts: all
tasks:
- debug:
msg: "Configure {{ item.name }}"
loop: "{{ lookup('vars', my_db) }}"
loop_control:
label: "{{ item.name }}"
vars:
my_db: "{{ 'databases_' ~ inventory_hostname.split('.').0 }}"
Thanks. I solved by creating a group_vars dir. Thanks for the input.

How to check if element is defined in a list of objects in ansible

I would like to run an ansible Task only if Object element exist in a list.
that's is the Array
local_users:
- name: user1
sshkey: key
sudo: yes
allow_remote_login: yes
- name: user2
sshkey: key
sudo: yes
allow_remote_login: yes
and that my task:
- name: Set up authorized keys
authorized_key:
user: "{{ item.name }}"
state: present
key: "{{ item.sshkey }}"
loop: "{{ local_users }}"
when: local_users.sshkey is defined
but ansible tell me that ssh_key is not defined:
"skip_reason": "Conditional result was False"
any suggestion?
You should be looking for item.sshkey instead of local_users.sshkey while iterating.
Example:
Using your variables, display a message when sshkey is defined:
- debug:
msg: "{{ item.name }}, has key"
loop: "{{ local_users }}"
when: item.sshkey is defined
Similarly, you should have:
- name: Set up authorized keys
authorized_key:
user: "{{ item.name }}"
state: present
key: "{{ item.sshkey }}"
loop: "{{ local_users }}"
when: item.sshkey is defined

Looped checks in Ansible

For example, I have a list like this:
vms:
- name: "vm1"
desc: "first"
network:
ip: "10.0.0.10"
prefix: "16"
- name: "vm2"
desc: "second"
network:
ip: "10.0.0.11"
prefix: "16"
I want to create VMs with OpenNebula in a loop with checking if there is already created VM. I've made a check task:
- name: Check if VM "{{ item.name }}" already created
become_user: oneadmin
ignore_errors: yes
shell: onevm list --csv | grep "{{ item.name }}"
register: created_vms
with_items:
- "{{ vms }}"
but I don't know how to make "Create VM" task correctly with loop + 'when' statement.
Thanks to #Konstantin Suvorov for his comment!
Here is an answer for my case:
- name: Check if VMs already created
become_user: oneadmin
ignore_errors: yes
shell: onevm list --csv | grep "{{ item.name }}"
register: created_vms
with_items: "{{ vms }}"
- name: Create VMs
become_user: oneadmin
command: onevm create --name "{{ item.1.name }}"
when: item.0|failed
with_together:
- "{{ created_vms.results }}"
- "{{ vms }}"

Resources