appending to nested dictionary

I am wondering how can i append the dictionary with subnet_id as key into vpc_result subnets element to look like below. Please find below all the tasks, vars and run output.

ok: [localhost] => {
“vpc_result”: [
{
“cidr_block”: “10.10.8.0/21”,
“igw_id”: “igw-0e06eba08c68ebf2b”,
“name”: “A”,
“region”: “eu-central-1”,
“subnets”: {
“10.10.8.0/24”: {
“az”: “a”,
“map_public”: true,
“subnet_id”: “subnet-08b6790e6e51b6b78” ←
},
“10.10.9.0/24”: {
“az”: “b”,
“subnet_id”: “subnet-0e8512e1ace7ddc1f” ←
}
},
“vpc_id”: “vpc-000d7431c4a4f6e83”
}
]
}

roles/aws/defaults/main.yml

project: killerapp
customer: loser
stage: dev

resource_tags:
Stage: “{{ stage }}”
Application: “{{ project }}”
Created-By: “Ansible for {{ customer }}”

vpc:

  • name: “A”
    cidr_block: “10.10.8.0/21”
    region: “eu-central-1”
    subnets:
    “10.10.8.0/24”:
    az: “a”
    map_public: true
    “10.10.9.0/24”:
    az: “b”
  • name: “B”
    cidr_block: “10.11.8.0/21”
    region: “eu-west-3”
    subnets:
    “10.11.8.0/24”:
    az: “a”
    map_public: true
    “10.11.9.0/24”:
    az: “b”

roles/aws/tasks/main.yml

  • name: Create VPC for each region
    loop: “{{ vpc }}”
    include_tasks: vpc.yml
    vars:
    region: “{{ item.region }}”
    name: “{{ item.name }}”
    cidr_block: “{{ item.cidr_block }}”
    subnets: “{{ item.subnets }}”

  • name: “Report of generated networking”
    debug:
    var: vpc_result

roles/aws/tasks/vpc.yml

  • name: Create virtual private network for VPC {{ name }}
    ec2_vpc_net:
    region: “{{ region }}”
    name: “{{ name }}”
    cidr_block: “{{ cidr_block }}”
    tags: “{{ resource_tags }}”
    register: created_vpc

  • name: Create internet gateway for VPC {{ name }} in {{ region }}
    ec2_vpc_igw:
    vpc_id: “{{ created_vpc.vpc.id }}”
    region: “{{ region }}”
    tags: “{{ resource_tags | combine({‘Name’: ‘VPC internet gateway’}) }}”
    register: created_igw

  • set_fact:
    vpc_result: “{{ vpc_result | default() + [{
    ‘name’: created_vpc.vpc.name,
    ‘igw_id’: created_igw.gateway_id,
    ‘vpc_id’: created_vpc.vpc.id,
    ‘region’: region,
    ‘subnets’: subnets,
    ‘cidr_block’: cidr_block
    }] }}”

  • name: Create subnets in VPC {{ name }} in {{ region }}
    loop: “{{ subnets | list }}”
    loop_control:
    loop_var: subnet
    include_tasks: vpc_subnet.yml

roles/aws/tasks/vpc_subnet.yml

  • name: Create subnet {{ subnet }} for {{ name }} VPC in region {{ region }}
    ec2_vpc_subnet:
    vpc_id: “{{ created_vpc.vpc.id }}”
    region: “{{ region }}”
    cidr: “{{ subnet }}”
    tags: “{{ resource_tags | combine({‘Name’: subnet, ‘Type’: subnets[subnet].map_public|default(false) | ternary(‘public’, ‘private’)}) }}”
    az: “{{ region }}{{ subnets[subnet].az }}”
    map_public: “{{ subnets[subnet].map_public | default(false) }}”
    register: created_subnet
    retries: 3
    delay: 5

  • debug:
    var: created_subnet.subnet.id

output

❯ ansible-playbook aws.yml

PLAY [localhost]

This is untested, but you would end up doing effectively the same as this:

set_fact:
  vpc_result: '{{ [vpc_result[0] | combine({"subnets":
                                             {"10.10.8.0/24":
                                               {"subnet_id": "subnet-08b6790e6e51b6b78"}
                                             },
                                             {"10.10.9.0/24":
                                               {"subnet_id": "subnet-0e8512e1ace7ddc1f"}
                                             }
                                            })] }}'

Here’s a link to the ansible.builtin.combine filter docs. Note that combine works on dictionaries, and vpc_result is a single element list. That’s why the whole expression is enclosed in and we combine on vpc_result[0] instead of vpc_result.