Ansible loop over list of dictionaries and individual dictionaries - loops

I want to loop over a list of dictionaries, as well as other dictionaries. They all have the same schema.
- set_fact:
my_list:
- { foo: 1, bar: 2, baz: 3 }
- { foo: 4, bar: 5, baz: 6 }
- { foo: 7, bar: 8, baz: 9 }
- debug:
msg: "{{item.foo}} {{item.bar}} {{item.baz}}"
loop:
- "{{ my_list }}"
- { foo: 10, bar: 11, baz: 12 }
- { foo: 13, bar: 14, baz: 15 }
But this gives:
FAILED! => {"msg": "The task includes an option with an undefined variable. The error was: 'list object' has no attribute 'foo'
How do I do this?

Concatenate the lists, e.g.
- debug:
msg: "{{ item.foo }} {{ item.bar }} {{ item.baz }}"
loop: "{{ my_list +
[{'foo': 10, 'bar': 11, 'baz': 12}] +
[{'foo': 13, 'bar': 14, 'baz': 15}] }}"
gives
msg: 1 2 3
msg: 4 5 6
msg: 7 8 9
msg: 10 11 12
msg: 13 14 15
The YAML format below gives the same result
- debug:
msg: "{{ item.foo }} {{ item.bar }} {{ item.baz }}"
loop: "{{ my_list + _list2 + _list3 }}"
vars:
_list2:
- foo: 10
bar: 11
baz: 12
_list3:
- foo: 13
bar: 14
baz: 15

Related

Ansible loop with additional filters

I have the following list
ids: [id1, id2, id3]
and the following registered result of an iteration
output2:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
changed: false
failed: false
item: id1
msg:
json:
key1: '0'
key2: '6'
key3: '4'
- ansible_loop_var: item
changed: false
failed: false
item: id2
msg:
json:
key1: '2'
key2: '6'
key3: '6'
- ansible_loop_var: item
changed: false
failed: false
item: id3
msg:
json:
key1: '8'
key2: '0'
key3: '9'
skipped: false
How can I create the dictionary below and select the keys?
"id1": {
"key1": "0",
"key3": "4"
}
"id2": {
"key1": "2",
"key3": "6"
}
"id3": {
"key1": "8",
"key3": "9"
}
Declare the below variables in vars
ids: [id1, id2, id3]
_values: "{{ output2.results|
json_query('[].msg.json.{key1: key1, key3: key3}') }}"
result: "{{ dict(ids|zip(_values)) }}"
As a hint, create the task below for testing. Select key1 and key3 from the resulting JSON
- debug:
msg: "{{ stat|from_yaml }}"
register: output2
loop: "{{ ids }}"
vars:
stat: |
json:
key1: "{{ range(10)|random }}"
key2: "{{ range(10)|random }}"
key3: "{{ range(10)|random }}"
gives
_values:
- {key1: '0', key3: '5'}
- {key1: '1', key3: '9'}
- {key1: '3', key3: '6'}
result:
id1: {key1: '0', key3: '5'}
id2: {key1: '1', key3: '9'}
id3: {key1: '3', key3: '6'}
Fit the query according to the content of your output2.
Example of a complete playbook for testing
- hosts: localhost
vars:
ids: [id1, id2, id3]
_values: "{{ output2.results|
json_query('[].msg.json.{key1: key1, key3: key3}') }}"
result: "{{ dict(ids|zip(_values)) }}"
tasks:
- debug:
msg: "{{ stat|from_yaml }}"
register: output2
loop: "{{ ids }}"
vars:
stat: |
json:
key1: "{{ range(10)|random }}"
key2: "{{ range(10)|random }}"
key3: "{{ range(10)|random }}"
- debug:
var: output2
- debug:
var: _values|to_yaml
- debug:
var: result|to_yaml
OK -- given that output2, this task will create foo:
- name: Build list
set_fact:
foo: |-
{
{% for item in output2.results %}
"{{ item.item }}": {
"key1": "{{ item.msg.json.key1 }}",
"key3": "{{ item.msg.json.key3 }}"
},
{% endfor %}
}

"msg": "'data_list' is undefined". How do I define this variable in my ansible playbook?

