join list of dictionaries - list

I have a list of macaddresses and network adapters that I'd like to combine into single list.
vars:
network_adapters:
- name: "Network adapter one"
- name: "Network adapter two"
macaddresses:
- "00:50:56:9c:8c:b1"
- "00:50:56:9c:8c:b2"
It can be done with below method using additional variable but I'm hoping for oneliner.
- name: "Set fact"
set_fact:
network_interfaces: >-
{{
network_interfaces | default([])
+ [item[0] | combine({'macaddress':item[1]})]
}}
loop: "{{ network_adapters | zip(macaddresses) }}"
outputs:
{
"macaddress": "00:50:56:9c:8c:b1",
"name": "Network adapter one"
},
{
"macaddress": "00:50:56:9c:8c:b2",
"name": "Network adapter two"
}
Here are some of my tryouts, maybe someone finds it useful.
add hash to the macaddresses list
"{{ macaddresses | json_query('[].{\"macaddress\": #}') }}"
{
"macaddress": "00:50:56:9c:8c:b1"
},
{
"macaddress": "00:50:56:9c:8c:b2"
}
combine into a list of hashes
"{{ macaddresses | json_query('[].{\"macaddress\": #}') | zip(network_adapters) }}"
[
{
"macaddress": "00:50:56:9c:8c:b1"
},
{
"name": "Network adapter one"
}
],
[
{
"macaddress": "00:50:56:9c:8c:b2"
},
{
"name": "Network adapter two"
}
]
combine into a dictionary
"{{ macaddresses | json_query('[].{\"macaddress\": #}') | map('combine', network_adapters) }}"
{
"macaddress": "00:50:56:9c:8c:b1",
"name": "Network adapter two"
},
{
"macaddress": "00:50:56:9c:8c:b2",
"name": "Network adapter two"
}
I'm specially interested in the last one. This outputs dictionary with the last network_adapters item both times.
Is it a bug?

