Ansible loop with vars_prompt and with_sequence - ansible

I have a question, can we pass a range of inputs with vars_prompt.
Can we achieve with_sequence
example:
From the below playbook, i am able to pass multiple port numbers split by ",".
prompt: Please enter the port name: 0/1,0/2,0/3,0/4,0/5
But when I want to pass the input of ports in a range.
prompt: Please enter the port name: 0/1-0/5
Do we have option how to run, will that works with nested loop
- hosts: localhost
gather_facts: no
vars_prompt:
- name: port_name
prompt: Please enter the port name
private: no
tasks:
- add_host:
name: "{{port_name}}"
groups: dynamically_created_hosts
with_items: "{{port_name.split(',')}}"
- name: Change port speed
port_speed_change:
switch_ip: "{{ip_address}}"
user: "{{user}}"
password: "{{password}}"
checkmode: "{{ansible_check_mode}}"
name: "{{items}}"
speed: "{{port_speed}}"

I think that's not possible directly with a library, however, you can achieve your purpose in several ways, one is using lists and loops.
Please consider two elements in your input:
I1/S1-I2/S2
I=Interface
S=Subport
In order to achieve this without errors it is necesary to run 3 loops:
Add elements in a list from your I1/S1 to I1/S_Max
Add middle elements from I_middle/0 to I_middle/S_Max
Add last elements I2/0 to I2/S2
First of all you collect your port_name variable, it is really important that you respect the sinxtax of input string:
- name: port_name
prompt: Please enter port name (format X/Y-X/Z, e.g 0/1-0/5)
private: no
Create 1st loop:
####1st cycle, first port first sub_loop
#1st sub_loop, first entry subport to maximum subport
- set_fact: port_sub_loop={{port_sub_loop|default([]) | union([item]) }}
with_sequence: start="{{ S1 }}" end="{{ S_MAX }}"
- name: Add first elements (first interface, first sub_loop)
set_fact: ports="{{ ports|default([]) | union([item.0+'/'+item.1]) }}"
with_nested:
- "{{ I1 }}"
- "{{ port_sub_loop }}"
2nd block is tricky, because it has to escape in case I1 >= I2 -2, you cannot use when condition with loops, so I decided to use a block with a rescue:
####2nd cycle, middle elements, subports are from 0 to S_MAX... There is a rescue because you cannot add a condition "with_sequence", in case I1 >= I2 -2 it will continue with rescue
#Set port interfaces loop ignoring first and last port
- name: run middle loop
block:
- set_fact: port_interfaces_middle={{port_interfaces_middle|default([]) | union([item]) }}
with_sequence: start="{{ I1|int + 1 }}" end="{{ I2|int - 1 }}"
#Set complete port sub_interfaces loop from 0 to S_MAX
- set_fact: port_sub={{port_sub|default([]) | union([item]) }}
with_sequence: start="0" end="{{ S_MAX }}"
# - name: Add middle elements (next ports starting from subport 0)
- set_fact: ports="{{ ports|default([]) | union([item.0+'/'+item.1]) }}"
with_nested:
- "{{ port_interfaces_middle }}"
- "{{ port_sub }}"
rescue:
- debug: msg='Not executed middle block'
Last part add elements for last I2:
#Set last interface sub_loop, from 0 to S2
- set_fact: port_sub_last={{port_sub_last|default([]) | union([item]) }}
with_sequence: start="0" end="{{ S2 }}"
- name: Add last elements (from last_interface)
set_fact: ports="{{ ports|default([]) | union([item.0+'/'+item.1]) }}"
with_nested:
- "{{ I2 }}"
- "{{ port_sub_last }}"
FINAL SCRIPT
vars_prompt:
- name: port_name
prompt: Please enter port name (format X/Y-X/Z, e.g 0/1-0/5)
private: no
tasks:
#Get first interface
- set_fact: I1={{ port_name | regex_search("^\d+") | int }}
#Get second interface
- set_fact: I2={{ port_name | regex_search("\-\d+") | regex_replace('^\-', '') | int }}
#Get first sub_interface
- set_fact: S1={{ port_name | regex_search("\d+\-") | regex_replace('\-$', '') | int }}
#Get second sub_interface
- set_fact: S2={{ port_name | regex_search("\d+$") | int }}
#Set highest/lowest subinterface value
- set_fact: S_MAX="{{ S1 if (S1 >= S2) else S2 }}"
- set_fact: S_MIN="{{ S1 if (S1 <= S2) else S2 }}"
####1st cycle, first port first sub_loop
#1st sub_loop, first entry subport to maximum subport
- set_fact: port_sub_loop={{port_sub_loop|default([]) | union([item]) }}
with_sequence: start="{{ S1 }}" end="{{ S_MAX }}"
- name: Add first elements (first interface, first sub_loop)
set_fact: ports="{{ ports|default([]) | union([item.0+'/'+item.1]) }}"
with_nested:
- "{{ I1 }}"
- "{{ port_sub_loop }}"
####2nd cycle, middle elements, subports are from 0 to S_MAX... There is a rescue because you cannot add a condition "with_sequence", in case I1 >= I2 -2 it will continue with rescue
#Set port interfaces loop ignoring first and last port
- name: run middle loop
block:
- set_fact: port_interfaces_middle={{port_interfaces_middle|default([]) | union([item]) }}
with_sequence: start="{{ I1|int + 1 }}" end="{{ I2|int - 1 }}"
#Set complete port sub_interfaces loop from 0 to S_MAX
- set_fact: port_sub={{port_sub|default([]) | union([item]) }}
with_sequence: start="0" end="{{ S_MAX }}"
# - name: Add middle elements (next ports starting from subport 0)
- set_fact: ports="{{ ports|default([]) | union([item.0+'/'+item.1]) }}"
with_nested:
- "{{ port_interfaces_middle }}"
- "{{ port_sub }}"
rescue:
- debug: msg='Not executed middle block'
####3rd cycle, last port until last element
#Set last interface sub_loop, from 0 to S2
- set_fact: port_sub_last={{port_sub_last|default([]) | union([item]) }}
with_sequence: start="0" end="{{ S2 }}"
- name: Add last elements (from last_interface)
set_fact: ports="{{ ports|default([]) | union([item.0+'/'+item.1]) }}"
with_nested:
- "{{ I2 }}"
- "{{ port_sub_last }}"
- debug: msg="{{ ports }}"
I don't add port_speed_change cause it must be a local library, I don't know how you are using it.
Regards

Related

Ansible - loop over multiple items in stdout_lines

I am performing a grep with multiple items.
---
- hosts: my_host
gather_facts: false
vars:
my_list:
- whatever
- something
tasks:
- name: grep for item in search path
shell: "grep -rIL {{ item }} /tmp"
register: the_grep
loop: "{{ my_list }}"
- debug:
msg: "{{ item.stdout_lines }}"
loop: "{{ the_grep.results }}"
Depending on the result, multiple files could match.
msg:
- /tmp/something.conf
- /tmp/folder/file.txt
Q: How would I configure Ansible to loop over the items in stdout_lines?
The use case I'm solving is to delete .ini sections based on the item, but in this case, Ansible doesn't loop over the stdout_lines.
- name: remove stanza from ini file
ini_file:
path: "{{ item.stdout_lines }}"
section: "{{ item.item }}"
mode: '0600'
state: absent
loop: "{{ the_grep.results }}"
when: item.stdout_lines | length > 0
It seems that this doesn't work, but configuring item.stdout_lines[0] gives the partially expected result, since Ansible will use only the first item in that list. But ofc, not the 2nd and so on.
Perhaps there's a prettier answer, but solved it by using with_nested and creating a json_query:
- name: remove stanza from ini file
ini_file:
path: "{{ item.0 }}"
section: "{{ item.1.item }}"
mode: '0600'
state: absent
with_nested:
- "{{ the_grep | json_query('results[].stdout_lines[]') }}"
- "{{ the_grep.results }}"

Ansible How do i sort an array in descending order upon the element substring

