#! /usr/bin/perl -w
# $Header: /global/src/etc/cl-manage-scripts/RCS/cl-add-rpms,v 1.26 2019/01/04 10:03:37 pb22 Exp $
# Use "pod2text" for info

=head1 NAME

add-rpms - generate apt-get or yum commands to add any missing RPMs or groups

=head1 SYNOPSIS

add-rpms [B<--rpm> I<$rpm>]* [I<$ListOfRPMsOrFiles>]

=head1 OPTIONS

=over 4

=item B<--auto|a>

do an auto install, i.e. those listed in /etc/user-config/bundles

=item B<--noexec|n>

do not execute the command, just display it

=item B<--verbose|v>

generate comments as to what it is doing.

=item B<--debian|d>

run on a debian (dpkg / apt-get) system

=item B<--forcegroup|f>

force all groups to be installed, even if they appear so to be.

=item B<--doremoves|d>

do any B<purge> or B<remove> operations, rather then just printing them

=item B<--remove|r>

B<remove> rather then I<purge> packages

=item B<--purge|p>

B<purge> rather then I<remove> packages (default)

=item B<--skipbroken|s>

skip broken RPMs, installing any which can be.

=item B<--help|h>

generate help info.

=back

=head1 DESCRIPTION

Read a list of RPMs to load and generate a command to install missing ones.

A prefix 'B<@>' can be used to specify a group, or file to read
(defaults to trying /etc/{cl-,}manage-scripts/ as a prefix).

A prefix 'B<NOT_>' can be used to specfify rpms not to be loaded

=cut

use Getopt::Long;
use Pod::Usage;
%opts = (
	"auto|a"	=> \$auto,
	"debian|de"	=> \$debian,
	"doremoves|d:s"	=> \$doremoves,
	"forcegroup|f"	=> \$forcegroup,
	"help|h"	=> \$help,
	"noexec|n"	=> \$noexec,
	"purge|p"	=> \@rpms,
	"rpms:s"	=> \@rpms,
	"remove|r"	=> \@remove,
	"skipbroken|s"	=> \$skipbroken,
	"verbose|v"	=> \$verbose,
	"yes|y"		=> \$yes,
);

# Things which need to be purged rather than removed
%purge = ( "example..."	=> 1,);
$purge_regexp = "^CL.isidle.*conf|^CL-cron-isidle-halt";

GetOptions (%opts) || pod2usage();
pverboseod2usage(-verbose => 2) if $help;
-r "/etc/debian_version" && $debian++;
$yum = ($debian) ? "apt-get" : "yum";
$AUTOFILE = "@/etc/user-config/bundles";
$src = "<--rpms>";
$SUDOUSER	= $ENV{'SUDO_USER'};
&readgroups;
if ($auto) { push @rpms, $AUTOFILE; $src = "-a" }
$rpm = join(',',@rpms);


$maxlevel = 10;
$base = "/etc/cl-manage-scripts/";
$base = "/etc/manage-scripts/" unless -e $base;
$base .= (($debian) ? "deb" : "RPM") . "s/";
$yes	= ($yes) ? ($debian) ? " -y -o DPkg::Options::=--force-confdef" : " -y" : "";
$ENV{'DEBIAN_FRONTEND'} = "noninteractive" if $yes && $debian;
$skipbroken	= ($skipbroken) ? (($debian) ? "" : " --skipbroken") : "";
$NOT	= "NOT_";

%rpms = ();
# If there is an explicit list of RPMs, add them
if ($rpm) {
	foreach $one (split(/[ ,:]+/, $rpm)) { &add_rpm($one, 0, 0, $src); }
} elsif ($#ARGV >= 0) {
	while (<>) { &add_rpm($_, 0, 0, $src); }
} else {
 	pod2usage(1);
}

$excludes = $adds = $purges = $removes = '';
foreach $rpm (keys %rpms) {
	print STDERR "# $rpm is $rpms{$rpm} ($have{$rpm})\n" if $verbose;
	if    ($rpms{$rpm} > 0) { $adds    .= " $rpm" if $have{$rpm} < 0 }
	elsif ($rpms{$rpm} < 0) {
		$excludes .= ",$rpm";
		if ($have{$rpm} > 0) {
			# print STDERR "# Add $rpm ...\n";
			if ($debian && ($purge{$rpm} || $rpm =~ /$purge_regexp/io))  { $purges .= " $rpm" }
			else { $removes .= " $rpm" }
		}
	}
}

if ($adds) {
	$cmd = "$yum install$yes$skipbroken$adds";
	if ($noexec) { print $cmd, "\n"; }
	else { print STDERR "# adds1: $cmd\n"; exec $cmd; }
}
if ($removes) {
	$cmd = ($purge || ! $remove) ? "purge" : "remove";
	$cmd = "$yum $cmd$yes$skipbroken$removes";
	if ($noexec || ! $doremoves) { print $cmd, "\n"; }
	else { print STDERR "# removes1: $cmd\n"; exec $cmd; }
}
if ($purges) {
	$cmd = ($remove) ? "remove" : "purge";
	$cmd = "$yum $cmd$yes$skipbroken$purges";
	if ($noexec || ! $doremoves) { print $cmd, "\n"; }
	else { print STDERR "# purges1: $cmd\n"; exec $cmd; }
}

