I want to add some lines to a file (they are ntp servers in /etc/ntp.conf). Unfortunately, I can't use a template for various reasons.
so I have a variable like so:
vars:
- dns:
- "{ 'region': 'syd', 'dns_servers': ['1.2.3.4', '2.3.4.5', '3.4.5.6' ] }"
- "{ 'region': 'lon', 'dns_servers': ['2.2.2.2', '3.3.3.3', '4.4.4.4' ] }"
- "{ 'region': 'ny', 'dns_servers': ['5.5.5.5', '6.6.6.6', '7.7.7.7' ] }"
- ntp:
- { 'region': 'syd', 'ntp_servers': ['syd1.pool.ntp.org','syd2.pool.ntp.org','syd2.pool.ntp.org'] }
- { 'region': 'lon', 'ntp_servers': ['lon1.pool.ntp.org','lon2.pool.ntp.org','lon2.pool.ntp.org'] }
- { 'region': 'ny', 'ntp_servers': ['ntp1.pool.ntp.org','ntp2.pool.ntp.org','ntp2.pool.ntp.org'] }
So there's 2 lists there; dns and ntp. Each list contains a dictionary of region -> server mappings.
I want to iterate over the (in this case the ntp list) list to add a line for each ntp server in turn. I've discovered the loop_control parameter to a loop construct, which allows me to set an offset into the ntp_servers list.
Here's one attempt I'm making to loop over the elements of the list in the dictionary:
- name: Replace ntp servers in /etc/ntp.conf
debug:
msg: "{{ item[loop_count] }}"
loop: "{{ ntp['region']['ntp_servers'] }}"
loop_control:
index_var: loop_count
when: item.region == region
(As an aside, how do you feel about using 'i' as the loop counter variable? )
I know this would be much easier with a template, and that lineinfile is an antipattern, but I am constrained by issues outside of my control.
The problem I have is that I want to loop over the items of the ntp_servers array, but my loop is looping over the ntp dictionary. I've tried various combinations, but suspect that I'm using the wrong data structure - can anyone advise, please?
Thanks
i see an issue with the vars structure. you should remove the hyphen from the ntp and dns variable declaration:
vars:
ntp:
.....
dns:
.....
Also, the double quotes in the dns list elements, are making the elements considered as strings, ntp look ok.
to get all 9 ntp servers of the ntp variable in a list, so you can process in a loop, you can use the expression (feel free to remove the sum(start=[]) and final list conversion, and then add them back to see why i used them):
"{{ ntp | map(attribute='ntp_servers') | list | sum(start=[]) | list }}"
example playbook:
- hosts: localhost
gather_facts: false
vars:
ntp:
- { 'region': 'syd', 'ntp_servers': ['syd1.pool.ntp.org','syd2.pool.ntp.org','syd2.pool.ntp.org'] }
- { 'region': 'lon', 'ntp_servers': ['lon1.pool.ntp.org','lon2.pool.ntp.org','lon2.pool.ntp.org'] }
- { 'region': 'ny', 'ntp_servers': ['ntp1.pool.ntp.org','ntp2.pool.ntp.org','ntp2.pool.ntp.org'] }
tasks:
- name: print
debug:
msg: "{{ item }}"
with_items:
- "{{ ntp | map(attribute='ntp_servers') | list | sum(start=[]) | list }}"
output:
TASK [print] ********************************************************************************************************************************************************************************************************
ok: [localhost] => (item=None) => {
"msg": "syd1.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "syd2.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "syd2.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "lon1.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "lon2.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "lon2.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "ntp1.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "ntp2.pool.ntp.org"
}
ok: [localhost] => (item=None) => {
"msg": "ntp2.pool.ntp.org"
}
PLAY RECAP
Related
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 a data structure that looks like this:
all_vms:
clusterA:
group1:
- vm-01
- vm-02
group2:
- vm-03
- vm-04
clusterB:
group1:
- vm-05
- vm-06
The key names are not known beforehand. There may be shared "group" key names between clusters.
I want to loop through that data structure and run a task with each of the arrays, but I need the names of the keys they're contained within. The looping task would look something like:
- task:
vms: "{{ item.value }}"
group: "{{ item.key }}"
cluster: "{{ item.parentkey }}"
loop: "{{ all_vms | ??? }}"
And that would unroll to:
- task:
vms:
- vm-01
- vm-02
group: group1
cluster: clusterA
- task:
vms:
- vm-03
- vm-04
group: group2
cluster: clusterA
- task:
vms:
- vm-05
- vm-06
group: group3
cluster: clusterB
I cannot change the main cluster/group structure, but I can change the structure of the elements that are currently arrays. I have considered just duplicating the keys as values, like this:
all_vms:
clusterA:
group1:
cluster: "clusterA"
group: "group1"
vms:
- vm-01
- vm-02
group2:
cluster: "clusterA"
group: "group2"
vms:
- vm-03
- vm-04
clusterB:
group1:
cluster: "clusterB"
group: "group1"
vms:
- vm-05
- vm-06
I would rather not do that, because it's terrible, but I can. But I can't even figure out a way to pop each of those things out into an array. (Edit: Actually, I think figured that out right after posting: all_vms | json_query('*.* | []'). I guess I can go with that if there's not a way to use the tidier data structure.)
Or if I could just use a #!#$% nested loop, if ansible would let me:
- block:
- task:
vms: "{{ item.value }}"
group: "{{ item.key }}"
cluster: "{{ cluster.key }}"
loop: "{{ cluster.value | dict2items }}"
loop: "{{ all_vms | dict2items }}"
loop_control:
loop_var: cluster
(Yes, I could do this with include_tasks, but having to have a separate file for a nested loop is just ridiculous.)
Any ideas how to iterate over this data structure without having to resort to a separate file just to do nested looping?
And here is the solution using several combinations of filters directly in Ansible / Jinja.
It combines the first level keys and values with a zip filter, in order to have a know subelements name — 1 — on which we can then use a subelements.
The second level key / value pair is accessible thanks to a dict2items mapped on the first level values.
The task ends up being
- set_fact:
tasks: "{{ tasks | default([]) + [_task] }}"
loop: >-
{{
all_vms.keys()
| zip(all_vms.values() | map('dict2items'))
| subelements([1])
}}
loop_control:
label: "{{ item.0.0 }} — {{ item.1.key }}"
vars:
_task:
task:
vms: "{{ item.1.value }}"
group: "{{ item.1.key }}"
cluster: "{{ item.0.0 }}"
Given the playbook:
- hosts: localhost
gather_facts: no
tasks:
- set_fact:
tasks: "{{ tasks | default([]) + [_task] }}"
loop: >-
{{
all_vms.keys()
| zip(all_vms.values() | map('dict2items'))
| subelements([1])
}}
loop_control:
label: "{{ item.0.0 }} — {{ item.1.key }}"
vars:
_task:
task:
vms: "{{ item.1.value }}"
group: "{{ item.1.key }}"
cluster: "{{ item.0.0 }}"
all_vms:
clusterA:
group1:
- vm-01
- vm-02
group2:
- vm-03
- vm-04
clusterB:
group1:
- vm-05
- vm-06
- debug:
var: tasks
This yields:
TASK [set_fact] ***************************************************************
ok: [localhost] => (item=clusterA — group1)
ok: [localhost] => (item=clusterA — group2)
ok: [localhost] => (item=clusterB — group1)
TASK [debug] ******************************************************************
ok: [localhost] =>
tasks:
- task:
cluster: clusterA
group: group1
vms:
- vm-01
- vm-02
- task:
cluster: clusterA
group: group2
vms:
- vm-03
- vm-04
- task:
cluster: clusterB
group: group1
vms:
- vm-05
- vm-06
Although this is definitely possible using several combinations of filters directly in Ansible/jinja2, my 2 cent: save you nerves and time using a custom filter.
Demo below is for a a filter in the dedicated filters_plugin folder adjacent to your playbook. See the roles and collections documentations for better ways to distribute your filter if need be. Note that this was written real quick to put you on track and although it is fully functional with your above data example, you will need to add some data and errors checks if you intend to use this for a larger audience.
filters_plugins/my_cluster_filters.py
def all_vms_list(vm_dict):
"""Return a list of dicts for all vms with their cluster an group information"""
vms_list = []
for cluster, groups in vm_dict.items():
for group_name, hosts in groups.items():
for host in hosts:
current_vm = {
'cluster': cluster,
'group': group_name,
'host': host
}
vms_list.append(current_vm)
return vms_list
class FilterModule(object):
"""my cluster data filters."""
def filters(self):
"""Return the filter list."""
return {
'all_vms_list': all_vms_list
}
Then the test playbook.yml:
---
- hosts: localhost
gather_facts: false
vars:
all_vms:
clusterA:
group1:
- vm-01
- vm-02
group2:
- vm-03
- vm-04
clusterB:
group1:
- vm-05
- vm-06
tasks:
- debug:
msg: "Host {{ item.host }} belongs to group {{ item.group }} inside cluster {{ item.cluster }}"
loop: "{{ all_vms | all_vms_list }}"
gives:
PLAY [localhost] **************************************************************************************************************************************************************************************************************
TASK [debug] ******************************************************************************************************************************************************************************************************************
ok: [localhost] => (item={'cluster': 'clusterA', 'group': 'group1', 'host': 'vm-01'}) => {
"msg": "Host vm-01 belongs to group group1 inside cluster clusterA"
}
ok: [localhost] => (item={'cluster': 'clusterA', 'group': 'group1', 'host': 'vm-02'}) => {
"msg": "Host vm-02 belongs to group group1 inside cluster clusterA"
}
ok: [localhost] => (item={'cluster': 'clusterA', 'group': 'group2', 'host': 'vm-03'}) => {
"msg": "Host vm-03 belongs to group group2 inside cluster clusterA"
}
ok: [localhost] => (item={'cluster': 'clusterA', 'group': 'group2', 'host': 'vm-04'}) => {
"msg": "Host vm-04 belongs to group group2 inside cluster clusterA"
}
ok: [localhost] => (item={'cluster': 'clusterB', 'group': 'group1', 'host': 'vm-05'}) => {
"msg": "Host vm-05 belongs to group group1 inside cluster clusterB"
}
ok: [localhost] => (item={'cluster': 'clusterB', 'group': 'group1', 'host': 'vm-06'}) => {
"msg": "Host vm-06 belongs to group group1 inside cluster clusterB"
}
PLAY RECAP ********************************************************************************************************************************************************************************************************************
localhost : ok=1 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
I have a scenario where I need to print only 0th array and 1st array of 'N'of array in ansible.
Could someone help me to achieve this
Sample code:
Array;
ID = [1,2,3,4]
Ansible Code:
- hosts: localhost
gather_facts: no
tasks:
- name: Print the first 2 values in the array
debug:
msg: "{{ item }}"
with_items: "{{ ID }}"
Expected Output:
PLAY [localhost] ***********************************************************************************************************************************************************************************************
TASK [Print the first 2 values in the array] *******************************************************************************************************************************************************************
ok: [localhost] => (item=1) => {
"msg": "1"
}
ok: [localhost] => (item=2) => {
"msg": "2"
}
Actual Output:
PLAY [localhost] ***********************************************************************************************************************************************************************************************
TASK [Print the first 2 values in the array] *******************************************************************************************************************************************************************
ok: [localhost] => (item=1) => {
"msg": "1"
}
ok: [localhost] => (item=2) => {
"msg": "2"
}
ok: [localhost] => (item=3) => {
"msg": "3"
}
ok: [localhost] => (item=4) => {
"msg": "4"
}
You could do this with Playbook Loops and with_sequence
- name: Show sequence
debug:
msg: "{{ item }}"
with_sequence:
- "0-1"
Thanks to
Ansible with_items in range
Loop from 0 to 100 with Ansible
To get the value of an array you would use ID[item]. You may have a look into the following loop example and how it works.
---
- hosts: localhost
become: false
gather_facts: false
vars:
ID: [A, B, C, D]
tasks:
- name: Show entry
debug:
msg:
- "{{ item }} {{ ID[item | int] }}"
with_sequence:
- "0-1"
Use slice notation, e.g.
- debug:
var: item
loop: "{{ ID[0:2] }}"
gives (abridged)
item: 1
item: 2
You can concatenate slices if you want to, e.g. to get the 1st, 2nd, and 4th item
- debug:
var: item
loop: "{{ ID[0:2] + ID[3:4] }}"
gives (abridged)
item: 1
item: 2
item: 4
I have a task wrote by someone else. It was aimed for a single use with a single variable. I would like to avoid rewriting it with a list instead of a simple variable.
Then I would like to set a list for each host in host_vars:
list:
- value_1
- ...
- value_n
and in a playbook, reuse the task for each value:
loop the list, and for each value:
reset the main variable used in task with the value_n
run the task
Is it possible?
That's so basic in shell but seems so complicated with Ansible.
OK -- I know you're new at this, but you should post code of what you have tried, and post the errors you're getting. Anyway, here's a simple loop example run on localhost (you may need to use a different host, if localhost is not in your inventory):
---
- hosts: localhost
connection: local
gather_facts: no
vars:
list:
- value_1
- value_2
- value_n
tasks:
- name: Loop with default variable (item)
debug:
msg: "item is {{ item }}"
loop: "{{ list }}"
- name: Loop with named variable (main_variable)
debug:
msg: "main_variable is {{ main_variable }}"
loop: "{{ list }}"
loop_control:
loop_var: main_variable
And here are the results from ansible-playbook ./simple_loop.yml:
$ ansible-playbook ./simple_loop.yml
PLAY [localhost] *******************************************************************************
TASK [Loop with default variable (item)] *******************************************************
ok: [localhost] => (item=value_1) => {
"msg": "item is value_1"
}
ok: [localhost] => (item=value_2) => {
"msg": "item is value_2"
}
ok: [localhost] => (item=value_n) => {
"msg": "item is value_n"
}
TASK [Loop with named variable (main_variable)] ************************************************
ok: [localhost] => (item=value_1) => {
"msg": "main_variable is value_1"
}
ok: [localhost] => (item=value_2) => {
"msg": "main_variable is value_2"
}
ok: [localhost] => (item=value_n) => {
"msg": "main_variable is value_n"
}
PLAY RECAP *************************************************************************************
localhost : ok=2 changed=0 unreachable=0 failed=0 skipped=0 rescued=0 ignored=0
Hi I have a file with list of filenames .I want to read each line and add a variable {{version}} to the end and pass it to another task which will download the artifact .
MY_File
cat file/branches.txt
mhr-
mtr-
tsr
I want to get each line add {{version}} to it
mhr-1.1-SNAPSHOT
mtr-1.1-SNAPSHOT
tsr-1.1-SNAPSHOT
I couldn't get the file names and append them. below is a failed attempt by me .
---
- name: get file names
shell: echo {{item}}{{version}}
register: result
with_lines: cat files/branches.txt
- include_tasks: 2main.yml
with_items: "{{result.results}}"
I think what you need a new variable which contains text like mhr-1.1-SNAPSHOT depending on the source file. You can use set_fact to achieve this.
Example:
vars:
file_versions: []
version: "1.1-SNAPSHOT"
tasks:
- set_fact:
file_versions: "{{ file_versions }} + [ '{{ item }}{{ version }}' ]"
with_lines: cat file/branches.txt
- debug:
msg: "{{ item }}"
with_items: "{{ file_versions }}"
- include_tasks: 2main.yml
with_items: "{{ file_versions }}"
Gives below debug output:
TASK [debug] ************************************************************************************************************************
ok: [localhost] => (item=mhr-1.1-SNAPSHOT) => {
"msg": "mhr-1.1-SNAPSHOT"
}
ok: [localhost] => (item=mtr-1.1-SNAPSHOT) => {
"msg": "mtr-1.1-SNAPSHOT"
}
ok: [localhost] => (item=tsr-1.1-SNAPSHOT) => {
"msg": "tsr-1.1-SNAPSHOT"
}
In a nutshell:
---
- name: append version to names found in a local text file line by line
hosts: localhost
gather_facts: false
vars:
# This is the version we want to append
version: 1.1-SNAPSHOT
# This is the file on local controller containing the names
my_file: /tmp/my_file.txt
# This var will contain a list of each name in file with appended version
file_version: "{{ lookup('file', my_file).splitlines() | map('regex_replace', '^(.*)$', '\\g<1>' + version) | list }}"
tasks:
- name: Show the result
debug:
var: file_version
- name: Loop on var with an include (the passed var is item in this case)
include_tasks: some_other_tasks_working_on_item.yml
loop: "{{ file_version }}"