jinja variables
It is possible to build variables fast and consistent with jinja2.
Jinja2 is much faster than any ansible code to construct variables and here we find a way to make a playbook run much faster.
By getting the variables straight out of a multilayer dict (eg. a satellite inventory or something like that), we can filter a subset to target only the systems we need.
That is a performance gain (but we could do that with a inventory filter too), but a jinja filter is more dynamic and works inside the playbook.
updated: 19-02-2026.
Index:
String
Integer
List
Dict
List of dicts
Json
First we go back to some jinja basics to create variable types:
String
String is the default type for the return value.
Integer
When you want to return an integer, you must convert it to integer.
In the task below, we loop through the output of a shell task in ansible to find a string and return the line number in a variable.
Since the linenumber is returned as a string, we need to convert it to number again.
- name: Loop over the output to find 'text'
ansible.builtin.set_fact:
line_number: >-
{%- for i in range(_output.stdout_lines | length) -%}
{%- if 'text' in _output.stdout_lines[i] -%}
{{ i | int }}
{%- endif -%}
{%- endfor -%}
NOTE Each time the searchstring is found, a line number is added to the output, be sure you use the correct separators, if needed.
Index
List
Verified with ansible version 2.15 and ansible 2.19.
A list is defined in python (therefore in ansible too) as:
['item1','item2',]
To return this structure in jinja is easy when you know how. A pain in the ... if you don't.
We take the previous example en make a few modifications:
- name: Loop over the output to find 'text'
ansible.builtin.set_fact:
line_number: >
{%- set return_value = [] -%}
{%- for i in range(_output.stdout_lines | length) -%}
{%- if 'text' in _output.stdout_lines[i] -%}
{%- set _ = return_value.append( i | int ) -%}
{%- endif -%}
{%- endfor -%}
{{ return_value }}
The code is almost identical to the previous example, but to create a list variable we added some lines:
We first create a list return_value variable (list definition) and then append the values as needed.
Be sure to place these on separate lines as shown in the example, or it won't work and a string will be returned instead.
The return value is now placed between single quotes, so these will be added to the output as these are wanted in the list definition.
In a list there must be a comma separator placed between each item, after the last item there is no comma, this is why we use the loop.last variable to determine if we need a comma or not.
Dict
Verified with ansible version 2.15 up to ansible 2.19.
A dict in python (and therefore ansible) is defined as follows:
{ "key": "value", "key2": "value2" }
To return this in a jinja created structure, we use the following jinja:
- name: Loop over the output to find 'text'
ansible.builtin.set_fact:
line_number: >
{%- set return_val = dict() -%}
{%- for i in range(_output.stdout_lines | length) -%}
{%- if 'text' in _output.stdout_lines[i] -%}
{%- set _ = return_val.update({ i : _output.stdout_lines[i] }) -%}
{%- endif -%}
{%- endfor -%}
{{ return_val }}
First we create the return_value as a dict in jinja.
We add items using the jinja "update" method, which will add an item to the dict.
We now get a dict returned in which there are, not only the line number, but also the contents of that particular line.
This way we can extract variables from an output or any text variable in a fast and predictable way.
Jinja has lost of other filters and search options, which makes it possible to search for elements in dicts, like select or select_attr.
Read the jinja2 documentation for more information on this.
Building a list of dicts
Verified with ansible version 2.15, in ansible 2.19, they revised the jinja2 engine and the return value will always be a string, killing this optimization.
Somtimes we have a list of dicts with a number of items that misses a number of values you want in there.
As the lists are differrent in size, a lists_mergeby is not working.
This is a solution I cresated for the configuration as code for ansible automation platform 2.5, where the role user assignment must be done on organization_id. Initially this will work fine installation works, but as time progresses, organizations will be deleted and added. This will disrupt the numbering of the organizations in your config on recovery and will render your configuration as code useless.
The code below is created to make the mapping dynamicly, so the configuration
Let's give an example:
List 1: ( this is a result from a query in rhaap )
"gateway_organizations": [
{
"description": "The default organization for Ansible Automation Platform",
"id": 1,
"name": "Default",
},
{
"description": "Automation platform managent id=2",
"id": 2,
"name": "MGT",
},
{
"description": "Database team collection based id=5",
"id": 5,
"name": "ORACLE_COLL",
},
{
"description": "Database team role based id=8",
"id": 9,
"name": "ORACLE_REQ",
},
{
"description": "Organization for team TST id=9",
"id": 8,
"name": "ORG_TST",
},
{
"description": "RHEL Organization collection based id=3",
"id": 3,
"name": "RHEL_COLL",
},
{
"description": "RHEL Organization role based id=6",
"id": 7,
"name": "RHEL_REQ",
},
{
"description": "WEB server team collection based id=4",
"id": 4,
"name": "WEB_COLL",
},
{
"description": "WEB server team role based id=7",
"id": 6,
"name": "WEB_REQ",
}
]
The above result is very much edited, only the items we need are remaining.
The list we need the information for is the following:
gateway_role_user_assignments_all:
- role_definition: Organization Member
user: wilco
name: MGT
- role_definition: Organization Member
user: coll_upload
name: MGT
- role_definition: Team Member
user: coll_upload
name: MGT
- role_definition: Organization Member
user: coll_get
name: MGT
- role_definition: Team Member
user: coll_get
name: MGT
- role_definition: Organization Member
user: ee_upload
name: MGT
- role_definition: Team Member
user: ee_upload
name: RHEL_COLL
- role_definition: Organization Member
user: ee_pull
name: MGT
- role_definition: Team Member
user: ee_pull
name: RHEL_COLL
- role_definition: Organization Admin
user: mgt
name: MGT
- role_definition: Organization Admin
user: CaC_admin_MGT
name: MGT
- role_definition: Organization Admin
user: CaC_admin_RHEL_COLL
name: RHEL_COLL
- role_definition: Organization Admin
user: CaC_admin_RHEL_REQ
name: RHEL_REQ
- role_definition: Organization Admin
user: CaC_admin_WEB_COLL
name: WEB_COLL
- role_definition: Organization Admin
user: CaC_admin_WEB_REQ
- role_definition: Organization Admin
user: CaC_admin_ORACLE_COLL
name: ORACLE_COLL
- role_definition: Organization Admin
user: CaC_admin_ORACLE_REQ
name: ORACLE_REQ
- role_definition: Organization Admin
user: CaC_admin_TST
name: ORG_TST
In this list, we added the organization name instead of the organization id.
The mapping will be done by the following jinja2 task:
- name: Create the gateway_role_user_assignments variable
ansible.builtin.set_fact:
gateway_role_user_assignments: >-
{%- set return_value = [] }
{%- for item in gateway_role_user_assignments_all -%}
{%- set ret = dict() -%}
{%- set _ = ret.update('role_definition': item.role_definition) -%}
{%- set _ = ret.update('user': item.user) -%}
{%- set _ = ret.update('object_id': orgs_list|selectattr('name', '==', item.name)|first).id) -%}
{%- set _ = return_value.append( ret) -%}
{%- endfor -%}
{{ return_vaule }}
This will create the variable that the config as code needs to map all users to their roles using the organization id.
And there is no problem when a organization is deleted and a recovery is done of the complete configuration (the gap
is resolved by the script ).
Json
To return any of the above variables as a json variable, just use a set_fact.
ansible.builtin.set_fact:
json_var: "{{ jinja_built_var | to_nice_json }}"
This should work.