#!/usr/bin/perl -w
######################################################################
#
# $Id: webjob-jqd-update-group,v 1.19 2012/01/07 08:01:19 mavrik Exp $
#
######################################################################
#
# Copyright 2007-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: Update an existing group.
#
######################################################################

use FindBin qw($Bin $RealBin); use lib ("$Bin/../lib/perl5/site_perl", "$RealBin/../lib/perl5/site_perl", "/usr/local/webjob/lib/perl5/site_perl", "/opt/local/webjob/lib/perl5/site_perl");

use strict;
use File::Basename;
use Getopt::Std;
use WebJob::JqdRoutines;
use WebJob::KvpRoutines 1.029;
use WebJob::Properties 1.035;

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);

  ####################################################################
  #
  # Initialize regex variables.
  #
  ####################################################################

  $hProperties{'CommonRegexes'} = PropertiesGetGlobalRegexes();

  ####################################################################
  #
  # 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('acG:g:m:', \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # The auto-create flag, '-a', is optional.
  #
  ####################################################################

  $$phProperties{'AutoCreate'} = (exists($hOptions{'a'})) ? 1 : 0;
  $$phProperties{'AutoCreate'} = 1 if (exists($hOptions{'c'})); # This is a legacy option, and it is being phased out.

  ####################################################################
  #
  # A group file, '-G', is optional.
  #
  ####################################################################

  $$phProperties{'GroupsFile'} = (exists($hOptions{'G'})) ? $hOptions{'G'} : "/var/webjob/config/jqd/groups";

  ####################################################################
  #
  # A group, '-g', is required.
  #
  ####################################################################

  $$phProperties{'Group'} = (exists($hOptions{'g'})) ? $hOptions{'g'} : undef;

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

  if ($$phProperties{'Group'} !~ /^$$phProperties{'CommonRegexes'}{'Group'}$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='The group ($$phProperties{'Group'}) does not pass muster.'\n";
    exit(2);
  }

  ####################################################################
  #
  # The mode, '-m', is optional.
  #
  ####################################################################

  $$phProperties{'Mode'} = (exists($hOptions{'m'})) ? lc($hOptions{'m'}) : "merge";

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

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

  ####################################################################
  #
  # Validate each member.
  #
  ####################################################################

  my (%hFailMembers, %hPassMembers);

  foreach my $sMember (@ARGV)
  {
    my $sMemberRegex = "(?:" . $$phProperties{'CommonRegexes'}{'GroupToken'} . "|" . $$phProperties{'CommonRegexes'}{'ClientId'} . ")";
    if ($sMember =~ /^$sMemberRegex$/)
    {
      $hPassMembers{$sMember}++;
    }
    else
    {
      $hFailMembers{$sMember}++;
    }
  }

  if (%hFailMembers)
  {
    print STDERR "$$phProperties{'Program'}: Error='One or more members (" . join(",", sort(keys(%hFailMembers))) . ") do not pass muster.'\n";
    exit(2);
  }

  ####################################################################
  #
  # Create group queue map.
  #
  ####################################################################

  my (%hGroupMap);

  %hGroupMap = ();

  if (-f $$phProperties{'GroupsFile'})
  {
    my $sKeyRegex = $$phProperties{'CommonRegexes'}{'Group'};
    my $sUnitRegex = "(?:" . $$phProperties{'CommonRegexes'}{'GroupToken'} . "|" . $$phProperties{'CommonRegexes'}{'ClientId'} . ")";
    my $sValueRegex = "(?:|$sUnitRegex(?:,$sUnitRegex)*)"; # Empty groups are allowed.
    my %hLArgs =
    (
      'File'           => $$phProperties{'GroupsFile'},
      'Properties'     => \%hGroupMap,
      'Template'       => { $sKeyRegex => $sValueRegex, },
      'VerifyValues'   => 1,
    );
    if (!KvpGetKvps(\%hLArgs))
    {
      print STDERR "$$phProperties{'Program'}: Error='$hLArgs{'Error'}'\n";
      exit(2);
    }
  }
  else
  {
    print STDERR "$$phProperties{'Program'}: Error='File ($$phProperties{'GroupsFile'}) does not exist or is not regular.'\n";
    exit(2);
  }

  ####################################################################
  #
  # Make sure that the specified group already exists. If it doesn't
  # exist and the create missing group flag has not been set, abort.
  #
  ####################################################################

  if (!exists($hGroupMap{$$phProperties{'Group'}}) && !$$phProperties{'AutoCreate'})
  {
    print STDERR "$$phProperties{'Program'}: Error='Group ($$phProperties{'Group'}) does not exist.'\n";
    exit(2);
  }

  ####################################################################
  #
  # Conditionally ensure that any groups in the member list exist.
  #
  ####################################################################

  if ($$phProperties{'Mode'} =~ /^(?:merge|replace)$/)
  {
    foreach my $sMember (keys(%hPassMembers))
    {
      if ($sMember =~ /^%(.+)/ && !exists($hGroupMap{$1}))
      {
        $hFailMembers{$sMember}++;
      }
    }

    if (%hFailMembers)
    {
      print STDERR "$$phProperties{'Program'}: Error='One or more members (" . join(",", sort(keys(%hFailMembers))) . ") represent groups that do not exist.'\n";
      exit(2);
    }
  }

  ####################################################################
  #
  # Make sure that the group, itself, is not in the member list.
  #
  ####################################################################

  foreach my $sMember (keys(%hPassMembers))
  {
    if ($sMember =~ /^%(.+)/ && $1 eq $$phProperties{'Group'})
    {
      print STDERR "$$phProperties{'Program'}: Error='Group contains a reference to itself.'\n";
      exit(2);
    }
  }

  ####################################################################
  #
  # Conditionally replace, remove, or merge the new group members.
  #
  ####################################################################

  if ($$phProperties{'Mode'} eq "replace")
  {
    $hGroupMap{$$phProperties{'Group'}} = join(",", sort(keys(%hPassMembers)));
  }
  elsif($$phProperties{'Mode'} eq "remove")
  {
    $hGroupMap{$$phProperties{'Group'}} = JqdRebuildGroup({ 'Group' => $$phProperties{'Group'}, 'GroupMap' => \%hGroupMap, 'Members' => join(",", sort(keys(%hPassMembers))), 'RemoveMembers' => 1 });
  }
  else
  {
    $hGroupMap{$$phProperties{'Group'}} = JqdRebuildGroup({ 'Group' => $$phProperties{'Group'}, 'GroupMap' => \%hGroupMap, 'Members' => join(",", sort(keys(%hPassMembers))), 'RemoveMembers' => 0 });
  }

  ####################################################################
  #
  # Write the new group file.
  #
  ####################################################################

  my $sKeyRegex = $$phProperties{'CommonRegexes'}{'Group'};
  my $sUnitRegex = "(?:" . $$phProperties{'CommonRegexes'}{'GroupToken'} . "|" . $$phProperties{'CommonRegexes'}{'ClientId'} . ")";
  my $sValueRegex = "(?:|$sUnitRegex(?:,$sUnitRegex)*)"; # Empty groups are allowed.
  my %hLArgs =
  (
    'File'           => $$phProperties{'GroupsFile'},
    'Properties'     => \%hGroupMap,
    'Template'       => { $sKeyRegex => $sValueRegex, },
    'VerifyValues'   => 0,
  );
  if (!KvpSetKvps(\%hLArgs))
  {
    print STDERR "$$phProperties{'Program'}: Error='$hLArgs{'Error'}'\n";
    exit(2);
  }

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

  1;


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

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-a] [-G group-file] [-m {merge|replace|remove}] -g group [member [member ...]]\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

webjob-jqd-update-group - Update an existing group.

=head1 SYNOPSIS

B<webjob-jqd-update-group> B<[-a]> B<[-G group-file]> B<[-m {merge|replace|remove}]> B<-g group> B<[member [member ...]]>

=head1 DESCRIPTION

This utility updates, creates, or replaces a group.  Duplicate members
(if any) are automatically squashed.

=head1 OPTIONS

=over 4

=item B<-a>

Instructs the script to automatically create missing groups.  This
option is disabled by default.

=item B<-G group-file>

Specifies an alternate group file.  The default group file is
/var/webjob/config/jqd/groups.

=item B<-g group>

Specifies the name of the group to update.

=item B<-m {merge|replace|remove}>

Specifies the mode of operation.  A value of 'merge' will cause
members to be merged into the specified group (if it exists); a value
of 'replace' will cause the specified group (if it exists) to be
completely replaced by the new members; and a value of 'remove' will
cause existing members to be deleted from the specified group (if it
exists).  The default mode of operation is 'merge'.

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

webjob-jqd-create-group(1), webjob-jqd-delete-group(1)

=head1 LICENSE

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

=cut
