Jordan Borean 2 weeks ago committed by GitHub
commit 5b34c69465
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

@ -0,0 +1,8 @@
deprecated_features:
- >-
Deprecated YAML parsing of unquoted datetime strings like ``2002-12-14``, ``2001-12-15 2:59:43.10``, ``2001-12-15T02:59:43.1Z``,
into a datetime object. Parsing as a datetime object will change the value used when using the value,
for example as a module option. To opt into the new behaviour before Ansible 2.21, quote the YAML value so it stays a string.
To preserve the existing behaviour use the ``to_datetime`` filter, for example ``key: 2023-05-19T11:49:52Z`` would now be
``key: '{{ "2023-05-19T11:49:52Z" | to_datetime("%Y-%m-%dT%H:%M:%S%z") }}'``. Jinja2 native needs to be enabled to
preserve the datetime value outside the template.

@ -118,6 +118,22 @@ class AnsibleConstructor(SafeConstructor):
data.extend(self.construct_sequence(node))
data.ansible_pos = self._node_position_info(node)
def construct_yaml_timestamp(self, node):
ds, line, column = self._node_position_info(node)
msg = (
f"Found YAML value '{node.value}' that has been converted to a datetime object from "
f"'{ds}', line {line}, column {column}. YAML parsing of unquoted datetime values "
"has been deprecated as it changes the value when converted back to a string. "
"To avoid this deprecation warning and opt into the future behaviour, "
"quote the value or add the '!!str'."
)
display.deprecated(msg, version="2.21")
# In 2.21 change the constructor for 'tag:yaml.org,2002:timestamp'
# below to use construct_yaml_str instead.
dt_value = super().construct_yaml_timestamp(node)
return dt_value
def construct_yaml_unsafe(self, node):
try:
constructor = getattr(node, 'id', 'object')
@ -165,6 +181,10 @@ AnsibleConstructor.add_constructor(
u'tag:yaml.org,2002:seq',
AnsibleConstructor.construct_yaml_seq)
AnsibleConstructor.add_constructor(
'tag:yaml.org,2002:timestamp',
AnsibleConstructor.construct_yaml_timestamp)
AnsibleConstructor.add_constructor(
u'!unsafe',
AnsibleConstructor.construct_yaml_unsafe)

@ -0,0 +1,25 @@
- hosts: localhost
gather_facts: false
vars:
plain_scalar: 2023-05-19T11:49:01Z
tag_shorthand: !!timestamp 2023-05-19T11:49:02Z
tag_full: !<tag:yaml.org,2002:timestamp> 2023-05-19T11:49:03Z
quoted: '2023-05-19T11:49:01Z'
tag_str: !!str 2023-05-19T11:49:01Z
template_str: '{{ "2023-05-19T11:49:01Z" | string }}'
template_to_dt: '{{ "2023-05-19T11:49:01Z" | to_datetime("%Y-%m-%dT%H:%M:%S%z") }}'
tasks:
- set_fact:
parse_result:
plain_scalar: {v: '{{ plain_scalar | string }}', t: '{{ plain_scalar | type_debug }}'}
tag_shorthand: {v: '{{ tag_shorthand | string }}', t: '{{ tag_shorthand | type_debug }}'}
tag_full: {v: '{{ tag_full | string }}', t: '{{ tag_full | type_debug }}'}
quoted: {v: '{{ quoted | string }}', t: '{{ quoted | type_debug }}'}
tag_str: {v: '{{ tag_str | string }}', t: '{{ tag_str | type_debug }}'}
template_str: {v: '{{ template_str | string }}', t: '{{ template_str | type_debug }}'}
template_to_dt: {v: '{{ template_to_dt | string }}', t: '{{ template_to_dt | type_debug }}'}
- name: print result
debug:
var: parse_result

@ -0,0 +1,48 @@
# Copyright: (c) 2024, Ansible Project
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import annotations
import json
def parse_datetime_result(value):
res_split = value.split("ok: [localhost] => ")[1]
raw_result = res_split.split("PLAY RECAP")[0]
return json.loads(raw_result)["parse_result"]
def parse_datetime_warnings(value):
warnings = []
buffer = []
start_capture = False
for line in value.split("\n"):
if not start_capture:
if line.startswith("[DEPRECATION WARNING]: "):
start_capture = True
line = line[23:]
else:
continue
elif line.startswith("[DEPRECATION WARNING]: "):
warnings.append("".join(buffer).strip())
buffer = []
line = line[23:]
buffer.append(line)
if buffer:
warnings.append("".join(buffer).strip())
return warnings
class FilterModule:
def filters(self):
return {
'parse_datetime_result': parse_datetime_result,
'parse_datetime_warnings': parse_datetime_warnings,
}

