How to traverse an ansible list with a variable index? - ansible

There is a list as:
list:
- zero
- one
- two
For which:
list.0 -> zero
list.1 -> one
list.2 -> two
Is there a way where we could use a variable instead of using 0,1,2, etc?
Something like we do in any high-level-programming language:
(In a loop from var=0 to var=2){
print(list.var)
}

Use range. For example
- hosts: localhost
vars:
start: 0
stop: 3
list:
- zero
- one
- two
tasks:
- debug:
msg: "{{ list[item] }}"
loop: "{{ range(start, stop) }}"
gives
ok: [localhost] => (item=0) =>
msg: zero
ok: [localhost] => (item=1) =>
msg: one
ok: [localhost] => (item=2) =>
msg: two

Related

Assign values to variables using a loop (set_fact or other?)

Using an Ansible task, I'm trying to create variables and associated values from a returned loop object.
Ref: https://docs.ansible.com/ansible/latest/user_guide/playbooks_loops.html
- name: Get values
someModule:
input_from_loop: "{{ item }}"
loop:
- "foo"
- "bar"
register: get_result
Gives
get_result:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
item: foo
alpha:
beta:
content: someFooValue
- ansible_loop_var: item
item: bar
alpha:
beta:
content: someBarValue
With this get_result object, I'm trying to create variables and associated values such that:
Pseudocode:
- name: Assign values
set_fact:
"{{ item.item }}":"{{ item.alpha.beta.content }}"
loop: get_result.results
To result in:
foo:someFooValue
bar:someBarValue
I can't seem to get around the error
Implicit map keys need to be followed by map values"
I do not want to create a new object, just variables with values for later use (in existing tasks).
I've tried a few approaches to no avail.
[Or can it happen on each iteration of the initial loop calling the module?]
Now that I am rereading your your requirements, I am wondering if you are not looking for variables at the top level of your host that would be named
foo and contain someFooValue
bar and contain someBarValue
If this is the case, then most of that is above does still apply, the registering of the fact only change:
- set_fact: { "{{ item.0 }}": "{{ item.1 }}" }
loop: "{{
get_result.results | map(attribute='item') | zip(
get_result.results | map(attribute='alpha.beta.content')
)
}}"
Which gives you the two expected variables with the corresponding values.
ok: [localhost] =>
foo: someFooValue
ok: [localhost] =>
bar: someBarValue
What you can do in those kind of cases is to break down the dictionary in multiple lists, all containing one of the field you are interested into, with the map filter, then reconstruct a list of list with the help of the zip filter. And finally, join everything together.
So, with a task like:
- set_fact:
formatted_fact: "{{
get_result.results | map(attribute='item') | zip(
get_result.results | map(attribute='alpha.beta.content')
) | map('join', ':') | join('\n')
}}"
You get your expected output:
formatted_fact: |-
foo:someFooValue
bar:someBarValue
Given those couple of tasks, we have the two possibilities:
- set_fact:
formatted_fact: "{{
get_result.results | map(attribute='item') | zip(
get_result.results | map(attribute='alpha.beta.content')
) | map('join', ':') | join('\n')
}}"
vars:
get_result:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
item: foo
alpha:
beta:
content: someFooValue
- ansible_loop_var: item
item: bar
alpha:
beta:
content: someBarValue
- debug:
var: formatted_fact
- set_fact: { "{{ item.0 }}": "{{ item.1 }}" }
loop: "{{
get_result.results | map(attribute='item') | zip(
get_result.results | map(attribute='alpha.beta.content')
)
}}"
vars:
get_result:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
item: foo
alpha:
beta:
content: someFooValue
- ansible_loop_var: item
item: bar
alpha:
beta:
content: someBarValue
- debug:
var: foo
- debug:
var: bar
They would yield:
TASK [set_fact] **************************************************************
ok: [localhost]
TASK [debug] *****************************************************************
ok: [localhost] =>
formatted_fact: |-
foo:someFooValue
bar:someBarValue
TASK [set_fact] **************************************************************
ok: [localhost] => (item=['foo', 'someFooValue'])
ok: [localhost] => (item=['bar', 'someBarValue'])
TASK [debug] *****************************************************************
ok: [localhost] =>
foo: someFooValue
TASK [debug] *****************************************************************
ok: [localhost] =>
bar: someBarValue
For example
_query: '[].{key: item, value: alpha.beta.content}'
result: "{{ get_result.results|json_query(_query)|items2dict }}"
gives the expected result
result:
bar: someBarValue
foo: someFooValue
Example of a complete playbook (for testing)
- hosts: localhost
vars:
get_result:
changed: false
msg: All items completed
results:
- ansible_loop_var: item
item: foo
alpha:
beta:
content: someFooValue
- ansible_loop_var: item
item: bar
alpha:
beta:
content: someBarValue
_query: '[].{key: item, value: alpha.beta.content}'
result: "{{ get_result.results|json_query(_query)|items2dict }}"
tasks:
- debug:
var: result
Regarding the question in the headline
How to assign values to variables using a loop (set_fact)?
and the mentioned "pseudocode" I like to note that such approach might work, as the following test
---
- hosts: localhost
become: false
gather_facts: false
vars:
varKey: "TEST"
varVal: "testValue"
tasks:
- name: Create variable dynamic
set_fact:
"{{ varKey }}_{{ item }}": "{{ varVal }}_{{ item }}"
loop: [1, 2, 3]
- name: Show variable
debug:
var: TEST_{{ item }}
loop: ["1", "2", "3"]
results into an output of
TASK [Create variable dynamic] *********
ok: [localhost] => (item=1)
ok: [localhost] => (item=2)
ok: [localhost] => (item=3)
TASK [Show variable] *********
ok: [localhost] => (item=1) =>
TEST_1: testValue_1
ansible_loop_var: item
item: '1'
ok: [localhost] => (item=2) =>
TEST_2: testValue_2
ansible_loop_var: item
item: '2'
ok: [localhost] => (item=3) =>
TEST_3: testValue_3
ansible_loop_var: item
item: '3'
and also
- name: Create variable dynamic
set_fact:
"{{ item }}": "{{ item }}"
loop: [A1, A2, A3]
- name: Show variable
debug:
var: "{{ item }}"
loop: [A1, A2, A3]
into
TASK [Create variable dynamic] **********
ok: [localhost] => (item=A1)
ok: [localhost] => (item=A2)
ok: [localhost] => (item=A3)
TASK [Show variable] **********
ok: [localhost] => (item=A1) =>
A1: A1
ansible_loop_var: item
item: A1
ok: [localhost] => (item=A2) =>
A2: A2
ansible_loop_var: item
item: A2
ok: [localhost] => (item=A3) =>
A3: A3
ansible_loop_var: item
item: A3

