# -*-perl-*-

# $Id: Triggers.plm,v 1.3 2004/08/02 21:28:34 thomas Exp $

# This perl module implements the files component of the host configuration
# environment.

# $Log: Triggers.plm,v $
# Revision 1.3  2004/08/02 21:28:34  thomas
# Pass parameters to external commands
#
# Revision 1.2  2003/12/15 16:28:35  thomas
# check-in for 0.1.1
#
# Revision 1.1.1.1  2003/12/02 14:47:34  thomas
# Initial version before export to 0.1.1
#

package  What::Node::Triggers;

use strict;
use FileHandle;
use DirHandle;
use Data::Dumper;

use base qw(What::Node);

# Specify the options (with default values) that
# will be set in the 'What::new' method.
our %opts = ();

# Set the priority for this module in $main::
$::module_priority{'Triggers'} = 0;

###############################################################################
#
# All the code to manage the trigger config tree
# is in the What::Node package.  The code to run triggers is in
# the What::Trigger package (and sub-packages).
#
# This module will, however, execute all the triggers that match
# /Triggers/apply* in the apply phase.  This is so that arbitrary code
# can be executed by the configurator (after all other configuration
# has taken place.
###############################################################################

=head1 NAME

What::Node::Triggers - Creating and activating trigger nodes.

=head1 DESCRIPTION

This B<what> configuration subtree manages triggers.  Triggers are
specific procedural components that are run as a result of activities
of other configuration modules.  There are currently 2 types of
triggers -- B<internal> and B<external>.

B<Internal> triggers call methods from perl packages of the form
B<What::Trigger::I<name>>, which are loaded when required.
The I<name> method is run in that package, and passed a parameter
structure containing information about the event that caused the
trigger to be run.

B<External> triggers can execute any program available to the system.
The parameters passed to the trigger will be passed as options to the
program, in the GNU long style.

In all cases, it is up to the configuration method calling the trigger
to know what parameters that trigger is expecting, and include an
appropriate parameter structure with the B<trigger> call.

=head1 TRIGGER DEFINITION

Triggers need to be defined before they can be called.  They are
defined by creating nodes in the B</Triggers> configuration sub-tree.

An B<internal> trigger could be defined with this XML:

<what>
  <node path="/Triggers/catchonfire" module="catchonfire" />
</what>

This XML would indicate the presence of a module
B<What::Trigger::catchonfire> that could be called,
presumably, to start a fire on the current computer.

Note that the B<what> driver program checks for all internal triggers
during its initialization, and automatically creates trigger nodes for
all internal triggers.  Thus, no definition is typically needed for
internal triggers.

An B<external> trigger could be defined with the following XML:

 <what>
   <node path="/Triggers/external/telinit" external-command="/sbin/telinit" args="Q" />
 </what>

This XML would create a B<trigger> node that, when called, would run
the B</sbin/telinit> program with a single argument B<Q>.  This would
cause the B<init> process to re-read the B</etc/inittab> file.

External triggers may contain the attribute B<params>, which is a
comma-separated list of attribute name that will be copied from the
trigger execution trigger node onto the command line of the external
command.  For instance, the following XML:

 <what>
   <node path="/Triggers/external/rpm-import"
         external-command="/bin/rpm" params="import" />

   <node path="/Files/usr/share/rhn/RPM-GPG-KEY-redhat" type="file"
         uid="0" gid="0" mode="0644"
         source="/s/what/xml/groups/gpg-pubkeys/RPM-GPG-KEY-redhat" >
     <trigger path="/external/rpm-import"
              import="/usr/share/rhn/RPM-GPG-KEY-redhat" />
   </node>
 </what>

...will cause the following command to be run when the file is replaced:

/bin/rpm --import /usr/share/rhn/RPM-GPG-KEY-redhat

=head1 TRIGGER EXECUTION

Triggers are installed on other nodes in the configuration tree as
B<trigger clauses>.  Then typically when the configuration is applied,
if a node is modified that has attached B<trigger clauses>, that
application subsystem will cause the trigger to be executed.  For
example, a B<Files> node to update /etc/inittab would likely have a
trigger attached to it:

<what>
  <node path="/Files/etc/inittab" type="file" . . . >
    <trigger path="/external/telinit" />
  </node>
</what>

This XML would cause the trigger B</external/telinit> to be run any
time there was a change to the file B</etc/inittab>.

Triggers can also be executed unconditionally via a mechanism
available in the B</Triggers> subtree.  When a configuration is
applied, any triggers attached to nodes that match the path glob
/Triggers/apply/* will be executed.  For example, the following XML
will cause the program /bin/true to be executed when the configuration
is applied:

<what>
  <node path="/Triggers/apply/true" >
    <trigger path="/run/true" />
  </node>
  <node path="/Triggers/run/true" external-command="/bin/true" />
</what>

=head1 SEE ALSO

What::Trigger

=cut

###############################################################################
# Run anything in the /Triggers/apply sub-directory.

sub apply {
  my $class = shift;
  my $section = shift;
  my $what = shift;
  my $sname = shift;
  my $cname = shift;

  my $children = $class->getchildren ($section, path => '/apply');
  defined $children or return;
  my ($child, %priority);

  # Make sure each child has a priority
  foreach $child (keys %$children) {
    $priority{$child} = $children->{$child}->getattrs ($section, attr => 'priority');
    if (!defined $priority{$child}) {
      $children->{$child}->setattrs ($section, {priority => 0});
      $priority{$child} = 0;
    }
  }
  foreach $child (sort {$priority{$b} <=> $priority{$a}} keys %$children) {
    $children->{$child}->run_triggers ($section, $what, "/Triggers/apply/$child");
  }
  return 1;
}


###############################################################################

1;
