Rev 7367 | Blame | Compare with Previous | Last modification | View Log | RSS feed
######################################################################### Copyright (C) 1998-2013 Vix Technology, All rights reserved## Module name : jats_quarantine.pl# Module type : Makefile system# Compiler(s) : Perl# Environment(s): jats## Description : Remove packages from dpkg_archive that are no longer# required - if they can be rebuilt.## Keep package version if# Cannot be rebuilt# It is in use by a non-archived release# It is a dependent package of a package in a non-archived release# It is an exposed package in an non-deprecated SDK# It is used in one of the last two SBOMs (within Deployment Manager)# defined within each state of each branch of each project.# It is a dependent package of one of the SBOM packages## Packages that are not in RM will be purged### Usage : See POD at end of file##......................................................................#require 5.008_002;use strict;use warnings;use Pod::Usage;use Getopt::Long;use JatsError;use JatsSystem;use Getopt::Long;use Pod::Usage; # required for help supportuse JatsRmApi;use ConfigurationFile;use File::Path;use File::Basename;use DBI;## Options - global#my $VERSION = "2.0.0"; # Update thismy $opt_verbose = 0;my $opt_help = 0;my $opt_manual;my $opt_test;my $opt_limit;my $opt_quick;my $opt_phase = '123'; # Default - do all, but don't save datamy $opt_purge;my $opt_pcount = 0;my $opt_explain;## Globals#my $progBase;my $RM_DB;my $DM_DB;my $now = time();my $quarantineInstance;my $logPath;my %pkgPvid;my @quarantineItems;my @StrayPackages;our %Releases;our %Packages;## Default config information# May be replaced by xxx.cnf file# qdirAge = 0 => No local quarantine#my %config = ('retain' => '31','qdirAge' => '90','snapAge' => '10','retainNoRm' => '31','quarantine' => '/export/devl/quarantine','dpkg_archive' => '/export/devl/dpkg_archive','logBase' => '/export/devl/dpkg_archive/.dpkg_archive/quarantinelog','verbose' => '0','S3Bucket' => 'auawsaddp001','S3Key' => 'Mandatory','S3Secret' => 'Mandatory',);# List of packages to be retained# May be supplemented by xxx.cnf filemy %retainPkgs = ('core_devl' => 1,);## Statistics# Listed here to ensure that they exist in the stats file#my %statistics = (timeStamp => 0, # Age of the stats filestatsName => 'Quarantine', # Name of the stats filestate => 'OK', # Overall reported state# Error countersQuarantineError => 0,S3TransferError => 0,# Major StatisticsQuarantine => 0,# Minor StatisticsfileNotInReleaseManager => 0,inDeploymentManager => 0,inSdk => 0,isPatch => 0,ManualBuild => 0,RetainTime => 0,NoBuildStandard => 0,NoPackageEntry => 0,NoPVid => 0,NotInArchive => 0,NotInReleaseManager => 0,NotLocked => 0,SecondLevelPackage => 0,TopLevelPackage => 0,TotalPackages => 0,);#-------------------------------------------------------------------------------# Function : Main Entry## Description :## Inputs :## Returns :#my $result = GetOptions ("help+" => \$opt_help, # flag, multiple use allowed"manual" => \$opt_manual, # flag"verbose:+" => \$opt_verbose, # flag"explain:+" => \$opt_explain, # flag"test:+" => \$opt_test, # Test a version string"limit:n" => \$opt_limit, #"phase:s" => \$opt_phase, # Phase to do"quick" => \$opt_quick, # Don't look for indirects"purge" => \$opt_purge, # Purge old quarantined packages"pcount:n" => \$opt_pcount, # Count of packages to purge in one hit);## Process help and manual options#pod2usage(-verbose => 0, -message => "Version: $VERSION") if ($opt_help == 1 || ! $result);pod2usage(-verbose => 1) if ($opt_help == 2 );pod2usage(-verbose => 2) if ($opt_manual || ($opt_help > 2));ErrorConfig( 'name' => 'QUARANTINE','verbose' => $opt_verbose );## This utility must be run on the package servermy $runHost = 'auawsaarc001';my $hostname = lc $ENV{HOSTNAME} || 'Unknown';Warning("Not running on $runHost") unless ( $hostname eq $runHost );## Needs to run as root so that packages can be moved no matter what the# file permissions are#Warning( "Not running as root") if ( $> );## Determine the base of this program# Will be used to find config and local utils#$progBase = $0;$progBase =~ s~/[^/]+$~~;Verbose("ProgBase: $0: $progBase");# Read config file# Use max of user verbosity or config verbosity#ReadConfig();if ( $config{verbose} > $opt_verbose ){$opt_verbose = $config{verbose};ErrorConfig( 'verbose' => $opt_verbose );}## Collect data from Release Manager#if ( $opt_phase =~ m~1~ && !$opt_purge ){getReleaseDetails();GetAllPackageData();getTopLevelPackages();GetRecentDMPackages();LocateStrays() unless ($opt_quick);GetSdkPackageData();if ( $opt_verbose > 1 ){print "=========================================================================\n";DebugDumpData("Releases", \%Releases);print "=========================================================================\n";DebugDumpData("Packages", \%Packages );}}## Scan dpkg_archive and quarantine packages#if ( $opt_phase =~ m~2~ ){prepQdir();unless ($opt_purge) {readInputData();processDpkgArchive();reportMissingPkgs();reportStats();}Verbose ("Quarantine to: $quarantineInstance");Verbose ("Log to: $logPath");}## Save internal data for reuse# Used only for testing of indiviual phases#unless ( $opt_phase =~ m~3~ ){savePhaseData();}ErrorDoExit();exit;#-------------------------------------------------------------------------------# Function : ReadConfig## Description : Read in config file# Must be inthe same directory as the executable## Inputs :## Returns :#sub ReadConfig{my $config = $0;$config =~ s~\.pl$~.cnf~;open (CF, '<', $config ) || Error ("Connot open: $config");while ( <CF> ){s~\s+$~~;s~^\s+~~;next if ( m~\s*#~ ); # Commentnext unless $_; # Emptyif ( m~(.*?)\s*=\s*(.*)~ ) {ReportError ("Unknown config value: $1") unless ( exists $config{$1} );$config{$1} = $2;} else {$retainPkgs{$_} = 1;}}close CF;ErrorDoExit();}#-------------------------------------------------------------------------------# Function : getReleaseDetails## Description : Determine all candiate releases## Inputs :## Returns :#sub getReleaseDetails{my (@row);Verbose ("Determine all Release Names");# if we are not or cannot connect then return 0 as we have not found anythingconnectRM(\$RM_DB) unless $RM_DB;# Get all Releases# From non-archived releasesmy $m_sqlstr = "SELECT prj.PROJ_NAME, rt.RTAG_NAME, rt.PROJ_ID, rt.RTAG_ID, rt.official, TRUNC (SYSDATE - rt.official_stamp) as OFFICIAL_STAMP_DAYS, TRUNC (SYSDATE - rt.created_stamp) as CREATED_STAMP_DAYS" ." FROM release_manager.release_tags rt, release_manager.projects prj" ." WHERE prj.PROJ_ID = rt.PROJ_ID " ." AND rt.official != 'A' ORDER BY UPPER(prj.PROJ_NAME), UPPER(rt.RTAG_NAME)";# " AND rt.official != 'Y'" .Verbose3("getReleaseDetails: $m_sqlstr");my $sth = $RM_DB->prepare($m_sqlstr);if ( defined($sth) ){if ( $sth->execute( ) ){if ( $sth->rows ){while ( @row = $sth->fetchrow_array ){my $rtag_id =$row[3];my $proj_id = $row[2];my $official = $row[4];my $age = defined($row[5]) ? $row[5] : $row[6];# Only retain recent snapshotif ($official eq 'S' && $age > $config{snapAge}) {next;}#if ( $official eq 'Y' ) {# Information("Closed Age ($proj_id) : $age : $row[0], $row[1]");#}# if ( $official eq 'Y' && $age && $age > 300 )# {# next;# }$Releases{$rtag_id}{pName} = $row[0];$Releases{$rtag_id}{name} = $row[1];$Releases{$rtag_id}{proj_id} = $proj_id;$Releases{$rtag_id}{rtag_id} = $rtag_id;$Releases{$rtag_id}{official} = $row[4];$Releases{$rtag_id}{officialDays} = defined($row[5]) ? $row[5] : $row[6] ;$Releases{$rtag_id}{createdDays} = $row[6];print join (',',@row), "\n" if ($opt_verbose > 2);}}$sth->finish();}else{Error("getReleaseDetails:Execute failure: $m_sqlstr", $sth->errstr() );}}else{Error("getReleaseDetails:Prepare failure" );}}#-------------------------------------------------------------------------------# Function : GetAllPackageData## Description : Extract all package data## Inputs :## Returns :#sub GetAllPackageData{my (@row);my $count = 0;# if we are not or cannot connect then return 0 as we have not found anythingconnectRM(\$RM_DB) unless $RM_DB;Verbose ("Extract all package data");# First get all packages# From non-archived releasesmy $limit = $opt_limit ? " AND rownum <= $opt_limit" : '';my $m_sqlstr = "SELECT DISTINCT " ."pv.PV_ID, " . #[0]"pkg.PKG_NAME, " . #[1]"pv.PKG_VERSION, " . #[2]"pv.DLOCKED, " . #[3]"pv.PKG_ID," . #[4]"pv.is_patch," . #[5]"pv.build_type,". #[6]"pbi.bsa_id," . #[7]# "pv.CREATOR_ID, " . #[8]# "pv.MODIFIED_STAMP, " . #[9]# "release_manager.PK_RMAPI.return_vcs_tag(pv.PV_ID), " . #[10]"999" ." FROM RELEASE_MANAGER.PACKAGE_VERSIONS pv,"."RELEASE_MANAGER.PACKAGES pkg,"."release_manager.package_build_info pbi" ." WHERE pv.PKG_ID = pkg.PKG_ID" ." AND pv.pv_id = pbi.pv_id(+)" .$limit ;Verbose3("GetAllPackageData: $m_sqlstr");my $sth = $RM_DB->prepare($m_sqlstr);if ( defined($sth) ){if ( $sth->execute( ) ){if ( $sth->rows ){while ( @row = $sth->fetchrow_array ){$count++;print join (',',@row), "\n" if ($opt_verbose > 2);my $pvid = $row[0];unless ( exists $Packages{$pvid}{name} ){$Packages{$pvid}{name} = $row[1];$Packages{$pvid}{version} = $row[2];$Packages{$pvid}{locked} = $row[3];$Packages{$pvid}{pkgid} = $row[4];$Packages{$pvid}{isPatch} = $row[5] || 0;$Packages{$pvid}{buildType} = $row[6] || 0;$Packages{$pvid}{buildStandard} = $row[7] || 0;#$Packages{$pvid}{Creator} = $row[8];#$Packages{$pvid}{Age} = $row[9];#$Packages{$pvid}{vcstag} = $row[10];}if ( $opt_limit ){last if ( $count > $opt_limit );}}}$sth->finish();}else{Error("GetAllPackageData:Execute failure: $m_sqlstr", $sth->errstr() );}}else{Error("GetAllPackageData:Prepare failure" );}Verbose ("All Packages: $count rows");}#-------------------------------------------------------------------------------# Function : getTopLevelPackages## Description : Extract top level packages from active releases## Inputs :## Returns :#sub getTopLevelPackages{my (@row);my $count = 0;# if we are not or cannot connect then return 0 as we have not found anythingconnectRM(\$RM_DB) unless $RM_DB;Verbose ("Extract toplevel dependencies");# First get all packages that are referenced in a Release# This will only get the top level packages# From non-archived releasesmy $limit = $opt_limit ? " AND rownum <= $opt_limit" : '';my $m_sqlstr = "SELECT DISTINCT " ."rc.PV_ID, " . #[0]"rt.RTAG_ID, " . #[1]"prj.PROJ_ID, " . #[2]"rt.official, " . #[3]"TRUNC (SYSDATE - rt.official_stamp),". #[4]"TRUNC (SYSDATE - rt.created_stamp)" . #[5]" FROM RELEASE_MANAGER.RELEASE_CONTENT rc, "."release_manager.release_tags rt,"."release_manager.projects prj" ." WHERE prj.PROJ_ID = rt.PROJ_ID" ." and rt.RTAG_ID = rc.RTAG_ID" ." AND rt.official != 'A'" .# " AND rt.official != 'Y' " .$limit;Verbose3("getTopLevelPackages: $m_sqlstr");my $sth = $RM_DB->prepare($m_sqlstr);if ( defined($sth) ){if ( $sth->execute( ) ){if ( $sth->rows ){while ( @row = $sth->fetchrow_array ){my $pvid = $row[0];my $rtag_id = $row[1];my $proj_id = $row[2];my $official = $row[3];my $age = defined($row[4]) ? $row[4] : $row[6];# Only retain recent snapshotif ($official eq 'S' && $age > $config{snapAge}) {next;}$count++;print join (',',@row), "\n" if ($opt_verbose > 2);$Packages{$pvid}{tlp} = 1;push @StrayPackages, $pvid;push @{$Packages{$pvid}{release}}, $rtag_id;push @{$Packages{$pvid}{projects}}, $proj_idunless (grep {$_ eq $proj_id} @{$Packages{$pvid}{projects}});if ( $opt_limit ){last if ( $count > $opt_limit );}}}$sth->finish();}else{Error("getTopLevelPackages:Execute failure: $m_sqlstr", $sth->errstr() );}}else{Error("getTopLevelPackages:Prepare failure" );}Verbose ("Extract toplevel dependencies: $count rows");}#-------------------------------------------------------------------------------# Function : GetSdkPackageData## Description : Extract Packages that are a part of a non-deprecated SDK# Only want the exposed packages## Don't care about the dependencies, so don't add them# to strays## Inputs :## Returns :#sub GetSdkPackageData{my (@row);my $count = 0;# if we are not or cannot connect then return 0 as we have not found anythingconnectRM(\$RM_DB) unless $RM_DB;Verbose ("Extract SDK Packages");# Get all packages that are a part of a non-deprecated SDK# Only get the 'exposed' packagesmy $limit = $opt_limit ? " AND rownum <= $opt_limit" : '';my $m_sqlstr = "SELECT sc.pv_id, " . #[0]" p.PKG_NAME, " . #[1]" pv.PKG_VERSION" . #[2]" FROM RELEASE_MANAGER.SDK_CONTENT sc," ." RELEASE_MANAGER.sdk_tags st," ." RELEASE_MANAGER.package_versions pv," ." RELEASE_MANAGER.PACKAGES p" ." WHERE sc.SDKTAG_ID = st.SDKTAG_ID" ." AND p.PKG_ID = pv.PKG_ID" ." AND pv.PV_ID = sc.pv_id" ." AND sc.SDKPKG_STATE = 'E'" ." AND st.SDK_STATE NOT IN ('D')" .$limit;Verbose3("GetSdkPackageData: $m_sqlstr");my $sth = $RM_DB->prepare($m_sqlstr);if ( defined($sth) ){if ( $sth->execute( ) ){if ( $sth->rows ){while ( @row = $sth->fetchrow_array ){$count++;print join (',',@row), "\n" if ($opt_verbose > 2);my $pvid = $row[0];$Packages{$pvid}{sdk} = 1;unless ( exists $Packages{$pvid}{name} ){$Packages{$pvid}{name} = $row[1];$Packages{$pvid}{version} = $row[2];}if ( $opt_limit ){last if ( $count > $opt_limit );}}}$sth->finish();}else{Error("GetSdkPackageData:Execute failure: $m_sqlstr", $sth->errstr() );}}else{Error("GetSdkPackageData:Prepare failure" );}Verbose ("Extract SDK Packages: $count rows");}#-------------------------------------------------------------------------------# Function : GetRecentDMPackages## Description : Extract Packages that referenced in Deployment Manager# Want all package-versions from the last two BOMS in each state# of all projects.## Inputs :## Returns :#sub GetRecentDMPackages{my (@row);my $count = 0;# if we are not or cannot connect then return 0 as we have not found anythingconnectDM(\$DM_DB) unless ($DM_DB);Verbose ("Extract DM Packages");# Get all packages that are a part of a non-deprecated SDK# Only get the 'exposed' packagesmy $limit = $opt_limit ? " AND rownum <= $opt_limit" : '';my $m_sqlstr ="SELECT DISTINCT pv.pv_id," . #[0]" pkg.pkg_name," . #[1]" pv.pkg_version" . #[2]" FROM DEPLOYMENT_MANAGER.bom_contents bc," ." DEPLOYMENT_MANAGER.operating_systems os," ." DEPLOYMENT_MANAGER.os_contents osc," ." DEPLOYMENT_MANAGER.PACKAGES pkg," ." DEPLOYMENT_MANAGER.PACKAGE_VERSIONS pv," ." DEPLOYMENT_MANAGER.PRODUCT_DETAILS pd" ." WHERE osc.os_id = os.os_id" ." AND os.node_id = bc.node_id" ." AND bc.bom_id IN" ." (SELECT bom_id" ." FROM" ." (SELECT bs.bom_id, b.branch_id, state_id, bn.bom_name ," ." RANK() OVER (PARTITION BY bs.state_id,b.branch_id, bn.bom_name ORDER BY bs.bom_id DESC) SRLNO" ." FROM DEPLOYMENT_MANAGER.bom_state bs ," ." DEPLOYMENT_MANAGER.boms b," ." DEPLOYMENT_MANAGER.bom_names bn" ." WHERE bs.bom_id = b.bom_id" ." AND b.BOM_NAME_ID = bn.BOM_NAME_ID" ." )" ." WHERE SRLNO <= 3" ." )" ." AND pd.PROD_ID (+) = osc.PROD_ID" ." AND pv.pkg_id = pkg.pkg_id" ." AND osc.prod_id = pv.pv_id" ." ORDER BY UPPER(pkg.pkg_name), " ." UPPER(pv.PKG_VERSION)" .$limit;Verbose3("GetRecentDMPackages: $m_sqlstr");my $sth = $DM_DB->prepare($m_sqlstr);if ( defined($sth) ){if ( $sth->execute( ) ){if ( $sth->rows ){while ( @row = $sth->fetchrow_array ){$count++;print join (',',@row), "\n" if ($opt_verbose > 2);my $pvid = $row[0];$Packages{$pvid}{dm} = 1;unless ( exists $Packages{$pvid}{name} ){$Packages{$pvid}{name} = $row[1];$Packages{$pvid}{version} = $row[2];}push @StrayPackages, $pvid;if ( $opt_limit ){last if ( $count > $opt_limit );}}}$sth->finish();}else{Error("GetRecentDMPackages:Execute failure: $m_sqlstr", $sth->errstr() );}}else{Error("GetRecentDMPackages:Prepare failure" );}Verbose ("Extract Deployed Packages: $count rows");}#-------------------------------------------------------------------------------# Function : GetDepends## Description :## Inputs : @plist - list of pvid's to process## Returns :#sub GetDepends{my (@plist) = @_;## Now extract the package dependacies# There may not be any#my $m_sqlstr = "SELECT "." pd.PV_ID, "." pd.DPV_ID " ." FROM RELEASE_MANAGER.PACKAGE_DEPENDENCIES pd "." WHERE pd.PV_ID in ( " . join(',', @plist) . " )";my $sth = $RM_DB->prepare($m_sqlstr);if ( defined($sth) ){if ( $sth->execute( ) ){if ( $sth->rows ){while ( my @row = $sth->fetchrow_array ){my $pvid = $row[0];my $dpvid = $row[1];push @StrayPackages, $dpvid;push @{$Packages{$dpvid}{usedBy}}, $pvid;$Packages{$dpvid}{slp} = 1 unless exists $Packages{$dpvid}{tlp};print join (',','GetDepends',@row), "\n" if ($opt_verbose > 2);}}$sth->finish();}else{Error("GetDepends:Execute failure: $m_sqlstr", $sth->errstr() );}}else{Error("GetDepends:Prepare failure" );}}#-------------------------------------------------------------------------------# Function : LocateStrays## Description : Locate stray packages# Try to do several (200) at a time to speed up processing## Inputs :## Returns :#sub LocateStrays{Verbose ("Locate indirectly referenced packages");while ( $#StrayPackages >= 0 ){#print "Strays Remaining: ", scalar @StrayPackages ,"\n";my @plist;while ( $#plist <= 200 && @StrayPackages ){my $pv_id = pop @StrayPackages;next if ( exists $Packages{$pv_id}{done} );push @plist, $pv_id;}GetDepends(@plist) if @plist;foreach ( @plist){$Packages{$_}{done} = 1;}}}#-------------------------------------------------------------------------------# Function : savePhaseData## Description : Save inter-phase data## Inputs :## Returns :#sub savePhaseData{my $count = 0;my $direct = 0;my $indirect = 0;my $buildable = 0;my $bad = 0;my $sdk = 0;foreach my $pvid ( keys %Packages ){my $entry = $Packages{$pvid};unless ( defined $entry->{name} && defined $entry->{version}){Warning ("Package Name or Version not known: $pvid");$bad++;next;}$count++;if ( $entry->{locked} && $entry->{locked} eq 'Y' && $entry->{buildStandard} > 0 ){$buildable++;}if ( $entry->{tlp} ) {$direct++;}elsif ( $entry->{slp} ) {$indirect++;}elsif ($entry->{sdk}) {$sdk++;}}my $file = "quarantine.raw.txt";Verbose ("Create: $file");my $fh = ConfigurationFile::New( $file );$fh->DumpData("\n# Package Data.\n#\n","Packages", \%Packages );$fh->DumpData("\n# Release Data.\n#\n","Releases", \%Releases );$fh->Close();Verbose("Packages: $count, Bad: $bad: Buildable: $buildable, Directly included: $direct, Indirect: $indirect, Sdk: $sdk");}#-------------------------------------------------------------------------------# Function : prepQdir## Description : Prepare the quarantine target directory# Setup logging## Done at the start of the 2nd phase## Inputs :## Returns :#sub prepQdir{my ( $ss, $mm, $hh, $dd, $mo, $yy ) = ( localtime($now) )[0..5];my $stamp = sprintf("%4.4d%2.2d%2.2d_%2.2d%2.2d%2.2d", $yy+1900, $mo+1, $dd, $hh,$mm,$ss);$quarantineInstance = join('/', $config{quarantine}, $stamp);my $logName = 'quarantine_' . $stamp . '.txt';$logPath = join('/', $config{logBase}, $logName );eval { mkpath($config{logBase}) } unless -d $config{logBase};Error ("Log directory not found/created: $config{logBase}") unless -d $config{logBase};## Start the log fileLog ("TEST Mode Enabled") if $opt_test;Log ("QuarantinePath: $quarantineInstance");Log ("Quarantine store Disabled") if ($config{qdirAge} <= 0);Log ("Config: $_ = $config{$_}") foreach ( sort keys %config );Log ("Ignore: $_") foreach ( sort keys %retainPkgs );## Create a 'nice' symlink to the latest log filemy $logLatest = join('/', $config{logBase}, 'latest');unlink ( $logLatest );symlink( $logName, $logLatest);## Clean up old files#if ($config{qdirAge} > 0) {opendir( Q, $config{quarantine} ) || Error ("opendir failed on: $config{quarantine}" );# delete any quarantine instance older than 90 dayswhile ( my $file = readdir( Q ) ){## Skip housekeeping directory entries#next if ( $file eq '.' );next if ( $file eq '..' );next if ( $file eq 'lost+found' );my $path = join( '/', $config{quarantine} . "/" . $file);my $age = checkTime( $path );if ( $age > $config{qdirAge} ){Log ("Old Quarantine Removed: $path");Verbose ("Test: Delete Dir: $path") if ( $opt_test );rmtree($path, 0, 1) unless $opt_test;}}closedir( Q );}}#-------------------------------------------------------------------------------# Function : checkTime## Description : Days since modification of a path## Inputs : Path elements## Returns : Days since midification#sub checkTime{my ($path) = @_;my ($dev,$ino,$mode,$nlink,$uid,$gid,$rdev,$size,$atime,$mtime,$ctime,$blksize,$blocks) = stat($path);unless(defined $mtime){Warning("Bad stat for $path");$mtime = 0;}return int( ($now - $mtime) / (60 * 60 * 24));}#-------------------------------------------------------------------------------# Function : processDpkgArchive## Description : Scan dpkg_archive## Inputs :## Returns :#sub processDpkgArchive{Verbose ("Scanning dpkg_archive");opendir( PKGS, $config{dpkg_archive} ) || Error ("Cannot open dpkg_archive");while ( my $pkgName = readdir(PKGS) ){next if ( $pkgName eq '.' );next if ( $pkgName eq '..' );next if ( $pkgName eq 'lost+found' );next if ( exists $retainPkgs{$pkgName} );my $pkgDir = join('/', $config{dpkg_archive}, $pkgName );if ( -d $pkgDir ){if (opendir (PV, $pkgDir ) ){while ( my $pkgVersion = readdir(PV) ){next if ( $pkgVersion eq '.' );next if ( $pkgVersion eq '..' );next if ( $pkgVersion eq 'latest' ); # Keep latest (often symlink for build system)my $pkgPath = join('/', $config{dpkg_archive}, $pkgName,$pkgVersion );my $mtime = checkTime($pkgPath);my $pvid;if ( exists ($pkgPvid{$pkgName}) && exists($pkgPvid{$pkgName}{$pkgVersion} ) ){$pvid = $pkgPvid{$pkgName}{$pkgVersion}{pvid};$Packages{$pvid}{dpkg_archive} = 1;$pkgPvid{$pkgName}{$pkgVersion}{mtime} = $mtime;}else{## Package is in dpkg-archive, but not in Release# Manager. Allow for a short while#$statistics{TotalPackages}++;$statistics{'NotInReleaseManager'}++;if ( $mtime > $config{retainNoRm} ){#Log("Package not in RM: $pkgName, $pkgVersion, Age: $mtime");quarantineItem( 'X', $mtime, $pkgPath );$statistics{'Quarantine'}++;}if ($opt_explain){Information("Reason:-, $pkgName, $pkgVersion, Reason:NotInReleaseManager");}}#Message("$pkgName, $pkgVersion, $pkgPvid{$pkgName}{$pkgVersion}{mtime}");}close(PV);}}elsif ( -f $pkgDir ){Warning("Unexpected file in dpkg_archive: $pkgName");Log("Unexpected file in dpkg_archive: $pkgName");quarantineItem( 'F', -1, $pkgDir );$statistics{'fileNotInReleaseManager'}++;$statistics{'Quarantine'}++;$statistics{'NotInReleaseManager'}++;if ($opt_explain){Information("Reason:-, $pkgDir, -, Reason:fileNotInReleaseManager");}}else{Warning("Unexpected entry in dpkg_archive: $pkgName");}}close(PKGS);### Scan all packages found in dpkg_archive and see if we should keep it# Quarantine those we cannot find a reason to keep#foreach my $pkgName ( sort keys %pkgPvid ){foreach my $pkgVersion ( sort keys %{$pkgPvid{$pkgName}} ){my $mtime = $pkgPvid{$pkgName}{$pkgVersion}{mtime} || 0;my $pvid = $pkgPvid{$pkgName}{$pkgVersion}{pvid};my $keepReason = '';my $entry = $Packages{$pvid};{# Examine entry. Determine a reason to keep the package# Some reasons to keep a package are no longer needed now that versions are pumped into S3unless ($entry) { $keepReason ='NoPackageEntry'; last;}unless ($entry->{dpkg_archive}) { $keepReason ='NotInArchive'; last;}unless ($pvid) { $keepReason = 'NoPVid'; last;}if (exists $entry->{tlp}) { $keepReason = 'TopLevelPackage'; last;}if (exists $entry->{slp}) { $keepReason = 'SecondLevelPackage'; last;}if (exists $entry->{sdk}) { $keepReason ='inSdk'; last;}if (exists $entry->{dm}) { $keepReason = 'inDeploymentManager'; last;}if ($entry->{isPatch}) { $keepReason = 'isPatch'; last;}if ($mtime <= $config{retain}) { $keepReason ='RetainTime:' . ($config{retain} - $mtime); last;}#unless ($entry->{buildStandard}) { $keepReason ='NoBuildStandard:' . $mtime; last;}if ($entry->{locked} ne 'Y') { $keepReason ='NotLocked:' . $entry->{locked}; last;}#if ($entry->{buildType} eq 'M') { $keepReason ='ManualBuild:' . $entry->{buildType}; last;}$pkgPvid{$pkgName}{$pkgVersion}{keepReason} = $keepReason;}unless ( $keepReason ){Verbose2("Quarantine:$pvid, $pkgName, $pkgVersion, Age:$mtime, Lock:$entry->{locked}, Patch:$entry->{isPatch}, BS:$entry->{buildStandard}, BT:$entry->{buildType}");quarantineItem( 'Q', $mtime, join('/', $config{dpkg_archive}, $pkgName, $pkgVersion ) );$keepReason = 'Quarantine';}if ($opt_explain){Information("Reason:$pvid, $pkgName, $pkgVersion, Reason:$keepReason");}## Maintain Stats# Only use the Base Reason - remove details after the ':' character#my $sReason = $keepReason;$sReason =~ s~:.*$~~;$statistics{$sReason}++;$statistics{TotalPackages}++;}}## Perform the quarantine#doQuarantine();}#-------------------------------------------------------------------------------# Function : reportMissingPkgs## Description : Report packages that 'should' be in dpkg_archive because# they are essential, but are not## Inputs :## Returns :#sub reportMissingPkgs{return;## Not very useful as there is too much information# It would appear that the quarantine process may have also# been deleting packages from 'closed' as well as 'archived'# releases at some stage.## Report packages used in not-archived or not-closed releases#my @missing;foreach my $pvid (keys %Packages ){my $entry = $Packages{$pvid};next unless ( exists $entry->{tlp} );# next unless ( exists $entry->{slp} );next if ( $entry->{dpkg_archive} );next unless ( exists $entry->{name} );## Missing package# Determine if its in use by an active release#my @releases = usedBy($pvid);foreach my $release (@releases ){next if ( $Releases{$release}{official} eq 'Y' );next if ( $Releases{$release}{official} eq 'A' );push @missing, $entry->{name} . ' ' . $entry->{version} . " ($pvid)";last;}}Warning ("Packages required by active releases that are not in dpkg_archive", sort @missing);}#-------------------------------------------------------------------------------# Function : usedBy## Description : Given a pvid, determine which release(s) need it## Inputs : $pvid## Returns : Nothing#sub usedBy{my ($pvid) = @_;my %seen;Error ("PVID is not an essential package") unless ( exists $Packages{$pvid} );my @releases = @{$Packages{$pvid}{'release'}} if exists($Packages{$pvid}{'release'});my @users = @{$Packages{$pvid}{'usedBy'}} if exists($Packages{$pvid}{'usedBy'});while ( @users ){my $pv = pop @users;next if ( exists $seen{$pv} );$seen{$pv} = 1;push @releases, @{$Packages{$pv}{'release'}} if (exists $Packages{$pv}{'release'});push @users, @{$Packages{$pv}{'usedBy'}} if (exists($Packages{$pv}{'usedBy'}));}return @releases;}#-------------------------------------------------------------------------------# Function : reportStats## Description : Report statistics# Write statistics to a file# Write to a tmp file, then rename.# Attempt to make the operation atomic - so that the file consumer# doesn't get a badly formed file.### Inputs :## Returns :#sub reportStats{## Time stamp the stats#$statistics{'timeStamp'} = time();## Save stats to a known file for Nagios to use#my $statsfiletmp = join('/', $config{logBase}, 'quarantine.stats.tmp' );my $statsfile = join('/', $config{logBase}, 'quarantine.stats');my $fh;unless (open ($fh, '>', $statsfiletmp)){$fh = undef;Warning("Cannot create temp stats file: $!");}else{foreach my $key ( sort { lc($a) cmp lc($b) } keys %statistics){print $fh $key . ':' . $statistics{$key} . "\n";Log('Statistics: '. $key . ':' . $statistics{$key});}close $fh;# Rename temp to real filerename $statsfiletmp, $statsfile;}}#-------------------------------------------------------------------------------# Function : quarantineItem## Description : Add item to the list of stuff to be quarantined## Inputs : $reason - Reason# $age - Age# $path - Path## Returns :#sub quarantineItem{my ($reason, $age, $path ) = @_;my %data;$data{reason} = $reason;$data{age} = $age;$data{path} = $path;push @quarantineItems, \%data;}#-------------------------------------------------------------------------------# Function : doQuarantine## Description : Quarantine files and folders that have been queued up## Inputs : None## Returns :#sub doQuarantine{my $testMsg = $opt_test ? 'Test,' : '';# Process entries - oldest first#my $countRemain = ( scalar @quarantineItems );foreach my $entry (sort {$b->{age} <=> $a->{age} } @quarantineItems){my $rv;my $emsg = ' - with error';my $s3error = 0;my $path = $entry->{path};my $tpath = $path;$tpath =~ s~^$config{dpkg_archive}~~;$tpath = $quarantineInstance.$tpath;my $tdir = dirname ( $tpath );unless ( $opt_test ){## Transfer to Amazon S3 storage first# The transfer is done via an external program (script)# The transfer will tar-zip the packageVersion#{my $s3msg = "";my $pv = $path;## Export the Secrets in EnvVars# Use program defaults so that we don't need to specify them# on the command line - for all to see#$ENV{AWSKEY} = $config{S3Key};$ENV{AWSSECRET} = $config{S3Secret};$rv = system ( "$progBase/savePkgToS3.sh", "--bucket=$config{S3Bucket}" ,"--path=$path" );if ( $rv ){ReportError ("Move $path to S3");$s3msg = ' - with S3 error';$s3error = 1;$emsg = ' - S3 Error prevented quarantine';$statistics{'S3TransferError'}++;}Log (sprintf("S3Quarantined:%s%s,%4.4s,%s%s", $testMsg, $entry->{reason}, $entry->{age}, $path, $s3msg));}unless ($s3error){if ($config{qdirAge} <= 0){## Just delete the package-version#rmtree( $path);if (-d $path){ReportError ("Deleting $path ");$statistics{'QuarantineError'}++;$emsg = ' - Delete error';}else{$emsg = '';}}else{## Transfer then delete to local directory#unless (-d $tdir){eval { mkpath($tdir) };ReportError ("Did not create quarantine target: $tdir")unless (-d $tdir);}if (-d $tdir){$rv = system ('mv', '-n', $path, $tdir);if ( $rv ){ReportError ("Move $path to $tdir");$statistics{'QuarantineError'}++;## Clean up what may have been moved# NOTE: deleted so that we don't loose stuff if it gets ugly# rmtree( $tpath);# rmdir ($tdir);}else{$emsg = '';}}}}}else{Verbose2("Test: 'mv', '$path', '$tdir'");$emsg = '';}# Log operation with frillsLog (sprintf("Quarantined:%s%s,%4.4s,%s%s", $testMsg, $entry->{reason}, $entry->{age}, $path, $emsg));# Limit packages quarantined$countRemain--;if ($opt_pcount > 0){$opt_pcount--;if ($opt_pcount == 0){Log ("Quarantine package count exceeded. Quarantine terminated. $countRemain packages remaining");last;}}}}#-------------------------------------------------------------------------------# Function : Log## Description : Log a string## Inputs : Line to log## Returns :#sub Log{my ($line) = @_;Verbose("Log: " . $line);if (open ( LF, '+>>', $logPath ) ){print LF $line . "\n";close LF;}}#-------------------------------------------------------------------------------# Function : readInputData## Description : Write out data in a form to allow post processing## Inputs :## Returns :#sub readInputData{unless ( keys(%Packages) > 0 ){my $fname = "quarantine.raw.txt";Verbose ("Reading: $fname");Error "Cannot locate $fname" unless ( -f $fname );require $fname;Error "Data in $fname is not valid\n"unless ( keys(%Packages) > 0 );}## Create a lookup from package name/version to pvid#Verbose ("Create PackageVersion to PVID hash");foreach my $pvid ( keys %Packages ){my $name = $Packages{$pvid}{name};my $version = $Packages{$pvid}{version};if ( $name && $version ){$pkgPvid{$name}{$version}{pvid} = $pvid;}}}#-------------------------------------------------------------------------------# Documentation#=pod=for htmltoc SYSUTIL::=head1 NAMEjats_quarantine - Determine packages to be quarantined=head1 SYNOPSISjats jats_quarantine [options]Options:-help - brief help message-help -help - Detailed help message-man - Full documentation-verbose[=n] - Control output-explain - Display each package version disposition-phase=nn - Perform named phases-purge - Just purge the old quarantined files-test - Do not delete files-limit=n - Limit packages processed. Test only-pcount=n - Limit package count=head1 OPTIONS=over 8=item B<-help>Print a brief help message and exits.=item B<-help -help>Print a detailed help message with an explanation for each option.=item B<-man>Prints the manual page and exits.=item B<-verbose[=n]>This option control the programs output. Normally this program will not generateany output. It will only generate output on error conditions. This is intentionalas the program will be run as a cron-job and output errors will be mailed out.A verbose level of 1. will display progress informationA verbose level of 3. will display detailed tracing of all operations=item B<-explain[=n]>This option will output a line per package-version explaining the reason thatpackages are retained.Only a level of 1 is supported.=item B<-phase=list>This option will limit the work done by the program. There are two phasescalled: 1 and 2.Phase-1 will examine Release Manager collect package-version information.Phase-2 will examine dpkg_archive and collect package-version information. Itwill then initiate the quarantine operation.The default operation is to perform phase-1 and phase-2.If only phase-1 is specified then the RM data is saved, to be used by alater phase.If only phase-2 is specified then saved RM data is restored.This option can simplify testing.=item B<-purge>This option will only purge the old quarantine directories. It will not quarantine newpackage versions.=item B<-test>Do not delete or move files and directories. Report what would have been done.=item B<-limit=n>Limit the number of packages processed by the tool. This is only used tosimplify testing of the program=back=head1 DESCRIPTIONThis program is a tool used in the maintainance of dpkg_archive.It will:=over 8=item *Determine package-versions in use by Release Manager.=item *Determine package-versions in recent Deployment Manager SBOMS.=item *Determine package-versions that can be rebuilt=item *Recursively find all the dependent packages of all packages. New packageversions are called 'indirect' dependencies. They are buried. This process cantake several minutes.=backThe data collected is dumped into a text file for later processing.=cut