I am trying to convert my scripts into Ansible for automation. I am stuck in understanding loops or the "with_items" use case.
The original bash script:
for i in apple banana orange; do
case $i in
apple) export var="var.1:apple1,var.2:apple2" ;;
banana) export var="var.1:banana1,var.2:banana2,var.3:banana3" ;;
orange) export var="var.1:orange1" ;;
esac
echo "$i"
What I have tried so far:
VARS file:
fruits:
- name: apple
var: "{{ item }}"
with_items:
- apple1
- apple2
- name: banana
var: "{{ item }}"
with_items:
- banana1
- banana2
- banana3
- name: orange
var: "{{ item }}"
with_items:
- orange1
TASKS file:
- include_vars: vars.yml
- debug:
msg: "{{ fruits }}"
- name: output in shell using echo
shell: |
echo "{{ fruits.name }}" ;
echo "{{ fruits.var }}"
loop: "{{ fruits }}"
Outputs:
The output from include_vars task:
{
"ansible_included_var_files": [
"/etc/ansible/roles/openssl/tasks/vars.yml"
],
"ansible_facts": {
"fruits": [
{
"var": "{{ item }}",
"name": "apple",
"with_items": [
"apple1",
"apple2"
]
},
{
"var": "{{ item }}",
"name": "banana",
"with_items": [
"banana1",
"banana2",
"banana3"
]
},
{
"var": "{{ item }}",
"name": "orange",
"with_items": [
"orange1"
]
}
]
},
"_ansible_no_log": false,
"changed": false
}
Debug
debug task failed
{
"msg": "The task includes an option with an undefined variable. The error was: 'item' is undefined\n\nThe error appears to be in '/etc/ansible/roles/openssl/tasks/main.yml': line 262, column 3, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n- debug:\n ^ here\n",
"_ansible_no_log": false
}
I know that Yaml are space sensitive and formatting is important. I am a novice at writing playbooks and any pointers would be helpful.
With the dictionary
fruits:
apple:
- apple1
- apple2
banana:
- banana1
- banana2
- banana3
orange:
- orange1
the loop
- hosts: localhost
tasks:
- include_vars:
vars.yml
- debug:
msg: "{{ item.key }} {{ item.value }}"
loop: "{{ fruits|dict2items }}"
gives (abridged)
"msg": "orange [u'orange1']"
"msg": "apple [u'apple1', u'apple2']"
"msg": "banana [u'banana1', u'banana2', u'banana3']"
It's possible to reference the items in the dictionary. For example
- debug:
var: fruits.banana
- debug:
var: fruits.apple.1
gives
"fruits.banana": [
"banana1",
"banana2",
"banana3"
]
"fruits.apple.1": "apple2"
Related
I have a list of lists of hosts:
[['host-0', 'host-1'], ['host-2', 'host-3'], ['host-4', 'host-5', 'host-6']]
How can I add a port number, e.g., 8000, to each host using ansible/ jinja2 to get:
[['host-0:8000', 'host-1:8000'], ['host-2:8000', 'host-3:8000'], ['host-4:8000', 'host-5:8000', 'host-6:8000']]
this task shall do it:
- name: convert items in list
set_fact:
my_new_list: "{{ my_new_list | default([])+ [ my_var ] }}"
vars:
my_var: "{{ item | map('regex_replace', '$', ':8000') | list }}"
with_items:
- "{{ my_list }}"
full playbook to run as demo:
---
- hosts: localhost
gather_facts: false
vars:
my_list:
- ['host-0', 'host-1']
- ['host-2', 'host-3']
- ['host-4', 'host-5', 'host-6']
tasks:
- name: print original variable
debug:
var: my_list
- name: convert items in list
set_fact:
my_new_list: "{{ my_new_list | default([])+ [ my_var ] }}"
vars:
my_var: "{{ item | map('regex_replace', '$', ':8000') | list }}"
with_items:
- "{{ my_list }}"
- name: print new variable
debug:
var: my_new_list
result:
TASK [print new variable] **********************************************************************************************************************************************************************************************
ok: [localhost] => {
"my_new_list": [
[
"host-0:8000",
"host-1:8000"
],
[
"host-2:8000",
"host-3:8000"
],
[
"host-4:8000",
"host-5:8000",
"host-6:8000"
]
]
}
PLAY RECAP
Use map + regex_replace.
- debug:
msg: "{{ foo | map('map', 'regex_replace', '$', ':8000') }}"
vars:
foo: [['host-0', 'host-1'], ['host-2', 'host-3'], ['host-4', 'host-5', 'host-6']]
"msg": [
[
"host-0:8000",
"host-1:8000"
],
[
"host-2:8000",
"host-3:8000"
],
[
"host-4:8000",
"host-5:8000",
"host-6:8000"
]
]
Here is my json output:
{
"kind": [
{
"inventory": "",
"inventory_sources": "",
"job_templates": "",
"workflow_job_templates": "104"
},
{
"inventory": "",
"inventory_sources": "",
"job_templates": "114",
"workflow_job_templates": ""
},
{
"inventory": "24",
"inventory_sources": "",
"job_templates": "",
"workflow_job_templates": ""
},
{
"inventory": "",
"inventory_sources": "108",
"job_templates": "",
"workflow_job_templates": ""
}
]
}
I'd like to display all items name that contain a specific value. For example, for a search value of 104 I want to get the key name workflow_job_templates
I tested some syntaxes without any success:
- debug:
msg: "104 is {{kind|json_query(query)}}"
vars:
query: "[?*==`104`].workflow_job_templates"
I know it is wrong but can someone tell me how he'd do for himself?
json_query could be part of the equation for your solution but is really not needed here.
Explanation of the below piece of code:
Apply the dict2items filter to each element of your list. This transforms each mapping to a list of {key: "key", value: "value"} pairs
Flatten the given list so we get all those elements to a single top level
Select elements having a value of '104' only
Extract the key attribute of each element in a list
Make that list unique and sort it.
- name: Display all element having a value of 104
debug:
msg: "{{ kind | map('dict2items') | flatten
| selectattr('value', '==', '104')
| map(attribute='key') | unique | sort }}"
Please note that this solution will give you a result if the same key name has different values but one of them is `104. With your above data the result is:
TASK [Display all element having a value of 104] ***************************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"workflow_job_templates"
]
}
(Update)
The task below
- debug:
msg: "{{ item }} {{ kind|
map('dict2items')|
map('json_query', query)|
flatten }}"
loop: [104, 114, 108, 24]
vars:
query: "[?to_string(value) == to_string('{{ item }}')].key"
gives
msg: 104 ['workflow_job_templates']
msg: 114 ['job_templates']
msg: 108 ['inventory_sources']
msg: 24 ['inventory']
(For the record. Brute-force approach)
Create a unique list of the keys
- set_fact:
my_keys: "{{ my_keys|default([]) + item.keys()|list }}"
loop: "{{ kind }}"
- set_fact:
my_keys: "{{ my_keys|unique }}"
gives
my_keys:
- inventory
- inventory_sources
- job_templates
- workflow_job_templates
Create a dictionary with all values
- set_fact:
my_dict: "{{ my_dict|default({})|combine({item: values}) }}"
loop: "{{ my_keys }}"
vars:
query: "[].{{ item }}"
values: "{{ kind|json_query(query) }}"
gives
my_dict:
inventory:
- ''
- ''
- '24'
- ''
inventory_sources:
- ''
- ''
- ''
- '108'
job_templates:
- ''
- '114'
- ''
- ''
workflow_job_templates:
- '104'
- ''
- ''
- ''
Then search the dictionary. For example
- debug:
msg: "{{ item }} {{ my_dict|dict2items|json_query(query) }}"
loop: [104, 114, 108, 24]
vars:
query: "[?value[?contains(#, '{{ item }}')]].key"
gives
msg: 104 ['workflow_job_templates']
msg: 114 ['job_templates']
msg: 108 ['inventory_sources']
msg: 24 ['inventory']
The correct alternative of selectattr with json_query is:
- debug:
msg: "{{ kind | map('dict2items') | flatten | json_query(query)}}"
vars:
- query: "[?value == `\"104\"`].key"
If you really just want to use json_query()
---
- name: PLAYBOOK Filtering
hosts: localhost # run locally
tasks:
- name: Create json
set_fact:
kind: '{{ lookup("file", "kind.json") }}'
- name: check the var was created properly
debug:
var: kind
- name: output the element that matches 104
debug:
msg: "{{ kind | json_query(\"kind[?workflow_job_templates=='104'].workflow_job_templates\") }}"
- name:
set_fact:
output: "{{ kind | json_query(\"kind[?workflow_job_templates=='104'].workflow_job_templates\") }}"
Output
TASK [output the element that matches 104] *************************************************************************************************************
ok: [localhost] => {
"msg": [
"104"
]
}
I'm creating some ec2 instances from a specific image, then trying to get a list of disks attached to these instances.
The problem is when I try to loop over the registered variable from the create instance task, I got an error
I have tried the solution from this post but with no luck
ansible get aws ebs volume id which already exist
- name: create instance
ec2:
region: us-east-1
key_name: xxxxxxx
group: xxxxxx
instance_type: "{{ instance_type }}"
image: "{{ instance_ami }}"
wait: yes
wait_timeout: 500
instance_tags:
Name: "{{ item.name }}"
vpc_subnet_id: "{{ item.subnet }}"
register: ec2
loop: "{{ nodes }}"
- name: show attached volumes Ids
debug:
msg: "{{ item.block_device_mapping | map(attribute='volume_id') }}"
loop: "{{ ec2.results[0].instances }}"
while printing only msg: "{{ item.block_device_mapping }}" I get:
"msg": {
"/dev/sda1": {
"delete_on_termination": true,
"status": "attached",
"volume_id": "vol-xxxxxxx"
},
"/dev/xvdb": {
"delete_on_termination": false,
"status": "attached",
"volume_id": "vol-xxxxxx"
},
"/dev/xvdc": {
"delete_on_termination": false,
"status": "attached",
"volume_id": "vol-xxxxxx"
}
}
but when I use
msg: "{{ item.block_device_mapping | map(attribute='volume_id') }}"
I get this error:
"msg": "[AnsibleUndefined, AnsibleUndefined, AnsibleUndefined]"
The task below
- debug:
msg: "{{ item }}: {{ block_device_mapping[item].volume_id }}"
loop: "{{ block_device_mapping.keys() }}"
gives the {device: volume_id} tuples (grep msg):
"msg": "/dev/xvdb: vol-xxxxxx"
"msg": "/dev/xvdc: vol-xxxxxx"
"msg": "/dev/sda1: vol-xxxxxxx"
To iterate instances use json_query. The task below
- debug:
msg: "{{ item.block_device_mapping|json_query('*.volume_id') }}"
loop: "{{ ec2.results[0].instances }}"
gives:
"msg": [
"vol-xxxxxx",
"vol-xxxxxx",
"vol-xxxxxxx"
]
and the task below with zip
- debug:
msg: "{{ item.block_device_mapping.keys()|zip(
item.block_device_mapping|json_query('*.volume_id'))|list }}"
loop: "{{ ec2.results[0].instances }}"
gives the list of lists:
"msg": [
[
"/dev/xvdb",
"vol-xxxxxx"
],
[
"/dev/xvdc",
"vol-xxxxxx"
],
[
"/dev/sda1",
"vol-xxxxxxx"
]
]
and the task below with dict
- debug:
msg: "{{ dict (item.block_device_mapping.keys()|zip(
item.block_device_mapping|json_query('*.volume_id'))) }}"
loop: "{{ ec2.results[0].instances }}"
gives the tuples
"msg": {
"/dev/sda1": "vol-xxxxxxx",
"/dev/xvdb": "vol-xxxxxx",
"/dev/xvdc": "vol-xxxxxx"
}
The mistake:
So the main mistake you made was thinking of item.block_device_mapping as if it was the map you wanted to work with instead of a map within a map. That is, the keys that you have to first find would, according to the msg that you printed /dev/sda, /dev/xvdb and /dev/xvdc.
So first you'd have to make an array with the keys of the parent map. In the question you can see the necessary code to make Jinja get you the necessary strings:
# The necessary filter to get that array should be something along these lines
item['block_device_mapping'] | list() | join(', ')
You should register that to then loop over,giving you the keys you need to access those elements' attributes.
As the title suggest i want to loop over an existing dictionary and change some values, based on the answer to this question i came up with the code below but it doesn't work as the values are unchanged in the second debug call, I'm thinking it is because in the other question they are creating a new dictionary from scratch, but I've also tried it without the outer curly bracket which i would have thought would have caused it to change the existing value.
- set_fact:
uber_dict:
a_dict:
some_key: "abc"
another_key: "def"
b_dict:
some_key: "123"
another_key: "456"
- debug: var="uber_dict"
- set_fact: "{ uber_dict['{{ item }}']['some_key'] : 'xyz' }"
with_items: "{{ uber_dict }}"
- debug: var="uber_dict"
You can not change existing variable, but you can register new one with the same name.
Check this example:
---
- hosts: localhost
gather_facts: no
vars:
uber_dict:
a_dict:
some_key: "abc"
another_key: "def"
b_dict:
some_key: "123"
another_key: "456"
tasks:
- set_fact:
uber_dict: "{{ uber_dict | combine(new_item, recursive=true) }}"
vars:
new_item: "{ '{{ item.key }}': { 'some_key': 'some_value' } }"
with_dict: "{{ uber_dict }}"
- debug:
msg: "{{ uber_dict }}"
result:
ok: [localhost] => {
"msg": {
"a_dict": {
"another_key": "def",
"some_key": "some_value"
},
"b_dict": {
"another_key": "456",
"some_key": "some_value"
}
}
}
I need to run a task only if one or more files from a pre-defined list of files are missing. I tried the following (and some variants):
touch a
cat test.yml
- hosts: localhost
vars:
filelist:
- a
- b
tasks:
- stat:
path: "{{ item }}"
with_items: "{{ filelist }}"
register: all_files
- debug:
var: all_files
- debug:
msg: "Some file(s) missing"
when: "false in all_files['results'][*]['stat']['exists']"
ansible-playbook test.yml
...
TASK [debug] ********************************************************************
ok: [localhost] => {
"all_files": {
...
"item": "a",
"stat": {
...
"exists": true,
...
"item": "b",
"stat": {
"exists": false
...
TASK [debug] ********************************************************************
fatal: [localhost]: FAILED! => {"failed": true, "msg": "The conditional check 'false in all_files['results'][*]['stat']['exists']' failed. The error was: template error while templating string: unexpected '*'. String: {% if false in all_files['results'][*]['stat']['exists'] %} True {% else %} False {% endif %}\n\nThe error appears to have been in 'test.yml': line 16, column 5, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - debug:\n ^ here\n"}
...
What is the correct syntax to use in the 'when:' clause? Or is this the wrong way altogether?
- hosts: localhost
gather_facts: false
vars:
file_vars:
- {name: file1}
- {name: file2}
tasks:
- name: Checking existing file name
stat:
path: ./{{ item.name }}
with_items: "{{ file_vars }}"
register: check_file_name
- debug:
msg: 'file name {{item.item.name}} not exists'
with_items: "{{ check_file_name.results }}"
when: item.stat.exists == False
- name: Create file
file:
path: "./{{item.item.name}}"
state: touch
with_items: "{{ check_file_name.results }}"
when: item.stat.exists == False