Ansible get all hosts in subgroup

I have the following hosts structure in the inventory:
all:
children:
sc:
hosts:
sc-finder01a.com:
sc-finder01b.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
ms:
hosts:
ms-finder01a.com:
ms-finder01a.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
I'm running on all hosts, where for each host I'd like to access the other one in the subgroup (sc/ms) in order to check a condition before executing it on the current host, but I'm struggling to find the syntax. Also, I have to prevent Ansible from executing the command on two hosts in the same subgroup in parallel.
Ideas?
Have a look at ansible's special variables. For controlling execution, check playbook strategies.
You can access the current host's groups with the group_names variable. If you want to be in control of host execution order, delegate_to and run_once may help, where you assign a run_once controlling server and delegate tasks looping over the group members list.
Q: "Access the other hosts in the subgroup (sc/ms) in order to check a condition before executing it on the current host."
A: Iterate the list of the hosts in the subgroup(s), e.g.
- debug:
msg: "Check a condition on {{ item }}"
loop: "{{ group_names|map('extract', groups)|flatten|unique|
difference([inventory_hostname]) }}"
gives
TASK [debug] *******************************************************
ok: [sc-finder01b.com] => (item=sc-finder01a.com) =>
msg: Check a condition on sc-finder01a.com
ok: [sc-finder01a.com] => (item=sc-finder01b.com) =>
msg: Check a condition on sc-finder01b.com
ok: [ms-finder01b.com] => (item=ms-finder01a.com) =>
msg: Check a condition on ms-finder01a.com
ok: [ms-finder01a.com] => (item=ms-finder01b.com) =>
msg: Check a condition on ms-finder01b.com
Debug
The inventory
shell> cat hosts
all:
children:
sc:
hosts:
sc-finder01a.com:
sc-finder01b.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
ms:
hosts:
ms-finder01a.com:
ms-finder01b.com:
vars:
default_port: 5679
version: 0.4.2-RELEASE
Display all hosts
- debug:
var: groups
run_once: true
gives
TASK [debug] ***************************************************************
ok: [sc-finder01a.com] =>
groups:
all:
- sc-finder01a.com
- sc-finder01b.com
- ms-finder01a.com
- ms-finder01b.com
ms:
- ms-finder01a.com
- ms-finder01b.com
sc:
- sc-finder01a.com
- sc-finder01b.com
ungrouped: []
Multiple subgroups
The code works also if a host is a member of multiple subgroups, e.g.
shell> cat hosts
all:
children:
sc:
hosts:
sc-finder01a.com:
ms:
hosts:
ms-finder01a.com:
foo:
hosts:
foo-bar.com:
sc-finder01a.com:
ms-finder01a.com:
gives
TASK [debug] ******************************************************
ok: [sc-finder01a.com] => (item=foo-bar.com) =>
msg: Check a condition on foo-bar.com
ok: [sc-finder01a.com] => (item=ms-finder01a.com) =>
msg: Check a condition on ms-finder01a.com
ok: [foo-bar.com] => (item=sc-finder01a.com) =>
msg: Check a condition on sc-finder01a.com
ok: [ms-finder01a.com] => (item=foo-bar.com) =>
msg: Check a condition on foo-bar.com
ok: [foo-bar.com] => (item=ms-finder01a.com) =>
msg: Check a condition on ms-finder01a.com
ok: [ms-finder01a.com] => (item=sc-finder01a.com) =>
msg: Check a condition on sc-finder01a.com

