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
So really awesome... tnx bro
what version of ansible do you use? it doesn;t work for me on 2.5.1. I always get only the last element of the iterated list added to the list ( created by set_fact ) as described in the OP here:
https://stackoverflow.com/questions/29399581/using-set-facts-and-with-i…
There was a regression in Ansible 2.5.0-2.5.1 that broke set_fact in a loop. Try upgrading to the latest version.
it works like expected with 2.5.4. thank you!
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?
Only if you have declared an empty list but you can set the default variable intialisation to a list so you don;t have to |default([])
> 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([]) + ...
Yep this is the way to go saves you relying on declaring an empty list elsewhere.
I think you can just do:
- name: Build a list of all the security group names.
set_fact:
ec2_security_group_names: "{{ ec2_security_group_names + ec2_security_groups.security_groups }}"
https://blog.crisp.se/2016/10/20/maxwenzin/how-to-append-to-lists-in-an…
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.