I'm a real Ansible beginner.
Is there any way to reconstruct a variable from another ansible variable?
For example, this playbook :
- hosts: servers
vars:
ex_server1: First
ex_server2: Second
ex_server3: Third
toto: ex_
tasks:
- debug:
msg: "{{ toto+ansible_hostname }}"
It print :
ok: [server2] => {
"msg": "ex_server2"
}
ok: [server3] => {
"msg": "ex_server3"
}
ok: [server1] => {
"msg": "ex_server1"
}
Instead of "First", "Second" and "Third".
Is there a way to print variable content instead of variable name in this situation or in a jinja template ?
Use lookup vars plugin
- debug:
msg: "{{ lookup('vars', toto + ansible_hostname) }}"
gives
TASK [debug] ***********************************************************
ok: [server1] =>
msg: First
ok: [server2] =>
msg: Second
ok: [server3] =>
msg: Third
The details about the plugin are available from the command-line
shell> ansible-doc -t lookup vars
Related
I'm wrapping my head around something that I'm probably overcomplicating.
I need to check if any of my hosts has ansible_virtualization_type == "openvz"
If this is the case, ALL hosts should execute a specific task.
I'm now trying to set a fact (virt_list) containing a list of hosts with their virtualization_type on localhost:
- name: Set fuse on virtualization OpenVZ
set_fact:
virt_list:
host: "{{item}}"
type: "openvz"
when: hostvars[item].ansible_virtualization_type == "openvz"
with_items: "{{ groups['all'] }}"
delegate_to: localhost
delegate_facts: true
but this doesn't work (both hosts in this play are on openvz):
TASK [roles/testvirt : debug vars ansible_virtualization_type ] ****************************
ok: [host1] => {
"ansible_virtualization_type": "openvz"
}
ok: [host2] => {
"ansible_virtualization_type": "openvz"
}
TASK [roles/testvirt : debug vars virt_list ] **********************************************
ok: [host1] => {
"msg": [
{
"host": "host1",
"type": "openvz"
}
]
}
ok: [host2] => {
"msg": [
{
"host": "host2",
"type": "openvz"
}
]
}
There should be a simpler way, maybe using jinjia2 to combine the lists directly.
Anyone has advices?
Q: "If any of my hosts has ansible_virtualization_type == "openvz" ALL hosts should execute a specific task."
A: For example, given the inventory for testing
shell> cat hosts
host1 ansible_virtualization_type=xen
host2 ansible_virtualization_type=xen
host3 ansible_virtualization_type=openvz
extract the variables
- debug:
msg: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_virtualization_type')|
list }}"
run_once: true
gives
msg:
- xen
- xen
- openvz
Test if the type is present
- debug:
msg: OK. ALL hosts should execute a specific task.
when: "'openvz' in vtypes"
vars:
vtypes: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_virtualization_type')|
list }}"
run_once: true
gives
msg: OK. ALL hosts should execute a specific task.
If this is working as expected proceed with all hosts
- set_fact:
all_hosts_execute_specific_task: true
when: "'openvz' in vtypes"
vars:
vtypes: "{{ ansible_play_hosts|
map('extract', hostvars, 'ansible_virtualization_type')|
list }}"
run_once: true
- debug:
msg: Execute a specific task.
when: all_hosts_execute_specific_task|default(false)
gives
TASK [set_fact] ************************************************************
ok: [host1]
TASK [debug] ***************************************************************
ok: [host1] =>
msg: Execute a specific task.
ok: [host3] =>
msg: Execute a specific task.
ok: [host2] =>
msg: Execute a specific task.
The task will be skipped if the type is missing.
The packages.yml file defined as:
---
- packages:
- name: Some description 1,
packageList:
- package1,
- package2,
- package3
- name: Some description 2,
package: package4
The first item contains a field packageList, the 2nd item does not have it, but only package field.
Playbook:
---
- hosts: all
become: yes
vars_files:
- packages.yml
How to iterate via all packageList items of the packages list only if this packageList is defined for an item.
Here is how I can iterate through items which contain package field:
- name: iteration
debug:
msg: "name: {{ item.package }}"
when: item.package is defined
with_items: "{{ packages }}"
As noted in my comment, if you simply want to install multiple yum/apt packages, it is usually more efficient to simply pass the list to the apt/yum/package module. As the docs state:
"When used with a loop: each package will be processed individually, it is much more efficient to pass the list directly to the name option."
However, if you really need a loop, here is a possible solution:
playbook.yml:
---
- hosts: all
gather_facts: false
vars_files:
- packages.yml
tasks:
- name: iteration over single items
debug:
msg: "name: {{ item.package }}"
when: item.package is defined
with_items: "{{ packages }}"
- name: iteration over lists
debug:
msg: "name: {{ item.packageList }}"
when: item.packageList is defined
with_items: "{{ packages }}"
- name: Do something with individual packages in the list
include_tasks: process_list.yml
vars:
mylist: "{{outer.packageList}}"
when: outer.packageList is defined
loop: "{{ packages }}"
loop_control:
loop_var: outer
process_list.yml:
- name: See what we have received
debug:
var: item
loop: "{{mylist}}"
result:
PLAY [all] *******************************************************************************************************************************
TASK [iteration over single items] *******************************************************************************************************
skipping: [localhost] => (item={u'packageList': [u'package1,', u'package2,', u'package3'], u'name': u'Some description 1,'})
ok: [localhost] => (item={u'name': u'Some description 2,', u'package': u'package4'}) => {
"msg": "name: package4"
}
TASK [iteration over lists] **************************************************************************************************************
ok: [localhost] => (item={u'packageList': [u'package1,', u'package2,', u'package3'], u'name': u'Some description 1,'}) => {
"msg": "name: [u'package1,', u'package2,', u'package3']"
}
skipping: [localhost] => (item={u'name': u'Some description 2,', u'package': u'package4'})
TASK [Do something with individual packages in the list] *********************************************************************************
skipping: [localhost] => (item={u'name': u'Some description 2,', u'package': u'package4'})
included: /root/tmp/process_list.yml for localhost
TASK [See what we have received] *********************************************************************************************************
ok: [localhost] => (item=package1,) => {
"ansible_loop_var": "item",
"item": "package1,"
}
ok: [localhost] => (item=package2,) => {
"ansible_loop_var": "item",
"item": "package2,"
}
ok: [localhost] => (item=package3) => {
"ansible_loop_var": "item",
"item": "package3"
}
PLAY RECAP *******************************************************************************************************************************
localhost : ok=4 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
The loop_control/loop_var part is used because otherwise both the outer and inner loop will use {{item}} as the loop variable - and this will cause... interesting results :)
You can define a default value with an empty list for the cases, where the packageList is undefined.
{{ item.packageList | default ([]) }}
If the packageList is undefined, the job iterates over an empty list, which means, it does not do anything.
You can use default, as #ceving mentioned:
---
- hosts: localhost
connection: local
vars_files:
- packages.yml
tasks:
- name: iteration
debug:
msg: "name: {{ item.packageList | default([item.package]) }}"
with_items: "{{ packages }}"
If packageList exists, it will use that, else package put into a single-element array to match the form of packageList:
PLAY [localhost] **********************************************************************************************
TASK [Gathering Facts] ****************************************************************************************
ok: [localhost]
TASK [iteration] **********************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "name: [u'package1,', u'package2,', u'package3']"
}
ok: [localhost] => (item=None) => {
"msg": "name: [u'package4']"
}
PLAY RECAP ****************************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0
I have a list of devices, each of which has a varying list of attributes that must be created on the devices, one at a time. Is there a way to build a nested set of loops to do this? The with_nested construct applies every attribute to every device; I need only a single attribute per device per call.
This playbook demonstrates what I have tried (Ansible 2.7.1, Python 2.7.1):
- name: Test nested list traversal
hosts: localhost
connection: local
vars:
Stuff:
- Name: DeviceA
Info: AInfo
Values:
- ValueA1
- ValueA2
- Name: DeviceB
Info: BInfo
Values:
- ValueB1
- ValueB2
- ValueB3
tasks:
- name: Loop test
debug:
msg: "{{ item.Name }},{{ item.Info }}, {{ item.Values }}"
with_items:
- "{{ Stuff }}"
What I get: (One call per device, containing all attributes at once)
ok: [localhost] => (item={u'Info': u'AInfo', u'Values': [u'ValueA1', u'ValueA2'], u'Name': u'DeviceA'}) =>
msg: DeviceA,AInfo, [u'ValueA1', u'ValueA2']
ok: [localhost] => (item={u'Info': u'BInfo', u'Values': [u'ValueB1', u'ValueB2', u'ValueB3'], u'Name': u'DeviceB'}) =>
msg: DeviceB,BInfo, [u'ValueB1', u'ValueB2', u'ValueB3']
What I want (each msg represents a separate operation to be performed on the device with just that one attribute)
msg: DeviceA, AInfo, ValueA1
msg: DeviceA, AInfo, ValueA2
msg: DeviceB, BInfo, ValueB1
msg: DeviceB, BInfo, ValueB2
msg: DeviceB, BInfo, ValueB3
You can get what you want using the subelements filter:
---
- hosts: localhost
gather_facts: false
vars:
Stuff:
- Name: DeviceA
Info: AInfo
Values:
- ValueA1
- ValueA2
- Name: DeviceB
Info: BInfo
Values:
- ValueB1
- ValueB2
- ValueB3
tasks:
- debug:
msg: "{{ item.0.Name }}, {{ item.0.Info }}, {{ item.1 }}"
loop: "{{ Stuff|subelements('Values') }}"
loop_control:
label: "{{ item.0.Name }}"
Running the above playbook gets you:
PLAY [localhost] ******************************************************************************************************************************************************************************
TASK [debug] **********************************************************************************************************************************************************************************
ok: [localhost] => (item=DeviceA) => {
"msg": "DeviceA, AInfo, ValueA1"
}
ok: [localhost] => (item=DeviceA) => {
"msg": "DeviceA, AInfo, ValueA2"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB1"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB2"
}
ok: [localhost] => (item=DeviceB) => {
"msg": "DeviceB, BInfo, ValueB3"
}
PLAY RECAP ************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0
I have constructed an ansible variable using two other defined ansible variables. The constructed variable is defined in the vars/main.yml and I want to access the defined value in vars/main.yml.
vars/main.yml
---
var1_var2: "some value"
Now, I construct the variable
---
- name: Construct and get the value
hosts: localhost
tasks:
- include_vars: "vars/main.yml"
- set_fact:
variable1: "var1"
variable2: "var2"
- set_fact:
final_variable: "{{ variable1 }}_{{ variable2 }}"
- set_fact:
ultimate_variable: "{{ final_variable }}"
If I run the playbook with -vvv flag, I can see that ultimate_variable sets to var1_var2 while I want to get the value defined in the vars/main.yml
i.e., some value
TASK [set_fact]
ok: [localhost] => {
"ansible_facts": {
"variable1": "var1",
"variable2": "var2"
},
"changed": false,
"failed": false
}
TASK [set_fact] task path: /home/ubuntu/test.yml:78
ok: [localhost] => {
"ansible_facts": {
"final_variable": "var1_var2"
},
"changed": false,
"failed": false
}
TASK [set_fact]
ok: [localhost] => {
"ansible_facts": {
"ultimate_variable": "var1_var2"
},
"changed": false,
"failed": false
}
updated answer:
use the lookup plugin to do the double replacement:
ultimate_variable: "{{ lookup('vars', '{{final_variable}}') }}"
playbook:
- include_vars: "vars/main.yml"
- set_fact:
variable1: "var1"
variable2: "var2"
- set_fact:
final_variable: "{{ variable1 }}_{{ variable2 }}"
- set_fact:
ultimate_variable: "{{ lookup('vars', '{{final_variable}}') }}"
- debug:
var: ultimate_variable
output:
PLAY [localhost] ****************************************************************************************************************************************************************************************************
TASK [include_vars] *************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [set_fact] *****************************************************************************************************************************************************************************************************
ok: [localhost]
TASK [debug] ********************************************************************************************************************************************************************************************************
ok: [localhost] => {
"ultimate_variable": "some value"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************
localhost : ok=5 changed=0 unreachable=0 failed=0
hope it helps.
You could also skip the intermediary variable definition:
- include_vars: "vars/main.yml"
- set_fact:
variable1: "var1"
variable2: "var2"
- set_fact:
ultimate_variable: "{{ lookup('vars', variable1 ~ '_' ~ variable2) }}"
- debug:
var: ultimate_variable
Explanation
Assuming I have a dictionary mydict set to { "key1": "value1" }:
The result of dictsort filter (mydict|dictsort) in Ansible seems to be a list containing another list:
[
[
"key1",
"value1"
]
]
However, when accessing the first element of this list directly in Jinja2 template (mydict|dictsort)[0], it renders to a strangely looking:
(u'key1', u'value1')
Then, if I set a fact with the value of (mydict|dictsort), it behaves like a regular list - accessing the first element with [0] results in:
[
"key1",
"value1"
]
Accessing its [0] element returns key1.
But if I set a fact with the value of (mydict|dictsort)[0], it behaves like a string - accessing [0] element returns the first character, i.e. (.
On the other hand, if I access subelements directly, for example (mydict|dictsort)[0][0], it behaves like a list, i.e. returns key1.
Questions
What is (u'key1', u'value1')? What kind of object does dictsort produce?
How to access the dictsort results in a consistent, reliable way?
Full playbook:
---
- hosts: localhost
gather_facts: no
connection: local
vars:
mydict:
key1: value1
tasks:
- name: show dict
debug:
msg: "{{ mydict }}"
- name: show mydict|dictsort
debug:
msg: "{{ mydict|dictsort }}"
- set_fact:
mydict_dictsorted: "{{ mydict|dictsort }}"
- name: show (mydict|dictsort)[0]
debug:
msg: "{{ (mydict|dictsort)[0] }}"
- name: show mydict_dictsorted[0]
debug:
msg: "{{ mydict_dictsorted[0] }}"
- name: show (mydict|dictsort|list)[0]
debug:
msg: "{{ (mydict|dictsort|list)[0] }}"
- name: show (mydict_dictsorted|list)[0]
debug:
msg: "{{ (mydict_dictsorted|list)[0] }}"
- set_fact:
mydict_dictsorted_element: "{{ (mydict|dictsort)[0] }}"
- name: mydict_dictsorted_element
debug:
msg: "{{ mydict_dictsorted_element }}"
- name: mydict_dictsorted_element[0]
debug:
msg: "{{ mydict_dictsorted_element[0] }}"
- name: (mydict|dictsort)[0][0]
debug:
msg: "{{ (mydict|dictsort)[0][0] }}"
Full transcript:
PLAY [localhost] ********************************************************************************************
TASK [show dict] ********************************************************************************************
ok: [localhost] => {
"msg": {
"key1": "value1"
}
}
TASK [show mydict|dictsort] *********************************************************************************
ok: [localhost] => {
"msg": [
[
"key1",
"value1"
]
]
}
TASK [set_fact] *********************************************************************************************
ok: [localhost]
TASK [show (mydict|dictsort)[0]] ****************************************************************************
ok: [localhost] => {
"msg": "(u'key1', u'value1')"
}
TASK [show mydict_dictsorted[0]] ****************************************************************************
ok: [localhost] => {
"msg": [
"key1",
"value1"
]
}
TASK [show (mydict|dictsort|list)[0]] ***********************************************************************
ok: [localhost] => {
"msg": "(u'key1', u'value1')"
}
TASK [show (mydict_dictsorted|list)[0]] *********************************************************************
ok: [localhost] => {
"msg": [
"key1",
"value1"
]
}
TASK [set_fact] *********************************************************************************************
ok: [localhost]
TASK [mydict_dictsorted_element] ****************************************************************************
ok: [localhost] => {
"msg": "(u'key1', u'value1')"
}
TASK [mydict_dictsorted_element[0]] *************************************************************************
ok: [localhost] => {
"msg": "("
}
TASK [(mydict|dictsort)[0][0]] ******************************************************************************
ok: [localhost] => {
"msg": "key1"
I checked the values with copy/content and they are the same as debug's (except indentation), so posting debug results for clarity.
dictsort produces a list of tuples. It uses dict.items() under the hood.
So when you access it as (mydict|dictsort)[0], you access Python's tuple.
Whereas if you access it after it is templated, you get generic list, because JSON doesn't make difference between tuples and lists, it has only lists.
Update: how to test – insert print into _dump_results here, like this:
print("Unaltered: {}".format(abridged_result))
return json.dumps(abridged_result, indent=indent, ensure_ascii=False, sort_keys=sort_keys)
And see this as the output:
TASK [show mydict|dictsort] ***************************
Unaltered: {'msg': [(u'key1', u'value1')]}
ok: [localhost] => {
"msg": [
[
"key1",
"value1"
]
]
}
Update2: why list of tuples becomes list of lists, but single tuple becomes string repr?
This is because of the fact that Jinja2 expression inside {{...}} can produce only string as its output, and there's some Ansible template magic done to try to type-cast it back to some complex type. But this magic only works with strings that looks like dicts or lists and not tuples. So if you have dict with tuples inside or list of tuples, you'll get it evaluated, but if you have a single tuple, it will remain a string. Here's demo of this:
- name: results in a string
debug:
msg: "{{ test_str }}"
vars:
test_str: "(u'a', u'b')"
- name: results in a list of tuples/lists
debug:
msg: "{{ test_str }}"
vars:
test_str: "[(u'a', u'b')]"
Output:
TASK [results in a string] ******************************************
ok: [localhost] => {
"msg": "(u'a', u'b')"
}
TASK [results in a list of tuples/lists] ****************************
ok: [localhost] => {
"msg": [
[
"a",
"b"
]
]
}