In order to get instances matching some criteria, I wrote the following tasks file:
---
# Required variables :
# - deployment_type: "vm" or "container"
# - cmp_operator: '<', '>', '<=', '>=' or '=='
# - cmp_days: number of days (positive or negative) from today to obtain the comparison date
#
# Output variable :
# - matching_instances
- name: Get comparaison date
ansible.builtin.set_fact:
__test_date: "{{ lookup('ansible.builtin.pipe', 'date -d \"' + cmp_days + ' days\" +%d-%m-%Y') }}"
- name: Call REST API to list all instances
ansible.builtin.uri:
url: "XXXXXXXXXXXXXXXXXXXXXXXX"
method: GET
status_code: 200
body_format: json
register: __all_instances
- name: Init return variable
ansible.builtin.set_fact:
matching_instances: []
- name: Built return variable
ansible.builtin.set_fact:
matching_instances: "{{ matching_instances + [item] }}"
when: >
item.{{ deployment_type }}_deployment is defined and
item.{{ deployment_type }}_deployment.end_date is defined and
(item.{{ deployment_type }}_deployment.end_date | to_datetime("%d-%m-%Y")) {{ cmp_operator }} (__test_date | to_datetime("%d-%m-%Y"))
loop: "{{ __all_instances.json.results }}"
loop_control:
label: "{{ item.name }}"
It can be used the following way:
- name: Get expired VM instances
ansible.builtin.include_tasks: get_instances_by_end_date_task.yml
vars:
deployment_type: "vm"
cmp_operator: "<"
cmp_days: "0"
or
- name: Get container instances that will end in 2 weeks
ansible.builtin.include_tasks: get_instances_by_end_date_task.yml
vars:
deployment_type: "container"
cmp_operator: "=="
cmp_days: "+14"
I did some tests and it works: the matching_instances variable is filled with expected instances. But I got the following warning: [WARNING]: when statements should not include jinja2 templating delimiters such as {{ }} or {% %}. about the statement in the tasks file.
The way I used jinja in the when statement is not usual. So I don’t know whether I should take into account or ignore this warning (whose purpose seems to be to prevent another problem as in when statements should not include jinja2 templating delimiters). Is it risky to ignore it in my case?
More generally, what is the best way to build a configurable when statement?
when: >
item[deployment_type % '_deployment'] is defined and
item[deployment_type % '_deployment']['end_date'] is defined and
(item[deployment_type % '_deployment']['end_date'] | to_datetime("%d-%m-%Y")) == (__test_date | to_datetime("%d-%m-%Y")) if cmp_operator == '==' else ... # <- you have to add all possible `cmp_operator` values here.
This is the only way I can think of that removes templating. Yeah, the operator part is quite ugly.
This would be the best thing here. A complex when statement does not pass the 2am test and can be very hard to understand the logic. Moving this to Python code can make your playbooks more descriptive and silo out the more complex logic in a language better designed for it.
import operator as py_operator
from ansible import errors
from ansible.module_utils.common.text.converters import to_native
from datetime import datetime
def date_compare(value, date, operator="eq"):
"""Perform a date comparison on a value"""
op_map = {
"==": "eq",
"=": "eq",
"eq": "eq",
"<": "lt",
"lt": "lt",
"<=": "le",
"le": "le",
">": "gt",
"gt": "gt",
">=": "ge",
"ge": "ge",
"!=": "ne",
"<>": "ne",
"ne": "ne",
}
if not isinstance(value, datetime):
raise errors.AnsibleFilterError("Input date value must be a datetime")
if not isinstance(date, datetime):
raise errors.AnsibleFilterError(
"Date parameter to compare against must be a datetime"
)
if operator in op_map:
operator = op_map[operator]
else:
raise errors.AnsibleFilterError(
"Invalid operator type (%s). Must be one of %s"
% (operator, ", ".join(map(repr, op_map)))
)
try:
method = getattr(py_operator, operator)
return method(value, date)
except Exception as e:
raise errors.AnsibleFilterError("Date comparison failed: %s" % to_native(e))
class TestModule(object):
"""Custom jinja2 tests"""
def tests(self):
return {"date_compare": date_compare}
And I modified the when statement like this:
item[deployment_type + '_deployment'] is defined and
item[deployment_type + '_deployment']['end_date'] is defined and
(item[deployment_type + '_deployment']['end_date'] | to_datetime("%d-%m-%Y")) is date_compare(__test_date | to_datetime('%d-%m-%Y'), cmp_operator)