@ -32,6 +32,66 @@
- '"found a duplicate dict key (foo)" not in duplicate_ignore.stderr'
- duplicate_ignore.rc == 0
- name: Test datetime parsing logic
command: ansible-playbook {{ role_path }}/datetime.yml
environment:
ANSIBLE_JINJA2_NATIVE: '{{ item }}'
ANSIBLE_NOCOLOR: 'true'
ANSIBLE_FORCE_COLOR: 'false'
ANSIBLE_DEPRECATION_WARNINGS: 'true'
loop:
- true
- false
register: datetime_result_raw
- name: Parse datetime results
set_fact:
datetime_jnative_true_result: '{{ datetime_result_raw.results[0].stdout | parse_datetime_result }}'
datetime_jnative_true_warnings: '{{ datetime_result_raw.results[0].stderr | parse_datetime_warnings }}'
datetime_jnative_false_result: '{{ datetime_result_raw.results[1].stdout | parse_datetime_result }}'
datetime_jnative_false_warnings: '{{ datetime_result_raw.results[1].stderr | parse_datetime_warnings }}'
- name: Assert datetime parsing logic
assert:
that:
- datetime_jnative_true_result.plain_scalar.v == '2023-05-19 11:49:01+00:00'
- datetime_jnative_true_result.plain_scalar.t == 'datetime'
- datetime_jnative_true_result.tag_shorthand.v == '2023-05-19 11:49:02+00:00'
- datetime_jnative_true_result.tag_shorthand.t == 'datetime'
- datetime_jnative_true_result.tag_full.v == '2023-05-19 11:49:03+00:00'
- datetime_jnative_true_result.tag_full.t == 'datetime'
- datetime_jnative_true_result.quoted.v == '2023-05-19T11:49:01Z'
- datetime_jnative_true_result.quoted.t != 'datetime'
- datetime_jnative_true_result.tag_str.v == '2023-05-19T11:49:01Z'
- datetime_jnative_true_result.tag_str.t != 'datetime'
- datetime_jnative_true_result.template_str.v == '2023-05-19T11:49:01Z'
- datetime_jnative_true_result.template_str.t != 'datetime'
- datetime_jnative_true_result.template_to_dt.v == '2023-05-19 11:49:01+00:00'
- datetime_jnative_true_result.template_to_dt.t == 'datetime'
- datetime_jnative_false_result.plain_scalar.v == '2023-05-19 11:49:01+00:00'
- datetime_jnative_false_result.plain_scalar.t == 'datetime'
- datetime_jnative_false_result.tag_shorthand.v == '2023-05-19 11:49:02+00:00'
- datetime_jnative_false_result.tag_shorthand.t == 'datetime'
- datetime_jnative_false_result.tag_full.v == '2023-05-19 11:49:03+00:00'
- datetime_jnative_false_result.tag_full.t == 'datetime'
- datetime_jnative_false_result.quoted.v == '2023-05-19T11:49:01Z'
- datetime_jnative_false_result.quoted.t != 'datetime'
- datetime_jnative_false_result.tag_str.v == '2023-05-19T11:49:01Z'
- datetime_jnative_false_result.tag_str.t != 'datetime'
- datetime_jnative_false_result.template_str.v == '2023-05-19T11:49:01Z'
- datetime_jnative_false_result.template_str.t != 'datetime'
- datetime_jnative_false_result.template_to_dt.v == '2023-05-19 11:49:01+00:00'
- datetime_jnative_false_result.template_to_dt.t != 'datetime'
- datetime_jnative_true_warnings | count == 3
- >-
"Found YAML value '2023-05-19T11:49:01Z' that has been converted to a datetime object from '" ~ role_path ~ "/datetime.yml', line 4, column 19" in datetime_jnative_true_warnings[0]
- >-
"Found YAML value '2023-05-19T11:49:02Z' that has been converted to a datetime object from '" ~ role_path ~ "/datetime.yml', line 5, column 20" in datetime_jnative_true_warnings[1]
- >-
"Found YAML value '2023-05-19T11:49:03Z' that has been converted to a datetime object from '" ~ role_path ~ "/datetime.yml', line 6, column 15" in datetime_jnative_true_warnings[2]
- datetime_jnative_true_warnings == datetime_jnative_false_warnings
- name: test unsafe YAMLism
import_tasks: unsafe.yml

Loading…
Cancel
Save