Below is my array:
- set_fact:
diskout:
- 85_20.198.65.132
- 86_52.140.118.141
- 84_20.198.75.31
- 82_20.204.75.114
- 83_20.204.24.160
I wish to sort this in descending order upon just the first substring separated by _ while ignoring whatever is after the underscore.
Thus, my expected output is:
- 86_52.140.118.141
- 85_20.198.65.132
- 84_20.198.75.31
- 83_20.204.24.160
- 82_20.204.75.114
I tried the below but it did not give me the desired output:
- debug:
msg: "The automation will run on {{ item }}"
with_items: "{{ diskout | reverse | list }}"
Can you please suggest?
Create index, e.g.
- debug:
msg: "{{ _dict|dict2items|
sort(attribute='key', reverse=true)|
map(attribute='value')|
list }}"
vars:
_index: "{{ diskout|map('regex_replace', '^(.*)_(.*)$', '\\1')|list }}"
_dict: "{{ dict(_index|zip(diskout)) }}"
gives
msg:
- 86_52.140.118.141
- 85_20.198.65.132
- 84_20.198.75.31
- 83_20.204.24.160
- 82_20.204.75.114
The next option might be faster
- debug:
msg: "{{ _dict|sort(reverse=true)|map('extract', _dict)|list }}"
vars:
_index: "{{ diskout|map('regex_replace', '^(.*)_(.*)$', '\\1')|list }}"
_dict: "{{ dict(_index|zip(diskout)) }}"

Ansible nested loops and conditional together in the same task

