Yum file parser.pl
From Cfwiki
Call using something like this:
control:
InstallFile = ( /tmp/yum_packages.lst )
RemoveFile = ( /tmp/yum_packages_remove.lst )
shellcommands:
# New package management system
centos.(Hr23|YumClean)::
"/usr/bin/yum clean all"
centos|(redhat.HasYum)::
"/usr/sbin/yum_file_parser.pl -q -f ${workdir}/inputs/cf.rpm_packages $(AllClasses) > ${InstallFile}"
"/usr/sbin/yum_file_parser.pl -q -r -f ${workdir}/inputs/cf.rpm_packages $(AllClasses) > ${RemoveFile}"
"/usr/bin/test -s ${InstallFile}" define=HasPkgsToInstall
"/usr/bin/test -s ${RemoveFile}" define=HasPkgsToRemove
"/usr/bin/yum -y -e 0 update >> /var/log/yum.log" ifelapsed=45 elsedefine=FailedYumUpdate define=_PostYum
HasPkgsToInstall.centos|(redhat.HasYum).CalledFromCron::
"/usr/bin/yum -y install `cat /tmp/yum_packages.lst` >> /var/log/yum.log" ifelapsed=45 elsedefine=FailedYumInstall define=_PostYum
HasPkgsToInstall.centos|(redhat.HasYum).!CalledFromCron::
"/usr/bin/yum -y install `cat /tmp/yum_packages.lst` >> /var/log/yum.log" elsedefine=FailedYumInstall define=_PostYum
HasPkgsToRemove.centos|(redhat.HasYum).CalledFromCron::
"/bin/cat /tmp/yum_packages_remove.lst | xargs -n 1 rpm -e 2> /dev/null >> /var/log/yum.log" ifelapsed=45 define=_PostYum
HasPkgsToRemove.centos|(redhat.HasYum).!CalledFromCron::
"/bin/cat /tmp/yum_packages_remove.lst | xargs -n 1 rpm -e 2> /dev/null >> /var/log/yum.log"
File below:
#!/usr/bin/perl -w
# $Id: yum_file_parser.pl 944 2009-03-27 15:11:52Z beckerjes $
# $Date: 2009-03-27 11:11:52 -0400 (Fri, 27 Mar 2009) $
use strict;
use Getopt::Std;
use Pod::Usage;
use Data::Dumper;
my %opts;
getopts('f:hqrv',\%opts);
if (exists $opts{h}) {
pod2usage(-verbose=>1, -msg=> "Requested help");
exit (1);
}
if (!exists $opts{f}) {
pod2usage(-verbose=>1, -msg=>'No -f option given');
exit (1);
}
if (!@ARGV) {
pod2usage(-verbose=>1,-msg=>'No groups listed');
exit (1);
}
my $debug=$opts{v};
#######################################################################
sub debug {
my ($msg,$code)=@_;
$code=$debug unless defined($code);
print STDERR "$msg\n" if $code;
}
#######################################################################
#######################################################################
sub ParseOptions {
my ($pkg, $option_string) = @_;
#Default options....
my %options = (
action => 'install',
);
if (!$option_string) {
debug("\tNo options, defaulting to installing package [$pkg]");
return %options;
}
my @tokens = split (/\s+/, $option_string);
foreach my $token (@tokens) {
my ($key,$value) = split (/=/, $token);
debug("\tParsing token [$token] into k:[$key] v:[$value]");
if ('action' eq $key) {
$value=lc($value);
if ('skip' eq $value || 'ignore' eq $value) {
debug("\tRemoving package [$pkg] from group");
$options{action}='skip';
next;
} elsif ($value eq 'install') {
debug("\tInstalling package [$pkg]");
} elsif ( $value eq 'remove' ) {
debug("\tUninstalling package [$pkg]");
$options{action}='remove';
next;
} else {
debug("\tUnknown action [$value]");
next;
}
}
$options{$key} = $value;
}
return %options;
}
#######################################################################
sub ParseInputFile {
my $filename=shift;
my %packages;
my %skipped_packages;
my %removed_packages;
my $current_group="any::";
debug("Parsing file [$filename]");
open(FILE,$filename) || die "Can't open file of RPM packages [$opts{f}]: ";
while (defined (my $line=<FILE>)) {
# Handle comments
$line =~ s/\s*#.*//;
next if $line =~ /^\s*$/;
chomp $line;
$line =~ s/^\s+//;
$line =~ s/\s+$//;
print STDERR "\nParsing Input(grp:$current_group): [$line]\n" if $opts{v};
if ($line =~ /(\S+)::/) {
$current_group=$1;
debug("\tFound group [$current_group]");
next;
}
elsif ( $line =~ /(\S+)\s*(.*)?/ ) {
my ($pkg,$opts)=($1,$2);
debug("\tFound package [$pkg] and options [$opts]");
my %options = ParseOptions($pkg, $opts);
# This will push the package $pkg into an array, which is the value
# for a given hash key for the current group. It looks something like this:
# $packages{DesktopSystems} = [ antiword, xpdf, gnuplot, ... ]
if (exists $options{action} and defined $options{action}) {
'install' eq $options{action} ? push @{$packages{$current_group}}, $pkg :
'skip' eq $options{action} ? push @{$skipped_packages{$current_group}}, $pkg :
'remove' eq $options{action} ? push @{$removed_packages{$current_group}}, $pkg :
debug ("Unhandled action [$options{actions}]",1);
}
} else {
debug("\tUnknown line!",1);
}
}
close FILE;
debug('Skipping these packages: '.Dumper(\%skipped_packages));
debug('Removing these packages: '.Dumper(\%removed_packages));
# We have a list of packages to ignore in @skip_packages.
# This block will remove these packages from list of things
# packages to install, iterating over each package_group (since
# a package could be installed from an stanza, we have to check each
# package_group for each skipped package. A nice O(n^2) problem...
foreach my $package_group (sort keys %packages) {
next if !exists $skipped_packages{$package_group};
foreach my $skipped_package (@{$skipped_packages{$package_group}}) {
delete $packages{$skipped_package};
}
print Dumper(\$packages{$package_group});
}
return (\%packages, \%removed_packages);
}
#######################################################################
my ($packages_ref, $remove_ref) = ParseInputFile($opts{f});
my %packages = %{$packages_ref};
my %remove_packages = %{$remove_ref};
my %assigned_groups;
my $tmp = join(' ', @ARGV);
$tmp =~ s/CFALLCLASSES=//o;
debug("TMP: $tmp");
%assigned_groups=map { $_, 1 } split (/[ :]/, $tmp);
$assigned_groups{any}=1;
my %a = %assigned_groups;
my @groups_used;
my %packages_to_manipulate;
print Dumper(\%packages) if $opts{v} and !$opts{q};
print Dumper(\%remove_packages) if $opts{v} and !$opts{q};
print Dumper(\%assigned_groups) if $opts{v} and !$opts{q};
my %working_set_of_packages = exists $opts{r} ? %remove_packages : %packages;
debug ("\n\nChecking groups against packages");
foreach my $package_group (sort keys %working_set_of_packages) {
$_=$package_group;
debug("Checking package group [$package_group]");
# just let Perl do the work with an eval...
# Laziness is a virtue...
# This block of regexes will convert the cfegine group
# aggregation syntax to something perl understands.
# Cfengine:
# | is a logical or
# . is a logical and
# ! is a logical negation
study;
# Here we build a perl expression for eval.
s/\s+/ /g; # whitespace to single space
s/\|+/||/g; # force pipes to double pipes for logical OR
s/[.&]+/&&/g; # force ampersands to logical AND
s/!+/ !/g; # handle negation...
# convert the class names into 'exists $a{class}' statements
s/([^|&!\s]+)/ exists(\$a{"$1"}) /g;
# Wrap in parens so the ?: operator doesn't bind strangely
$_="($_)";
s/$/ ? 1 : 0/;
debug("\tTranslated: $package_group ->\n\t $_");
my $eval=eval($_);
debug("\tEvals to: $eval");
if ($eval) {
map { $packages_to_manipulate{$_}++ } @{$working_set_of_packages{$package_group}};
push @groups_used, $package_group;
}
}
#print Dumper(\%packages_to_install);
print STDERR "Using packages from groups: ", join(', ',@groups_used),"\n" unless $opts{q};
if (%packages_to_manipulate) {
print join(' ', sort keys %packages_to_manipulate), "\n";
}
exit 0;
# end of script
########################################################################
########################################################################
########################################################################
__END__
=pod
=head1 NAME
yum_file_parser.pl - Compares a list of CFEngine groups against a list of RPM packages, and syncronizes the two.
=cut
=head1 SYNOPSIS
yum_file_parser.pl -f <rpm_list> [-r] [-v] <group1> [group2 [group2] [...]]
=cut
=head1 OPTIONS
=over
=item -f <rpm_list>
Indicates the path to the file containing the RPM group/package list.
=item -v Verbose mode
=item -h Print this help message
=item -r Print packages that will be *removed*, instead of installed.
=item -q Suppress certain messages (interacts oddly with -v, above)
=back
=begin text
Usually the group list is generated via cfengine directly, and passed to the
script in a shellcommand action.
# The config file lists the RPM packages that should be installed
# for each cfengine group. The syntax is similar to that of
# cfengine:
#
# group_name::
# package1 [option1 [option2] [...]]
# package2 [option1 [option2] [...]]
# [...]
#
# Three different actions are supported: 'install', 'skip' (or 'ignore')
# and 'remove'. If no action is listed, the action defaults to 'install'.
# If a package is given the option "action=skip", processing will be skipped;
# the package will *NOT* be removed. To list packages that will be removed
# use the -r command line option.
#
# Packages slated for removal will *NOT* remove all packages that depend on
# it. If any packages depend the one being removed, the entire removal
# process will fail.
#
# For example, package A depends on package B; nothing depends on package A.
# Removing only package B will fail, since package A requires it.
# Removing only package A will succeed. If both packages A and B are slated
# for removal, both will be removed at the same time.
#
# You *can* use the cfengine AND, OR, and NOT notation.
# Thus, these are legit:
# groupA|groupB::
# groupA.groupC::
# groupA.groupB|!groupC
#
# The order of operations follows Perl:
# ! preceeds 'and', which preceeds 'or'
#
# The lists of packages to be installed are indented to be passed to a
# program such as 'yum'. Packages to be removed can go to either 'yum'
# or directly 'rpm'
=end text
=cut