use base "installedtest"; use strict; use JSON::PP; use Time::Piece; use testapi; use utils; # This test checks that the descriptions in /etc/os-release file are correct and that they # match the current version. sub strip_marks { # Remove the quotation marks from the string: my $string = shift; $string =~ tr/"//d; return $string; } sub json_to_hash { # This will convert a Json string into a valid # Perl hash for further processing. my $json = shift; my $hash; eval { my $hash = JSON::PP->new->utf8->decode($json); }; die("Failed to parse JSON: $@") if ($@); return $hash; } sub date_to_epoch { # This takes the date in YYYY-MM-DD and converts it into # the epoch integer. my $dstring = shift; my $date = Time::Piece->strptime($dstring, '%Y-%m-%d'); my $epoch = $date->epoch; return $epoch; } sub epoch_to_date { # This will convert the epoch integer into YYYY-MM-DD. my $epoch = shift; my $time = localtime($epoch); my $date = $time->strftime('%Y-%m-%d'); return $date; } sub get_bodhi_eol { # This reads the Bodhi info file (downloaded in collect_web_data.pm), # parses it and returns the EOL date from that file. # As argument it takes the version number from which the EOL # date should be returned. my $ver = shift; # The content of the downloaded file is a JSON string. my $json = script_output("cat ~/version_data/bodhi-$ver.json"); my $bodhi = json_to_hash($json); my $eol = $bodhi->{"eol"}; $eol = date_to_epoch($eol); return $eol; } sub get_schedule_eol { # This reads the Fedora Schedule info file (downloaded # previously in collect_web_data.pm), parses it and returns # the EOL date from that file. As argument, it takes the version # number from which the EOL date should be returned. my $ver = shift; my $json = script_output("cat ~/version_data/schedule-$ver.json"); my $schedule = json_to_hash($json); my $eol; # The format of the json is quite complicated, so we need to do # quite a lot of magic to arrive at the correct date, so let's # hope the format stays the same in the future. # FIXME parsing the tasks my $tasks = $json->{tasks}[0]{tasks}[0]{tasks}; my $eol; foreach my $task (@$tasks) { if ($task->{name} && $task->{name} =~ /End of Life/) { $eol = $task->{end}; last; } } # The EOL date is provided as an epoch, so 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 will take the EOL date from the /etc/os-release # file and it will check 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 will take the EOL dates from the /etc/os-release # file and it will compare 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); # The resulting code will be 0 if the local SUPPORT_END cannot be validated # against Bodhi or Schedule and os-release is the only source. my $rcode = 0; my $overall = 1; # Change the exit code based on the resulting condition. We are only # interested in the first condition really, but we want to know what # the situation is exactly, so we will test all options. 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; } $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 run { # First, let us define some variables needed to run the program. my $self = shift; # The file to be checked my $filename = '/etc/os-release'; # Read the content of the file to compare. Let us parse the file # and create a hash with those values, so that we can easily access # them and assert them. my $infile = script_output "cat /etc/os-release"; my @infile = split /\n/, $infile; my %content = (); foreach (@infile) { chomp $_; my ($key, $value) = split /=/, $_; $content{$key} = $value; } # Now, we have all the data ready and we can start testing, first let us get # correct variables to compare the system data with. # First, we know the basic stuff # Should be "fedora" my $id = get_var("DISTRI"); # extract expected version components from ISO name for canned variants, # which have their os-release rewritten by rpm-ostree, see: # https://github.com/projectatomic/rpm-ostree/blob/master/docs/manual/treefile.md # we use the ISO name because rpm-ostree uses elements from the compose # ID for nightlies, but from the label for candidate composes; BUILD # always gives us the compose ID, but the ISO name contains the compose # ID for nightlies but the label for candidate composes, so it works for # our purposes here. my $isovar = get_var("ISO"); # Split the ISO variable at "-" and read second-to-last (release # number) and last (compose ID: date and respin, label: major and # minor) fields. my ($cannedver, $cannednum) = (split /-/, $isovar)[-2, -1]; # Get rid of the ".iso" part of the tag. $cannednum =~ s/\.iso//g; # also get rid of the arch, which osbuild puts in here my $arch = get_var("ARCH"); $cannednum =~ s/\.$arch//g; # Now, we merge the fields into one expression to create the correct canned tag # that will contain both the version number and the build number. my $cannedtag = "$cannedver.$cannednum"; # If this is a CoreOS build, though, throw all that away and # just use the build version my $build = get_var("BUILD"); if ($build =~ /^Fedora-CoreOS/) { $cannedtag = (split /-/, $build)[-1]; } my $name = ucfirst($id); my $fullname = $name . " Linux"; my $rawrel = get_var("RAWREL", ''); # Should be the version number or Rawhide. my $version_id = get_var("VERSION"); # IoT has a branch that acts more or less like Rawhide, but has # its version as the Rawhide release number, not 'Rawhide'. This # handles that $version_id = 'Rawhide' if ($version_id eq $rawrel); my $varstr = spell_version_number($version_id); my $target = lc($version_id); $version_id = $rawrel if ($version_id eq "Rawhide"); # the 'generic' os-release in fedora-release has no VARIANT or # VARIANT_ID and the string used in values like VERSION, that in other # cases is the VARIANT, is 'Rawhide' for Rawhide and the spelt version # number for other releases. These are the values we'll see for an # Everything image. my $variant_id = ""; my $variant = "generic"; # now replace the values with the correct ones if we are testing a # subvariant that maps to a known variant my $subvariant = get_var('SUBVARIANT'); my %variants = ( Server => ["server", "Server Edition"], Workstation => ["workstation", "Workstation Edition"], AtomicHost => ["atomic.host", "Atomic Host"], CoreOS => ["coreos", "CoreOS"], KDE => ["kde", "KDE Plasma"], Silverblue => ["silverblue", "Silverblue"], IoT => ["iot", "IoT Edition"], ); if (exists($variants{$subvariant})) { ($variant_id, $variant) = @{$variants{$subvariant}}; $varstr = $variant; } # If fedora-release-common release starts with a 0, we'll have # "Prerelease" in varstr my $reltag = script_output 'rpm -q fedora-release-common --qf "%{RELEASE}\n"'; if (index($reltag, "0.") == 0) { $varstr .= " Prerelease"; # ...however, we shouldn't just wave this through if we're # an RC candidate or update compose, those should never be # done with a 0.x fedora-release-common. so let's blow up # here if so. unless it's IoT, because IoT is weird my $label = get_var("LABEL"); if ($label =~ /^(RC|Update)-/ && $subvariant ne "IoT") { die "RC candidate or update compose should not have 0.x versioned fedora-release!"; } } my $version = "$version_id ($varstr)"; # for canned variants, we need to form a different string here by using # the above created cannedtag. See earlier comment if (get_var("CANNED")) { $version = "$cannedtag ($varstr)"; } my $platform_id = "platform:f$version_id"; my $pretty = "$fullname $version_id ($varstr)"; # Same problem is when testing the PRETTY_NAME. if (get_var("CANNED")) { $pretty = "$fullname $cannedtag ($varstr)"; # ...and FCOS uses a different format, sigh if ($build =~ /^Fedora-CoreOS/) { $pretty = "Fedora CoreOS $cannedtag"; } } #Now. we can start testing the real values from the installed system. my @fails = (); my $failref = \@fails; # Test for name my $strip = strip_marks($content{'NAME'}); rec_log "NAME should be $fullname and is $strip", $strip eq $fullname, $failref; # Test for version. $strip = strip_marks($content{'VERSION'}); rec_log "VERSION should be $version and is $strip", $strip eq $version, $failref; # Test for version_id rec_log "VERSION_ID should be $version_id and is $content{'VERSION_ID'}", $content{'VERSION_ID'} eq $version_id, $failref; # Test for platform_id $strip = strip_marks($content{'PLATFORM_ID'}); rec_log "PLATFORM_ID should be $platform_id and is $strip", $strip eq $platform_id, $failref; # Test for pretty name $strip = strip_marks($content{'PRETTY_NAME'}); rec_log "PRETTY_NAME should be $pretty and is $strip", $strip eq $pretty, $failref; # Test for RH Bugzilla Product $strip = strip_marks($content{'REDHAT_BUGZILLA_PRODUCT'}); rec_log "REDHAT_BUGZILLA_PRODUCT should be $name and is $strip", $strip eq $name, $failref; # Test for RH Bugzilla Product Version rec_log "REDHAT_BUGZILLA_PRODUCT_VERSION should be $target and is $content{'REDHAT_BUGZILLA_PRODUCT_VERSION'}", $content{'REDHAT_BUGZILLA_PRODUCT_VERSION'} eq $target, $failref; # Test for RH Support Product $strip = strip_marks($content{'REDHAT_SUPPORT_PRODUCT'}); rec_log "REDHAT_SUPPORT_PRODUCT should be $name and is $strip", $strip eq $name, $failref; # Test for RH Support Product Version rec_log "REDHAT_SUPPORT_PRODUCT_VERSION should be $target and is $content{'REDHAT_SUPPORT_PRODUCT_VERSION'}", $content{'REDHAT_SUPPORT_PRODUCT_VERSION'} eq $target, $failref; # Test for Variant but only in case of Server or Workstation if ($variant ne "generic") { $strip = strip_marks($content{'VARIANT'}); rec_log "VARIANT should be $variant and is $strip", $strip eq $variant, $failref; # Test for VARIANT_ID rec_log "VARIANT_ID should be $variant_id and is $content{'VARIANT_ID'}", $content{'VARIANT_ID'} eq $variant_id, $failref; } else { print "VARIANT was not tested because the compose is not Workstation or Server Edition.\n"; print "VARIANT_ID was not tested because the compose is not Workstation or Server Edition.\n"; } # 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); # Test for EOL dates match each other. $result = check_eols_match($os_release_eol); rec_log($result->[1], $result->[0] == 1, $failref); # Check for fails, count them, collect their messages and die if something was found. my $failcount = scalar @fails; script_run "echo \"There were $failcount failures in total.\" >> /tmp/os-release.log"; upload_logs "/tmp/os-release.log", failok => 1; my $failmessages = ""; foreach my $fail (@fails) { $failmessages .= "\n" . $fail; } die $failmessages if ($failcount > 0); } sub test_flags { return {always_rollback => 1}; } 1; # vim: set sw=4 et: