Ansible - using for loops inside user module - ansible

I recently discovered the use of for loops in Ansible and was very excited about it.
Tries to use it inside the debug module and it worked superfine, but when I am trying to use the same inside "user" module, the control flow is not able to identify the "name" keyword of user module. Below is my poetry,
- hosts: testservers
tasks:
- name: Setting user facts
set_fact:
username: "{{ lookup('ini', 'name section=userDetails file=details.ini') }}"
userpass: "{{ lookup('ini', 'password section=userDetails file=details.ini') }}"
- name: User creation
become: true
# debug:
# msg: |
# {% for x,y in item.1,item.2 %}
# {{ x }} is the username and its password is {{ y }}
# {% endfor %}
# with_items:
# - { 1: "{{ username.split(',') }}", 2: "{{ userpass.split(',') }}" }
user: |
{% for x,y in item.1,item.2 %}
name: "{{ x }}"
password: "{{ y }}"
{% endfor %}
with_items:
- { 1: "{{ username.split(',') }}", 2: "{{ userpass.split(',') }}" }
Details.ini file contents below
#User basic details
[userDetails]
name=vasanth,vasanthnagkv
password=vasanth12,pass2
The commented part above works fine. but the uncommented part throws the below error
failed: [10.0.0.47] (item={1: [u'vasanth', u'vasanthnagkv'], 2: [u'vasanth12', u'pass2']}) => {
"changed": false,
"invocation": {
"module_args": {
"append": false,
"create_home": true,
"force": false,
"move_home": false,
"non_unique": false,
"remove": false,
"ssh_key_bits": 0,
"ssh_key_comment": "ansible-generated on APUA-02",
"ssh_key_type": "rsa",
"state": "present",
"system": false,
"update_password": "always"
}
},
"item": {
"1": [
"vasanth",
"vasanthnagkv"
],
"2": [
"vasanth12",
"pass2"
]
},
"msg": "missing required arguments: name"
}
to retry, use: --limit #/home/admin/ansiblePlaybooks/userCreation/userCreate.retry
PLAY RECAP ************************************************************************************************************************************************************************************
10.0.0.47 : ok=2 changed=0 unreachable=0 failed=1
Appreciate any kind of help here.

This line user: | means your passing a string to user module using the block style indicator: https://yaml-multiline.info/)
Since Ansible will just treat it as a string, you are not passing the required name parameter to the user module
Try splitting the names after the lookup in the first task so you can have the names and passwords list:
- name: Setting user facts
set_fact:
username: "{{ lookup('ini', 'name section=userDetails file=details.ini').split(',') }}"
userpass: "{{ lookup('ini', 'password section=userDetails file=details.ini').split(',') }}"
Once you have both the username and password list, you can use both variables by:
- user:
name: "{{ item }}"
password: "{{ userpass[index] }}"
loop: "{{ username }}"
loop_control:
index_var: index

Related

Create Dictionary from Ansible inventory file

