From 72b78e261f61f89a8fdac84c9ba1ce1f1ac933a5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mina=20Gali=C4=87?= Date: Mon, 14 Aug 2023 23:32:14 +0000 Subject: [PATCH] analyze: fix (unexpected) timestamp parsing In case of very unexpected timestamps, we pass them on to date. Unfortunately, only GNU date is able to perform this level of deduction (guess work). Rework the code and tests to look for GNU date on non-Linux platforms. Only fail, by throwing an Exception! if GNU date cannot be found. add `timestampstr` to that exception, so we know why we're in this code path to begin with. Sponsored by: The FreeBSD Foundation This patch (partially) Fixes: GH-4333 Co-authored-by: Brett Holman --- cloudinit/analyze/dump.py | 15 ++++++++++++--- tests/unittests/analyze/test_dump.py | 8 ++++++-- 2 files changed, 18 insertions(+), 5 deletions(-) diff --git a/cloudinit/analyze/dump.py b/cloudinit/analyze/dump.py index 6e61648f991..60a2fd9b28d 100644 --- a/cloudinit/analyze/dump.py +++ b/cloudinit/analyze/dump.py @@ -4,7 +4,7 @@ import sys from datetime import datetime -from cloudinit import atomic_helper, subp +from cloudinit import atomic_helper, subp, util stage_to_description = { "finished": "finished running cloud-init", @@ -44,14 +44,23 @@ def parse_timestamp(timestampstr): dt = datetime.strptime(timestampstr, CLOUD_INIT_ASCTIME_FMT) timestamp = dt.strftime("%s.%f") else: - # allow date(1) to handle other formats we don't expect + # allow GNU date(1) to handle other formats we don't expect + # This may throw a ValueError if no GNU date can be found timestamp = parse_timestamp_from_date(timestampstr) return float(timestamp) def parse_timestamp_from_date(timestampstr): - out, _ = subp.subp(["date", "+%s.%3N", "-d", timestampstr]) + cmd = "date" + if not util.is_Linux(): + if subp.which("gdate"): + cmd = "gdate" + else: + raise ValueError( + f"Unable to parse timestamp without GNU date: [{timestampstr}]" + ) + out, _ = subp.subp([cmd, "+%s.%3N", "-d", timestampstr]) timestamp = out.strip() return float(timestamp) diff --git a/tests/unittests/analyze/test_dump.py b/tests/unittests/analyze/test_dump.py index 04c896e25c2..3fb0046aa59 100644 --- a/tests/unittests/analyze/test_dump.py +++ b/tests/unittests/analyze/test_dump.py @@ -11,7 +11,7 @@ parse_timestamp, ) from cloudinit.subp import which -from cloudinit.util import write_file +from cloudinit.util import is_Linux, write_file from tests.unittests.helpers import mock, skipIf @@ -44,7 +44,11 @@ def test_parse_timestamp_handles_journalctl_format_adding_year(self): assert float(dt.strftime("%s.%f")) == parse_timestamp(journal_stamp) @skipIf(not which("date"), "'date' command not available.") - @pytest.mark.allow_subp_for("date") + @skipIf( + not is_Linux() and not which("gdate"), + "'GNU date' command not available.", + ) + @pytest.mark.allow_subp_for("date", "gdate") def test_parse_unexpected_timestamp_format_with_date_command(self): """Dump sends unexpected timestamp formats to date for processing.""" new_fmt = "%H:%M %m/%d %Y"