diff --git a/check-release.py b/check-release.py new file mode 100755 index 00000000..df5a0bc9 --- /dev/null +++ b/check-release.py @@ -0,0 +1,209 @@ +#!/usr/bin/python3 + +# Copyright Red Hat +# +# This file is part of os-autoinst-distri-fedora. +# +# os-autoinst-distri-fedora is free software; you can redistribute it +# and/or modify it under the terms of the GNU General Public License +# as published by the Free Software Foundation, either version 2 of +# the License, or (at your option) any later version. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program. If not, see . +# +# Author: Lukas Ruzicka + + +""" +This script provides a simple test for Eof of Life dates on Fedora. +You can use it for two types of testing. The first test checks that +the SUPPORT_END value is at least a year ahead (in the time of testing), +which it should be. The second test checks if the End of Life date is +consisant across the three sources, the os-release file, Bodhi, and Fedora +Schedule. + +When the test passes, it returns 0, otherwise there is one of the error codes. +""" + + +import argparse +import sys +from datetime import date, datetime, timedelta +import requests + +VERBOSE = False +RESULT = 100 + + +def cli(): + """Return the CLI arguments.""" + + parser = argparse.ArgumentParser( + description="Fedora '/etc/os-release' support date validator." + ) + + parser.add_argument( + "--test", + "-t", + type=str, + required=True, + help="Test to perform [future, compare]", + ) + + parser.add_argument( + "--release", + "-r", + type=str, + required=False, + help="Fedora release number (42, 43, ...)", + ) + + parser.add_argument( + "--verbose", + "-v", + action="store_true", + help="Prints detailed info on the screen.", + ) + + args = parser.parse_args() + return args + + +def log(*args, **kwargs): + """Print out messages on CLI if VERBOSE.""" + if VERBOSE: + print(*args, **kwargs) + else: + pass + + +def epochdate(epoch: int) -> date: + """Return the date object calculated from the epoch integer.""" + converted = datetime.fromtimestamp(epoch) + return converted.date() + + +def isodate(iso: str) -> date: + """Return the date object calculated from the ISO format.""" + converted = date.fromisoformat(iso) + return converted + + +def get_file_support() -> date: + """Returns the support date from the os-release file.""" + with open("/etc/os-release", "r", encoding="utf-8") as release: + lines = release.readlines() + log("The /etc/os-release successfully read.") + + support_day = epochdate(0) + for line in lines: + if "SUPPORT_END" in line: + _, value = line.split("=") + value = value.strip() + if value: + support_day = isodate(value) + return support_day + + +def support_date_in_future(eol: date) -> bool: + """This function checks the support date from the os-release + file, compares it with the current system date and tests if + the os-release support date lies at least 12 months in the future.""" + + # Get the necessary values from the operating system. + today = datetime.today().date() + log("Current date on tested system is:", today) + tomorrow = today + timedelta(days=365) + log("Minimal SUPPORT_END calculated from system time is:", tomorrow) + log("Real /etc/os-release SUPPORT_END is:", eol) + + # Test if the support end date is in the future. + result = False + if eol >= tomorrow: + log("Real SUPPORT_END is one year in the future.") + result = 0 + else: + log("Real SUPPORT_END is NOT one year in the future.") + result = 1 + return result + + +def compare_eol_dates(release: int, eol: date) -> bool: + """This function checks the support date on Fedora Schedule, Bodhi + and the os-release file and compares them whether they are the same + and fails if they are not.""" + log("The EOL date shown by the os-release file is:", eol.isoformat()) + # Get the Bodhi EOL date + bodhi_response = requests.get( + f"https://bodhi.fedoraproject.org/releases/F{release}", timeout=60 + ) + bodhi = bodhi_response.json() + # Only convert the date if it is present, otherwise record 0. + if bodhi["eol"]: + bodhi_eol = isodate(bodhi["eol"]) + else: + bodhi_eol = epochdate(0) + log("The EOL date shown by Bodhi is:", bodhi_eol.isoformat()) + + # Get the Schedule EOL date + schedule_response = requests.get( + f"https://fedorapeople.org/groups/schedule/f-{release}/" + f"f-{release}-key.json", + timeout=60, + ) + schedule = schedule_response.json() + tasks = schedule["tasks"][0]["tasks"][0]["tasks"] + schedule_eol = epochdate(0) + for task in tasks: + if "End of Life" in task["name"]: + schedule_eol = epochdate(int(task["end"])) + break + log("The EOL date shown by Fedora Schedule is:", schedule_eol.isoformat()) + + # Compare the dates + result = None + if eol == bodhi_eol and eol == schedule_eol: + log("All EOL dates have the same value.") + result = 0 + elif eol == bodhi_eol: + log("The os-release matches Bodhi but Fedora Schedule is different.") + result = 1 + elif eol == schedule_eol: + log("The os-release matches Fedora Schedule but Bodhi is different.") + result = 2 + elif bodhi_eol == schedule_eol: + log("Bodhi matches Fedora Schedule, but os-release is different.") + result = 3 + else: + log("All EOL dates have different values.") + result = 4 + return result + + +arguments = cli() +VERBOSE = arguments.verbose +os_release_eol = get_file_support() + +RESULT = 100 +if arguments.test == "compare": + if not arguments.release: + print("Please, run again with --release option. Finishing.") + sys.exit(5) + + RESULT = compare_eol_dates(arguments.release, os_release_eol) +else: + RESULT = support_date_in_future(os_release_eol) + +if RESULT != 0: + log("Test failed.") +else: + log("Test passed.") + +# Sysexit based on the results. +sys.exit(RESULT) diff --git a/tests/os_release.pm b/tests/os_release.pm index acaa7eba..89965214 100644 --- a/tests/os_release.pm +++ b/tests/os_release.pm @@ -14,136 +14,11 @@ sub strip_marks { return $string; } -sub json_to_hash { - # Take a string formed as json and return a valid Perl hash - # for further processing. - my $json = shift; - my $hash = decode_json($json); - # If the json is not decoded correctly, fail. - die("Failed to parse JSON: $@") if ($@); - return $hash; -} - -sub date_to_epoch { - # Take the date as YYYY-MM-DD and convert it into epoch. - my $dstring = shift; - my $date = Time::Piece->strptime($dstring, '%Y-%m-%d'); - my $epoch = $date->epoch; - return $epoch; -} - -sub epoch_to_date { - # Take the epoch and return YYYY-MM-DD. - my $epoch = shift; - my $time = localtime($epoch); - my $date = $time->strftime('%Y-%m-%d'); - return $date; -} - -sub get_bodhi_eol { - # Load the Bodhi json file and return the EOL epoch. - my $ver = shift; - # Use the openQA script output to read the json file. We use jq to make - # sure that the content is a valid json file. - my $json = script_output("cat ~/version_data/bodhi-$ver.json | jq -c"); - my $bodhi = json_to_hash($json); - my $eol = $bodhi->{"eol"}; - $eol = date_to_epoch($eol); - return $eol; -} - -sub get_schedule_eol { - # Load the Fedora Schedule json file and return the EOL epoch. - my $ver = shift; - my $json = script_output("cat ~/version_data/schedule-$ver.json | jq -c"); - my $schedule = json_to_hash($json); - my $eol; - # Identify the events and find the EOL field. - my $tasks = $schedule->{'tasks'}[0]{'tasks'}[0]{'tasks'}; - my $eol; - foreach my $task (@$tasks) { - if ($task->{'name'} and $task->{'name'} =~ /End of Life/) { - $eol = $task->{'end'}; - last; - } - } - # The EOL is already formed as epoch, just return it. - return $eol; -} - -sub get_current_date { - # This returns the current date in as the epoch and YYYY-MM-DD. - # which we need to see if the EOL is correctly set in the future. - my $time = localtime; - my $dates = {}; - $dates->{'date'} = $time->strftime('%Y-%m-%d'); - $dates->{'epoch'} = date_to_epoch($dates->{'date'}); - return $dates; -} - -sub check_eol_in_year { - # This reads the EOL date from the /etc/os-release - # file and checks that it is at least a year in - # the future (when tested on non-published ISOs). - # Returns true if successful. - my $tested = shift; - $tested = date_to_epoch($tested); - my $dates = get_current_date(); - my $current = $dates->{epoch}; - # The EOL in the os-release.pm must be at least a year - # in the future, so we calculate the epoch difference - # between $tested and $current. - # An epoch year should be - # 1 * 60 (min) *60 (hour) *24 (day) *365 (year) - my $year = 1 * 60 * 60 * 24 * 365; - my $delta = $tested - $current; - my $bool = 1; - if ($delta < $year) { - $bool = 0; - } - return $bool; -} - -sub check_eols_match { - # This takes the EOL dates from the /etc/os-release - # file and compares the value with those from - # Bodhi and Fedora Schedule and will succeed when they - # match each other. - my $tested = shift; - my $version = get_release_number(); - my $bodhi = get_bodhi_eol($version); - $bodhi = epoch_to_date($bodhi); - my $schedule = get_schedule_eol($version); - $schedule = epoch_to_date($schedule); - # Let's set the return code to 0 to indicate that none of the EOL date - # can be matched with another one. - my $rcode = 0; - my $overall = 1; - # Start comparisons among the EOL dates. - if ($bodhi eq $schedule and $bodhi eq $tested) { - $rcode = 1; - } - elsif ($bodhi eq $schedule) { - $rcode = 2; - } - elsif ($tested eq $bodhi) { - $rcode = 3; - } - elsif ($tested eq $schedule) { - $rcode = 4; - } - # If the EOL dates do not match each other, consider this a test failure. - $overall = 0 if ($rcode != 1); - my $return_codes = { - 0 => "No EOL dates do match:\n\tos-release: $tested\n\tBodhi: $bodhi\n\tSchedule: $schedule", - 2 => "The os-release doesn't match Bodhi or Schedule, but they match each other:\n\tos-release: $tested\n\tBodhi: $bodhi\n\tSchedule: $schedule", - 3 => "The os-release file matches Bodhi but Schedule differs:\n\tos-release: $tested\n\tBodhi: $bodhi\n\tSchedule: $schedule", - 4 => "The os-release file matches Schedule but Bodhi differs:\n\tos-release: $tested\n\tBodhi: $bodhi\n\tSchedule: $schedule", - 1 => "All EOL dates match:\n\tos-release: $tested\n\tBodhi: $bodhi\n\tSchedule: $schedule" - }; - - my $result = [$overall, $return_codes->{$rcode}]; - return $result; +sub download_python_tests { + # Download the Python test script and change mode to rwx,rx,rx. + assert_script_run("curl https://pagure.io/fedora-qa/os-autoinst-distri-fedora/raw/os-release-addon/f/check-release.py -o ~/check-release.py --retry 10 --retry-delay 2", timeout => 60); + assert_script_run("chmod 755 ~/check-release.py", timeout => 20); + sleep(5); } sub run { @@ -316,15 +191,16 @@ sub run { } + # Download Python test script to run the tests. + download_python_tests(); # Test for EOL date in the distant future. my $os_release_eol = $content{'SUPPORT_END'}; - my $result = check_eol_in_year($os_release_eol); - my $current = get_current_date(); - rec_log("The SUPPORT_END date is $os_release_eol which is at least a year ahead in time (now is $current->{date})", $result == 1, $failref); + my $result = script_run("~/check-release.py --test future --verbose"); + rec_log("The SUPPORT_END date is $os_release_eol which is at least a year ahead in time.", $result == 0, $failref); # Test for EOL dates match each other. - $result = check_eols_match($os_release_eol); - rec_log($result->[1], $result->[0] == 1, $failref); + $result = script_run("~/check-release.py --test compare --release $version_id --verbose"); + rec_log("The End of Life dates match each other (local, Bodhi, Schedule)", $result == 0, $failref); # Check for fails, count them, collect their messages and die if something was found. my $failcount = scalar @fails;