I have the vars defined like this-
vars:
values:
- key1: value1
key2:
- value1.1
- value1.2
- key1: value2
key2:
- value2.1
- value2.2
Want to iterate on key1 with corresponding values in key2
I am running ansible 2.7.10 with python 2.7.10. Here is what I have written in my task based on some suggestions I found online-
(used with_subelements)
- name: test loops
debug:
msg: "This is key1: {{ item.0.key1 }}, and here is corresponding key2 element {{ item.1 }}"
with_subelements:
- values
- key2
Expected output:
This is key1: value1, and here is corresponding key2 element value1.1
This is key1: value1, and here is corresponding key2 element value1.2
This is key1: value2, and here is corresponding key2 element value2.1
This is key1: value2, and here is corresponding key2 element value2.2
Error I get when I execute the playbook:
fatal: [localhost]: FAILED! => {"msg": "subelements lookup expects a dictionary, got 'values'"}
Any ideas how to achieve this?
Correct syntax is
with_subelements:
- "{{ values }}"
- key2
, or Migrated from with_X to loop
loop: "{{ values|subelements('key2') }}"
The play below
- hosts: localhost
vars:
values:
- key1: value1
key2:
- value1.1
- value1.2
- key1: value2
key2:
- value2.1
- value2.2
tasks:
- debug:
msg: "{{ item.0.key1 }} - {{ item.1 }}"
with_subelements:
- "{{ values }}"
- key2
gives (abridged):
"msg": "value1 - value1.1"
"msg": "value1 - value1.2"
"msg": "value2 - value2.1"
"msg": "value2 - value2.2"
Related
I've got a file containing a few lines of simple shell-style (key=value, no whitespace or special characters) assignments. How would I go about converting this to a set of top-level facts using ansible.builtin.set_fact? expandvars looks like it might be relevant, but I can't find any examples or even any decent documentation.
For example, given the configuration file
shell> cat conf.ini
key1=alpha=beta=charlie
key2=value2
key3= value3
The variable below
config_vars: "{{ dict(lookup('file', 'conf.ini').split('\n')|
map('split', '=', 1)|
map('map', 'trim')) }}"
expands to
config_vars:
key1: alpha=beta=charlie
key2: value2
key3: value3
You can remove the last map/trim filter from the pipe if you're sure there are no spaces in the configuration file. But, to be on the safe side, I'd keep it.
Config file at the remote host
The solution in the previous section works at the controller only because the lookup plugins execute at the localhost. Fetch the files first if they are at the remote hosts. For example, given the configuration files
shell> ssh admin#test_11 cat /tmp/conf.ini
key1=alpha=beta=charlie
key2=value2
key3= value3
shell> ssh admin#test_12 cat /tmp/conf.ini
key1=value1
key2= value2
key3=alpha=beta=charlie
the playbook below
- hosts: test_11,test_12
vars:
conf_ini_path: "conf_ini/{{ inventory_hostname }}/tmp/conf.ini"
config_vars: "{{ dict(lookup('file', conf_ini_path).split('\n')|
map('split', '=', 1)|
map('map', 'trim')) }}"
tasks:
- fetch:
dest: conf_ini
src: /tmp/conf.ini
- debug:
var: config_vars
gives (abridged)
TASK [debug] ***********************************************
ok: [test_11] =>
config_vars:
key1: alpha=beta=charlie
key2: value2
key3: value3
ok: [test_12] =>
config_vars:
key1: value1
key2: value2
key3: alpha=beta=charlie
Alow no value
For example, given the configuration file
shell> cat conf.ini
key1=alpha=beta=charlie
key2=value2
key3= value3
key4
The variable below
config_vars: "{{ dict(lookup('file', 'conf.ini').split('\n')|
map('split', '=', 1)|
map('map', 'trim')|
map('json_query', '[]|[[0], [1]]')) }}"
expands to
config_vars:
key1: alpha=beta=charlie
key2: value2
key3: value3
key4: null
I need to set variables whose names must be my_dict keys and the values to store into the variables must be the value of stdout in the my_list element that has the item value1, which is also the value of key1 inside my_dict.
---
- name: Vars from dict
tasks:
- name: Get vars from dict
my_dict:
key1: "value1"
key2: "value2"
# list of dicts
my_list: [ { item: "value1", stdout: "1"}, { item: "value2", stdout: "2" } ]
# I'm setting variables here
# variable key1 must be set to the value of "stdout" key belonging to the dict whose "item" key contains "value1"
# (i.e. "1" in this case)
- set_fact:
key1: "1"
key2: "2"
I hope the code example clarifies a bit.
How is this achievable?
To get familiar with the data structure and a better understanding how to access certain parts of it, you may have a look into the following debug example.
---
- hosts: localhost
become: false
gather_facts: false
vars:
my_list: [ { item: "value1", stdout: "1"}, { item: "value2", stdout: "2" } ]
tasks:
- name: Show var
debug:
var: my_list
- name: Show values
debug:
msg:
- "{{ item }}"
- "{{ item.stdout }}"
loop: "{{ my_list }}"
loop_control:
extended: true
label: "{{ ansible_loop.index0 }}"
Resulting into an output of
TASK [Show var] *************
ok: [localhost] =>
my_list:
- item: value1
stdout: '1'
- item: value2
stdout: '2'
TASK [Show values] ***********
ok: [localhost] => (item=0) =>
msg:
- item: value1
stdout: '1'
- '1'
ok: [localhost] => (item=1) =>
msg:
- item: value2
stdout: '2'
- '2'
Further Readings
Data manipulation
Extended loop variables
I would like to run a task in Ansible like this:
- name: Task
set_fact:
var: >-
{"system": [{
"key1": "value1",
"key2": "value2"}]}
The variable var is considered as a dict by Ansible but I want it to be a string to template it in a config file. Any suggestion ?
Thanks.
You're mistaken. The value of the variable is not a dictionary. It's a string. For example
- set_fact:
_var: >-
{"system": [{
"key1": "value1",
"key2": "value2"}]}
- debug:
var: _var
- debug:
var: _var|type_debug
- debug:
var: _var.system.0.key1
You can see that the type is AnsibleUnicode and the path in the dictionary is undefined
_var:
system:
- key1: value1
key2: value2
_var|type_debug: AnsibleUnicode
_var.system.0.key1: VARIABLE IS NOT DEFINED!
You can convert the string to the dictionary
- debug:
var: _var|from_yaml
- debug:
var: _var|from_yaml|type_debug
- debug:
var: (_var|from_yaml).system.0.key1
You can see that the type is dict and the path in the dictionary is defined
_var|from_yaml:
system:
- key1: value1
key2: value2
_var|from_yaml|type_debug: dict
(_var|from_yaml).system.0.key1: value1
I'm having the folowing ansible task:
- name: Task1
command: "{{ item }}"
with_dict:
- { key1: "aaa", key2: "aaa-file"}
- { key1: "bbb", key2: "bbb-fie"}
- ...
loop:
- cd /home/{{ dict.key1 }}
- rm -f {{ dict.key2 }}
It's just a concocted example but what I'm trying to achieve is the following:
run all commands with the first entry values from with_dict, then run all commands with the second entry values from the with_dict etc.
I've looked over with_nested, but not sure if it helps.
There are at least two ways to perform this.
First : one command line
- name: Task1
command: "cd /home/{{ item.key1 }} && rm -f {{ item.key2 }}"
with_items:
- key1: "aaa"
key2: "aaa-file"
- key1: "bbb"
key2: "bbb-fie"
- ...
If you really wants to perform a cd command, use this :
- name: Task1
command: "rm -f {{ item.key2 }}"
args:
chdir: "/home/{{ item.key1 }}"
with_items:
- key1: "aaa"
key2: "aaa-file"
- key1: "bbb"
key2: "bbb-fie"
- ...
Second : use two tasks
- name: Task1
command: "cd /home/{{ item.key1 }}"
with_items:
- key1: "aaa"
key2: "aaa-file"
- key1: "bbb"
key2: "bbb-fie"
- ...
- name: Task2
command: "rm -f {{ item.key2 }}"
with_items:
- key1: "aaa"
key2: "aaa-file"
- key1: "bbb"
key2: "bbb-fie"
- ...
In this case, I suggest you to define a variable with your list, at play level for example :
- hosts: app_servers
vars:
my_list:
- key1: "aaa"
key2: "aaa-file"
- key1: "bbb"
key2: "bbb-fie"
- ...
tasks:
- name: Task1
command: "cd /home/{{ item.key1 }}"
with_items: "{{ my_list }}"
- name: Task2
command: "rm -f {{ item.key2 }}"
with_items: "{{ my_list }}"
I'm trying to use external dictionary with somehow "mapped" variables:
one_changes:
key1: value1
key2: value2
key3: value3
In my playbook I'm using vars_files which is recognized. Now, how do I do something like this:
name: variables_one
replace:
dest=/one/file.php
regexp="{{ one_changes.key }}"
replace="{{ one_changes.value }}"
with_items:
- one_changes
I cannot for sake of god figure it out since few hours. There are many variables for many files so I'd like to keep them mapped separately.
There is a chapter about this in documentation.
First, mind the indentation in your yaml file:
one_changes:
key1: value1
key2: value2
key3: value3
Second, use with_dict:
- name: variables_one
replace:
dest: /one/file.php
regexp: "{{ item.key }}"
replace: "{{ item.value }}"
with_dict: "{{ one_changes }}"