Cfengine stanzas.pl
From Cfwiki
Perl script below...
#!/usr/bin/perl -w
# cfengine_stanzas.pl
# Copyright 2007 Jesse Becker
# See BSD license at end of file
my $version = 0.5;
use strict;
use Getopt::Long;
use Data::Dumper;
use Pod::Usage;
my %classes;
my @classes = qw(any);
my $verbose = 0;
my $helpme = 0;
my $indent = 4;
GetOptions('class|c=s' => \@classes,
'verbose|v+' => \$verbose,
'indent|i=i' => \$indent,
'help|h|?' => \$helpme,
);
if ($helpme || !@ARGV) {
pod2usage(-verbose=>2);
exit 1;
}
@classes=split(/[,: ]/,join(',',@classes));
my %stanzas;
# Needed for an eval() later on
foreach (@classes) {
$classes{$_} = 1;
}
debug('Using these classes: '.join(',',@classes),2);
foreach my $file (@ARGV) {
debug("Parsing file [$file]");
open(my $FILE, $file ) || warn "Failed to open [$file]: $!";
ParseInputFile($FILE,$file);
close($FILE);
}
PrintStanzas(\%stanzas);
exit 0;
######################################################################
sub ParseInputFile {
my $FH=shift || return;
my $filename=shift || return;
my $current_action;
my $current_stanza;
my @stanza_lines;
while (defined (my $line=<$FH>)) {
$line =~ s/\s*#.*//;
next if $line =~ /^\s*$/;
chomp $line;
$line = trim($line);
debug($line,3);
# Find a new action line.
if ($line =~ /^\s*(\w+):\s*$/) {
if (@stanza_lines) {
AddStanza($current_action,$current_stanza, \@stanza_lines);
@stanza_lines=();
}
$current_action=$1;
$current_stanza='any';
debug(" New action: $current_action".':');
next;
}
# Found a new class line
elsif ($line =~ /^\s*(\S+)::\s*$/) {
if (@stanza_lines) {
AddStanza($current_action,$current_stanza, \@stanza_lines);
@stanza_lines=();
}
$current_stanza=$1;
debug(" New stanza: $current_stanza".'::');
@stanza_lines=();
next;
}
else {
if (defined($line)) {
debug(" Appending [$line] to $current_action -> $current_stanza");
push @stanza_lines, $line;
}
}
}
}
sub AddStanza {
debug(" In AddStanza:",2);
my $action = shift || return;
my $stanza = shift || return;
my $arr_ref = shift || return;
my $evalstring = $stanza;
$evalstring =~ s/ \|+ /||/xg; # Logical OR
$evalstring =~ s/ [\.&]+ /&& /xg; # Logical AND
$evalstring =~ s/ !+ / !/xg; # Negation
$evalstring =~ s/ \s+ / /xg; # squash whitespace
$evalstring =~ s/([^()|&!\s]+)/ exists \$classes{$1} /xg; # The actual perl testing
$evalstring = "($evalstring) ? 1 : 0";
my $eval = eval ($evalstring);
debug("\n Checking evalstring [ $evalstring ] = $eval\n");
if ($eval) {
push @{$stanzas{$action}->{$stanza}}, join("\n",@$arr_ref);
}
}
sub PrintStanzas {
my $href=shift || return;
my @actions = keys %$href;
@actions = grep { !/control/ } @actions;
unshift @actions,'control';
foreach my $action (@actions) {
PrintAction($action);
my @stanzas = keys %{$href->{$action}};
# @stanzas = grep { lc($_) eq 'any' } @stanzas;
# unshift @stanzas, 'any';
foreach my $stanza (@stanzas) {
PrintStanza($stanza,$indent);
PrintStanzaContents($href,$action,$stanza,$indent);
}
}
}
sub PrintAction {
my $action=shift || return;
print "\n$action:\n";
}
sub PrintStanza {
my ($stanza,$indent)=@_;
print "\n", ' ' x $indent, $stanza, "::\n";
}
sub PrintStanzaContents {
my ($href,$action,$stanza, $indent) = @_;
my @entries = @{$href->{$action}->{$stanza}};
my $entry = join ("\n\n", @entries);
$entry =~ s/(^|\n)/' ' x ($indent*2) .$1/gmse;
print $entry,"\n";
return;
}
sub trim {
$_=shift || '';
s/^\s+//;
s/\s+$//;
return $_;
}
sub debug {
my ($msg,$code)=@_;
$code=1 unless defined($code);
print STDERR "$msg\n" if $verbose>=$code;
return;
}
#####################################################################################
__END__
=pod
=head1 NAME
cfengine_stanzas.pl - Prints the cfengine configuration stanzas that will be used based on a set of classes
=cut
=head1 SYNOPSIS
cfengine_stanzas.pl [-v|--verbose] [-i|--indent <indent>] [-c|--classes <classes>] <cfagent config files>
=cut
=head1 OPTIONS
=over
=item -v
Verbose mode. Prints debugging information to STDERR, and can be used multiple times.
=item -i <indent>
Sets the number of spaces used when indenting stanzas. Default is 4 spaces.
=item -c <classlist>
Sets the list of classes to be considered. Multiple classes can be specified by using
-c multiple times, or using a colon, comma, or space character to join mulitple classes.
For example, the following are all equivalent:
-c foo -c bar -c baz
-c foo:bar:baz
-c 'foo bar baz'
=back
=head1 NOTES
This script attempts to parse a collection of cfagent configuration files,
and display which stanzas will be used based on set of classes. Minor formatting
is also done, although this aspect is a work in progress.
The "any" class is always defined, although all other hard classes are not. All
classes (except "any") must be defined if you wish to include them in processing.
=cut
=head1 EXMAPLES
(slightly abbreviated for space)
=over
=item ./cfengine_stanzas.pl -c linux cf.ntp
Check NTP settings for Linux boxes.
=item ./cfengine_stanzas.pl -c DesktopSystems:Friday:HasCupsOn:HasNtpdOn:HasNvidiaDriver:centos /var/cfengine/inputs/{cfagent.conf,cf.*}
A more complex example, with multiple classes and input files.
=back
=cut
=head1 BUGS
While 'control' sections are listed first, the remaining actions are completely unordered.
Specific items in actions should be formatted consistantly, based on the action time. E.g. copy: actions should be formatted differenly from editfiles:
The script can't process group: definitions, in order to build a list of soft classes.
=head1 LICENSE
Copyright (c) 2007 Jesse Becker
All rights reserved.
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer.
2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution.
3. All advertising materials mentioning features or use of this software must display the following acknowledgement:
This product includes software developed by Jesse Becker
4. The name of the contributors may NOT be used to endorse or promote products derived from this software without specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE REGENTS AND CONTRIBUTORS ``AS IS'' AND
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
ARE DISCLAIMED. IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
SUCH DAMAGE.
=cut
