Adding strings to an array in Ansible

From time to time, I need to dynamically build a list of strings (or a list of other things) using Ansible's set_fact module.

Since set_fact is a module like any other, you can use a with_items loop to loop over an existing list, and pull out a value from that list to add to another list.

For example, today I needed to retrieve a list of all the AWS EC2 security groups in a region, then loop through them, building a list of all the security group names. Here's the playbook I used:

- hosts: localhost
  connection: local
  gather_facts: no

  vars:
    ec2_security_group_names: []

  tasks:
    - name: Get security groups from EC2.
      ec2_group_facts:
        region: us-east-1
        filters:
          "tag:myapp": "example"
      register: ec2_security_groups

    - name: Build a list of all the security group names.
      set_fact:
        ec2_security_group_names: "{{ ec2_security_group_names }} + [ '{{ item.group_name }}' ]"
      with_items: "{{ ec2_security_groups.security_groups }}"

    - name: Print the security group names to the console.
      debug: var=ec2_security_group_names

Before building the ec2_security_group_names list, I made sure to set it as an empty list in vars (ec2_security_group_names: []). That way the first time the loop adds a group_name, it has something to add to (an empty list)!

When run, you get something like:

TASK [debug] ***********************************************************************************************************
ok: [localhost] => {
    "changed": false,
    "ec2_security_group_names": [
        "security-group-1",
        "security-group-2",
        "security-group-3",
    ]
}

Comments

Really awesome, nice way to use set_fact module.
Thanks

This is a great example, and a good start on a problem I'm trying to solve: how to create a list of dictionaries. Specifically, I'm trying to register a list of instance IDs and their forwarding ports to an AWS Application ELB. I tried the method you demonstrated above, but I keep getting a null value for the first item in the list of dictionaries. I'm using YAML syntax rather than the JSON syntax to create the list of dicts.

I actually ran into this exact same issue when concatenating dictionaries on another project recently. I think it was a syntax issue, e.g. I was doing something like "{} + dictionary_here", but the end result was a giant string of all the dictionary data as JSON stuck together.

Unfortunately, I'm now not able to remember which project that was, nor how I fixed it, but I know I spent a couple hours trying to vary what syntax I was using to set the variable initially, then concatenate values inside the dict using set_fact. It was less than intuitive; dicts seem to behave a bit differently than arrays in this regard.

I think there's also a Jinja2 filter that can combine dicts better than using +.

You would want to use the combine filter of Jinja to do this:

set_fact:
    collectionname: "{{collectionname|default({}) | combine({item.name: item.value})}}"

This method only seems to work when all entries are unique. When I run it where two indexes have the same name but different values, it only adds the first one it encounters to the collection.

example:
item 1 name - foo, item 1 value - w
item 2 name - bar, item 2 value - x
item 3 name - foo, item 3 value - y
item 4 name - bar, item 4 value - z

will only print:
foo: w
bar: x

That is great! I tried to find a simple way to populate ec2_id list, without using ec2.py until I read Jeff's blog. When I used set_fact, the list contained only one ec2 instance. After I upgrade ansible from 2.5.1 to 2.6.1, ec2_id list includes all ec2 instances.

Thanks. Next show us how to push a dict onto an array. I am looping over an array, each which have json content. I need to do a |from_json for each, build a hash/dict with keys I care about and push it onto an array....struggling :(

Thanks, this solved my (very specific) problem after 3 days of searching!

I was trying to figure this out for a couple of days. I have the idea of doing combining an empty list with the items from the for loop but couldn't get it working. Using the [] in the manner you did on the looped data resolved my issue. Good information.

Nice example in a exemplary way , thanks to Jeff adoring ansible .

Sorry but this does not look good to me, I think the right format should be:

"{{ ec2_security_group_names + [ item.group_name ] }}"

Or maybe both will work?

> ec2_security_group_names: "{{ ec2_security_group_names }} + [ '{{ item.group_name }}' ]"

In certain cases this may not be interpolated properly, e.g. when the value of item.group_name has special characters such as ' or newline. The result would be something like: "ec2_security_group_names": "[] + [ 'group's name' ] + [ 'group2' ] + [ 'group3' ]"

To avoid this we can use either
ec2_security_group_names: "{{ ec2_security_group_names }} + {{ [item.group_name] }}"
or
ec2_security_group_names: "{{ ec2_security_group_names + [item.group_name] }}"

Nice article. I think with current version Ansible, you can initialize empty list with set_fact: like "{{ ec2_security_group_names | default([]) + ...

Hi, friend!
This block looks excessive:
vars:
ec2_security_group_names: []
and can be improved by:
ec2_security_group_names: "{{ ec2_security_group_names | default([]) }} + [ '{{ item.group_name }}' ]"
like that.

Thank you so much. This helped me tremendously with a thing I was trying to figure out.