Simple ask: I want to delete some files if partition utilization goes over a certain percentage.
I have access to "size_total" and "size_available" via "ansible_mounts". i.e.:
ansible myhost -m setup -a 'filter=ansible_mounts'
myhost | success >> {
"ansible_facts": {
"ansible_mounts": [
{
"device": "/dev/mapper/RootVolGroup00-lv_root",
"fstype": "ext4",
"mount": "/",
"options": "rw",
"size_available": 5033046016,
"size_total": 8455118848
},
How do I access those values, and how would I perform actions conditionally based on them using Ansible?
Slava's answer definitely was on the right track, here is what I used:
- name: test for available disk space
assert:
that:
- not {{ item.mount == '/' and ( item.size_available < item.size_total - ( item.size_total|float * 0.8 ) ) }}
- not {{ item.mount == '/var' and ( item.size_available < item.size_total - ( item.size_total|float * 0.8 ) ) }}
with_items: ansible_mounts
ignore_errors: yes
register: disk_free
- name: free disk space
command: "/some/command/that/fixes/it"
when: disk_free|failed
The assert task simply tests for a condition, by setting ignore_errors, and registering the result of the test to a new variable we can perform a conditional task later in the play instead of just failing when the result of the assert fails.
The tests themselves could probably be written more efficiently, but at the cost of readability. So I didn't use a multiple-list loop in the example. In this case the task loops over each item in the list of mounted filesystems (an ansible-created fact, called ansible_mounts.)
By negating the test we avoid failing on file system mounts not in our list, then simple math handles the rest. The part that tripped me up was that the size_available and size_total variables were strings, so a jinja filter converts them to a float before calculating the percentage.
In my case, all I care about is the root partition. But I found when using the example from frameloss above, that I needed a negated 'or' condition, because each mount point will get tested against the assertion. If more than one mount point existed, then that meant the assertion would always fail. In my example, I'm testing for if the size_available is less than 50% of size_total directly, rather than calculate it as frameloss did.
Secondly, at least in the version of ansible I used, it was necessary to include the {{ }} around the variable in with_items. A mistake that I made that wasn't in the example above was not aligning the 'when' clause at the same indentation as the 'fail' directive. ( If that mistake is made, then the solution does not work... )
# This works with ansible 2.2.1.0
- hosts: api-endpoints
become: True
tasks:
- name: Test disk space available
assert:
that:
- item.mount != '/' or {{ item.mount == '/' and item.size_available > (item.size_total|float * 0.4) }}
with_items: '{{ ansible_mounts }}'
ignore_errors: yes
register: disk_free
- name: Fail when disk space needs attention
fail:
msg: 'Disk space needs attention.'
when: disk_free|failed
I didn't test it but I suggest to try something like this:
file:
dest: /path/to/big/file
state: absent
when: "{% for point in ansible_mounts %}{% if point.mount == '/' and point.size_available > (point.size_total / 100 * 85) %}true{% endif %}{% endfor %}" == "true"
In this example, we iterate over mount points and find "/", after that we calculate is there utilization goes over 85 percentage and prints "true" if it's true. Next, we compare that string and decide should this file be deleted.
Inspired by examples from the following blog: https://blog.codecentric.de/en/2014/08/jinja2-better-ansible-playbooks-templates/
My solution
- name: cleanup logs, free disk space below 20%
sudo: yes
command: find /var -name "*.log" \( \( -size +50M -mtime +7 \) -o -mtime +30 \) -exec truncate {} --size 0 \;
when: "item.mount == '/var' and ( item.size_available < item.size_total * 0.2 )"
with_items: ansible_mounts
This will truncate any *.log files on the volume /var that are either older than 7 days and greater than 50M or older than 30 days if the free disk space falls below 20%.
Related
i am new to Ansible, and have write .yml file to empty a file, just like ">file_name"
---
tasks:
- name: Empty Log Files greater then 400M
shell: 'find "{{ item }}" -name "messages*" -size +400M -exec sh -c '> {}' \;'
with_items:
- /var/log
- /var/opt
- /var/spool/mail
ignore_errors: yes
and i am getting this following error
ERROR! Syntax Error while loading YAML.
The error appears to have been in
'/tmp/clean.yml': line 7, column 11, but may
be elsewhere in the file depending on the exact syntax problem.
The offending line appears to be:
name: Delete Log Files greater then 400M
shell: 'find "{{ item }}" -name "messages*" -size +400M -exec sh -c '> {}' \;'
^ here We could be wrong, but this one looks like it might be an issue with missing quotes. Always quote template expression
brackets when they start a value. For instance:
with_items:
{{ foo }}
Should be written as:
with_items:
- "{{ foo }}"
exception type: exception: mapping
values are not allowed in this context in "", line
7, column 11
where im getting it wrong?
Kindly refer below playbook as a reference for the same.
---
- name: play to check the file size
hosts: "{{ servers }}"
tasks:
- name: Recursively find /tmp files older than 4 weeks and equal or greater than 1 megabyte
find:
paths: "{{ item }}"
age: 4w
size: 1m
recurse: yes
with_items:
- path_1
- path_2
- path_3
I need to create a check very similar to that one explained here: Ansible to check diskspace for mounts mentioned as variable
Except I need it only for specified paths (for example /var).
{{ ansible_mounts }} is an array of dictionaries, each containing variable mount that is the actual path. I need to perform the check in a loop for all items in {{ ansible_mounts }} only if mount is equal to some value.
Example of what I want to achieve in pseudo code:
foreach (mountpoint in ansible_mounts)
{
if (mountpoint["mount"] == "/var" || mountpoint["mount"] == "/opt")
{
// do the check
}
}
How can I do this in Jinja / Ansible? This code does the check for every single item. I need to filter it only for explicitly specified paths:
- name: Ensure that free space on the tested volume is greater than 15%
assert:
that:
- mount.size_available > mount.size_total|float * 0.15
msg: Disk space has reached 85% threshold
vars:
mount: "{{ ansible_mounts | selectattr('mount','equalto',item.mount) | list | first }}"
with_items:
- "{{ ansible_mounts }}"
you'll need to add a when condition, for example
vars:
my_mounts:
- '/var/log'
- '/var/logs/foo'
tasks:
- name: do the check
when: item.mount in my_mounts
with_items: '{{ ansible_mounts }}'
When a task is skipped on the basis of condition and the result of register also differ which cause of another task has been failed.
- name: Check if the partition is already mounted
shell: df | grep "{{item.partition}}" | wc -l
with_items:
- "{{ ebs_vol }}"
register: ebs_checked
when: ebs_vol is defined
- name: Make filesystem of the partition
filesystem: fstype=ext4 dev={{item.item.partition}} force=no
when: ( ebs_vol is defined and "{{item.stdout}} == True" )
changed_when: True
with_items:
- ebs_checked.results
Use default filter to handle corner cases:
- name: Make filesystem of the partition
filesystem: fstype=ext4 dev={{item.item.partition}} force=no
when: item.stdout | bool
changed_when: True
with_items: "{{ ebs_checked.results | default([]) }}"
This will iterate over empty list (read "will do nothing") if there is no results in ebs_checked.
Also you should not check for ebs_vol is defined because when statement in looped tasks is applied inside a loop, and keeping in mind that you check for ebs_vol is defined in the previous task, makes this check unnecessary inside a loop.
I am new to ansible and currently working on a play which will see if disk space of remote machines has reached 70% threshold. If they have reached it should throw error.
i found a good example at : Using ansible to manage disk space
but at this example the mount names are hard coded. And my requirement is to pass them dynamically. So i wrote below code which seems to not work:
name: test for available disk space
assert:
that:
- not {{ item.mount == '{{mountname}}' and ( item.size_available <
item.size_total - ( item.size_total|float * 0.7 ) ) }}
with_items: '{{ansible_mounts}}'
ignore_errors: yes
register: disk_free
name: Fail the play
fail: msg="disk space has reached 70% threshold"
when: disk_free|failed
This play works when i use:
item.mount == '/var/app'
Is there any way to enter mountname dynamically ? and can i enter multiple mount names ??
I am using ansible 2.3 on rhel
Thanks in advance :)
Try this:
name: Ensure that free space on {{ mountname }} is grater than 30%
assert:
that: mount.size_available > mount.size_total|float * 0.3
msg: disk space has reached 70% threshold
vars:
mount: "{{ ansible_mounts | selectattr('mount','equalto',mountname) | list | first }}"
that is a raw Jinja2 expression, don't use curly brackets in it.
why do you use separate fail task, if assert can fail with a message?
For those who cannot use selectattr (like me), here is a variant of the first answer using when and with_items to select the mount point to check.
name: 'Ensure that free space on {{ mountname }} is grater than 30%'
assert:
that: item.size_available > item.size_total|float * 0.3
msg: 'disk space has reached 70% threshold'
when: item.mount == mountname
with_items: '{{ ansible_mounts }}'
Note: To be able to use the variable {{ ansible_mounts }} you need to turn gather_facts to yes that can be limited to gather_subset=!all,hardware.
I'm running Ansible 2.5 and was able to get Konstantin Suvorov's solution to work with a slight mod by adding with_items. Sample code below:
- name: Ensure that free space on the tested volume is greater than 15%
assert:
that:
- mount.size_available > mount.size_total|float * 0.15
msg: Disk space has reached 85% threshold
vars:
mount: "{{ ansible_mounts | selectattr('mount','equalto',item.mount) | list | first }}"
with_items:
- "{{ ansible_mounts }}"
In my Ansible script, I want to generate UUIDs on the fly and use them later on.
Here is my approach:
- shell: echo uuidgen
with_sequence: count=5
register: uuid_list
- uri:
url: http://www.myapi.com
method: POST
body: "{{ item.item.stdout }}"
with_items: uuid_list.result
However I get the following error:
fatal: [localhost] => One or more undefined variables: 'str object' has no attribute 'stdout'
How can I solve this issue?
In ansible 1.9 there is a new filter : to_uuid , which given a string it will return an ansible domain specific UUID,you can find the usage in here https://docs.ansible.com/playbooks_filters.html#other-useful-filters
As metioned by Xingxing Gao, there is to_uuid which can be used with a large enough number and the random filter to produce a random UUID. The larger the number the greater the randomness. eg:
{{ 99999999 | random | to_uuid }}
or
{{ 9999999999999999999999 | random | to_uuid }}
Generate a random UUID from a 20 char string with upper/lower case letters and digits:
{{ lookup('password', '/dev/null chars=ascii_letters,digits') | to_uuid }}
This is very close. I only had to change a few things. I figured this out by using the task "debug: var=uuid_list" and iterating.
- shell: uuidgen # note 1
with_sequence: count=5
register: uuid_list
- uri:
url: http://www.myapi.com
method: GET
body: "{{ item.stdout }}" # note 2
timeout: 1 # note 3
with_items: uuid_list.results # note 4
Notes:
echo caused the string uuidgen to be printed. removed echo, kept uuidgen.
item.item.stdout needed to be item.stdout
I used a short timeout so I could test this without having a rest endpoint available. This gives failure error messages but it's clear that it is correct.
uuid_list.stdout needed to be uuid_list.results
Be aware if you use Willem's solution, Jinja and Ansible will cache the result for multiple executions of the same filter, so you must change the source number each time
api_key_1: "{{ 999999999999999999995 | random | to_uuid }}"
api_key_2: "{{ 999999999999999999994 | random | to_uuid }}"
and for situations where you need a normal md5 instead of to_uuid, hash('md5') does not take an integer. The most convenient way to convert the random to a string I've found is to use to_uuid:
api_key_3: "{{ 999999999999999999999 | random | to_uuid | hash('md5') }}"
api_key_4: "{{ 999999999999999999998 | random | to_uuid | hash('md5') }}"
One solution which should be immune to caching/stale fact gathering and give you a reasonably random UUID each time you use it is:
{{ (999999999999999999999 | random | string + (lookup('pipe', 'date +%s%N'))) | to_uuid() }}
It concatenates a random number between 0 and 999999999999999999999 with current nanoseconds since Unix epoch and feeds that through Ansible's to_uuid() filter (available since version 1.9). Fact caching should not cause a problem as lookups are always evaluated each and every time they are invoked.
If you want a UUID that remains constant throughout the plays in a playbook (but doesn't persist between multiple invocations of the playbook - even with fact caching turned on) then use:
set_fact: uuid={{ (999999999999999999999 | random | string + (lookup('pipe', 'date +%s%N'))) | to_uuid() }}
I tried to avoid using the filter to_uuid, because it does not only take numbers as input but also characters. So, if I would only give it numbers as input, I would not be able to create every uuid possible.
I came up with this solution:
- name: Generate UUID
vars:
# This creates a random 128-bit integer and translates it to hexadecimal string.
# Zeros are put infront of the hex string, to make it always 32 hex-digits long.
__uuid: "{{ '%032x' % ((2**128) | random) }}"
# Format the hexadecimal string to a UUID format: XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX
uuid: "{{ __uuid[0:8] }}-{{ __uuid[8:12] }}-{{ __uuid[12:16] }}-{{ __uuid[16:20] }}-{{ __uuid[20:32] }}"
debug:
msg: "{{ uuid }}"