I am trying to create a dictionary with below ansible inventory file.
"myfile": {
"all": {
"hosts": null,
"vars": {
"ansible_connection": "local",
"ansible_python_interpreter": "{{ ansible_playbook_python }}"
}
},
"USA": {
"children": {
"MYDC1": {
"children": {
"MYDC1-CLU01": {
"hosts": {
"MYDC2-VM1": null,
"MYDC2-VM2": null,
"MYDC2-VM3": null,
"MYDC2-VM4": null,
"MYDC2-VM5": null,
"MYDC2-VM6": null
}
}
}
},
"MYDC2": {
"children": {
"MYDC2-CLU01": {
"hosts": {
"MYDC1-VM1": null
"MYDC1-VM2": null
}
}
}
},
"MYDC3": {
"children": {
"MYDC3-CLU01": {
"hosts": {
"MYDC3-VM1": null
"MYDC3-VM2": null
"MYDC3-VM3": null
}
}
}
}
},
"vars": {
"vcenter_hostname": "myvcenter.esxihost.com"
}
}
}
}
From this file, expected output is:
{
'MYDC1-CLU01': ['MYDC1-VM1.myhost.com','MYDC1-VM2.myhost.com'],
'MYDC2-CLU01':['MYDC2-VM1.myhost.com','MYDC2-VM2.myhost.com','MYDC2-VM3.myhost.com','MYDC2-VM4.myhost.com','MYDC2-VM5.myhost.com','MYDC2-VM6.myhost.com'],
'MYDC3-CLU01':['MYDC3-VM1.myhost.com','MYDC3-VM2.myhost.com','MYDC3-VM3.myhost.com'],
}
The code given below works fine:
my_domain: myhost.com
my_groups: [MYDC1-CLU01, MYDC2-CLU01, MYDC3-CLU01]
cluster_dict: "{{ dict(my_groups|
zip(my_groups|
map('extract', groups)|
map('product', [my_domain])|
map('map', 'join', '.')|
list)) }}"
Output:
$ ansible-playbook main.yml -i hosts.yml
[WARNING]: Invalid characters were found in group names but not replaced, use -vvvv to see details
PLAY [Check Service status] ***********************************************************************************************************************************************************************************************
TASK [Gathering Facts] ****************************************************************************************************************************************************************************************************ok: [localhost]
TASK [my_checks : Show host inventory file] *************************************************************************************************************************************************************************ok: [localhost] => {
"cluster_dict": {
"MYDC1-CLU01": [
"MYDC1-VM1.myhost.com",
"MYDC1-VM2.myhost.com"
],
"MYDC2-CLU01": [
"MYDC2-VM1.myhost.com",
"MYDC2-VM2.myhost.com",
"MYDC2-VM3.myhost.com",
"MYDC2-VM4.myhost.com",
"MYDC2-VM5.myhost.com",
"MYDC2-VM6.myhost.com"
],
"MYDC3-CLU01": [
"MYDC3-VM1.myhost.com",
"MYDC3-VM2.myhost.com"
]
}
}
I have 2 questions:
1: Is there anyway I can store variable in "cluster_dict" without passing the hosts.yml file in the command line. Like converting hosts file into below
host_intent_data: "{{ lookup('file', 'hosts.yml')|from_yaml }}"
and process the host_intent_data.
The CLUSTER/DC values will be growing each day and we can't use static fields as mentioned in my_groups.Is there any way we can get the desired dictionary dynamically?
Given the inventory
shell> cat hosts
all:
hosts:
vars:
ansible_connection: local
ansible_python_interpreter: '{{ ansible_playbook_python }}'
USA:
children:
MYDC1:
children:
MYDC_CLU01:
hosts:
MYDC1-VM1:
MYDC1-VM2:
MYDC2:
children:
MYDC2_CLU01:
hosts:
MYDC2-VM1:
MYDC2-VM2:
MYDC2-VM3:
MYDC2-VM4:
MYDC2-VM5:
MYDC2-VM6:
vars:
vcenter_hostname: myvcenter.esxihost.com
and the variables
my_domain: myhost.com
my_groups: [MYDC_CLU01, MYDC2_CLU01]
Put the below declaration into the vars
my_dict: "{{ dict(my_groups|
zip(my_groups|
map('extract', groups)|
map('product', [my_domain])|
map('map', 'join', '.')|
list)) }}"
gives what you want
my_dict:
MYDC2_CLU01:
- MYDC2-VM1.myhost.com
- MYDC2-VM2.myhost.com
- MYDC2-VM3.myhost.com
- MYDC2-VM4.myhost.com
- MYDC2-VM5.myhost.com
- MYDC2-VM6.myhost.com
MYDC_CLU01:
- MYDC1-VM1.myhost.com
- MYDC1-VM2.myhost.com
Example of a complete playbook for testing
- hosts: all
gather_facts: false
vars:
my_domain: myhost.com
my_groups: [MYDC_CLU01, MYDC2_CLU01]
my_dict: "{{ dict(my_groups|
zip(my_groups|
map('extract', groups)|
map('product', [my_domain])|
map('map', 'join', '.')|
list)) }}"
tasks:
- debug:
var: my_dict
run_once: true
Q: "Convert hosts file to 'cluster_dict'."
A: Use ansible-inventory and convert the file to yaml. Select the region (e.g. USA) and create the dictionary. For example, if you provide a valid inventory file
cluster: "{{ lookup('pipe', 'ansible-inventory -i hosts.json --list --yaml')|from_yaml }}"
cluster_USA: "{{ cluster.all.children.USA.children|json_query('*.children') }}"
cluster_dict_raw: |
{% for i in cluster_USA %}
{{ i.keys()|first }}:
{{ (i.values()|first).hosts.keys() }}
{% endfor %}
cluster_dict: "{{ cluster_dict_raw|from_yaml }}"
gives
cluster_dict:
MYDC1-CLU01: [MYDC2-VM1, MYDC2-VM2, MYDC2-VM3, MYDC2-VM4, MYDC2-VM5, MYDC2-VM6]
MYDC2-CLU01: [MYDC1-VM1, MYDC1-VM2]
MYDC3-CLU01: [MYDC3-VM1, MYDC3-VM2, MYDC3-VM3]
Notes:
Test the inventory file before complaining!
shell> ansible-inventory -i hosts.json --list --yaml
The names of the groups are not valid. You must have seen the warning. Fix it before complaining!
[WARNING]: Invalid characters were found in group names ...
Fit the template to your needs. For example, add domain and create FQDN
cluster_dict_raw: |
{% for i in cluster_USA %}
{{ i.keys()|first }}:
{{ (i.values()|first).hosts.keys()|product(['myhost.com'])|
map('join', '.')|list }}
{% endfor %}