$adds = $removes = '';
foreach $group (keys %groupa) {
	print STDERR "# $group is $groupa{$group} ($group{$group})\n" if $verbose;
	if    ($groupa{$group}>0) { $adds   .= " \"$group\"" if $forcegroup || $group{$group}<0 }
	elsif ($groupa{$group}<0) { $removes.= " \"$group\"" if $group{$group}>0 }
}

if ($adds) {
	# was $excludes =~ s/^,/ --exclude=/; but (e.g.) lbtp doesn't grok ,
	$excludes =~ s/,/ --exclude=/g;
	$cmd = "$yum groupinstall$yes$skipbroken$adds$excludes";
	if ($noexec) { print $cmd, "\n"; }
	else { print STDERR "# adds2: $cmd\n"; exec $cmd; }
}
if ($removes) {
	$cmd = "$yum groupremove$yes$skipbroken$removes";
	if ($noexec || ! $doremoves) { print $cmd, "\n"; }
	else { print STDERR "# removes2: $cmd\n"; exec $cmd; }
}

# =============================================================================

sub add_rpm {
	my ($rpm, $level, $remove, $src) = @_;
	$ind = "  " x $level;
	chomp($rpm);
	return if $rpm =~ /^#/;
	return if $rpm =~ /^$/;

	if ($level++ > $maxlevel) {
		print STDERR "$0: recursion stopped at level $level for $rpm\n";
		return;
	}
	$remove = 1 if $rpm =~ s/^$NOT//o;

	if ($rpm =~ /^\@(.+)/ && $group{$1}) {
		print "Asked for group '$1' -- $group{$1}\n" if $verbose;
		$groupa{$1} = 0 unless defined $groupa{$1};
		$groupa{$1} += ($remove) ? -1 : 1;
		# Also look for "R-not-*" and invert that group
		if ((($not = $1) =~ s/^(R-)/$1not-/) && $group{$not}) {
			print "  not for group '$not' -- $group{$not}\n" if $verbose;
			$groupa{$not} = 0 unless defined $groupa{$not};
			$groupa{$not} -= ($remove) ? -1 : 1;
		}
		return;
	}
	# Look for recusrion
	if ($rpm =~ /^\@(\S+)/) {
		my($file) = $1;
		$remove = 1 if $file =~ s/^$NOT//o;
		unless (open(FILE, $file) || open(FILE, "$base$file")) {
			$file = "file" if $SUDOUSER && $auto;
			print STDERR "$0: failed to find $file\n";
			return;
		}
		print STDERR "$0:$ind START $file\n" if $verbose;
		my(@lines) = <FILE>;
		close(FILE);
		foreach $line (@lines) {
			&add_rpm($line, $level, $remove, "$src, \@$file");
		}
		print STDERR "$0:$ind END $file\n" if $verbose;
		return;
	}

	# Ignore if it doesn't look like an RPM name
	if ($rpm !~ /^[A-Za-z0-9._+:-]+$/) {
		$rpm = '' if $SUDOUSER && $auto;
		print STDERR "$0: ignoring spurious RPM '$rpm'\n";
		return;
	}
	if ($rpm =~ /\.rpm$/) {
		$rpm = '' if $SUDOUSER && $auto;
		print STDERR "$0: Do not include trailing '.rpm' $rpm\n";
		return;
	}

	# Map "CL-*" to "cl-*" on debian systems
	$rpm =~ s/^CL-/cl-/ if $debian;
	# See if the RPM is already installed
	$have{$rpm} = &have_package($rpm) ? -1:1 unless $have{$rpm};
	print STDERR "$0: $ind$rpm $have{$rpm} ($remove) [$level: $src]\n" if $verbose;
	$rpms{$rpm} += ($remove) ? -1 : 1;
}

# =============================================================================

sub readgroups {
	my ($cmd) = "$yum grouplist|";
	my ($groups) = "";
	my ($group) = "?";

	# Pro tem, ignore on debian systems
	return if $debian;

	open(GROUPS, $cmd) || die("$0: '$cmd' failed: $!\n");
	while (<GROUPS>) {
		chomp;
		print STDERR ": $group $_\n" if $verbose;
		$groups .= $group;	# remember what we have seen
		if (s/^  *//) {
			$group{$_} = ($group eq "a") ? -1 : 1;
			next;
		}
		$group = "i" if /^Installed Groups:/;
		$group = "a" if /^Available Groups:/;
		$group = "d" if /^Done$/;
	}
	close(GROUPS) || die("$0: '$cmd' failed: $?\n");
	die("$0: no 'END' found in $cmd\n") unless $group eq "d";
	die("$0: no 'Available Groups' found in $cmd\n") unless $groups =~ /a/;
	die("$0: no 'Installed Groups' found in $cmd\n") unless $groups =~ /i/;
}

# Do we have a package loaded
sub have_package {
	my ($package) = @_;

	if ($debian) { system("dpkg -l $package 2>&1 | grep ^i > /dev/null") }
	else { system("rpm -q --quiet $package") }
}