I want to extract data from a csv, turn it into a list that would be a variable (data_list in this case) and input the information according to the parameters listed in the task file.
But I am getting this error, "'data_list' is undefined".
Here is the main 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 }}"
when:
- item.key != 'Name'
- debug:
msg: "{{ item.name }}"
with_items: "{{ data_list }}"
- name: Create multiple users
hosts: "{{ hostname }}"
gather_facts: false
any_errors_fatal: false
become: yes
become_method: runas
become_user: admin
roles:
- { role: Create Multiple Users }
Here is my tasks file:
---
- 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 }}"
loop:
with_items:
- "{{ data_list }}"
After running the playbooks, I received this error:
TASK [Create Multiple Users : Create user] *************************************
fatal: [10.12.201.20]: FAILED! => {"msg": "'data_list' is undefined"}
While keeping everything else default, I've already tried adding vars to:
- name: Create multiple users
hosts: "{{ hostname }}"
gather_facts: false
any_errors_fatal: false
become: yes
become_method: runas
become_user: admin
roles:
- { role: Create Multiple Users }
vars:
- "{{ data_list }}"
---
- name: Create user
vars:
- "{{ data_list }}"
community.windows.win_domain_user:
name: "{{ item.name }}"
firstname: "{{ item.firstname }}"
surname: "{{ item.surname }}"
attributes:
displayName: "{{ item.firstname + ' ' + item.surname }}"
groups:
- "{{ item.groups }}"
loop:
with_items:
- "{{ data_list }}"
And also tried the following syntax:
vars:
- "{{ data_list }}"
vars: "{{ data_list }}"
with_items:
- "{{ data_list }}"
with_items: "{{ data_list }}"
But I am still getting errors.
So in this case, how do I define data_list?
Update
Some of you have suggested me to combine the content in the task file into the first playbook.
But after running the playbook, the portion on "- name: Create multiple Windows AD user accounts" seems to have been entirely skipped by ansible.
Whatever troubleshooting that I have done either results in some error or it gets skipped by ansible. I am not sure which is the right way of writing the playbook so that I don't face anymore errors and I am able to create users.
My playbook looks like this now:
---
- 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 }}"
when:
- item.key != 'Name'
- debug:
msg: "{{ item.name }}"
with_items: "{{ data_list }}"
- name: Create multiple users
hosts: "{{ hostname }}"
gather_facts: false
any_errors_fatal: false
become: yes
become_method: runas
become_user: admin
vars:
data_list: []
tasks:
- 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 }}"
loop:
with_items: "{{ data_list }}"

Include variable name with the item value in ansible

I'm trying to get input from CSV file and then add that as a dynamic inventory for a play, below is the ansible playbook:
---
- hosts: localhost
vars:
ansible_user: root
ansible_password: "User#123"
csv_file: "/var/lib/awx/projects/patching/server.csv"
tasks:
- read_csv:
key: servername
path: "{{ csv_file }}"
register: list
- debug:
msg: "{{ item.value.servername }}"
loop: "{{ list.dict|dict2items }}"
- add_host:
name: "{{ item.value.servername }}"
ansible_user: "{{ ansible_user }}"
ansible_password: "{{ ansible_password }}"
groups: patching
loop: "{{ list.dict|dict2items }}"
- name: test
hosts: patching
tasks:
- shell: hostname
register: hostname
- debug: "{{ hostname }}"
This playbook works fine, but I would like make the parameter "key" in the read_csv module as a variable as below and incorporate it with the item value as below, it throws error
"as The error was: 'dict object' has no attribute 'column'"
---
- hosts: localhost
vars:
ansible_user: root
ansible_password: "user#123"
csv_file: "/var/lib/awx/projects/patching/server.csv"
column: servername
tasks:
- read_csv:
key: "{{ column }}"
path: "{{ csv_file }}"
register: list
- debug:
msg: "{{ item.value.column }}"
loop: "{{ list.dict|dict2items }}"
- add_host:
name: "{{ item.value.column }}"
ansible_user: "{{ ansible_user }}"
ansible_password: "{{ ansible_password }}"
groups: patching
loop: "{{ list.dict|dict2items }}"
- name: test
hosts: patching
tasks:
- shell: hostname
register: hostname
- debug: "{{ hostname }}"
Need to understand how to incorporate the variable "column" to the item.value.servername change as item.value.column
Below is the CSV file:
#cat server.csv
servername,ip
172.17.92.60,172.17.92.60
172.17.92.38,172.17.92.38
172.17.92.39,172.17.92.39
172.17.92.70,172.17.92.70
try this playbook:
tasks:
- read_csv:
key: "{{ column }}"
path: "{{ csv_file }}"
register: list
- debug:
msg: "{{ item.value[column] }}"
loop: "{{ list.dict|dict2items }}"
- add_host:
name: "{{ item.value[column] }}"
ansible_user: "{{ ansible_user }}"
ansible_password: "{{ ansible_password }}"
groups: patching
loop: "{{ list.dict|dict2items }}"

ansible loop list and range