Register return values from tasks_from when looping over include_role

I'd like to build an array of ticket numbers that are created by the create_srq.yaml task in role servicenow. Is it possible to do this when looping over an include_role?
roles/request_signed_certificate/tasks/main.yaml
- name: Create SNOW Records for Certificate Request
include_role:
name: servicenow
tasks_from: create_srq.yaml
register: result
loop: "{{ spreadsheet }}"
loop_control:
loop_var: csr
vars:
short_description: CSR for {{ csr.certname }}-{{ csr.env }}
attachment: "{{ cert_path }}/{{ csr.certname }}-{{ csr.env }}.csr"
- name: debug
debug:
var: result
roles/servicenow/tasks/create_srq.yaml
- name: Create a SRQ
snow_record:
state: present
table: u_request
username: "{{ snow_username }}"
password: "{{ snow_password }}"
instance: "{{ servicenow_instance }}"
data:
short_description: "{{ short_description }}"
attachment: "{{ attachment }}"
register: srq
- name: Attach file to {{ srq.record.number }}
snow_record:
state: present
table: u_request
username: "{{ snow_username }}"
password: "{{ snow_password }}"
instance: "{{ servicenow_instance }}"
number: "{{ srq.record.number }}"
attachment: "{{ attachment }}"
When running the playbook:
---
- hosts: "{{ hosts_list }}"
connection: local
gather_facts: false
vars:
cert_path: "/tmp/certs"
cert_version: "2023"
pre_tasks:
- name: Create facts from csv
csv_to_facts:
src: "file.csv"
delegate_to: localhost
run_once: true
roles:
- role: request_signed_certificate
The result does not include the registered srq variable from create_srq.yaml:
TASK [request_signed_certificate : debug] **************************************************************
ok: [host.example.com] => {
"result": {
"changed": false,
"msg": "All items completed",
"results": [
{
"ansible_loop_var": "csr",
"csr": {
"certname": "example_one",
"common_name": "domain_one.example.com",
"dns1": "domain_two.example.com",
"env": "development",
},
"include_args": {
"name": "servicenow",
"tasks_from": "create_srq.yaml"
}
},
{
"ansible_loop_var": "csr",
"csr": {
"certname": "example_two",
"common_name": "domain_123.example.com",
"dns1": "domain_456.example.com",
"env": "test",
},
"include_args": {
"name": "servicenow",
"tasks_from": "create_srq.yaml"
}
}
]
}
}
I was able to do this by appending results to a list. While this works, it wasn't exactly what I was after as I would have preferred to install a reusable role to create the srqs.
Adding this bit to the bottom of create_srq.yaml gave me what I was looking for:
- name: output csv entry + ticket number
set_fact:
csv: "{{ csv | default({}) | combine ( { item.key : item.value } ) | combine( { 'ticket': srq.record.number } ) }}"
loop: "{{ csr | dict2items }}"
# Create list of dictionaries to track
# also, a list is required for the to_csv plugin
- name: create list of dicts
set_fact:
contents: "{{ contents | default([]) + [ csv ] }}"

