#!/bin/sh
#!webjob-perl -w
eval 'exec perl -x ${0} ${1+"${@}"} ;'
  if (0);
######################################################################
#
# $Id: webjob-setup-cronjobs,v 1.16 2012/01/07 08:01:23 mavrik Exp $
#
######################################################################
#
# Copyright 2003-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Deploy/Remove/Update WebJob crontab entries.
#
######################################################################

use strict;

######################################################################
#
# Adjust @INC. This must be done before any other modules are loaded.
# Note that special provisions may be required when this script runs
# via WebJob to ensure that downloaded Perl scripts are able to find
# locally installed, WebJob-specific modules.
#
######################################################################

my @aLibs;

BEGIN
{
  use FindBin qw($Bin $RealBin);
  @aLibs =
  (
    "$Bin/../lib/perl5/site_perl",
    "$RealBin/../lib/perl5/site_perl",
    "/usr/local/webjob/lib/perl5/site_perl",
    "/opt/local/webjob/lib/perl5/site_perl",
  );
  if (($ENV{'WEBJOB_TEMP_DIRECTORY'} || "") =~ /^(.+[\/\\][.]?webjob[\/\\](?:profiles[\/\\][^\/\\]+[\/\\])?)run$/)
  {
    my $sLib = $1 . "lib/perl5/site_perl";
    $sLib =~ s,\\,/,g; # According to the documentation, lib.pm only works with UNIX-style paths.
    unshift(@aLibs, $sLib); # This path needs to be first since it's closer to the install point.
  }
}

use lib (@aLibs);

######################################################################
#
# Load any remaining modules needed by this script.
#
######################################################################

use File::Basename;
use Getopt::Std;
use WebJob::KvpRoutines 1.029;
use WebJob::Properties 1.027;

BEGIN
{
  ####################################################################
  #
  # The Properties hash is essentially private. Those parts of the
  # program that wish to access or modify the data in this hash need
  # to call GetProperties() to obtain a reference.
  #
  ####################################################################

  my (%hProperties);

  ####################################################################
  #
  # Define helper routines.
  #
  ####################################################################

  sub GetProperties
  {
    return \%hProperties;
  }
}