---
- hosts: leaf-1, leaf-2
vars_files:
- /vars/all.yml
tasks:
- name: collect the vlan databse
raw: "show vlan brief"
register: vlan_db
- name: compare vlan_id(.*)Ports against the vlan_db
set_fact:
fact1: "{{ item.1.vlan_id }}"
fact2: "{{ item.1.sap.inventory_hostname | join(', ') }}"
fact3: "{{ fact1 }}(.*){{ fact2 }}"
failed_when: not vlan_db.stdout is regex(fact3)
when: inventory_hostname in item.1.sap
with_subelements:
- "{{ customers }}"
- services
I collect an output from a switch
I want to loop through the "customer" list and check if the "inventory_hostname" is listed in the "services.sap"
I thought I can do this with following:
when: inventory_hostname in item.1.sap
If the above check is true, I want to make a regex compare between fact3 and the stdout of the first task and raise a failure if the regex is not present in the stdout of the first task:
set_fact:
fact1: "{{ item.1.vlan_id }}"
fact2: "{{ item.1.sap.inventory_hostname | join(', ') }}"
fact3: "{{ fact1 }}(.*){{ fact2 }}"
failed_when: not vlan_db.stdout is regex(fact3)
This is like nested loops and conditional together. Is there a way to do this with Ansible?
What I am doing wrong?
Just to make sure that my intention is clear:
loop through the items in the customers list
loop through the items in the services list in each customer
make a check if item.1.sap has a key which matches inventory_hostname
if the above check is true, make a comparison between the regex pattern above and the output of the first task.
/vars/all.yml looks like this:
customers:
- name: "cust-1"
l3_vni: "101"
services:
- vlan_id: "10"
vni: "1010"
gw: "10.0.0.254/24"
sap:
leaf-1: ["Eth3"]
leaf-2: ["Eth3"]
- vlan_id: "11"
vni: "1011"
gw: "10.0.1.254/24"
sap:
leaf-1: ["Eth3"]
leaf-2: ["Eth3"]
- name: "cust-2"
l3_vni: "102"
services:
- vlan_id: "20"
vni: "1020"
gw: "20.0.0.254/24"
sap:
leaf-3: ["Eth3", "Eth4"]
leaf-4: ["Eth3"]
- vlan_id: "21"
vni: "1021"
gw: "20.0.1.254/24"
sap:
leaf-3: ["Eth3"]
leaf-4: ["Eth3"]
```
a switch vlan database usually looks like this:
```
leaf-1#show vlan brief
VLAN Name Status Ports
---- -------------------------------- --------- ----------------------
1 default active Eth5
10 cust-1 active Eth3, Eth4
20 cust-2 active Eth1, Eth2
```
error log:
fatal: [leaf-1]: FAILED! => {
"msg": "The task includes an option with an undefined variable. The error was: 'fact1' is undefined\n\nThe error appears to be in '/vagrant/Ansible Folder Setup/NetAutHardWay/Step4-CICD/vlan_test.yml': line 23, column 7, but may\nbe elsewhere in the file depending on the exact syntax problem.\n\nThe offending line appears to be:\n\n\n - name: compare vlan_id(.*)Ports against the vlan_db\n ^ here\n"
}
There are several problems here.
First, your playbook is syntactically invalid: it simply won't run. It will fail with the error:
ERROR! unexpected parameter type in action: <class 'ansible.parsing.yaml.objects.AnsibleSequence'>
This is because the content of set_fact should be a dictionary, not a list:
- name: "compare vlan_id(.*)Ports against the vlan_db"
set_fact:
fact1: "{{ item.1.vlan_id }}"
fact2: "{{ item.1.sap.inventory_hostname | join(', ') }}"
fact3: "{{ fact1 }}(.*){{ fact2 }}"
failed_when: not vlan_db.stdout is regex(fact3)
when: inventory_hostname in item.1.sap
with_subelements:
- "{{ customers }}"
- services
However, you've got another problem here: your facts aren't available until after the set_fact task runs, so your conditional which is checking is regex(fact3) is never going to match correctly. If you want to use the value of that expression, you will need to expose the value as a variable.
But before we look at that, there's nother issue: when you write:
fact2: "{{ item.1.sap.inventory_hostname | join(', ') }}"
You are looking for a literal key named "inventory_hostname" in the sap dictionary. Remember, when you write foo.bar.baz, that's effectively shorthand for foo["bar"]["baz"]. You need instead:
fact2: "{{ item.1.sap[inventory_hostname] | join(', ') }}"
So, back to variables. If you want to use the value of fact3 in your conditional, there are a couple of ways of doing that. One option is to use a vars block on your task, which creates new variables that exist for the duration of the task. I've used a debug task here to demonstrate what's happening; you could obviously replace that with a set_fact task if you need to persist some or all of those variables after the task completes:
- name: "compare vlan_id(.*)Ports against the vlan_db"
debug:
msg:
- "{{ fact1 }}"
- "{{ fact2 }}"
- "{{ fact3 }}"
vars:
fact1: "{{ item.1.vlan_id }}"
fact2: "{{ item.1.sap[inventory_hostname] | join(', ') }}"
fact3: "{{ fact1 }}(.*){{ fact2 }}"
failed_when: not vlan_db.stdout is regex(fact3)
when: inventory_hostname in item.1.sap
with_subelements:
- "{{ customers }}"
- services
That is hopefully enough to point you in the right direction. Let me know if I can clarify anything.
Thanks larsks. After some struggle I was able to achieve my goal with the below play.
I had to change the failed_when syntax as well.
- hosts: leaf-1
gather_facts: no
tags: [ verify ]
vars_files:
- ../Step2-config/roles/services_config/vars/main.yml
tasks:
- name: collect the vlan databse
raw: "show vlan brief"
register: vlan_db
- debug:
var: vlan_db.stdout
var: inventory_hostname, vlan_db
- name: compare vlan_id(.*)Ports against the vlan_db
set_fact:
fact1: "{{ item.1.vlan_id }}"
fact2: "{{ item.0.name }}"
fact3: "{{ item.1.sap[inventory_hostname] | join('.*')}}"
when: "inventory_hostname in item.1.sap"
failed_when: not vlan_db.stdout_lines is regex(fact1 + '.*' + fact2 + '.*' + fact3)
with_subelements:
- "{{ customers }}"
- services

How to add disk to VMware's VM host more efficiently by using Ansible offical module?

Env
OS Version: CentOS 7.5
Ansible Version: 2.8.4 with python 2.7.5
ESXi and VCenter Version: 6.5.2
Purpose
I have already completed my code, but I wondering if there are more better way to add disk to VM host by Ansible offical module.
My Method
The return message from vmware_guest_disk_facts
TASK [get VM info] *******************************************************************************************************
ok: [TFGH0001.TFGH.COM -> localhost] => changed=false
guest_disk_facts:
'0':
backing_datastore: VCENTER01_DS01
backing_eagerlyscrub: false
backing_filename: '[VCENTER01_DS01] TFGH0001/TFGH0001-000001.vmdk'
backing_thinprovisioned: false
backing_type: FlatVer2
backing_uuid: 6000C294-d22d-06a9-c89b-119e13fffe33
backing_writethrough: false
capacity_in_bytes: 32212254720
capacity_in_kb: 31457280
controller_key: 1000
key: 2000
label: Hard disk 1
summary: 31,457,280 KB
unit_number: 0
'1':
backing_datastore: VCENTER01_DS01
backing_eagerlyscrub: false
backing_filename: '[VCENTER01_DS01] TFGH0001/TFGH0001_3.vmdk'
backing_thinprovisioned: false
backing_type: FlatVer2
backing_uuid: 6000C293-ae37-30f6-b684-d5b2416ff2f8
backing_writethrough: false
capacity_in_bytes: 10737418240
capacity_in_kb: 10485760
controller_key: 1000
key: 2001
label: Hard disk 2
summary: 10,485,760 KB
unit_number: 1
My Playbook
---
- hosts: all
remote_user: kai
become: yes
become_user: root
become_method: sudo
gather_facts: true
vars:
vcenter_host: "VCENTER01"
vcenter_username: "TFGH\\kaikudou"
vcenter_password: xxxxxxxxx
target_host: "TFGH0001"
tasks:
- name: get VM info
vmware_guest_disk_facts:
hostname: "{{ vcenter_host }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: False
datacenter: "{{ vcenter_host }}"
name: "{{ target_host }}"
delegate_to: localhost
register: vm_disk_info
# - name: show vm_disk_info
# debug:
# msg: "{{ vm_disk_info.guest_disk_facts['0'].backing_datastore }}"
- name: Set empty list to store varialbe
set_fact:
all_scsi_number_list: [] # A list to store all scsi device number
scsi_0: [] # A list to store scsi 0's device for counting the quantity
scsi_1: [] # A list to store scsi 1's device for counting the quantity
scsi_2: [] # A list to store scsi 2's device for counting the quantity
scsi_3: [] # A list to store scsi 3's device for counting the quantity
all_unit_number_list: [] # A list to store the device number from scsi controller
- name: Set variable of datastore
set_fact:
host_datastore: "{{ vm_disk_info.guest_disk_facts['0'].backing_datastore }}"
- name: Store scsi_number into all_scsi_number_list
set_fact:
all_scsi_number_list: "{{ all_scsi_number_list + [vm_disk_info.guest_disk_facts[item].controller_key] }}"
loop: "{{ vm_disk_info.guest_disk_facts.keys() }}"
- name: Find out scsi_controller 0 and store into scsi_0
set_fact:
scsi_0 : "{{ scsi_0 + [item] }}"
loop: "{{ all_scsi_number_list }}"
when: item == 1000
- name: Find out the scsi_controller 1 and store into scsi_1
set_fact:
scsi_1 : "{{ scsi_1 + [item] }}"
loop: "{{ all_scsi_number_list }}"
when: item == 1001
- name: Find out the scsi_controller 2 and store into scsi_2
set_fact:
scsi_2 : "{{ scsi_2 + [item] }}"
loop: "{{ all_scsi_number_list }}"
when: item == 1002
- name: Find out the scsi_controller 3 and store into scsi_3
set_fact:
scsi_3 : "{{ scsi_3 + [item] }}"
loop: "{{ all_scsi_number_list }}"
when: item == 1003
- name: Calcualte the quantity of scsi_*
set_fact:
scsi_0_number: "{{ scsi_0 | length }}"
scsi_1_number: "{{ scsi_1 | length }}"
scsi_2_number: "{{ scsi_2 | length }}"
scsi_3_number: "{{ scsi_3 | length }}"
- name: Verify the scsi controller's number because snapshot will also cost the device so less than 7 to prevent
set_fact:
scsi_number: "{{ item.src }}"
loop:
- { src: "0", when: "{{ (scsi_0_number <= '6' and scsi_0_number != '0') or (scsi_0_number == '0') }}" }
- { src: "1", when: "{{ (scsi_1_number <= '6' and scsi_1_number != '0') or (scsi_1_number == '0') }}" }
- { src: "2", when: "{{ (scsi_2_number <= '6' and scsi_2_number != '0') or (scsi_2_number == '0') }}" }
- { src: "3", when: "{{ (scsi_3_number <= '6' and scsi_3_number != '0') or (scsi_3_number == '0') }}" }
when: item.when
# - name: Show scsi_number which we get
# debug:
# msg: "{{ scsi_number }}"
- name: Change the scsi_number we obtained to 4 digit number
set_fact:
scsi_digit_number: "{{ item.src | int }}"
loop:
- { src: "1000", when: "{{ scsi_number == '0' }}" }
- { src: "1001", when: "{{ scsi_number == '1' }}" }
- { src: "1002", when: "{{ scsi_number == '2' }}" }
- { src: "1003", when: "{{ scsi_number == '3' }}" }
when: item.when
# - name: Show scsi_digit_number which we get
# debug:
# msg: "{{ scsi_digit_number }}"
- name: Check the number of devices from the scci_number we obtained
set_fact:
all_unit_number_list: "{{ all_unit_number_list + [vm_disk_info.guest_disk_facts[item].unit_number] }}"
loop: "{{ vm_disk_info.guest_disk_facts.keys() }}"
when: vm_disk_info.guest_disk_facts[item].controller_key == scsi_digit_number | int
# - name: Show all_unit_number_list which we get
# debug:
# msg: "{{ all_unit_number_list | length | type_debug }}"
- name: Find the max number in all_unit_number_list then plus 1 to add new disk
set_fact:
disk_number: "{{ all_unit_number_list | max + 1 }}"
ignore_errors: yes
- name: If we have to add new scsi controller then the all_unit_number_list will be empty list, so we need to prevent it failed
set_fact:
disk_number: 0
when: all_unit_number_list | length == 0
- name: add disk
vmware_guest_disk:
hostname: "{{ vcenter_host }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: False
datacenter: "{{ vcenter_host }}"
name: "{{ target_host }}"
disk:
- size_gb: 2
type: thin
state: present
datastore: "{{ host_datastore }}"
# autoselect_datastore: True
scsi_controller: "{{ scsi_number | int }}"
unit_number: "{{ disk_number | int }}"
scsi_type: 'paravirtual'
According to VMware offical document 1 VM host can only have 4 scsi controller and each of them can have 15 devices to attach. So I have to write many condition to prevent it.
The strange thing is that when adding a hard disk from VCenter, it will not trigger the problem that the number of hard disks exceeds 7. However, Ansible's module cannot increase the hard disk when unit_number exceeds 7, and must change another SCSI Controller.
Is there any other better way besides doing this? or something I can refer to and study it?
Thank you for your help and advice!
I tried to rephrase the problem a bit. This is far from perfect as it will not take into a account "holes" in numbering (i.e. disk or controllers removed from the sequence). But I don't think your current implementation does either. Mine should work with far less variable assignment and will gracefully fail if there are no more slots available.
Get disk info from guest (pasted in a var for the example).
From info, loop on number of configured controller. For each controller record its key, number, number of devices and max configured device id.
Extract from the list the first controller having an available slot
If this fail create an empty controller info incrementing the scsi device number or fail if max is reached.
Note: I used json_query so you will need to pip(3) install jmespath on your ansible controller machine to run the example.
The playbook:
---
- name: My take to your vmware disk management question
hosts: localhost
gather_facts: false
vars:
# Info copied from your example.
vcenter_host: "VCENTER01"
vcenter_username: "TFGH\\kaikudou"
vcenter_password: xxxxxxxxx
target_host: "TFGH0001"
# Max number of devices per scsi controller
scsi_max_devices: 15
# Max id for scsi controllers
scsi_max_controller_id: 3
# Calling the two following vars before getting facts will fail
# but since we don't need to loop to get them they can be statically
# declared in playbook vars
scsi_controller_unique_keys: >-
{{
vm_disk_info.guest_disk_facts
| dict2items
| map(attribute='value.controller_key')
| list
| unique
| sort
}}
host_datastore: "{{ vm_disk_info.guest_disk_facts['0'].backing_datastore }}"
# Your example data to play with (minified, single line)
# To take from module call return IRL.
vm_disk_info: {"guest_disk_facts":{"0":{"backing_datastore":"VCENTER01_DS01","backing_eagerlyscrub":false,"backing_filename":"[VCENTER01_DS01] TCB0945/TFGH0001-000001.vmdk","backing_thinprovisioned":false,"backing_type":"FlatVer2","backing_uuid":"6000C294-d22d-06a9-c89b-119e13fffe33","backing_writethrough":false,"capacity_in_bytes":32212254720,"capacity_in_kb":31457280,"controller_key":1000,"key":2000,"label":"Hard disk 1","summary":"31,457,280 KB","unit_number":0},"1":{"backing_datastore":"VCENTER01_DS01","backing_eagerlyscrub":false,"backing_filename":"[VCENTER01_DS01] TFGH0001/TFGH0001_3.vmdk","backing_thinprovisioned":false,"backing_type":"FlatVer2","backing_uuid":"6000C293-ae37-30f6-b684-d5b2416ff2f8","backing_writethrough":false,"capacity_in_bytes":10737418240,"capacity_in_kb":10485760,"controller_key":1000,"key":2001,"label":"Hard disk 2","summary":"10,485,760 KB","unit_number":1}}}
tasks:
- name: Create a list holding all the info we need for each existing controller
vars:
scsi_controller_devices_query: >-
[?to_string(value.controller_key)=='{{ controller_key }}'].value.unit_number[]
scsi_controller_devices: >-
{{
vm_disk_info.guest_disk_facts |
dict2items |
json_query(scsi_controller_devices_query)
}}
# Construct object directly as json so that we retain int type for further comparison usage.
current_controller: >-
{
"controller_number": {{ controller_number | int }},
"controller_key": {{ controller_key | int }},
"number_of_devices": {{ scsi_controller_devices | length | int }},
"max_unit_number": {{ scsi_controller_devices | max | int }},
}
set_fact:
scsi_controllers_info: "{{ scsi_controllers_info | default([]) + [current_controller] }}"
loop: "{{ scsi_controller_unique_keys }}"
loop_control:
loop_var: controller_key
index_var: controller_number
- block:
# Note: This was already sorted when we got controllers list in our first loop
- name: "Extract first controller having less than {{ scsi_max_devices }} disks"
set_fact:
scsi_controller: >-
{{
(
scsi_controllers_info |
selectattr('number_of_devices', '<', scsi_max_devices) |
list
).0
}}
rescue:
- name: Fail if we cannot add an other controller id
# i.e.controllernumber of our last element in list is equal (or greater for tests) that scsi_max_controller_id
fail:
msg: All scsi controllers are full, disk cannot be added.
when: scsi_controllers_info[-1].controller_number >= scsi_max_controller_id
- name: Return an empty controller with incremented id
set_fact:
scsi_controller: >-
{
"controller_number": {{ scsi_controllers_info[-1].controller_number + 1 | int }},
"controller_key": {{ scsi_controllers_info[-1].controller_key + 1 | int }},
"number_of_devices": 0,
"max_unit_number": -1,
}
- name: Show what the call to vmware_guest_disk would look like
vars:
vmware_guest_disk_params:
hostname: "{{ vcenter_host }}"
username: "{{ vcenter_username }}"
password: "{{ vcenter_password }}"
validate_certs: False
datacenter: "{{ vcenter_host }}"
name: "{{ target_host }}"
disk:
- size_gb: 2
type: thin
state: present
datastore: "{{ host_datastore }}"
# autoselect_datastore: True
scsi_controller: "{{ scsi_controller.controller_number }}"
unit_number: "{{ scsi_controller.max_unit_number + 1 }}"
scsi_type: 'paravirtual'
debug:
msg: "{{ vmware_guest_disk_params }}"

Ansible: Sequence range using loop variable

I need to create a certain number of systemd unit files based on name and included variable with number of files to create, like:
app-name#1.service
app-name#2.service
app-name#3.service
script-name#1.service
script-name#2.service
script-name#3.service
script-name#4.service
It works with a nested loop using range function, but I not understand how to use loop variable (item.1.workers) into range() parameters.
- hosts: localhost
vars:
apps:
- name: app-name
workers: 3
- name: script-name
workers: 5
connection: local
tasks:
- name: test
debug:
msg: "{{ item.1.name }}#{{ item.0 }}.service"
loop: "{{ range(1, 3) | product(apps) | list }}"
Let's create the lists of workers in the first task and loop with subelements in the second task
- set_fact:
apps1: "{{ apps1|default([]) +
[{'name': item.name,
'workers': range(1, item.workers + 1)|list}] }}"
loop: "{{ apps }}"
- debug:
msg: "{{ item.0.name }}#{{ item.1 }}.service"
loop: "{{ apps1|subelements('workers') }}"
gives
msg: app-name#1.service
msg: app-name#2.service
msg: app-name#3.service
msg: script-name#1.service
msg: script-name#2.service
msg: script-name#3.service
msg: script-name#4.service
msg: script-name#5.service

Resources