Is it possible to set an Ansible fact only if a variable has a value?

I have an Ansible playbook that I'm running from AWX. The playing uses the Infoblox nios module to retrieve information about Infoblox host registrations.
I'm using the set_fact module to to take the output of the query and then define a number of new facts to use elsewhere in the playbook.
The problem that I have is that the query can return a differing number of variables depending on the format of the registration and this breaks the playbook.
What I am trying to do is workout if I can set a new fact only if the specific variable is returned in the original query.
I've tried using "if defined" but that doesn't seem to work.
In example 1 the play "fetch host record" returns the following values. host, ipv4addr and mac as the host has a Mac Address in Infoblox
ok: [localhost] => {
"ansible_facts": {
"host": {
"ipv4addrs": [
{
"host": "myhost1.test.com",
"ipv4addr": "192.168.30.1",
"mac": "00:22:33:11:44:55"
}
],
"name": "myhost1.test.com",
"view": "Internal"
}
},
"changed": false
}
In example 2 the same play only returns host and ipv4addr as the host does not have a Mac Address registered.
ok: [localhost] => {
"ansible_facts": {
"host": {
"ipv4addrs": [
{
"host": "myhost2.test.com",
"ipv4addr": "192.168.30.2"
}
],
"name": "myhost2.test.com",
"view": "Internal"
}
},
"changed": false
}
My playbook contains the following and works only if the host contains a Mac Address as the fact host, doesn't contain a value for host.ipv4addrs[0].mac so it crashes out. I'd like to add some logic to only try and set niosmac if host.ipv4addrs[0].mac is defined.
tasks:
- name: fetch host record
set_fact:
host: "{{ lookup('nios', 'record:host', filter={niossearchcatagory: searchcriteria, 'view': 'Internal'}, provider=nios_provider) }}"
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
niosmac: "{{ host.ipv4addrs[0].mac }}"
Here's the version I attempted using is defined
tasks:
- name: fetch host record
set_fact:
host: "{{ lookup('nios', 'record:host', filter={niossearchcatagory: searchcriteria, 'view': 'Internal'}, provider=nios_provider) }}"
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
niosmac: "{{ host.ipv4addrs[0].mac }}"
when: host.ipv4addrs[0].mac is defined
Cheers
Spence
Sorry, I must have typed something wrong before as I've tried again and it now seems to work. Here's the correct code for clarification.
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
niosmac: "{{ host.ipv4addrs[0].mac }}"
when: host != [] and host.ipv4addrs[0].mac is defined
- name: Set niosip
set_fact:
niosip: "{{ host.ipv4addrs[0].ipv4addr }}"
nioshostname: "{{ host.name }}"
niosdhcp: "{{ host.ipv4addrs[0].configure_for_dhcp }}"
when: host != [] and host.ipv4addrs[0].mac is undefined

Variable won't be filtered correctly