######################################################################
#
# Main Routine
#
######################################################################

  ####################################################################
  #
  # Punch in and go to work.
  #
  ####################################################################

  my ($phProperties);

  $phProperties = GetProperties();

  $$phProperties{'Program'} = basename(__FILE__);

  ####################################################################
  #
  # Get Options.
  #
  ####################################################################

  my (%hOptions);

  if (!getopts('a:H:o:', \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # An action, '-a', is required.
  #
  ####################################################################

  $$phProperties{'Action'} = (exists($hOptions{'a'})) ? lc($hOptions{'a'}) : undef;

  if (!defined($$phProperties{'Action'}))
  {
    Usage($$phProperties{'Program'});
  }

  if ($$phProperties{'Action'} !~ /^(?:deploy|remove|update)$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Action must be \"deploy\", \"remove\", or \"update\".'\n";
    exit(2);
  }

  ####################################################################
  #
  # A webjob home directory, '-H', is optional.
  #
  ####################################################################

  $$phProperties{'WebJobHome'} = (exists($hOptions{'H'})) ? $hOptions{'H'} : "/usr/local/webjob";

  ####################################################################
  #
  # The option list, '-o', is optional.
  #
  ####################################################################

  $$phProperties{'AutoCreate'} = 0;
  $$phProperties{'DryRun'} = 0;
  $$phProperties{'Force'} = 0;

  $$phProperties{'Options'} = (exists($hOptions{'o'})) ? $hOptions{'o'} : undef;

  if (exists($hOptions{'o'}) && defined($hOptions{'o'}))
  {
    foreach my $sActualOption (split(/,/, $$phProperties{'Options'}))
    {
      foreach my $sTargetOption ('AutoCreate', 'DryRun', 'Force')
      {
        if ($sActualOption =~ /^$sTargetOption$/i)
        {
          $$phProperties{$sTargetOption} = 1;
        }
      }
    }
  }

  ####################################################################
  #
  # If any arguments remain, it's an error.
  #
  ####################################################################

  if (scalar(@ARGV) > 0)
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # Update the client's crontab entries.
  #
  ####################################################################

  my ($sError);

  if (!UpdateCronTabEntries($phProperties, \$sError))
  {
    print STDERR "$$phProperties{'Program'}: Error='$sError'\n";
    exit(2);
  }

  ####################################################################
  #
  # Shutdown and go home.
  #
  ####################################################################

  1;


######################################################################
#
# UpdateCronTabEntries
#
######################################################################

sub UpdateCronTabEntries
{
  my ($phProperties, $psError) = @_;

  ####################################################################
  #
  # Extract worker key/value pairs ignoring any that are not related
  # to the time specification, and build the command line.
  #
  ####################################################################

  my (%hWorkers);

  $$phProperties{'CustomRegexes'}{'TimeSpec'} = qq([*]|\\d+[,\\d+]*);
  $$phProperties{'WebJobCfg'} = $$phProperties{'WebJobHome'} . "/etc/upload.cfg";
  $$phProperties{'WebJobExe'} = $$phProperties{'WebJobHome'} . "/bin/webjob";
  $$phProperties{'WorkersDirectory'} = $$phProperties{'WebJobHome'} . "/workers";

  for my $sWorker ("hourly", "daily")
  {
    my %hLArgs =
    (
      'File'           => $$phProperties{'WorkersDirectory'} . "/$sWorker.cfg",
      'Properties'     => \%{$hWorkers{$sWorker}},
      'RequireAllKeys' => 1,
      'Template'       =>
      {
        'Minute'       => $$phProperties{'CustomRegexes'}{'TimeSpec'},
        'Hour'         => $$phProperties{'CustomRegexes'}{'TimeSpec'},
        'DayOfWeek'    => $$phProperties{'CustomRegexes'}{'TimeSpec'},
        'DayOfMonth'   => $$phProperties{'CustomRegexes'}{'TimeSpec'},
        'Month'        => $$phProperties{'CustomRegexes'}{'TimeSpec'},
      },
    );
    if (!KvpGetKvps(\%hLArgs))
    {
      $$psError = $hLArgs{'Error'};
      return undef;
    }
    $hWorkers{$sWorker}{'TimeSpec'} = join(" ",
      $hWorkers{$sWorker}{'Minute'},
      $hWorkers{$sWorker}{'Hour'},
      $hWorkers{$sWorker}{'DayOfMonth'},
      $hWorkers{$sWorker}{'Month'},
      $hWorkers{$sWorker}{'DayOfWeek'}
      );
    $hWorkers{$sWorker}{'CommandLine'} = qq($$phProperties{'WebJobExe'} -e -f $$phProperties{'WebJobCfg'} $sWorker > /dev/null 2>&1);
  }

  ####################################################################
  #
  # Use the cronjob manager to deploy/remove crontab entries. Note
  # that the arguments for this command have been put in a specific
  # order. This was done because the arguments for the CommandLine
  # value in the uploaded .env file are not properly quoted, which
  # makes them difficult to parse.
  #
  ####################################################################

  for my $sWorker ("hourly", "daily")
  {
    my ($sCommandLine, $sOutput, $sStatus);
    $sCommandLine = qq("$$phProperties{'WebJobExe'}" -e -f "$$phProperties{'WebJobCfg'}" cronjob-manager);
    if ($$phProperties{'Action'} =~ /^deploy$/)
    {
      $sCommandLine .= qq( --deploy);
      $sCommandLine .= qq( -n) if ($$phProperties{'DryRun'});
      $sCommandLine .= qq( -t '$hWorkers{$sWorker}{'TimeSpec'}');
      $sCommandLine .= qq( -c '$hWorkers{$sWorker}{'CommandLine'}');
    }
    elsif ($$phProperties{'Action'} =~ /^remove$/)
    {
      $sCommandLine .= qq( --remove);
      $sCommandLine .= qq( -F) if ($$phProperties{'Force'});
      $sCommandLine .= qq( -n) if ($$phProperties{'DryRun'});
      $sCommandLine .= qq( -e '$hWorkers{$sWorker}{'CommandLine'}');
    }
    elsif ($$phProperties{'Action'} =~ /^update$/)
    {
      $sCommandLine .= qq( --update);
      $sCommandLine .= qq( -a) if ($$phProperties{'AutoCreate'});
      $sCommandLine .= qq( -F) if ($$phProperties{'Force'});
      $sCommandLine .= qq( -n) if ($$phProperties{'DryRun'});
      $sCommandLine .= qq( -t '$hWorkers{$sWorker}{'TimeSpec'}');
      $sCommandLine .= qq( -c '$hWorkers{$sWorker}{'CommandLine'}');
    }
    else
    {
      $$psError = "Invalid action ($$phProperties{'Action'}). Action must be \"deploy\", \"remove\", or \"update\".";
      return undef;
    }
    $sOutput = qx($sCommandLine);
    $sStatus = ($? >> 8) & 0xff;
    if ($sStatus != 0)
    {
      my $phExitCodes = PropertiesGetGlobalExitCodes();
      my $sFailure = (exists($$phExitCodes{'webjob'}{$sStatus})) ? $$phExitCodes{'webjob'}{$sStatus} : $sStatus;
      $$psError = "WebJob failed ($sFailure).";
      return undef;
    }
  }

  1;
}


######################################################################
#
# Usage
#
######################################################################

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-H webjob-home] [-o option[,option[,...]]] -a {deploy|remove|update}\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

webjob-setup-cronjobs - Deploy/Remove/Update WebJob crontab entries.

=head1 SYNOPSIS

B<webjob-setup-cronjobs> B<[-H webjob-home]> B<[-o option[,option[,...]]]> B<-a {deploy|remove|update}>

=head1 DESCRIPTION

This utility deploys, removes, or updates WebJob hourly/daily crontab
entries.  These entries are based on worker config files deployed on
the client.

=head1 OPTIONS

=over 4

=item B<-a {deploy|remove|update}>

Specifies the action to perform.

=item B<-H webjob-home>

Specifies the home directory of the WebJob client.  The default value
is '/usr/local/webjob'.

=item B<-o option,[option[,...]]>

Specifies the list of options to apply.  Currently, the following
options are supported:

=over 4

=item AutoCreate

Automatically create crontab entries if they don't exist.  This option
only has meaning when the action is 'update'.

=item DryRun

Don't actually change the crontab.  Instead, report what the changes
would have been.

=item Force

Force all crontab entries matching the internally defined regular
expression to be removed.  Normally, there should only be one
hourly/daily crontab entry.  However, there may be cases were multiple
entries are detected (e.g., an old entry may have been commented out
or two entries may execute the same command, but with different time
specifications).  If that condition is found, the cronjob manager must
be given the force option or it will refuse to make the requested
change.

=back

=back

=head1 RETURN VALUES

Upon successful completion, a value of B<0> (B<XER_OK>) is returned.
Otherwise, one of the following error codes is returned:

=over 5

=item *

B<1 => B<XER_Usage>

=item *

B<2 => B<XER_Abort>

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

webjob(1)

=head1 LICENSE

All documentation and code are distributed under same terms and
conditions as B<WebJob>.

=cut
