#!/usr/bin/perl -w
######################################################################
#
# $Id: webjob-jqd-list-members,v 1.14 2012/01/07 08:01:19 mavrik Exp $
#
######################################################################
#
# Copyright 2007-2012 The WebJob Project, All Rights Reserved.
#
######################################################################
#
# Purpose: List the members of the specified group or group set.
#
######################################################################

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('e:D:G:i:n', \%hOptions))
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # The output field delimiter, '-D', is optional.
  #
  ####################################################################

  $$phProperties{'OutputDelimiter'} = (exists($hOptions{'D'})) ? $hOptions{'D'} : "\\n";

  if ($$phProperties{'OutputDelimiter'} !~ /^(\\n|\\t|[ ,:;=|])$/)
  {
    print STDERR "$$phProperties{'Program'}: Error='Invalid output field delimiter ($$phProperties{'OutputDelimiter'}).'\n";
    exit(2);
  }

  $$phProperties{'OutputDelimiter'} =~ s/\\n/\n/g; # Unescape newlines.
  $$phProperties{'OutputDelimiter'} =~ s/\\t/\t/g; # Unescape tabs.

  ####################################################################
  #
  # A comma delimited list of excludes, '-e', is optional.
  #
  ####################################################################

  $$phProperties{'ExcludeList'} = (exists($hOptions{'e'})) ? $hOptions{'e'} : "";

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

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

  ####################################################################
  #
  # A comma delimited list of includes, '-i', is required.
  #
  ####################################################################

  $$phProperties{'IncludeList'} = (exists($hOptions{'i'})) ? $hOptions{'i'} : "";

  if ($$phProperties{'IncludeList'} eq "")
  {
    Usage($$phProperties{'Program'});
  }

  ####################################################################
  #
  # The no resolution flag, '-n', is optional.
  #
  ####################################################################

  $$phProperties{'NoResolution'} = (exists($hOptions{'n'})) ? 1 : 0;

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

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

  ####################################################################
  #
  # Validate each include.
  #
  ####################################################################

  my (%hFailIncludes);

  foreach my $sInclude (split(/,/, $$phProperties{'IncludeList'}))
  {
    if ($sInclude !~ /^$$phProperties{'CommonRegexes'}{'GroupToken'}$/)
    {
      $hFailIncludes{$sInclude}++;
    }
  }

  if (%hFailIncludes)
  {
    print STDERR "$$phProperties{'Program'}: Error='One or more includes (" . join(",", sort(keys(%hFailIncludes))) . ") 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);
  }

  ####################################################################
  #
  # Resolve the include and exclude lists.
  #
  ####################################################################

  my (%hExcludes, %hIncludes, %hResolverArgs);

  %hResolverArgs =
  (
    'ForwardMap'     => {}, # Not needed/used for this application.
    'GroupMap'       => \%hGroupMap,
    'NoRecursion'    => ($$phProperties{'NoResolution'}) ? 1 : 0,
    'QueueMap'       => \%hIncludes,
    'QueueList'      => $$phProperties{'IncludeList'},
    'RecursionStack' => [],
  );
  if (!JqdResolveQueueList(\%hResolverArgs))
  {
    print STDERR "$$phProperties{'Program'}: IncludeList='$$phProperties{'IncludeList'}' Error='$hResolverArgs{'Error'}'\n";
    exit(2);
  }

  %hResolverArgs =
  (
    'ForwardMap'     => {}, # Not needed/used for this application.
    'GroupMap'       => \%hGroupMap,
    'NoRecursion'    => ($$phProperties{'NoResolution'}) ? 1 : 0,
    'QueueMap'       => \%hExcludes,
    'QueueList'      => $$phProperties{'ExcludeList'},
    'RecursionStack' => [],
  );
  if (!JqdResolveQueueList(\%hResolverArgs))
  {
    print STDERR "$$phProperties{'Program'}: ExcludeList='$$phProperties{'ExcludeList'}' Error='$hResolverArgs{'Error'}'\n";
    exit(2);
  }

  ####################################################################
  #
  # Bash the excludes against the includes, and remove the overlap.
  #
  ####################################################################

  foreach my $sExclude (sort(keys(%hExcludes)))
  {
    if (exists($hIncludes{$sExclude}))
    {
      delete($hIncludes{$sExclude});
    }
  }

  ####################################################################
  #
  # List members that remain in the include list.
  #
  ####################################################################

  print join($$phProperties{'OutputDelimiter'}, sort(keys(%hIncludes))), "\n";

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

  1;


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

sub Usage
{
  my ($sProgram) = @_;
  print STDERR "\n";
  print STDERR "Usage: $sProgram [-n] [-D delimiter] [-e {group|queue}[,{group|queue}[...]]] [-G group-file] -i group[,group[...]]\n";
  print STDERR "\n";
  exit(1);
}


=pod

=head1 NAME

webjob-jqd-list-members - List the members of the specified group or group set.

=head1 SYNOPSIS

B<webjob-jqd-list-members> B<[-n]> B<[-D delimiter]> B<[-e {group|queue}[,{group|queue}[...]]]> B<[-G group-file]> B<-i group[,group[...]]>

=head1 DESCRIPTION

This utility lists the members of one or more groups.

=head1 OPTIONS

=over 4

=item B<-D delimiter>

Specifies the output field delimiter.  Valid delimiters include the
following characters: newline ('\n'), tab ('\t'), space (' '), comma
(','), colon (':'), semi-colon (';'), equal ('='), and pipe ('|').
The default delimiter is a newline.

=item B<-e group[,group[...]]>

Specifies one or more queues or queue groups that are to be excluded
from the operation.  Note that group names must be prefixed with '%'.
For example, a group called 'test' must be specified as '%test'.

=item B<-G group-file>

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

=item B<-i group[,group[...]]>

Specifies one or more queue groups that are to be included in the
operation.  Note that group names must be prefixed with '%'.  For
example, a group called 'test' must be specified as '%test'. Also,
note that individual queues are not allowed for this argument.

=item B<-n>

This option disables group token resolution.

=back

=head1 AUTHOR

Klayton Monroe

=head1 SEE ALSO

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

=head1 LICENSE

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

=cut
