Iterate through a dictionary with subelements in Ansible - dictionary

I have to create a list based on a dictionary.
To get the element from the dictionary, I need to join "the server" + "the domain". The problem is that I have 3 different domains.
At the moment, I'm repeating the code to be able to use the 3 different domains.
- name: "Get server instances {{ ansible_fqdn }}"
set_fact:
app_ps_mon_list: "{{ app_ps_mon_list | default ([]) + [ app_instance ] }}"
vars:
app_instance: |
[Java,<event_type>]
java critical 1-
*ARGS {{item.value.INSTANCE_NAME.split("/")[1]}}
with_dict: '{{ server_instances[ansible_hostname + "<DOMAIN1>"] }}'
when: '{{server_instances[ansible_hostname + "<DOMAIN1>"] is defined and item.key != "SERVER_IMPACT"}}'
- name: "Get server instances {{ ansible_fqdn }}"
set_fact:
app_ps_mon_list: "{{ app_ps_mon_list | default ([]) + [ app_instance ] }}"
vars:
app_instance: |
[Java,<event_type>]
java critical 1-
*ARGS {{item.value.INSTANCE_NAME.split("/")[1]}}
with_dict: '{{ server_instances[ansible_hostname + "<DOMAIN2>"] }}'
when: '{{server_instances[ansible_hostname + "<DOMAIN2>"] is defined and item.key != "SERVER_IMPACT"}}'
- name: "Get server instances {{ ansible_fqdn }}"
set_fact:
app_ps_mon_list: "{{ app_ps_mon_list | default ([]) + [ app_instance ] }}"
vars:
app_instance: |
[Java,<event_type>]
java critical 1-
*ARGS {{item.value.INSTANCE_NAME.split("/")[1]}}
with_dict: '{{ server_instances[ansible_hostname + "<DOMAIN3>"] }}'
when: '{{server_instances[ansible_hostname + "<DOMAIN3>"] is defined and item.key != "SERVER_IMPACT"}}'
I've been trying to do the same with_subelements without success. Also I tried to use "ansible_fqdn", but the fqdn domain usually don't match the actual domain (I know its a mess).
Is there any workaround I could use to avoid repeating the code?
UPDATE
The idea of the playbook is to obtain the solutions from the server I'm using as host (ansible_hostname varaible).
Once it get the solutions, create a list using some information from the INSTANCE_NAME
This is a generic version of the dictionary:
{
"<server_name><domain>": {
"<solution_id>": {
"INSTANCE_NAME": "",
"SOLUTION_CATEGORY": "",
"SOLUTION_NAME": ""
},
"<solution_id>": {
"INSTANCE_NAME": "",
"SOLUTION_CATEGORY": "",
"SOLUTION_NAME": ""
},
SERVER_IMPACT: "",
...,
}
This is how the list should look like (its a multiline string variable, dumb but useful):
[
[Java]
java critical 1-
*ARGS <iINSTANCE_NAME info>
],
[
[Java]
java critical 1-
*ARGS <iINSTANCE_NAME info>
]

Related

Error "Vars in a Task must be specified as a dictionary, or a list of dictionaries"

'data_list' consists of the values in the csv file. I want to use the values in 'data_list' to loop through the parameters in the 'Create user' section of the playbook, but I am getting this error after running my playbook:
TASK [Create Multiple Users : Create multiple users] ***************************
fatal: [10.16.220.30]: FAILED! => {"reason": "Vars in a Task must be specified as a dictionary, or a list of dictionaries\n\nThe error appears to be in '/runner/project/Windows AD/roles/Create Multiple Users/tasks/Create_multiple_users.yml': line 14, column 9, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n - \"{{ item.groups }}\"\n vars: data_list\n ^ here\n"}
This is my playbook:
---
- name: Read Users
hosts: localhost
vars:
data_list: []
tasks:
- read_csv:
path: user.csv
key: name
fieldnames: name,firstname,surname,displayName,groups
delimiter: ','
register: userdata
- name: Extract the list
set_fact:
data_list: "{{ data_list + [{ 'name': item.value.name, 'firstname': item.value.firstname, 'surname': item.value.surname, 'displayName': item.value.displayName, 'groups': item.value.groups }] }}"
loop: "{{ userdata.dict|dict2items }}"
- name: Create user accounts
hosts: "{{ hostname }}"
gather_facts: false
any_errors_fatal: false
become: yes
become_method: runas
become_user: admin
roles:
- { role: Create Multiple Users }
- name: Create users
community.windows.win_domain_user:
name: "{{ item.name }}"
firstname: "{{ item.firstname }}"
surname: "{{ item.surname }}"
attributes:
displayName: "{{ item.firstname + ' ' + item.surname }}"
groups:
- "{{ item.groups }}"
vars: data_list
with_items:
- "{{ data_list }}"
What is the correct vars that I should write?
This is the line causing the error in your task
vars: data_list
As mentioned in your error message, the vars section should look like:
vars:
var1: value1
var2: value2
But this is not the only problem in you above script. You are gathering your csv data in a separate play on localhost and setting that info as a fact in variable data_list. When your first play is over, that var will only be known from the localhost target. If you want to reuse it in a second play targeting other hosts, you'll have to get that var from the hostvars magic variable
{{ hostvars.localhost.data_list }}
This is not the best approach here as you can easily shorten your playbook to a single play. The trick here is to delegate your csv gathering task to localhost and set run_once: true so that the registered var is calculated only once and distributed to all hosts with the same value. You can also drop the set fact which basically copies the same key: value to a new var.
Here is an (untested) example playbook to show you the way:
---
- name: Create multiple Windows AD user accounts from CSV
hosts: "{{ hostname }}"
gather_facts: false
tasks:
- name: read csv from localhost (single run same registered var for all hosts)
read_csv:
path: user.csv
key: name
fieldnames: name,firstname,surname,displayName,groups
delimiter: ','
register: userdata
run_once: true
delegate_to: localhost
- name: Create users
community.windows.win_domain_user:
name: "{{ item.name }}"
firstname: "{{ item.firstname }}"
surname: "{{ item.surname }}"
attributes:
displayName: "{{ item.firstname + ' ' + item.surname }}"
groups:
- "{{ item.groups }}"
# This will work on any number of hosts as `userdata`
# now has the same value for each hosts inside this play.
# we just have to extract the values from each keys from
# `userdata` and loop on that list
loop: "{{ userdata.dict | dict2items | map(attribute='value') }}"

Dictionary value access using ansible

I have a situation where we have 2 dictionary defined in ansible role default and the selection of dictionary is based of an input variable. I want to set the fact with one of the dict's specific key value.
Below is the example code:
test.yml paybook content:
- hosts: localhost
gather_facts: true
roles:
- role1
tags: ['role1']
roles/role1/tasks/main.yml content:
- name: set fact
set_fact:
node_vip: "{% if node_vip_run == 'no' %}node_vip_no{% elif node_vip_run == 'yes' %}node_vip_yes{% endif %}"
- debug:
var: node_vip
verbosity: 1
- debug:
var: "{{ node_vip }}.ece_endpoint"
verbosity: 1
- name: set fact
set_fact:
ece_endpoint_fact: "{{ node_vip[ece_endpoint] }}"
- debug:
var: ece_endpoint
verbosity: 1
roles/role1/defaults/main.yml content:
node_vip_yes:
ece_endpoint: "https://1.1.1.1:8080"
cac_endpoint: "https:2.2.2.2:8080"
node_vip_no:
ece_endpoint: "http://3.3.3.3:8080"
cac_endpoint: "http:4.4.4.4:8080"
Run playbook:
ansible-playbook test.yaml --extra-vars 'node_vip_run=no' -v
The set fact of variable "ece_endpoint_fact" should have value "https://1.1.1.1:8080 OR http://3.3.3.3:8080" depending on the parameter input in ansible command. But I keep on getting below error:
TASK [role1 : set fact] *******************************************************************************************************
fatal: [localhost]: FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'unicode object' has no attribute u'http://3.3.3.3:8080'\n\nThe error appears to be in '/root/roles/role1/tasks/main.yml': line 46, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- name: set fact\n ^ here\n"}
Please suggest what needs to be done to resolve this.
Thanks
Right now, you set node_vip to either the literal string "node_vip_no" or "node_vip_yes". But if you change it to do {{ node_vip_no }} / {{ node_vip_yes }}, then node_vip will have the value of the variable node_vip_no / node_vip_yes instead of being a literal string.
- name: set fact
set_fact:
node_vip: "{% if node_vip_run == 'no' %}{{ node_vip_no }}{% elif node_vip_run == 'yes' %}{{ node_vip_yes }}{% endif %}"
This will have node_vip's value be something like:
TASK [debug] ***************************************************************
ok: [localhost] => {
"node_vip": {
"cac_endpoint": "https:2.2.2.2:8080",
"ece_endpoint": "https://1.1.1.1:8080"
}
}
Then in your other set_fact, it should work if you put quotes around the property name:
- name: set fact
set_fact:
ece_endpoint_fact: "{{ node_vip['ece_endpoint'] }}"
# Added quotes ^ ^

Using Ansible set_fact to create a dictionary from register results systemctl

In Ansible I've used register to save the results of a task in the variable services.
It has this structure:
"stdout_lines": [
"arp-ethers.service \u001b[1;31mdisabled\u001b[0m",
"auditd.service \u001b[1;32menabled \u001b[0m",
"autovt#.service \u001b[1;31mdisabled\u001b[0m",
"blk-availability.service \u001b[1;31mdisabled\u001b[0m"]
and I would like to receive this:
{
"arp-ethers.service": "disabled",
"auditd.service": "enabled",
"autovt#.service": "disabled",
"blk-availability.service":"disabled"
}
I'd like to use a subsequent set_fact task to generate a new variable with a dictionary, but I'm going round in circles with no luck so far.
- name: Collect all services for SYSTEMD
raw: systemctl list-unit-files --type=service --no-pager -l --no-legend`
register: services
changed_when: false
- debug:
var: services
- debug:
msg: "{{ item.split()[0]|to_json }} : {{ item.split()[1]|to_json }}"
with_items:
- "{{ services.stdout_lines }}"
- name: Populate fact list_services for SYSTEMD
set_fact:
cacheable: yes
list_services: "{{ list_services|default({}) | combine ( {item.split()[0]|to_json: item.split()[1]|to_json} ) }}"
with_items: "{{ services.stdout_lines }}"
This return :
FAILED! => {"msg": "|combine expects dictionaries, got u'arp-ethers.service \\x1b[1;31mdisabled\\x1b[0m\\r\\nauditd.service \\x1b[1;32menabled \\x1b[0m\\r\\nautovt#.service \\x1b[1;31mdisabled\\x1b[0m\\r\\nblk-availability.service \\x1b[1;31mdisabled\\x1b[0m\\r\\n'"}
What you want is to switch list-unit-files into json output using --output=json (yes, that's a link to the journalctl man page, because the systemctl one links there)
roughly like this, although I didn't test it:
- name: Collect all services for SYSTEMD
raw: systemctl --output=json list-unit-files --type=service
register: services_json
changed_when: false
- set_fact:
services: '{{ services_json.stdout | from_json }}'
Use service_facts. For example
- service_facts:
- set_fact:
dict_services: "{{ dict(ansible_facts.services|
dict2items|
json_query('[].[key, value.status]')) }}"

Ansible include_vars into dictionary

In my ansible playbook, I read a list of directories into a list. I then want to read a "config.yml" file from each of these directories and put their content into dictionary, so that I can reference the config-data via the directory name from that dictionary.
The first part is no problem, but I cannot get the second part to work:
Step 1, load directories:
- name: Include directories
include_vars:
file: /main-config.yml
name: config
Step 2, load configs from directories:
- name: load deploymentset configurations
include_vars:
file: /path/{{ item }}/config.yml
name: "allconfs.{{ item }}" ## << This is the problematic part
with_items:
- "{{ config.dirs }}"
I tried different things like "allconfs['{{ item }}'], but none seemed to work. The playbook completed successfully, but the data was not in the dictionary.
I also tried defining the outer dictionary beforehand, but that did not work either.
The config files themselves are very simple:
/main-config.yml:
dirs:
- dir1
- dir2
- dir3
/path/dir1/config.yml:
some_var: "some_val"
another_var: "another val"
I want to be able to then access the values of the config.yml files like this:
{{ allconfs.dir1.some_var }}
UPDATE to try Konstantins approach:
- name: load deploymentset configurations
include_vars:
file: /repo/deploymentsets/{{ item }}/config.yml
name: "default_config"
with_items:
- "{{ config.deploymentsets }}"
register: default_configs
- name: combine configs
set_fact:
default_configs: "{{ dict(default_configs.results | json_query('[].[item, ansible_facts.default_config]')) }}"
Error message:
fatal: [127.0.0.1]: FAILED! => {"failed": true, "msg": "Unexpected templating type error occurred on ({{ dict(default_configs.results | json_query('[].[item, ansible_facts.default_config]')) }}): <lambda>() takes exactly 0 arguments (1 given)"}
Here is a piece of code from one of my projects with similar functionality:
- name: Load config defaults
include_vars:
file: "{{ item }}.yml"
name: "default_config"
with_items: "{{ config_files }}"
register: default_configs
tags:
- configuration
- name: Combine configs into one dict
# Здесь мы делаем словарь вида
# default_configs:
# config_name_1: { default_config_object }
# config_name_2: { default_config_object }
# config_name_3: { default_config_object }
# ...
set_fact:
default_configs: "{{ dict(default_configs.results | json_query('[].[item, ansible_facts.default_config]')) }}"
tags:
- configuration
default_config is a dummy var to temporary load var data.
The trick is to use register: default_configs with include_vars and parse it with the following task stripping out unnecessary fields.
AFAIK it isn't possible to create a single dictionary that encompasses multiple include_vars. From my testing it would create separate dictionaries for each included directory. Here's what you can do instead.
Remove allconfs. from your variable name.
- name: load deploymentset configurations
include_vars:
file: /path/{{ item }}/config.yml
name: "{{ item }}"
with_items:
- "{{ config.dirs }}"
You can then either access variables directly with
debug:
msg: "{{ dir1.some_var }}"
with_items: "{{ config.dirs }}"
Or if you need to loop through all variables in your included directories use this (hoisted from Ansible: how to construct a variable from another variable and then fetch it's value).
debug:
msg: "{{ hostvars[inventory_hostname][item].some_var }}"
with_items: "{{ config.dirs }}"

Create a list of dictionaries from host groups in ansible playbook

I want to dynamically create a list of dictionaries that looks like this:
[ {'host': 'hostname1', 'id': 1}, {'host': 'hostname2', 'id': 2}, ]
And assign it to a variable in my playbook.
This variable is needed for a role I am using.
My attempt is the following:
- hosts:
- some-hosts
vars:
zk_hosts: []
tasks:
- name: create my var
set_fact:
zk_hosts: "{{ zk_hosts + [ {'host': item.1, 'id': item.0} ] }}"
with_indexed_items: "{{ groups.some-hosts }}"
However, when I run the playbook I have this warning:
[WARNING]: While constructing a mapping from stack.yml, line 16, column 3, found a duplicate dict key (vars). Using last defined value only.
And and error at this play:
fatal: [192.168.0.21]: FAILED! => {"failed": true, "msg": "ERROR! 'zk_hosts' is undefined"}
If I don't define zk_hosts before trying to set the fact, I get an error that the variable is undefined.
How can I solve?
EDIT
Easy fix, I just defined zk_hosts within the same task...
tasks:
- name: create my var
vars:
zk_hosts: []
set_fact:
zk_hosts: "{{ zk_hosts + [ {'host': item.1, 'id': item.0} ] }}"
with_indexed_items: "{{ groups.some-hosts }}"
Anyway, if there is a less cumbersome way of achieving the same, please advise!
You can use default filter:
set_fact:
zk_hosts: "{{ zk_hosts|default([]) + [ {'host': item.1, 'id': item.0} ] }}"
with_indexed_items: "{{ groups.some-hosts }}"

Resources