The task does the job
- debug:
msg: "{{ dict(network_adapters|map(attribute='name')|zip(macaddresses))|
dict2items(key_name='name', value_name='macaddress') }}"
gives
msg:
- macaddress: 00:50:56:9c:8c:b1
name: Network adapter one
- macaddress: 00:50:56:9c:8c:b2
name: Network adapter two
Q: "More complex data. Keep other attributes, like 'ip'?"
network_adapters:
- name: "Network adapter one"
ip: "192.168.0.1"
- name: "Network adapter two"
ip: "192.168.0.2"
A: No "one-liner", just the loop (you've already found out), e.g.
- set_fact:
na2: "{{ na2|default([]) + [item.0|combine({'macaddress': item.1})] }}"
with_together:
- "{{ network_adapters }}"
- "{{ macaddresses }}"
gives
na2:
- ip: 192.168.0.1
macaddress: 00:50:56:9c:8c:b1
name: Network adapter one
- ip: 192.168.0.2
macaddress: 00:50:56:9c:8c:b2
name: Network adapter two

Related

Ansible returns 'AnibleUnicode' instead of JSON, unable to filter

I'm unable to filter the following output with json_query, even though it works on a jmespath validator website.
Let me explain in detail with the code.
type_debug returns AnsibleUnicode for members value, so when I tried to use json_query to filter output it returned null. I need to get value from key ip, but I don't know how to do it in a good way.
---
- hosts: localhost
gather_facts: false
vars:
- RPN:
stdout: >-
"members": [
{
"id": 0,
"ip": "x.x.x.x",
"owner": "buzut",
"private_ip": "10.91.154.39",
"speed": 100,
"status": "active"
}
]
tasks:
- debug:
msg: "{{ RPN.stdout | type_debug }}"
From your question I understand that you like to gather all member with a json_query. To do so I've corrected the JSON slightly.
---
- hosts: localhost
become: false
gather_facts: false
vars:
RPN:
stdout: >-
{
"members": [{
"id": 0,
"ip": "x.x.x.x",
"owner": "buzut",
"private_ip": "10.91.154.39",
"speed": 100,
"status": "active"
}]
}
tasks:
- name: Show type(s)
debug:
msg:
- "{{ RPN.stdout | type_debug }}"
- "{{ RPN.stdout | from_json | type_debug }}"
- "{{ RPN.stdout | to_json | type_debug }}"
- name: Show members
debug:
msg: "{{ RPN.stdout | from_json | json_query('members') }}"
resulting into an output of
TASK [Show type(s)] *********
ok: [localhost] =>
msg:
- AnsibleUnicode
- dict
- str
TASK [Show members] *********
ok: [localhost] =>
msg:
- id: 0
ip: x.x.x.x
owner: buzut
private_ip: 10.91.154.39
speed: 100
status: active
I need to get value from key ip
- name: Show value of key 'ip'
debug:
msg: "{{ RPN.stdout | from_json | json_query('members[*].ip') }}"
resulting into an output of
TASK [Show value of key 'ip'] *********
ok: [localhost] =>
msg:
- x.x.x.x
Further Documentation
Selecting JSON data: JSON queries

Ansible - remove empty elements from the list

I have an ansible output from which I want to remove all empty (none) elements for a specific attribute. Here is the example list:
"resources": [
{
"id": "9c40900909",
"name": "some_name1"
},
{
"id": "pc4b09090"
},
{
"id": "8lknkkn45"
},
{
"id": "9df40900909",
"name": "some_name2"
}
]
Here is how I reduced the list to be just "resources" with attribute "name":
- set_fact:
resources_names: "{{ output.resources | map(attribute='name') }}"
Problem with that is that I get the elements that does not have any value for the name. These are "AnsibleUndefined" in the following output:
- debug:
msg: resources_names list is "{{ resources_names }}"
ok: [localhost] => {
"msg": "resources_names list is \"['some_name1', AnsibleUndefined, AnsibleUndefined, 'some_name2']\""
}
I tried to remove it with reject and regexp but that's not working.
- set_fact:
list2: "{{ resources_names | reject('match', '^$') | list }}"
Same with this one:
- set_fact:
resources_names: "{{ output.resources | map(attribute='name') | rejectattr('name', 'none') }}"
Any idea?
Thanks.
In a nutshell:
---
- hosts: localhost
gather_facts: false
vars:
resources: [
{
"id": "9c40900909",
"name": "some_name1"
},
{
"id": "pc4b09090"
},
{
"id": "8lknkkn45"
},
{
"id": "9df40900909",
"name": "some_name2"
}
]
tasks:
- debug:
msg: "{{ resources | selectattr('name', 'defined') | map(attribute='name') }}"
which gives:
$ ansible-playbook /tmp/play.yml
[WARNING]: No inventory was parsed, only implicit localhost is available
PLAY [localhost] *******************************************************************************************************************************************************************************
TASK [debug] ***********************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"some_name1",
"some_name2"
]
}
PLAY RECAP *************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Note for your next question: please make sure you double check your examples and paste valid json/yaml data in your question. Thanks
Use the filter json_query. This filter ignores missing attributes
resources_names: "{{ output.resources|json_query('[].name') }}"
gives
resources_names:
- some_name1
- some_name2

How do I add to my existing Ansible Dictionary by looking something up from another dictionary?

