Yum file parser.pl

From Cfwiki

Jump to: navigation, search

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 

Personal tools