Ansible variables

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

How to conditionally replace text in a list with regex_replace?

I have a variable in my playbook that's derived from a list. In some instances this variable contains a "-" to separate two values. For example,
Numbers:
- 2211
- 2211-2212
When this is the case I would like to replace the "-" with a "_" based on a conditional: If the number is 4 characters long, do this. Else, replace the "-" with a " _ " and do that.
I've already tried to fiddle around with jinja2 ans regex in my playbooks but so far no luck. Here's what I tried,
number: {% if length(item) == 4 %} {{ item | regex_replace("^(.*)$", "Number_\1") | string }} {% else %} {{ item | regex_replace("^(.*)$", "Number_\1") |replace("-", "_") | string }}
The result that I would like to have,
Number is four characters long:
number: Number_2211
Number is more then 4 characters long:
number: Number_2211_2212
Some of the Error messages I have received are,
ERROR! Syntax Error while loading YAML.
did not find expected key
ERROR! Syntax Error while loading YAML.
found character that cannot start any token
Is there a way to achieve this within the Ansible playbook?
Thanks in advance!
It's not really clear how you're trying to use this data. Ansible isn't great at modifying complex data structures in place, but it has lots of way of transforming data when you access it. So for example, this playbook:
---
- hosts: localhost
gather_facts: false
vars:
numbers:
- "2211"
- "2211-2212"
tasks:
- debug:
msg: "number: {{ item.replace('-', '_') }}"
loop: "{{ numbers }}"
Will output:
TASK [debug] **********************************************************************************
ok: [localhost] => (item=2211) => {
"msg": "number: 2211"
}
ok: [localhost] => (item=2211-2212) => {
"msg": "number: 2211_2212"
}
If you really need to make the transformation conditional on the length (and it's not clear that you do), you could do something like:
- debug:
msg: "{{ item.replace('-', '_') if item|length > 4 else item }}"
loop: "{{ numbers }}"
Update
I see you've selected the other answer. The solution presented here seems substantially simpler (there is no "incomprehensible sequence of filters, regex expressions, and equality checks"), and produces almost identical output:
TASK [debug] **********************************************************************************
ok: [localhost] => (item=445533) => {
"msg": "445533"
}
ok: [localhost] => (item=112234-538) => {
"msg": "112234_538"
}
ok: [localhost] => (item=11) => {
"msg": "11"
}
ok: [localhost] => (item=1111) => {
"msg": "1111"
}
ok: [localhost] => (item=1111-1111) => {
"msg": "1111_1111"
}
ok: [localhost] => (item=11-11) => {
"msg": "11_11"
}
It's not clear, given 11-11, whether you expect 11_11 or 11-11 as output. If you expect the former, this answer is more correct.
You can use an incomprehensible sequence of filters, regex expressions, and equality checks to do this.
#!/usr/bin/env ansible-playbook
- name: Lets munge some data
hosts: localhost
gather_facts: false
become: false
vars:
array:
- 445533
- 112234-538
- 11
- 1111
- 1111-1111
- 11-11
tasks:
- name: Replace hypens when starting with 4 numbers
debug:
msg: "{{ ((item | string)[0:4] | regex_search('[0-9]{4}') | string != 'None')
| ternary((item | regex_replace('-', '_')), item) }}"
loop: "{{ array }}"
PLAY [Lets munge some data] *****************************************************************************************************************************************************************************************************
TASK [Replace hypens when starting with 4 numbers] ******************************************************************************************************************************************************************************
ok: [localhost] => (item=445533) => {
"msg": "445533"
}
ok: [localhost] => (item=112234-538) => {
"msg": "112234_538"
}
ok: [localhost] => (item=11) => {
"msg": "11"
}
ok: [localhost] => (item=1111) => {
"msg": "1111"
}
ok: [localhost] => (item=1111-1111) => {
"msg": "1111_1111"
}
ok: [localhost] => (item=11-11) => {
"msg": "11-11"
}
PLAY RECAP **********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0

How can I traverse nested lists in Ansible?

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

Resources