diff --git a/changelogs/fragments/2284-influxdb_retention_policy-idempotence.yml b/changelogs/fragments/2284-influxdb_retention_policy-idempotence.yml new file mode 100644 index 0000000000..0df25ca462 --- /dev/null +++ b/changelogs/fragments/2284-influxdb_retention_policy-idempotence.yml @@ -0,0 +1,4 @@ +bugfixes: + - influxdb_retention_policy - ensure idempotent module execution with different + duration and shard duration parameter values + (https://github.com/ansible-collections/community.general/issues/2281). diff --git a/plugins/modules/database/influxdb/influxdb_retention_policy.py b/plugins/modules/database/influxdb/influxdb_retention_policy.py index 2d27e6163e..2c2f9674b7 100644 --- a/plugins/modules/database/influxdb/influxdb_retention_policy.py +++ b/plugins/modules/database/influxdb/influxdb_retention_policy.py @@ -31,7 +31,9 @@ options: type: str duration: description: - - Determines how long InfluxDB should keep the data. + - Determines how long InfluxDB should keep the data. If specified, it + should be C(INF) or at least one hour. If not specified, C(INF) is + assumed. Supports complex duration expressions with multiple units. required: true type: str replication: @@ -46,9 +48,10 @@ options: default: false shard_group_duration: description: - - Determines the size of a shard group. - - Value needs to be integer literal followed immediately (with no spaces) by a duration unit. - Supported duration units are C(h) for hours, C(d) for days, and C(w) for weeks. For example C(10d), C(1h), C(2w). + - Determines the time range covered by a shard group. If specified it + must be at least one hour. If none, it's determined by InfluxDB by + the rentention policy's duration. Supports complex duration expressions + with multiple units. type: str version_added: '2.0.0' extends_documentation_fragment: @@ -96,6 +99,17 @@ EXAMPLES = r''' ssl: no validate_certs: no shard_group_duration: 1w + +- name: Create retention policy with complex durations + community.general.influxdb_retention_policy: + hostname: "{{influxdb_ip_address}}" + database_name: "{{influxdb_database_name}}" + policy_name: test + duration: 5d1h30m + replication: 1 + ssl: no + validate_certs: no + shard_group_duration: 1d10h30m ''' RETURN = r''' @@ -115,6 +129,51 @@ from ansible_collections.community.general.plugins.module_utils.influxdb import from ansible.module_utils._text import to_native +VALID_DURATION_REGEX = re.compile(r'^(\d+(ns|u|µ|ms|s|m|h|d|w))+$') + +DURATION_REGEX = re.compile(r'(\d+)(ns|u|µ|ms|s|m|h|d|w)') +EXTENDED_DURATION_REGEX = re.compile(r'(?:(\d+)(ns|u|µ|ms|m|h|d|w)|(\d+(?:\.\d+)?)(s))') + + +def check_duration_literal(value): + return VALID_DURATION_REGEX.search(value) is not None + + +def parse_duration_literal(value, extended=False): + duration = 0.0 + + if value == "INF": + return duration + + lookup = (EXTENDED_DURATION_REGEX if extended else DURATION_REGEX).findall(value) + + for duration_literal in lookup: + if extended and duration_literal[3] == 's': + duration_val = float(duration_literal[2]) + duration += duration_val * 1000 * 1000 * 1000 + else: + duration_val = int(duration_literal[0]) + + if duration_literal[1] == 'ns': + duration += duration_val + elif duration_literal[1] == 'u' or duration_literal[1] == 'µ': + duration += duration_val * 1000 + elif duration_literal[1] == 'ms': + duration += duration_val * 1000 * 1000 + elif duration_literal[1] == 's': + duration += duration_val * 1000 * 1000 * 1000 + elif duration_literal[1] == 'm': + duration += duration_val * 1000 * 1000 * 1000 * 60 + elif duration_literal[1] == 'h': + duration += duration_val * 1000 * 1000 * 1000 * 60 * 60 + elif duration_literal[1] == 'd': + duration += duration_val * 1000 * 1000 * 1000 * 60 * 60 * 24 + elif duration_literal[1] == 'w': + duration += duration_val * 1000 * 1000 * 1000 * 60 * 60 * 24 * 7 + + return duration + + def find_retention_policy(module, client): database_name = module.params['database_name'] policy_name = module.params['policy_name'] @@ -129,6 +188,11 @@ def find_retention_policy(module, client): break except requests.exceptions.ConnectionError as e: module.fail_json(msg="Cannot connect to database %s on %s : %s" % (database_name, hostname, to_native(e))) + + if retention_policy is not None: + retention_policy["duration"] = parse_duration_literal(retention_policy["duration"], extended=True) + retention_policy["shardGroupDuration"] = parse_duration_literal(retention_policy["shardGroupDuration"], extended=True) + return retention_policy @@ -140,6 +204,21 @@ def create_retention_policy(module, client): default = module.params['default'] shard_group_duration = module.params['shard_group_duration'] + if not check_duration_literal(duration): + module.fail_json(msg="Failed to parse value of duration") + + influxdb_duration_format = parse_duration_literal(duration) + if influxdb_duration_format != 0 and influxdb_duration_format < 3600000000000: + module.fail_json(msg="duration value must be at least 1h") + + if shard_group_duration is not None: + if not check_duration_literal(shard_group_duration): + module.fail_json(msg="Failed to parse value of shard_group_duration") + + influxdb_shard_group_duration_format = parse_duration_literal(shard_group_duration) + if influxdb_shard_group_duration_format < 3600000000000: + module.fail_json(msg="shard_group_duration value must be at least 1h") + if not module.check_mode: try: if shard_group_duration: @@ -159,38 +238,30 @@ def alter_retention_policy(module, client, retention_policy): replication = module.params['replication'] default = module.params['default'] shard_group_duration = module.params['shard_group_duration'] - duration_regexp = re.compile(r'(\d+)([hdw]{1})|(^INF$){1}') + changed = False - duration_lookup = duration_regexp.search(duration) + if not check_duration_literal(duration): + module.fail_json(msg="Failed to parse value of duration") - if duration_lookup.group(2) == 'h': - influxdb_duration_format = '%s0m0s' % duration - elif duration_lookup.group(2) == 'd': - influxdb_duration_format = '%sh0m0s' % (int(duration_lookup.group(1)) * 24) - elif duration_lookup.group(2) == 'w': - influxdb_duration_format = '%sh0m0s' % (int(duration_lookup.group(1)) * 24 * 7) - elif duration == 'INF': - influxdb_duration_format = '0' + influxdb_duration_format = parse_duration_literal(duration) + if influxdb_duration_format != 0 and influxdb_duration_format < 3600000000000: + module.fail_json(msg="duration value must be at least 1h") - if shard_group_duration: - shard_group_duration_lookup = duration_regexp.search(shard_group_duration) - if not shard_group_duration_lookup: - module.fail_json( - msg="Failed to parse value of shard_group_duration. Please see the documentation for valid values") - if shard_group_duration_lookup.group(2) == 'h': - influxdb_shard_group_duration_format = '%s0m0s' % duration - elif shard_group_duration_lookup.group(2) == 'd': - influxdb_shard_group_duration_format = '%sh0m0s' % (int(shard_group_duration_lookup.group(1)) * 24) - elif shard_group_duration_lookup.group(2) == 'w': - influxdb_shard_group_duration_format = '%sh0m0s' % (int(shard_group_duration_lookup.group(1)) * 24 * 7) + if shard_group_duration is None: + influxdb_shard_group_duration_format = retention_policy["shardGroupDuration"] else: - influxdb_shard_group_duration_format = retention_policy['shardGroupDuration'] + if not check_duration_literal(shard_group_duration): + module.fail_json(msg="Failed to parse value of shard_group_duration") - if (not retention_policy['duration'] == influxdb_duration_format or - not retention_policy['replicaN'] == int(replication) or - not retention_policy['shardGroupDuration'] == influxdb_shard_group_duration_format or - not retention_policy['default'] == default): + influxdb_shard_group_duration_format = parse_duration_literal(shard_group_duration) + if influxdb_shard_group_duration_format < 3600000000000: + module.fail_json(msg="shard_group_duration value must be at least 1h") + + if (retention_policy['duration'] != influxdb_duration_format or + retention_policy['shardGroupDuration'] != influxdb_shard_group_duration_format or + retention_policy['replicaN'] != int(replication) or + retention_policy['default'] != default): if not module.check_mode: try: client.alter_retention_policy(policy_name, database_name, duration, replication, default,