I try to increment a variable for each item contained in "with_items".
For exemple, I would like that the "counter" variable to increment by 1 for each item
- set_fact:
number: 0
- set_fact:
esx_conf:
counter : {{ number | int + 1 }}
value1 : {{ item.ansible_facts.hardware.memorySize }}
value2 : {{ item.ansible_facts.summary.quickStats.overallMemoryUsage }}
value3 :"{{ item.item.name }}
with_items: "{{ esxi_infos.results }}"
register: all_esx_conf
It would seem that this is not possible.
I tried to play with "with_sequence" or "with_nested" as well.
Do you have any idea ?
Thanks & regards,
Ansible is a declarative language, which makes that kind of thing tricky.
The whole point of the language is to just describe the tasks that you want done, and not the way to do them, which makes procedural tasks like those difficult to achieve.
You can take a look a the following stack overflow answer, that might help you getting what you are trying to achieve:
Using set_facts and with_items together in Ansible
However, maybe if you could give a little more info on what the high level objective is, there might be a better way to achieve it using Ansible than looping and incrementing a counter.
Thanks for your answer.
My goal is to have a list like this :
ESX 1 - RAM 50% - CPU 40%
ESX 2 - RAM 50% - CPU 40%
ESX 3 - RAM 50% - CPU 40%
....
Data is collected with "community.vmware.vmware_host_facts". Currently, here is my playbook :
- name: "ESX information"
community.vmware.vmware_host_facts:
hostname: "{{ vcenter_server }}"
username: "{{ vcenter_user }}"
password: "{{ vcenter_pass }}"
validate_certs: no
esxi_hostname: "{{ item.name }}"
schema: vsphere
properties:
- hardware.memorySize
- hardware.cpuInfo.numCpuCores
- hardware.cpuInfo.hz
- summary.quickStats.overallMemoryUsage
- summary.quickStats.overallCpuUsage
- config.fileSystemVolume.mountInfo
with_items: "{{ liste_ESX | json_query('*.value') }}"
register: esxi_infos
- set_fact:
esx_conf: "{{ item.item.name }} - RAM : {{ (( item.ansible_facts.summary.quickStats.overallMemoryUsage / ( item.ansible_facts.hardware.memorySize / (1024*1024) ) ) * 100 ) | int }}% - CPU : {{ (( item.ansible_facts.summary.quickStats.overallCpuUsage / ( item.ansible_facts.hardware.cpuInfo.numCpuCores * ( item.ansible_facts.hardware.cpuInfo.hz / 1000000 ) ) ) * 100) | int }}%"
with_items: "{{ esxi_infos.results }}"
register: all_esx_conf
- set_fact:
esx_conf: "{{ item.ansible_facts.esx_conf }}"
with_items: "{{ all_esx_conf.results }}"
register: all_esx_conf
- debug:
msg: "{{ all_esx_conf.results | map(attribute='ansible_facts.esx_conf') | list }}"
The result is :
TASK [debug] ***************************************************************************************************************************************************************************
ok: [localhost] => {
"msg": [
"ESX 1 - RAM : 74% - CPU : 11%",
"ESX 2 - RAM : 70% - CPU : 11%",
"ESX 3 - RAM : 65% - CPU : 13%",
...
]
}
After that, I have a pause :
- pause:
prompt: "ESX chosen ?"
register: prompt
So, with a counter for each "ITEM", the user will type 1,2,3...to choose the ESX.
What do you think?
Thanks !
Related
I am trying to compare two filesystem values /var and /tmp and as per the result need to execute the corresponding remediation playbook.
- name: check top utilized files.
shell: du -sx --exclude=/proc --exclude=/sys* --exclude=/dev* --exclude=/u02* --exclude=/usr* --exclude=/boot* --exclude=/swapfile* --exclude=/run* -t 200M "{{fs_input}}"* | sort -n -r
register: top_files
- debug:
msg: "{{ top_files.stdout_lines }}"
- debug:
msg: "{{ item.split('\t/')[0]}}"
with_items: "{{ top_files.stdout_lines[0] }}"
register: value1
- debug:
msg: "{{ value1 |int * 1024 }}"
- debug:
msg: "{{ item.split('\t/')[0]}}"
with_items: "{{ top_files.stdout_lines[1] }}"
register: value2
- debug:
msg: "{{ value2 |int * 1024 }}"
The debug value gives 0 as output.
There's a lot going on here. register saves the result of a task, but you are trying to treat the registered variable as though it contains the single value you want. You're looping over single items, which will make the registered result more complicated than it needs to be.
debug + register is not a good way to do variable manipulation, but you can make it work:
# Removing the unnecessary loop
- debug:
msg: "{{ top_files.stdout_lines.0.split('\t/')[0] }}"
register: value1
- debug:
msg: "{{ value1.msg | int * 1024 }}"
# Or you can leave it in, if you really want
- debug:
msg: "{{ item.split('\t/')[0]}}"
loop:
- "{{ top_files.stdout_lines[1] }}"
register: value2
- debug:
msg: "{{ value2.results.0.msg | int * 1024 }}"
However, it's better to use the Ansible features that are actually intended for this type of work.
- set_fact:
value1: "{{ top_files.stdout_lines.0.split('\t/')[0] }}"
value2: "{{ top_files.stdout_lines.1.split('\t/')[0] }}"
- debug:
msg: "{{ value1 | int * 1024 }} / {{ value2 | int * 1024 }}"
or
- debug:
msg: "{{ value1 | int * 1024 }} / {{ value2 | int * 1024 }}"
vars:
value1: "{{ top_files.stdout_lines.0.split('\t/')[0] }}"
value2: "{{ top_files.stdout_lines.1.split('\t/')[0] }}"
Personally, I might write it more like this:
- debug:
msg: "{{ values.0 | int * 1024 }} / {{ values.1 | int * 1024 }}"
vars:
values: "{{ top_files.stdout_lines | map('split', '\t/') | map('first') }}"
I'm struggling to group some common values on a same line, with ansible.
I have the vg name and disks on each line, as follows:
ok: [localhost] => {
"msg": [
"vg01 /dev/xvdk",
"vg01 /dev/xvdj",
"vg02 /dev/xvdi",
"vg02 /dev/xvdh",
"vg03 /dev/xvdg",
"vg03 /dev/xvdf"
]
}
Now, I want the vg with all it's disks, on one same line, one per vg, as below:
"vg01 /dev/xvdk, /dev/xvdj",
"vg02 /dev/xvdi, /dev/xvdh",
"vg03 /dev/xvdg, /dev/xvdf",
Still not able to achieve this; Can someone help, please?
Thank you.
Create a list of dictionaries. For example, given the data is stored in the variable disks
- set_fact:
l1: "{{ l1|default([]) + [{'dsk': item.split(' ')|first,
'dev': item.split(' ')|last}] }}"
loop: "{{ disks }}"
gives
l1:
- dev: /dev/xvdk
dsk: vg01
- dev: /dev/xvdj
dsk: vg01
- dev: /dev/xvdi
dsk: vg02
- dev: /dev/xvdh
dsk: vg02
- dev: /dev/xvdg
dsk: vg03
- dev: /dev/xvdf
dsk: vg03
Use groupby filter and format the lines in the loop, for example
- debug:
msg: "{{ item.0 }} {{ item.1|flatten|map(attribute='dev')|join(', ') }}"
loop: "{{ l1|groupby('dsk') }}"
gives
msg: vg01 /dev/xvdk, /dev/xvdj
msg: vg02 /dev/xvdi, /dev/xvdh
msg: vg03 /dev/xvdg, /dev/xvdf
A more robust option, using regex_replace/from_yaml, might be able to handle some malformed data where simple split/first/last would fail e.g.
- set_fact:
l1: "{{ l1|default([]) + [item.0|combine(item.1)] }}"
with_together:
- "{{ disks|map('regex_replace', '^(.*?) (.*)$', 'dsk: \\1')|
map('from_yaml')|list }}"
- "{{ disks|map('regex_replace', '^(.*?) (.*)$', 'dev: \\2')|
map('from_yaml')|list }}"
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 }}"
In order to generate random cron executions for each host, I'd like to randomize the hour when it is going to be executed, to spread the load and avoid all hosts to run hard tasks at the same time.
So, if it would be possible to generate a different hour and minute for each host, it would spread it automatically.
Example:
cron:
name: "Conquer the world"
minute: "{{ ansible.hostname | str2num(0, 59) }}"
hour: "{{ ansible.hostname | str2num(4, 6) }}"
job: "conquer_the_world.sh"
Wanted function is what I named str2num, that should generate the same number for the same host in a deterministic way, and may be different for each host.
Is already there any solution for this or should I create a custom filter for this?
ANSWER
I finally found the answer by myself, thanks to the blog post: https://ansibledaily.com/idempotent-random-number/:
cron:
name: "Conquer the world"
minute: "{{ (59 |random(seed=ansible_hostname)) }}"
hour: "{{ (2 |random(seed=ansible_hostname)) + 4 }}"
job: "conquer_the_world.sh"
In general: {{ ( (MAX - MIN) | random(seed=ansible_hostname)) + MIN }}
{{ ( inventory_hostname | hash('sha256') | int(0, 16) ) % 59 }}
Use Random Number Filter. There is a cron example
"{{ 60|random }} * * * * root /script/from/cron"
# => '21 * * * * root /script/from/cron'
An option would be to create a "schedule" and then use it. The attribute seed makes the task idempotent. For example the play below
- hosts: test_jails
tasks:
- set_fact:
mcs: "{{ mcs|default([])|
combine({item :{'my_cron_hour':(24|random(seed=item)),
'my_cron_minute':(60|random(seed=item))}}) }}"
loop: "{{ play_hosts }}"
run_once: true
- debug:
var: my_cron_schedule
run_once: true
gives
mcs:
test_11:
my_cron_hour: 2
my_cron_minute: 4
test_12:
my_cron_hour: 5
my_cron_minute: 11
test_13:
my_cron_hour: 13
my_cron_minute: 27
I am trying to create a list of unused disks from gather facts
-
key | search ("sd")
with_dict: "{{ ansible_devices }}"
But its only displaying one disk, like if the server have two unused disks sdb and sdc , its only displaying sdb. How can I modify my code to include all unused disks.
the way you have it, set_fact gets equal to the "current" item from the iteration, probably this is why you see only 1 disk in the final result. You need to set the disks as a list of elements and append to that list the current item.key. you can use this syntax:
set_fact:
disks: "{{ disks|default([]) + ['/dev/{{item.key}}'] }}"
to understand how many results you have in the loops of the with_dict clause, you can try a debug task:
- name: Print disk result
debug:
msg: "/dev/{{item.key}}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
(indentation may need fixes, i just copied from your code)
hope it helps
thanks for the reply, I used the mentioned code for printing the disk result, it was displaying multiple disks, but when I used set_fact, and the msg to display the variable, its showing like this:-
"disks": [
"/dev/sdc",
"/dev/{{item.key}}"
]
- name: Print disk result
set_fact:
disks: "{{ disks|default([]) + ['/dev/{{item.key}}'] }}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | search ("sd")
with_dict: "{{ ansible_devices }}"
- debug: var=disks
If anyone is ever looking for the answer, this works for me:
- name: Print disk result
set_fact:
disks: "{{ disks|default([]) + ['/dev/' + item.key] }}"
when:
- not item.value.partitions
- not item.value.holders
- not item.value.links.ids
- item.key | regex_search ("sd")
with_dict: "{{ ansible_devices }}"
- name: debug
debug:
msg: "{{disks}}"
It returns:
ok: [localhost] => {
"msg": [
"/dev/sdb"
]
}