Been at this about a week and I think it's time to post this. I'm pretty decent at ansible but clearly terrible at dictionaries.
How do I add a sub-key to a dictionary using update_list?
Alright so I have two dictionaries, one is the output of a command to pull AD Creds, the dictionary looks like this called userresult:
{
"objects": [
{
"Name" : "Administrator"
"objectSid": {
"Name" : "DOMAIN\\Administrator"
"Sid": "S-1-5-21"
}
},
{
"Name" : "Guest"
"objectSid": {
"Name" : "DOMAIN\\Guest"
"Sid": "S-1-5-22"
}
]
}
Now, I've got a dictionary that has the names and info I want to lookup (inputdictionary.yml):
dictionary:
users:
Nonexistentuser:
old_sid: 1-1-1-1
domain: nonexistentdomain
Administrator:
old_sid: 2-2-2-2
domain: DOMAIN
What I've tried:
---
- name: Update dictionary
hosts: myhosts
gather_facts: no
tasks:
- name: Get SID for user - you can ignore this part it just gets userresult
community.windows.win_domain_object_info:
filter: ObjectClass -eq 'user' -and objectCategory -eq 'Person'
properties:
- objectSid
register: userresult
- name: ready yaml dictionary
delegate_to: localhost
slurp:
path: '/home/inputdictionary.yml'
register: SourceInfo
- name: extract dictionary
set_fact:
myinfo: "{{SourceInfo['content'}] | b64decode | from_yaml ))"
- name: build a list of updates for dictionary
set_fact:
update_list: "{{ update list + update }}"
loop: "{{ myinfo.dictionary.users | dict2items }}"
loop_control:
index_var: idx
vars:
update_list: []
update:
- path: myinfo.dictonary.users[{{idx}}].new_sid
value: "TEST"
- debug:
var: update_list
Expected output:
dictionary:
users:
Nonexistentuser:
old_sid: 1-1-1-1
domain: nonexistentdomain
new_sid: TEST
Administrator:
old_sid: 2-2-2-2
domain: DOMAIN
new_sid: TEST
Current output:
TASK [build a list of updates for dictinoary]
FAILED! -> {
"msg": "template error while templating string: expected token 'end of print statement', got 'list. String: {{update list + update}}"
}
Test needs to be replaced with the new SID eventually, but I just want this step to work first..
Been trying to follow this but no avail: https://docs.ansible.com/ansible/latest/collections/ansible/utils/update_fact_module.html
Any help would be appreciated while I continue to try to fix this..
TEST needs to be replaced with the new SID eventually, but I just want this step to work first
- name: "make this working"
hosts: localhost
vars:
dictionary:
users:
Nonexistentuser:
old_sid: 1-1-1-1
domain: nonexistentdomain
Administrator:
old_sid: 2-2-2-2
domain: DOMAIN
tasks:
- name: build a list of updates for dictionary
set_fact:
update_list: "{{ update_list | d({}) | combine({ item.key: _v}) }}"
loop: "{{ dictionary.users | dict2items }}"
vars:
_k: "{{ item.key }}"
_v: "{{ item.value | combine({'new_sid': newsid}) }}"
newsid: "TEST"
- debug:
msg: '{{ update_list }}'
result:
ok: [localhost] => {
"msg": {
"Administrator": {
"domain": "DOMAIN",
"new_sid": "TEST",
"old_sid": "2-2-2-2"
},
"Nonexistentuser": {
"domain": "nonexistentdomain",
"new_sid": "TEST",
"old_sid": "1-1-1-1"
}
}
}

Ansible and AWS Subnets

I am relatively new to working with Ansible Core / Tower and I am at a complete loss what is causing the following issues. I have spent the past two days reading everything I could find on the topic and I am still stuck, looking for help.
Here is what I currently have setup (among other Ansible playbooks, roles, and tasks to create brand new VPC).
Below are the tasks that I am using to create a set of new subnets, one per availability zone, and get the results back from what is created. These tasks all works perfectly as verified through the AWS Console.
### Create the Internet-facing DMZ subnets ###
- name: Create Subnet(s) in VPC - DMZ
ec2_vpc_subnet:
state: present
vpc_id: "{{ new_vpc_info['vpcs'][0]['id'] }}"
region: "{{ vpc_region }}"
az: "{{ item.az }}"
cidr: "{{ item.subnet }}"
resource_tags:
Name: "{{ item.name }}"
Role: "{{ role_tag }}"
Team: "{{ team_tag }}"
Product Area: "{{ product_area_tag }}"
Portfolio: "{{ portfolio_tag }}"
with_items: "{{ dmz_subnet_az }}"
- name: Get Sunbet Info - DMZ
ec2_vpc_subnet_facts:
region: "{{ vpc_region }}"
filters:
"tag:Name": "{{ item.name }}"
with_items: "{{ dmz_subnet_az }}"
register: new_dmz_subnets
- debug:
var=new_dmz_subnets
The output of the "debug" command is provided below, truncated to remove the rest of the subnets and redacted so I do not get yelled at, which matches up to what is in the AWS Console.
{
"changed": false,
"_ansible_verbose_always": true,
"new_dmz_subnets": {
"msg": "All items completed",
"changed": false,
"results": [
{
"_ansible_parsed": true,
"subnets": [
{
"tags": {
"Product Area": "Engineering Tools",
"Portfolio": "Shared Platform and Operations",
"Role": "splunk-proof-of-concept",
"Name": "DMZ_Subnet_A",
"Team": "Engineering Tools"
},
"subnet_id": "subnet-XXXX",
"assign_ipv6_address_on_creation": false,
"default_for_az": false,
"state": "available",
"ipv6_cidr_block_association_set": [],
"availability_zone": "us-east-1a",
"vpc_id": "vpc-XXXX",
"cidr_block": "x.x.x.x/24",
"available_ip_address_count": 251,
"id": "subnet-XXXX",
"map_public_ip_on_launch": false
}
],
"changed": false,
"_ansible_item_label": {
"subnet": "x.x.x.x/24",
"az": "us-east-1a",
"name": "DMZ_Subnet_A"
},
"item": {
"subnet": "x.x.x.x/24",
"az": "us-east-1a",
"name": "DMZ_Subnet_A"
},
"_ansible_item_result": true,
"failed": false,
"invocation": {
"module_args": {
"profile": null,
"aws_secret_key": null,
"aws_access_key": null,
"security_token": null,
"region": "us-east-1",
"filters": {
"tag:Name": "DMZ_Subnet_A"
},
"ec2_url": null,
"subnet_ids": [],
"validate_certs": true
}
},
"_ansible_ignore_errors": null,
"_ansible_no_log": false
},
{
"_ansible_parsed": true,
"subnets": [
{
"tags": {
"Product Area": "Engineering Tools",
"Portfolio": "Shared Platform and Operations",
"Role": "splunk-proof-of-concept",
"Name": "DMZ_Subnet_B",
"Team": "Engineering Tools"
},
"subnet_id": "subnet-XXXX",
"assign_ipv6_address_on_creation": false,
"default_for_az": false,
"state": "available",
"ipv6_cidr_block_association_set": [],
"availability_zone": "us-east-1b",
"vpc_id": "vpc-XXXX",
"cidr_block": "x.x.x.x/24",
"available_ip_address_count": 251,
"id": "subnet-XXXX",
"map_public_ip_on_launch": false
}
],
"changed": false,
"_ansible_item_label": {
"subnet": "x.x.x.x/24",
"az": "us-east-1b",
"name": "DMZ_Subnet_B"
},
"item": {
"subnet": "x.x.x.x/24",
"az": "us-east-1b",
"name": "DMZ_Subnet_B"
},
"_ansible_item_result": true,
"failed": false,
"invocation": {
"module_args": {
"profile": null,
"aws_secret_key": null,
"aws_access_key": null,
"security_token": null,
"region": "us-east-1",
"filters": {
"tag:Name": "DMZ_Subnet_B"
},
"ec2_url": null,
"subnet_ids": [],
"validate_certs": true
}
},
"_ansible_ignore_errors": null,
"_ansible_no_log": false
},
......
}
]
},
"_ansible_no_log": false
}
Now onto the tasks that I am having issues getting working, below is my most recent attempt, which may be completely in left field due to me trying everything I found to get it working. I am attempting to get a list of the "subnet_id" from the registered "new_dmz_subnets" variable, then concatenating it with a "name" that is set in a vars file, and finally using that information to create a NAT Gateway within each of the subnets.
### Create the NAT Gateway in VPC ###
- name: Set DMZ Subnet facts
set_fact:
subnet_id_items:
subnet_id: '{{ item.subnets | map(attribute="subnet_id") | list }}'
with_items: "{{ new_dmz_subnets }}"
register: subnet_id_list
- name: Set Name and DMZ Subnet loop facts
set_fact:
name_subnet_items:
name: "{{ nat_gateway.name }}"
subnet_id: "{{ item.subnet_id }}"
loop: "{{ subnet_id_list }}"
register: name_subnet_list
- debug:
var=name_subnet_list
- name: Create NAT Gateway, allocate new EIP, in VPC
ec2_vpc_nat_gateway:
state: present
subnet_id: "{{ item.subnet_id }}"
region: "{{ vpc_region }}"
wait: yes
if_exist_do_not_create: true
tags:
Name: "{{ item.name }}"
Role: "{{ role_tag }}"
Team: "{{ team_tag }}"
Product Area: "{{ product_area_tag }}"
Portfolio: "{{ portfolio_tag }}"
with_items: "{{ name_subnet_list }}"
register: new_nat_gateway
- debug:
var=new_nat_gateway
When I ran this setup, I got the following fatal error message, which is pretty much the same across every variation I have attempted.
12:55:15
fatal: [localhost]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'ansible.utils.unsafe_proxy.AnsibleUnsafeText object' has no attribute 'subnets'\n\nThe error appears to have been in '/var/lib/awx/projects/_6__erik_andresen_git/ansible/splunk_poc_playbook/roles/create_networking_role/tasks/create_gateways_task.yml': line 21, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n### Starting working on this Task ###\n- name: Set DMZ Subnet facts\n ^ here\n"
}
Please let me know if I can provide any additional details and thanks in advance for the help!!!
-- Erik
I came across a setup that actually works! It may not be the best way to do it and I am still open to suggestions, but it at least works.
Here is code of the "ec2_vpc_subnet" module and collecting the Subnet IDs for later use in the playbook.
### Create the Internet-facing DMZ subnets ###
- name: Create Subnet(s) in VPC - DMZ
ec2_vpc_subnet:
state: present
vpc_id: "{{ vpc_id }}"
region: "{{ vpc_region }}"
az: "{{ item.az }}"
cidr: "{{ item.subnet }}"
resource_tags:
Name: "{{ item.name }}"
Role: "{{ role_tag }}"
Team: "{{ team_tag }}"
Product Area: "{{ product_area_tag }}"
Portfolio: "{{ portfolio_tag }}"
Created By: "{{ created_by }}"
with_items: "{{ dmz_subnet_az }}"
register: new_dmz_subnets
- name: Set facts for Subnet - DMZ
set_fact:
subnet_dmz_id: "{{ subnet_dmz_id | default({}) | combine({ item.subnet.tags.Name: item.subnet.id }) }}"
loop: "{{ new_dmz_subnets.results }}"
- debug:
var=subnet_dmz_id
And here is the use of the Subnet IDs in the "ec2_vpc_nat_gateway" module to create a NAT Gateway within each Availability Zone.
### Create the NAT Gateway in VPC ###
- name: Create NAT Gateway, allocate new EIP, in VPC
ec2_vpc_nat_gateway:
state: present
# NAT Gateways being deployed in DMZ subnets
subnet_id: "{{ subnet_dmz_id[item.subnet_name] }}"
region: "{{ vpc_region }}"
wait: yes
if_exist_do_not_create: true
# Tags not supported in the "ec2_vpc_nat_gateway" module
# https://github.com/ansible/ansible/issues/44339
#tags:
# Name: "{{ item.name }}"
# Role: "{{ role_tag }}"
# Team: "{{ team_tag }}"
# Product Area: "{{ product_area_tag }}"
# Portfolio: "{{ portfolio_tag }}"
# Created By: "{{ created_by }}"
with_items: "{{ nat_gateway }}"
register: new_nat_gateway
- debug:
var=new_nat_gateway

