Reputation: 81
I would like to create a complex var (nginx_ssl_vhosts_list) with a loop from another simple var (reverse_proxy_ssl) but I'm unable to get this to work. Is it possible to have something like the following code or shall I use a custom plugin?
Here is what how I thought it would have worked:
- hosts: localhost
gather_facts: no
connection: local
vars:
reverse_proxy_ssl:
- name: foo.org
frontport: 443
fronturl : /
backend: http://192.168.1.1:8080
- name: bar.org
frontport: 443
fronturl : /
backend: http://192.168.1.2:443
nginx_ssl_vhosts_list:
- listen: "{{ item.frontport }} ssl"
server_name: "{{ item.name }}"
access_log: "{{ item.name }}.access.log"
error_log: "{{ item.name }}.error.log"
extra_parameters: |
location {{ item.fronturl }} {
proxy_pass {{ item.backend }};
}
ssl_certificate {{ item.name }}.crt;
ssl_certificate_key {{ item.name }}.key;
with_items: "{{ reverse_proxy_ssl }}"
tasks:
- debug: msg="{{ nginx_ssl_vhosts_list }}"
Upvotes: 8
Views: 24051
Reputation: 3446
I'm aware this is an old question, but I had a similar problem. Here is my approach.
First, I think @ydaetskcoR is right that the best place to solve this is NOT to create a complex variable, but move loops to the template.
That said, there are some cases where you want to loop over dicts or lists. In my case, because I wanted to ensure that every required key was set in a list of dicts, and if not, fill in a default, that depends on some logic that was to convoluted to write in a template.
The approach by sonance207 seems very powerful, but I noted that I got some incompatibilities by the "set" variables, which are defined by Jinja2, and the filters and variables set by Ansible. E.g in the above example, the line
{%- set _= temp_dic.update({'var': vars.var }) -%}
can't be written as
{%- set temp_dic = temp_dic | combines({'var': vars.var }) -%}
If you do that, you get weird errors, like Ansible thinking that temp_dic is None instead of a dictionary.
In the end, I used a simplified version of what's written by Konstantin Suvorov on https://ansibledaily.com/process-complex-variables-with-set_fact-and-with_items/. However, instead of writing to a temporary variable with the register
keyword, and retrieving it later, I simply opted for a simple with_item
loop that grew the desired nginx_ssl_vhosts_list
variable with a single entry each loop.
- hosts: localhost
gather_facts: no
connection: local
vars:
reverse_proxy_ssl:
- name: foo.org
frontport: 443
fronturl : /
backend: http://192.168.1.1:8080
- name: bar.org
frontport: 443
fronturl : /
backend: http://192.168.1.2:443
nginx_ssl_vhosts_list: []
tasks:
- set_fact:
nginx_ssl_vhosts_list: "{{ nginx_ssl_vhosts_list + [{
'listen': (item.frontport |string) + ' ssl',
'server_name': item.name,
'access_log': item.name + '.access.log',
'error_log': item.name + '.error.log'
}] }}"
with_items: "{{ reverse_proxy_ssl }}"
- debug: msg="{{ nginx_ssl_vhosts_list }}"
(Note that this example leaves out the multi-line formatting of extra_parameters
. Such formatting are best served in a jinja2 template file.
Upvotes: 2
Reputation: 2087
This is possible.
I did this by using Jinja2 templating and using normal python inside the templating. At the end just return the list with {{- temp_list -}}
Here is a example from my default/main.yml
# Arch List type
Mock_ArchType: ['epel-6-x86_64','epel-7-x86_64']
# Vars list Dictionary
Platform_Vars_list: [
{ var: "log_path", value: "'{{ Default_Home_String }}/logs/'" },
{ var: "python_site_packages_path", value: "'{{ Default_Python_String }}/lib/python{{ python_main_version }}/site-packages/'" }
]
Platform_Vars: "{%- set temp_list = [] -%}
{%- for item in Mock_ArchType if (client_code | search('Build')) -%}
{%- for vars in Platform_Vars_list -%}
{%- set temp_dic = {} -%}
{%- set _= temp_dic.update({'var': vars.var }) -%}
{%- set _= temp_dic.update({'value': vars.value }) -%}
{%- set _= temp_dic.update({'path': '/var/lib/mock/' + item + '/root/builddir/build/BUILDROOT' + Default_Home_String }) -%}
{%- set _= temp_list.append(temp_dic) -%}
{%- endfor -%}
{%- endfor -%}
{{- temp_list -}}"
Credit goes to this. https://groups.google.com/forum/#!topic/ansible-project/aiBiuUGWZGs
Upvotes: 3
Reputation: 56997
As far as I know you can't loop in a variable definition, however you can loop inside a template or task.
In this exact case you are probably best creating a template task that generates your virtual hosts from the reverse_proxy_ssl
list variable with something like this:
...
tasks:
- name: template virtual hosts
template:
src: apache2.conf.j2
dest: /etc/apache2/apache2.conf
...
...
{% for vhost in reverse_proxy_ssl %}
<VirtualHost *: {{ vhost.frontport }}>
ServerName {{ vhost.name }}
ProxyPreserveHost On
ProxyPass {{ vhost.fronturl }} {{ vhost.backend }}
ProxyPassReverse {{ vhost.fronturl }} {{ vhost.backend }}
</VirtualHost>
{% endfor %}
...
In the more general case you should be able to create a variable with set_fact and use that in a loop:
- name: set foo
set_fact: foo="{{ item.foo }}"
with_items: foos
Upvotes: 3