###
### Copyright 2000-2007 University of Illinois Board of Trustees
### All rights reserved.
###
### PSGConf.pm - main library for psgconf
###
### Campus Information Technologies and Educational Services
### University of Illinois at Urbana-Champaign
###
package PSGConf;
use strict;
use File::Basename;
use File::Copy;
use File::Path;
use File::stat;
use POSIX;
use Proc::ProcessTable;
use PSGConf::Data::String;
use PSGConf::Util;
our $AUTOLOAD;
###############################################################################
### Global Defaults
###############################################################################
my %defaults = (
config_dir => '/usr/local/share/psgconf/config',
files_dir => '/usr/local/share/psgconf/files',
modules_file => '/usr/local/etc/psgconf_modules',
tmpdir => '/var/tmp/' . basename $0 . ".$$",
verbose => 0,
do_fix => 0,
restart_daemons => 1,
rm_tmpfiles => 1,
do_diffs => 0,
update_pkgs => 1,
trace => 0,
need_reboot => 0
);
###############################################################################
### Constructor
###############################################################################
sub new
{
my ($class, %opts) = @_;
my ($self, $opt);
### create object
$self = \%opts;
bless($self, $class);
### Set from the environment variables if
### not set on the command line.
$0 = basename $0;
$self->{tmpdir} = $ENV{'PSGCONF_TMPDIR'} . "/$0.$$"
if ( !defined $self->{tmpdir} &&
defined $ENV{'PSGCONF_TMPDIR'} &&
-d $ENV{'PSGCONF_TMPDIR'} );
### set default options
foreach $opt (keys %defaults)
{
$self->{$opt} = $defaults{$opt}
if (!defined($self->{$opt}));
}
### initialize tmpdir
print $0 . ": creating temp directory...\n"
if ($self->{verbose});
rmtree($self->{tmpdir})
if (-e $self->{tmpdir});
mkpath($self->{tmpdir}, 0, 0700);
### initialization
$self->{datastores} = [];
$self->{controls} = [];
$self->{data} = {};
$self->{data_owners} = {};
$self->{actions} = [];
$self->{policy} = {};
$self->{policy_order} = [];
$self->{num_changed} = 0;
### set timestamps
$self->{timestamp} = time;
$self->{backupext} = $0 . '.' . POSIX::strftime("%Y%m%d%H%M%S",
(localtime($self->{timestamp})))
if ( ! defined $self->{backupext} );
### set platform name
($self->{platform}, @{$self->{platform_suffixes}}) = platform_name();
$self->register_data(
backupext => PSGConf::Data::String->new(
value => $self->{backupext}
),
platform => PSGConf::Data::String->new(
value => $self->{platform}
)
);
### look at process table
$self->{ps} = new Proc::ProcessTable;
### load modules
$self->_load_modules();
return $self;
}
###############################################################################
### Load modules
###############################################################################
sub _load_modules
{
my ($self) = @_;
my ($type, $module, @args);
my ($obj, $line, $method);
print $0 . ": loading modules...\n"
if ($self->{verbose});
open(MODLIST, "<$self->{modules_file}")
|| die "open('$self->{modules_file}'): $!\n";
while ($line = <MODLIST>)
{
### strip newlines
chomp($line);
### strip comments
$line =~ s/#.*$//;
### skip empty lines
next
if (length($line) == 0);
### parse line
($type, @args) = split(/\s+/, $line);
if (lc($type) eq 'policy')
{
$method = shift(@args);
die $0 . ": too many arguments in \"policy\" entry"
if (@args > 0);
die $0 . ": unknown policy method \"$method\"\n"
if (!exists($self->{policy}->{$method}));
push(@{$self->{policy_order}}, $method);
next;
}
### not a policy line, so
### it must be a datastore or control entry
$module = shift(@args);
print $0 . ": TRACE: loading module '$module'\n"
if ($self->{trace});
eval "use $module";
die $0 . ": cannot load '$module' module: $@\n"
if ($@);
if (lc($type) eq 'datastore')
{
$obj = $module->new($self, @args);
push(@{$self->{datastores}}, $obj);
next;
}
if (lc($type) eq 'control')
{
$obj = $module->new($self, @args);
push(@{$self->{controls}}, $obj)
if (defined($obj));
next;
}
### unknown module type
die $0 . ": unknown module type '$type'\n";
}
close(MODLIST);
}
###############################################################################
### Phase 0 - Access Data Store(s)
###############################################################################
sub access_data_stores
{
my ($self) = @_;
my ($obj);
print $0 . ": accessing data store(s)...\n"
if ($self->{verbose});
foreach $obj (@{$self->{datastores}})
{
die $0 . ": data store module '" . ref($obj) . "' failed\n"
if (! $self->_call_method($obj, 'read_config'));
}
}
###############################################################################
### _call_method() - call a method on an object
###############################################################################
sub _call_method
{
my ($self, $obj, $method) = @_;
return undef
if (! $obj->can($method));
if ($self->{trace}) {
my ($class) = ref($obj);
print $0 . ": TRACE: calling $class->$method()\n";
}
return $obj->$method($self);
}
###############################################################################
### Phase 1a - enforce policy
###############################################################################
sub enforce_policy
{
my ($self) = @_;
my ($policy, $obj, $method);
print $0 . ": invoking policy methods...\n"
if ($self->{verbose});
foreach $policy (@{$self->{policy_order}})
{
if ( exists $self->{policy}->{$policy} ) {
$obj = $self->{policy}->{$policy}->{control_obj};
$method = $self->{policy}->{$policy}->{method};
$self->_call_method($obj, $method);
# Delete the policy after we invoked it, so we can see
# which ones we did not invoke at all.
delete $self->{policy}->{$policy};
} else {
warn "\t!!! Could not find the policy ($policy), maybe duplicate policy entries?\n";
}
}
foreach $policy ( keys %{$self->{policy}} ) {
warn "\t!!!Policy registered but not in psgconf_modules '$policy'\n";
}
}
###############################################################################
### Phase 1b - instantiate actions
###############################################################################
sub instantiate_actions
{
my ($self) = @_;
my ($obj, $class);
print $0 . ": instantiating actions...\n"
if ($self->{verbose});
foreach $obj (@{$self->{controls}})
{
$self->_call_method($obj, 'decide');
}
}
###############################################################################
### Phase 2 - process actions
###############################################################################
sub process_actions
{
my ($self) = @_;
my ($obj, $ret);
print "\n"
if ($self->{verbose});
foreach $obj (@{$self->{actions}})
{
printf($0 . ': checking %-45s ... ', $obj->name)
if ($self->{verbose} && ! $obj->quiet);
$ret = $obj->check($self);
print "ok\n"
if ($ret == 0
&& $self->{verbose}
&& ! $obj->quiet);
next
if ($ret <= 0);
if ($ret > 0)
{
print "CHANGED\n"
if ($self->{verbose} && ! $obj->quiet);
$self->{num_changed}++;
$self->{need_reboot} = 1
if ($obj->requires_reboot);
}
}
print "\n"
if ($self->{verbose});
foreach $obj (grep { $_->changed } @{$self->{actions}})
{
if ($self->{do_diffs})
{
$obj->diff($self);
}
elsif (! $self->{do_fix})
{
print $0 . ': ' . $obj->name . " requires update\n";
}
}
if ($self->{do_fix})
{
foreach $obj (grep { $_->changed } @{$self->{actions}})
{
print $0 . ': updating ' . $obj->name . "\n";
next
if ($obj->do($self) == -1);
$self->{num_changed}--;
}
}
}
###############################################################################
### Phase 3 - Cleanup
###############################################################################
sub _call_cleanup_hooks
{
my ($self) = @_;
my ($obj);
print "\n$0: calling cleanup hooks...\n"
if ($self->{verbose});
foreach $obj (@{$self->{controls}})
{
$self->_call_method($obj, 'cleanup');
}
if ($self->{need_reboot})
{
print "\n"
if ($self->{verbose});
print $0 . ": NOTE: system may need to be rebooted for changes to take effect\n";
print "\n"
if ($self->{verbose});
}
}
sub cleanup
{
my ($self) = @_;
$self->_call_cleanup_hooks();
$self->_clean_tmpdir();
return $self->{num_changed};
}
sub _clean_tmpdir
{
my ($self) = @_;
if (defined($self->{tmpdir}) && $self->{rm_tmpfiles})
{
print "\n$0: cleaning up...\n"
if ($self->{verbose});
rmtree($self->{tmpdir});
delete $self->{tmpdir};
}
}
###############################################################################
### DESTROY method
###############################################################################
sub DESTROY
{
my ($self) = @_;
$self->_clean_tmpdir();
}
###############################################################################
### AUTOLOAD method
###############################################################################
sub AUTOLOAD
{
my ($self) = @_;
my ($name);
$name = $AUTOLOAD;
$name =~ s/.*:://;
return undef
if ($name eq 'DESTROY');
# my (@caller);
# @caller = (caller(1));
# @caller = (caller(2))
# if ($caller[3] =~ m/^PSG::Abstraction::UserDB::UDB/);
# print "[$name] : access from $caller[3]\n";
print $0 . ": TRACE: accessing data object '$name'\n"
if ($self->{trace});
# push(@{$self->{data_funcs}->{$name}}, $self->{func});
return $self->{data}->{$name}->{value}
if (exists($self->{data}->{$name}));
return undef;
}
sub data_obj
{
my ($self, $name) = @_;
print $0 . ": TRACE: accessing data object '$name'\n"
if ($self->{trace});
# push(@{$self->{data_funcs}->{$name}}, $self->{func});
return $self->{data}->{$name}
if (exists($self->{data}->{$name}));
return undef;
}
###############################################################################
### Utility Functions for Modules
### FIXME: these don't really belong here...
###############################################################################
sub save_file
{
my ($self, $file, $copyflag) = @_;
my ($savename, $st);
$savename = "$file.$self->{backupext}";
if ($copyflag && -f $file)
{
$st = stat($file);
if (! copy($file, $savename))
{
warn "\t!!! copy('$file', '$savename'): $!\n";
return 0;
}
# print "CHMOD(" . $st->mode . ", $savename)\n";
if (! chmod($st->mode, $savename))
{
warn "\t!!! chmod("
. $st->mode
. ", '$savename'): $!\n";
return 0;
}
# print "CHOWN(" . $st->uid . ", " . $st->gid . ", $savename)\n";
if (! chown($st->uid, $st->gid, $savename))
{
warn "\t!!! chown(%d, %d, '%s'): $!\n",
$st->uid, $st->gid, $savename;
return 0;
}
}
else
{
if (! rename($file, $savename))
{
warn "\t!!! rename('$file', '$savename'): $!\n";
return 0;
}
}
return 1;
}
###############################################################################
### data object registry functions
###############################################################################
sub register_data
{
my ($self, %objs) = @_;
map {
$self->{data}->{$_} = $objs{$_};
$self->{data_owners}->{$_} = [
scalar(ref($objs{$_})),
scalar(caller)
];
} keys %objs;
}
sub get_data_type
{
my ($self, $data) = @_;
return $self->{data_owners}->{$data}->[0];
}
sub get_data_owner
{
my ($self, $data) = @_;
return $self->{data_owners}->{$data}->[1];
}
sub get_all_data
{
my ($self) = @_;
return $self->{data_owners};
}
###############################################################################
### action object registry functions
###############################################################################
sub register_actions
{
my ($self, @actions) = @_;
push(@{$self->{actions}}, @actions);
}
sub get_action
{
my ($self, $action) = @_;
###
### Changed from a foreach loop to a grep to
### help search performance, as reported by dprofpp.
###
grep ($_->{name} eq $action && (return $_), @{$self->{actions}});
return undef;
}
###############################################################################
### policy method registry functions
###############################################################################
sub register_policy
{
my ($self, $control_obj, %methods) = @_;
my ($method);
foreach $method (keys %methods)
{
die("$0: cannot register undefined policy method '$method'\n")
if (! $control_obj->can($methods{$method}));
# warn "\t!!!Policy registered but not in psgconf_modules '$method'\n"
# if ( grep (/^${method}$/, @{$self->{policy_order}}));
$self->{policy}->{$method} = {
method => $methods{$method},
control_obj => $control_obj
};
}
}
sub get_all_policy
{
my ($self) = @_;
return $self->{policy};
}
###############################################################################
### documentation
###############################################################################
1;
__END__
=head1 NAME
PSGConf - main Perl module for psgconf package
=head1 SYNOPSIS
use PSGConf;
$psgconf = PSGConf->new(%opts);
$psgconf->access_data_stores();
$psgconf->enforce_policy();
$psgconf->instantiate_actions();
$psgconf->process_actions();
$psgconf->cleanup();
=head1 DESCRIPTION
The B<PSGConf> module provides a modular framework for automating system
configuration tasks. No real work is done by the B<PSGConf> object;
its only function is to coordinate the activity of other objects.
For a high-level overview of the B<psgconf> system, see
L<psgconf-intro>.
=head1 APPLICATION METHODS
The B<PSGConf> class supports the following methods for use by the
B<psgconf> application:
=over 4
=item new()
Creates a new B<PSGConf> object. The arguments are interpretted as a
hash of attributes for the object. The following attributes are
supported:
=over 4
=item config_dir
Full path to B<psgconf> config files. Default is
F</usr/local/share/psgconf/config>.
=item files_dir
Full path to B<psgconf> data files. Default is
F</usr/local/share/psgconf/files>.
=item modules_file
Full path to B<psgconf> modules file. Default is
F</usr/local/etc/psgconf_modules>.
=item tmpdir
Full path to B<psgconf> temporary directory. Default is the
environment variable F<$PSGCONF_TMPDIR/$0.$$> or if that
is not set, F</var/tmp/$0.$$>.
=item verbose
Boolean value to indicate whether B<psgconf> should be verbose. Default
is no.
=item do_fix
Boolean value to indicate whether B<psgconf> should actually implement
necessary changes. (This is done by calling the do() method of any
registered action objects.) Default is no.
=item restart_daemons
Boolean value to indicate whether B<psgconf> should restart daemons when
their configuration files are changed. Default is yes.
=item rm_tmpfiles
Boolean value to indicate whether B<psgconf> should remove files from
the temp directory before exiting. Default is yes.
=item do_diffs
Boolean value to indicate whether B<psgconf> should print diffs to show
what changes are needed. Default is no.
=item update_pkgs
Boolean value to indicate whether B<psgconf> should update software
packages. Default is yes.
=back
The new() method also loads the Control and DataStore modules specified
in the modules file.
=item access_data_stores()
Invokes the read_config() method of all DataStore modules.
=item enforce_policy()
Invokes the policy methods specified in the modules file.
=item instantiate_actions()
Invokes the decide() method of all Control modules.
=item process_actions()
Invokes the check() method of all registered Action objects. Also invokes
the diff() and/or do() methods of all registered Action objects,
depending on whether the I<do_fix> or I<do_diff> attributes are set.
=item cleanup()
Invokes the cleanup() method of all Control modules. If the I<do_fix>
attribute is enabled, returns the number of actions that were
unsuccessful; otherwise, returns the number of actions that need to be
performed.
=back
=head1 UTILITY METHODS
The B<PSGConf> class supports the following utility methods for use by
B<psgconf> modules:
=over 4
=item data_obj()
Return the Data object named by the argument.
=item I<object_name>
Calls the get() method of the Data object named I<object_name>.
=item save_file()
Saves a backup copy of the file named by the argument. If the second
argument is true, the backup is made by copying instead of renaming
(i.e., the original file is not removed).
=item register_data()
Add Data objects.
=item get_data_type()
Return the name of the Data module that provided the specified Data object.
=item get_data_owner()
Return the name of the Control module that provided the specified Data object.
=item get_all_data()
Return a hash mapping Data object names to an array containing their
type and the name of the module the provided them.
=item register_actions()
Add Action objects.
=item get_action()
Find a named Action object.
=item register_policy()
Add policy methods.
=item get_all_policy()
Return a hash mapping policy method names to the name of the Control
module that provided them.
=back
=head1 SEE ALSO
L<perl>
L<psgconf-intro>
=cut
syntax highlighted by Code2HTML, v. 0.9.1