Searching Ansible debug messages for a string

I'm trying to find all the up interfaces on a switch, by looking at the results of some output from nxos_facts:
- name: get nxos facts via nxapi
nxos_facts:
provider: "{{ provider['nxapi'] }}"
gather_subset:
- "interfaces"
register: nxfacts_nxapi
- debug:
msg: "{{ nxfacts_nxapi.ansible_facts.ansible_net_interfaces | to_nice_json}} "
And I can successfully print out the debug to show the structure of the dictionary:
"ansible_net_interfaces": {
"Ethernet1/1": {
"bandwidth": 1000000,
"duplex": "full",
"ipv4": {
"address": "10.0.1.2",
"masklen": 24
},
"macaddress": "0800.276d.ee15",
"mtu": "1500",
"speed": "1000 Mb/s",
"state": "up",
"type": "100/1000/10000 Ethernet"
},
"Ethernet1/10": {
"bandwidth": 10000000,
"duplex": "auto",
"macaddress": "0800.276c.eecc",
"mode": "access",
"mtu": "1500",
"speed": "auto-speed",
"state": "down",
"type": "100/1000/10000 Ethernet"
},
But I'm struggling with the syntax to dereference the dictionary to only print when the "state" is "up"?
I'm running with the following version:
ansible 2.3.1.0
Any help is much appreciated.
You can iterate over the dictionary of interfaces and print only those elements for which the condition is true. Example:
- name: mytask
debug:
msg: "{{ item }}"
when: "item.value.state == 'up'"
with_dict: "{{ nxfacts_nxapi.ansible_facts.ansible_net_interfaces }}"