in the example below, how do I retrieve the grade of students listed in selected_students_list? I need to process the grade of the student. In other programming languages I can use nested loops but I'm a bit confused how to implement this in Ansible
---
- name: Test
hosts: localhost
vars:
- all_students_list:
- {
name: "Charlie",
grade: 9,
}
- {
name: "Alice",
grade: 8,
}
- {
name: "Bob",
grade: 7,
}
- {
name: "Darla",
grade: 10,
}
- {
name: "Edith",
grade: 11,
}
- selected_students_list:
- Alice
- Bob
tasks:
- name: Process selected students
debug:
msg: "<PROCESS THE GRADE OF SELECTED STUDENTS i.e. Alice and Bob>"
with_nested:
- "{{ all_students_list }}"
- "{{ selected_students_list }}"
It's possible either to list selected students item by item. For example
- name: Process selected students
debug:
msg: "PROCESS THE GRADES [{{ item.grade }}]
OF SELECTED STUDENT {{ item.name }}"
loop: "{{ all_students_list|
selectattr('name', 'in', selected_students_list)|
list }}"
gives
msg: PROCESS THE GRADES [8] OF SELECTED STUDENT Alice
msg: PROCESS THE GRADES [7] OF SELECTED STUDENT Bob
Or, to display the whole list at once. For example
- name: Process selected students
debug:
msg: "{{ msg.split('\n')[:-1] }}"
vars:
my_list: "{{ all_students_list|
selectattr('name', 'in', selected_students_list)|
list }}"
msg: |
PROCESS THE GRADES OF SELECTED STUDENTS
{{ my_list|to_yaml }}
gives
msg:
- PROCESS THE GRADES OF SELECTED STUDENTS
- '- {grade: 8, name: Alice}'
- '- {grade: 7, name: Bob}'
If the data is in a dictionary, instead of a list, the listing item by item is very simple. For example
- name: Test
hosts: localhost
vars:
- all_students_dict:
Charlie:
grade: 9
Alice:
grade: 8
Bob:
grade: 7
Darla:
grade: 10
Edith:
grade: 11
- selected_students_list:
- Alice
- Bob
tasks:
- name: Process selected students
debug:
msg: "PROCESS THE GRADES [{{ all_students_dict[item].grade }}]
OF SELECTED STUDENT {{ item }}"
loop: "{{ selected_students_list }}"
gives
msg: PROCESS THE GRADES [8] OF SELECTED STUDENT Alice
msg: PROCESS THE GRADES [7] OF SELECTED STUDENT Bob
It's possible to use filters dict2items and items2dict to create a dictionary of selected students. For example
- name: Process selected students
debug:
msg: "{{ msg.split('\n')[:-1] }}"
vars:
my_dict: "{{ all_students_dict|
dict2items|
selectattr('key', 'in', selected_students_list)|
list|
items2dict }}"
msg: |
PROCESS THE GRADES OF SELECTED STUDENTS
{{ my_dict|to_yaml }}
gives
msg:
- PROCESS THE GRADES OF SELECTED STUDENTS
- 'Alice: {grade: 8}'
- 'Bob: {grade: 7}
This should do
vars:
...
query: "[?name == `{{ item }}`].grade"
tasks:
- name: Process selected students
debug:
msg: "{{ all_students_list | json_query(query) }}"
loop: "{{ selected_students_list }}"
Check json-query-filter for more details.
Related
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)) }}"
I would like to create a dictionary of users which has list of folders in it.
users:
name: user1
folders:
- user1-1
- user1-2
name: user2
folders:
- user2-1
- user2-2
name: userN
folders:
- userN-1
- userN-2
I am trying to create this user dictionary list dynamically.
- hosts: localhost
gather_facts: no
vars:
userCount: 5
folderCount: 5
tasks:
- set_fact:
user_lists: []
- name: creating list of users
set_fact:
user_lists: "{{ user_lists + [ 'user-' ~ item ] }}"
with_sequence: count="{{userCount|int}}"
- debug:
var: user_lists
- set_fact:
usersObj: []
userDict: {}
- name: add each user to dict
set_fact:
usersObj: "{{ usersObj + [userDict| combine({'name': item})] }}"
with_items: "{{user_lists}}"
- debug: var=usersObj
I have created 5 users in dictionary. Now each user should have folder list created based on folderCount. How to resolve this? Can Jinja2 templates be used to simplify this?
The task below does the job
- set_fact:
user_lists: []
- name: creating dictionary of users with lists of folders
set_fact:
user_lists: "{{ user_lists|combine({key: val}) }}"
with_sequence: start=1 end="{{ userCount }}"
vars:
folder_range: "{{ range(1, folderCount|int + 1)|list }}"
key: "{{ 'user-' ~ item }}"
val: "{{ [key]|
product(folder_range)|
map('join', '-')|
list }}"
- debug:
var: user_lists
gives (abridged)
"user_lists": {
"user-1": [
"user-1-1",
"user-1-2",
"user-1-3",
"user-1-4",
"user-1-5"
],
"user-2": [
"user-2-1",
"user-2-2",
"user-2-3",
"user-2-4",
"user-2-5"
],
"user-3": [
"user-3-1",
"user-3-2",
"user-3-3",
"user-3-4",
"user-3-5"
],
"user-4": [
"user-4-1",
"user-4-2",
"user-4-3",
"user-4-4",
"user-4-5"
],
"user-5": [
"user-5-1",
"user-5-2",
"user-5-3",
"user-5-4",
"user-5-5"
]
}
I have an inventory look like this
host:
vars:
fruit:
- {type: melon, id: 1}
- {type: apple, id: 2}
currently I used to get id like below
list: >-
{{groups['host']|map('extract',hostvars,'fruits')|first}}
- set_fact:
fruit_id: "{{list[2].id}}"
How can I get the id value by using type equal to apple?
The task
- debug:
msg: "{{ list|
selectattr('type', '==', 'apple')|
map(attribute='id')|
list|first }}"
gives
"msg": "2"
The same task in the loop
- debug:
msg: "{{ list|
selectattr('type', '==', item)|
map(attribute='id')|
list|first }}"
loop:
- melon
- apple
gives
"msg": "1"
"msg": "2"
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 }}"
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