I have a variable that looks like (the reality is a lot longer):
deploy_dirs:
conf:
base: '/etc/projects/'
subs:
apache: '/httpd'
modsec: '/modsec'
php: '/php'
haproxy: '/haproxy'
varnish: '/varnish'
logs:
base: '/var/log/projects/'
subs:
apache: '/httpd'
modsec: '/modsec'
php: '/php'
haproxy: '/haproxy'
varnish: '/varnish'
Then I have a task that looks like:
- debug:
msg: '{{ item.0.value.base }}SOME-OTHER-VAR{{ item.1 }}'
loop: '{{ deploy_dirs | dict2items | subelements("value.subs") }}'
Which I would love to output:
/etc/projects/SOME-OTHER-VAR/httpd
/etc/projects/SOME-OTHER-VAR/modsec
/etc/projects/SOME-OTHER-VAR/php
/etc/projects/SOME-OTHER-VAR/haproxy
/etc/projects/SOME-OTHER-VAR/varnish
/var/log/projects/SOME-OTHER-VAR/httpd
/var/log/projects/SOME-OTHER-VAR/modsec
/var/log/projects/SOME-OTHER-VAR/php
/var/log/projects/SOME-OTHER-VAR/haproxy
/var/log/projects/SOME-OTHER-VAR/varnish
These variables get re-used in various places all over my playbooks, and the structure makes sense to me and seems to be straightforward enough.
I've tried changing the subelements()
filter in to the product()
filter (with_nested
equivalent), but it doesn't seem able to reference the variable from the earlier loop in the same way that the subelements()
filter can.
Obviously the problem with the above is that subelements()
needs a list, not a dictionary. I'm unable to convert the "value.subs"
argument in to a list, as I can't find a way to nest filters like that..?
Does anyone have any ideas on how to get this working?
Thanks!
UPDATE: If I also have:
www:
base: '/srv/www/projects/'
subs: {}
And I just want it to output:
/srv/www/projects/SOME-OTHER-VAR
Do you know how I can swing the subs
bit so that it runs and generates the expected result? At the moment it just skips it. i.e., no loop occurs for www
.
You can transform your subs
dict in a list with json_query before using the subelements filter.
- debug:
msg: "{{ item.0.base }}SOME-OTHER-VAR{{ item.1 }}"
loop: >-
{{
deploy_dirs
| json_query('*[].{base: base, subs: subs.*}')
| subelements('subs')
}}
Have a look at jmespath documentation for the syntax. json_query is really handy when your data structure gets complex.
This solution will not loop over the elements with an empty subs
element like the 'www'
map entry in your example and I have no "clean and easy" way to do it in a single task. You can easily have a second task looping over the elements with empty subs => deploy_dirs | json_query("* | [?!subs]")
I guess your final goal is not to have debug msg written to the screen. If you really need a single structure to make your final task (e.g. create directories) in a single step, you can populate a var with set_fact and the above techniques and then use it to acheive your real task
- name: Get all elements with subs to loop over
set_fact:
my_var: >-
{{
deploy_dirs
| json_query('*[].{base: base, subs: subs.*}')
| subelements('subs')
}}
- name: Add entries with empty item1 for elements with empty subs
set_fact:
my_var: >-
{{
my_var
+
[[item, '']]
}}
loop: >-
{{ deploy_dirs | json_query("* | [?!subs]") }}
- name: This will be replaced with a real task
debug:
msg: "{{ item.0.base }}SOME-OTHER-VAR{{ item.1 }}"
loop: "{{ my_var }}"