what am I doing wrong?
I use below task to get all defined log_dirs of a host. Those information are stored in a fact, which is a dict and this works like a charm.
- name: get all_log_dirs
set_fact:
all_log_dirs="{{ (all_log_dirs|default({})) | combine( { item.key:vars[item.key] } ) }}"
with_dict: "{{ vars }}"
when: item.key is search('^((?!splunk).)*_log_dir')
Here the appropriate output:
"ansible_facts": {
"all_log_dirs": {
"springboot_server_log_dir": "{{ server_deployment_dir }}/logs"}
But the problem is, if I now want to use the new dict for e. g.:
- name: create symlink for splunk if not present
file:
src: "{{ item.value }}"
dest: "{{ splunk_log_dir }}/{{ item.key | regex_replace('_server_log_dir|_log_dir') | regex_replace('eap','jboss-eap') }}"
state: link
with_dict: "{{ all_log_dirs }}"
I only get:
failed: [...] (item={'value': u'{{ server_deployment_dir }}/logs', 'key': u'springboot_server_log_dir'}) => {
"changed": false,
"invocation": {
"module_args": {
"dest": "/somedir/springboot",
"path": "/somedir/springboot",
"src": "{{ server_deployment_dir }}/logs",
"state": "link",
}
},
"msg": "src file does not exist, use \"force=yes\" if you really want to create the link: /somedir/{{ server_deployment_dir }}/logs",
"path": "/somedir/springboot",
"src": "{{ server_deployment_dir }}/logs",
"state": "absent"
}
Why isn't {{ server_deployment_dir }} filtered correctly by Ansible?
Even I change src to dest and the way around, it won't work, because the variable isn't being filtered.
The value of {{ server_deployment_dir }} is of course host specific and is sth like /opt/applicationXY/appDeployDir
Don't use vars object. Period.
It is internal variable storage intended for under-the-hood usage.
When Ansible template engine detects vars access, it stops further templating chain!
Example:
---
- hosts: localhost
connection: local
gather_facts: no
vars:
myvar1: hello
myvar2: world
myvar3: "{{ myvar2 }}"
tasks:
- debug:
msg: "{{ myvar1 }} {{ myvar3 }}"
- debug:
msg: "{{ vars['myvar1'] }} {{ vars['myvar3'] }}"
Result:
TASK [debug] ***************************
ok: [localhost] => {
"msg": "hello world"
}
TASK [debug] ***************************
ok: [localhost] => {
"msg": "hello {{ myvar2 }}"
}
Update: if you utterly need to access variable trough vars object, there's vars lookup available in Ansible 2.5; and it templates values as usual:
E.g.
- debug:
msg: "{{ lookup('vars','myvar1') }} {{ lookup('vars','myvar3') }}"
results to hello world in the context of my previous example.

Ansible parse JSON output

I am trying to parse the Ansible output the print a value
- name: Creating a new instance
os_server:
state: present
auth:
auth_url: "{{ auth_url }}"
username: "{{ username }}"
password: "{{ password }}"
project_name: "{{ project_name }}"
name: "{{ item.hostname }}"
image: "{{ item.image }}"
nics: "{{ nics }}"
with_items: "{{ servers }}"
register: "os"
Output:
"server": {
"OS-DCF:diskConfig": "MANUAL",
"OS-EXT-AZ:availability_zone": "zoneA",
"OS-EXT-STS:power_state": 1,
"OS-EXT-STS:task_state": null,
"OS-EXT-STS:vm_state": "active",
"OS-SRV-USG:launched_at": "2018-04-01T18:53:16.000000",
"OS-SRV-USG:terminated_at": null,
"accessIPv4": "10.190.230.23",
"accessIPv6": "",
"addresses": {
"provider_corenet_bif_757": [
{
"OS-EXT-IPS-MAC:mac_addr": "fa:1:3:3:5e:6a",
"OS-EXT-IPS:type": "fixed",
"addr": "10.19.23.23",
"version": 4
}
],
"provider_nmnet_bif_912": [
{
"OS-EXT-IPS-MAC:mac_addr": "fa:1:3:39:b:57",
"OS-EXT-IPS:type": "fixed",
"addr": "10.25.13.64",
"version": 4
}
]
server.addresses.provider_nmnet_bif_912.addr
},
I want to parse addr "10.25.13.64".
I tried {{ item.server.addresses.provider_nmnet_bif_912.addr }} and {{os.server.addresses.provider_nmnet_bif_912.addr}} both didnot work.
Need Help!!!
Finally figured it out:
"{{ item.server.addresses.provider_nmnet_bif_912[0].addr }}"

Resources