I have a list:
['bob', 'john', 'jack', 'rick']
I have a fixed range: 10,20 step: 2
I want to build this variable:
my_var:
- name: bob
content: '10'
- name: john
content: '12'
- name: jack
content: '14'
- name: rick
content: '16'
It seems I have to use loop but I don't understand how !
Loop the lists with the zip filter. For example, the playbook
- hosts: localhost
vars:
my_users: [bob, john, jack, rick]
tasks:
- set_fact:
my_var: "{{ my_var|default([]) +
[{'name': item.1, 'content': item.0}] }}"
loop: "{{ range(10,20,2)|zip(my_users)|list }}"
- debug:
var: my_var
gives
my_var:
- {content: 10, name: bob}
- {content: 12, name: john}
- {content: 14, name: jack}
- {content: 16, name: rick}
The iteration is not needed if you can use the collection community.general. For example, the playbook below gives the same result
- hosts: localhost
vars:
my_users: [bob, john, jack, rick]
my_var: "{{ range(10,20,2)|
zip(my_users)|
map('zip', ['content', 'name'])|
map('map', 'reverse')|
map('community.general.dict')|
list }}"
tasks:
- debug:
var: my_var
The values of the attribute content are integers in both options above. Convert the integers to strings if you need them. For example, in the loop, change the task
- set_fact:
my_var: "{{ my_var|default([]) +
[{'name': item.1, 'content': item.0|string}] }}"
loop: "{{ range(10,20,2)|zip(my_users)|list }}"
, or change the declaration
my_var: "{{ range(10,20,2)|map('string')|
zip(my_users)|
map('zip', ['content', 'name'])|
map('map', 'reverse')|
map('community.general.dict')|
list }}"
Both options give the same result (note the quoted numbers (strings) instead of the unquoted numbers in the first two options)
my_var:
- {content: '10', name: bob}
- {content: '12', name: john}
- {content: '14', name: jack}
- {content: '16', name: rick}

Ansible create EC2 instances with subsequent numbers in the name in different subnets

I am able to create ec2 instances with subsequent numbers in the same subnet.
However, I have 2 internal subnets and I am trying to create web3 and web5 in internal_subnet_ids[0] and web4 in internal_subnet_ids[1]. How do I do this?
---
- hosts: localhost
gather_facts: no
vars:
region: us-east-1
state: present
aws_ec2_specs:
- image: "{{ ami_id }}"
key_name: "{{ default_key_name }}"
server_category: web
vpc_subnet_id: "{{ internal_subnet_ids[0] }}"
instance_type: t2.small
server_numbers:
- '3'
- '4'
- '5'
exact_count: 1
tasks:
- name: Create EC2 Instances
ec2:
count: "{{ item.0.count | default(omit) }}"
count_tag:
Name: "{{ item.0.server_category + item.1 }}"
exact_count: "{{ item.0.exact_count | default(omit) }}"
image: "{{ item.0.image | mandatory }}"
instance_tags: "{{ {'Name': item.0.server_category + item.1 }|combine(item.0.instance_tags) }}"
instance_type: "{{ item.0.instance_type | mandatory }}"
key_name: "{{ item.0.key_name | mandatory }}"
region: "{{ region | mandatory }}"
vpc_subnet_id: "{{ item.0.vpc_subnet_id | default(omit) }}"
state: "{{ item.0.state | default(omit) }}"
with_subelements:
- "{{ aws_ec2_specs }}"
- server_numbers
when: state == "present"
register: ec2lauched
I figured out a solution, not the very 'pretty' but it would do for now.
---
- hosts: localhost
gather_facts: no
vars:
region: us-east-1
state: present
aws_ec2_specs:
- image: "{{ ami_id }}"
key_name: "{{ default_key_name }}"
server_category: web
instance_type: t2.small
server_numbers_subnet:
- server_numbers: '3'
vpc_subnet_id: "{{ internal_subnet_ids[0] }}"
- server_numbers: '4'
vpc_subnet_id: "{{ internal_subnet_ids[1] }}"
- server_numbers: '5'
vpc_subnet_id: "{{ internal_subnet_ids[0] }}"
exact_count: 1
tasks:
- name: Create EC2 Instances
ec2:
count: "{{ item.0.count | default(omit) }}"
count_tag:
Name: "{{ item.0.server_category + item.1.server_numbers }}"
exact_count: "{{ item.0.exact_count | default(omit) }}"
image: "{{ item.0.image | mandatory }}"
instance_tags: "{{ {'Name': item.0.server_category + item.1.server_numbers }|combine(item.0.instance_tags) }}"
instance_type: "{{ item.0.instance_type | mandatory }}"
key_name: "{{ item.0.key_name | mandatory }}"
region: "{{ region | mandatory }}"
vpc_subnet_id: "{{ item.1.vpc_subnet_id | default(omit) }}"
state: "{{ item.0.state | default(omit) }}"
with_subelements:
- "{{ aws_ec2_specs }}"
- server_numbers_subnet
when: state == "present"
register: ec2lauched

Resources