#!/usr/bin/perl

###############################################################################
## Copyright (c) 2009-2012 SUSE LINUX Products GmbH, Nuernberg, Germany.
###############################################################################

use strict;
use warnings;
use FindBin;
BEGIN { unshift @INC, "$FindBin::Bin/../www/perl-lib" }

use SMT::CLI;
use SMT::Utils;
use SMT::Client;
use SMT::JobQueue;
use SMT::Job;
use SMT::Job::Constants;
use XML::Writer;
use XML::Parser;
use Text::ASCIITable;
use File::Basename;
use Locale::gettext ();

use POSIX ();     # Needed for setlocale()
use Data::Dumper; # DEBUG

POSIX::setlocale(&POSIX::LC_MESSAGES, "");
my $PWD = $ENV{PWD} || '';  # read 'pwd' in case dropPrivileges is active, which changes the current 'pwd'

#### START LOCAL FUNCTIONS ####

#### END LOCAL FUNCTIONS ####

# If dropPrivileges is active then reading files for "createjob" templates is no longer possible if they are in /root/
# smt-job however is only doing read operations or interacting with the SMT database
#
#if(!SMT::Utils::dropPrivileges())
#{
#    print STDERR __("Unable to drop privileges. Abort!\n");
#    exit 1;
#}

# connect to database
my ($cfg, $dbh) = SMT::CLI::init();
if (! defined $dbh) {
    print __("Cannot connect to database");
    exit 3;
}


my ($verbose, $help, $quiet) = undef;
my ($create, $edit, $delete, $list, $construct, @guid, $jobid, $deleteall, $type, $name, $description, $targeted, $expires, $timelag, $finished, $upstream, $cacheresult, $verbosejob, $persistent, $parent, $parentreset, $enable, $disable, @SWpackagelist, @SWpatchlist, @SWpatternlist, @SWproductlist, $SWforce, $SWagree, $SWreboot, $UPpatchwithupdate, $EXECcommand, $WAITtime, $WAITstatus, $CDAction, $jobtemplate, @targetguid, @targetjob, $autoPatchstatus, $inventoryPackages, $inventoryPatches, @inventoryHWcommand) = undef;

use Getopt::Long;
&Getopt::Long::Configure( 'no_autoabbrev', 'no_ignore_case');
if (! &Getopt::Long::GetOptions(
         'help|h' => \$help,
         'verbose|v=i' => \$verbose,
         'quiet|q' => \$quiet,
         # the four basic modes of smt job
         'create|c' => \$create,
         'edit|e' => \$edit,
         'delete|d' => \$delete,
         'list|l' => \$list,
         'construct' => \$construct,
         # job identifier
         'guid|g=s' => \@guid,
         'jobid|j=i' => \$jobid,
         'deleteall|A' => \$deleteall,
         # data fields for create and edit:
         'type|t=s' => \$type,
         'name|n=s' => \$name,
         'description=s' => \$description,
         'targeted=s' => \$targeted,
         'expires=s' =>  \$expires,
         'timelag=s' =>  \$timelag,
         # general switches
         'finished|f!' => \$finished,
         'upstream!' => \$upstream,
         'cacheresult!' => \$cacheresult,
         'verbosejob|V!' => \$verbosejob,
         'persistent!' => \$persistent,
         'parent=i' => \$parent,
         'parentreset' => \$parentreset,
         'enable' => \$enable,
         'disable' => \$disable,
         # job specific arguments
         'package|P=s@' => \@SWpackagelist,
         'patch=s@' => \@SWpatchlist,
         'pattern=s@' => \@SWpatternlist,
         'product=s@' => \@SWproductlist,
         'forceinstall|F!' => \$SWforce,
         'agreelicense!' => \$SWagree,
         'reboot=s' => \$SWreboot,
         'patchwithupdate' => \$UPpatchwithupdate,
         'exec|X=s' => \$EXECcommand,
         'waittime=i' => \$WAITtime,
         'waitstatus=i' => \$WAITstatus,
         'action=s' => \$CDAction,
         'jobtemplate=s' => \$jobtemplate,
         'targetguid=s' => \@targetguid,
         'targetjob=s' => \@targetjob,
         'inventorypackages!' => \$inventoryPackages,
         'inventorypatches!' => \$inventoryPatches,
         'inventorycommand=s' => \@inventoryHWcommand ,
         'autopatchstatus!' => \$autoPatchstatus,
) )
{
  print __("Invalid arguments.")."\n";
  exit 4;
};



if ( defined $help and ( $help == 1 ) )
{
  print basename($0) . " : " . __("list, create, edit and delete jobs from SMT job queue")."\n";
  print "\n";
  print __("Usage:"). ' ' . basename($0) . ' ' . __("<operation mode>  <parameters>")."\n";
  print "\n";
  print "   --help        (-h)        : " . __("show this help")."\n";
  print "   --verbose     (-v) <level>: " . __("show detailed job information")."\n";
  print "                               " . __("level can be a number from 1 to 3")."\n";
  print "   --quiet       (-q)        : " . __("quiet mode, suppress success messages")."\n";
  print "\n";
  print __("operation modes")."\n";
  print "   --list        (-l) : " . __("list jobs (default)")."\n";
  print "   --create      (-c) : " . __("create job")."\n";
  print "   --edit        (-e) : " . __("edit job")."\n";
  print "   --delete      (-d) : " . __("delete job")."\n";
  print "   --construct        : " . __("like create, but only ouputs the job formatted in XML")."\n";
  print "                        " . __("this XML representation can be used to create a job of the type 'createjob'")."\n";
  print "\n";
  print __("main parameters")."\n";
  print "   --guid        (-g) <guid> : " . __("the clients guid")."\n";
  print "                               " . __("can be used multiple times when creating a job")."\n";
  print "   --jobid       (-j) <id>   : " . __("the jobid")."\n";
  print "                               " . __("will be ignored when creating a job")."\n";
  print "   --deleteall   (-A)        : " . __("allows to omit either the guid or the jobid parameter")."\n";
  print "                               " . __("the missing parameter will then match all")."\n";
  print "\n";
  print __("parameters for creating, editing and searching jobs")."\n";
  print "   --type        (-t) <type> : " . __("job type (name or its id)")."\n";
  print "   --name        (-n) <name> : " . __("job name")."\n";
  print "   --description      <desc> : " . __("job description")."\n";
  print "   --parent           <id>   : " . __("parent job, describes a dependency")."\n";
  print "   --parentreset             : " . __("reset the parent id of a job")."\n";
  print "   --enable                  : " . __("(re)enable a job that was disabled or returned without success")."\n";
  print "   --disable                 : " . __("disable a job")."\n";
  print "   --verbosejob  (-V)        : " . __("verbosity of job")."\n";
  print "   --persistent              : " . __("persistency of job")."\n";
  print "   --finished                : " . __("finds finished jobs")."\n";
  print "   --upstream                : " . __("job that is contolled by an upstream SMT server")."\n";
  print "   --cacheresult             : " . __("job results will be cached, used by upstream jobs")."\n";
  print "\n";
  print __("parameters for timing (timeformat: see below)")."\n";
  print "   --targeted                : " . __("targeted date and time")."\n";
  print "   --expires                 : " . __("date and time when job expires")."\n";
  print "   --timelag                 : " . __("time interval for persistent jobs")."\n";
  print "\n";
  print __("job specific parameters (only for creating a job)")."\n";
  print '  '.__("for software push jobs")."\n";
  print "   --package      (-P) <pack>: " . __("a package name")."\n";
  print "                               " . __("can be used multiple times")."\n";
  print "                               " . __("may contain a version instruction of '< <= = >= >', example:") . ' "abc <= 3.2.1"'."\n";
  print "   --patch            <patch>: " . __("a patch name, usage like '--package'")."\n";
  print "   --pattern        <pattern>: " . __("a pattern name, usage like '--package'")."\n";
  print "   --product        <product>: " . __("a product name, usage like '--package'")."\n";
  print "   --forceinstall (-F)       : " . __("enforce the installation")."\n";
  print "   --agreelicense            : " . __("auto-agree to licenses")."\n";
  print "   --autopatchstatus         : " . __("add a following patchstatus job automatically (enabled by default)")."\n";
  print "   --reboot         <policy> : " . __("reboot policy: 'never'|'ifneeded'|'enforce'")."\n";
  print '  '.__("for update jobs")."\n";
  print "   --patchwithupdate         : " . __("patches the system and updates all packages")."\n";
  print "                               " . __("note: this will install new versions of packages if available")."\n";
  print "   --agreelicense            : " . __("auto-agree to licenses")."\n";
  print "   --reboot         <policy> : " . __("reboot policy: 'never'|'ifneeded'|'enforce'")."\n";
  print '  '.__("for execute jobs")."\n";
  print "   --exec         (-X) <cmd> : " . __("the command to execute")."\n";
  print '  '.__("for wait jobs")."\n";
  print "   --waittime         <time> : " . __("time in seconds the job should take")."\n";
  print "   --waitstatus       <exit> : " . __("exit code for the job to return with")."\n";
  print '  '.__("for eject jobs")."\n";
  print "   --action         <action> : " . __("action of CD or DVD tray: 'open'|'close'|'toggle'")."\n";
  print '  '.__("for createjob jobs")."\n";
  print "   --jobtemplate      <file> : " . __("file containing the XML job definition for the job to be created")."\n";
  print "   --targetguid       <guid> : " . __("create the job for the target GUID on the remote SMT server")."\n";
  print "                               " . __("can be used multiple times")."\n";
  print '  '.__("for report jobs")."\n";
  print "   --targetjob       <jobid> : " . __("query the results of the job <jobid> from the remote SMT server")."\n";
  print "                               " . __("can be used multiple times")."\n";
  print '  '.__("for inventory jobs")." " . __("(mainly internal use)")."\n";
  print "   --inventorypackages       : " . __("query information about installed packages")."\n";
  print "   --inventorypatches        : " . __("query information about installed and available patches")."\n";
  print "   --inventorycommand  <cmd> : " . __("query hardware information by executing a command")."\n";
  print "                               " . __("commands are filtered and executed by suseRegister")."\n";
  print "                               " . __("can be used multiple times")."\n";
  print "\n";
  print __("time format")."\n";
  print "   targeted/expires          : " . __("'YYYY-MM-DD[-HH:MM[:SS]]'")."\n";
  print "   timelag                   : " . __("'HH:MM[:SS]'")."\n";
  print "\n";
  print __("negatable parameters")."\n";
  print "   --finished        :  --no-finished"."\n";
  print "   --upstream        :  --no-upstream"."\n";
  print "   --cacheresult     :  --no-cacheresult"."\n";
  print "   --persistent      :  --no-persistent"."\n";
  print "   --verbosejob      :  --no-verbosejob"."\n";
  print "   --forceinstall    :  --no-forceinstall"."\n";
  print "   --agreelicense    :  --no-agreelicense"."\n";
  print "   --autopatchstatus :  --no-autopatchstatus"."\n";
  print "\n";
  print __("available job types")."\n";
  print "   0 : -invalid job- : " . __("Invalid job type.") . "\n";
  print '                       ' . __("Jobs of type 0 will be cleaned up regularly.")."\n";
  print "   1 : patchstatus   : " . __("Request patchstatus information from the client.") . "\n";
  print "   2 : softwarepush  : " . __("Install software on the client.") . "\n";
  print "   3 : update        : " . __("Install available updates.") . "\n";
  print "   4 : execute       : " . __("Execute custom command on the client.") . "\n";
  print "   5 : reboot        : " . __("Reboot the client.") . "\n";
  print "   6 : -reserved-    : " . __("Reserved") . "\n";
  print "   7 : wait          : " . __("Wait and return with specified status (for debugging).") . "\n";
  print "   8 : eject         : " . __("Eject CD/DVD drive (for debugging).") . "\n";
  print "  51 : createjob     : " . __("Creates a job in a downstream SMT server.") . "\n";
  print "  52 : report        : " . __("Retrieves the results of a job that was created via 'createjob'.") . "\n";
  print "  53 : inventory     : " . __("Collect system information.") . "\n";
  print "\n";
  print __("job status IDs")."\n";

  # dynamically list all available job status IDs
  my $X=SMT::Job::Constants::JOB_STATUS;
  foreach my $ID (sort keys %$X)
  {
      next unless $ID =~ /^\d+$/;
      my $count = length("$ID");
      print ((" " x (3 - $count))." $ID : ".SMT::Job::Constants::JOB_STATUS->{$ID}."\n");
  }
  print "\n";
  exit 0;
}

########################################################################################
#
# get and check all passed parameters
#

$autoPatchstatus = defined $autoPatchstatus ? $autoPatchstatus:1;

my %query = ();

my $GUID = undef;
if (@guid && @guid > 0)
{
    # searching only supports one guid
    $query{'GUID'} = $guid[0];
    # for methods that only operate on one guid
    $GUID = $guid[0];
}
else
{
    # array needs to be emptied with the undef function instead setting it undef
    undef @guid;
}
$query{ID} = $jobid if (defined $jobid);

if (defined $SWreboot)
{
    if ( $SWreboot ne 'never'    &&
         $SWreboot ne 'ifneeded' &&
         $SWreboot ne 'enforce'    )
    {
        print _("Invalid reboot policy. Possible values:")." (never, ifneeded, enforce)"."\n";
        exit 4;
    }
}

my $queryJob = SMT::Job->new({ 'dbh' => $dbh });
my $typeID = $queryJob->jobTypeToID($type);
if ( defined $type && not defined $typeID )
{
    print _("Job type unkown.")."\n";
    exit 4;
}
$query{TYPE} = $typeID if (defined $typeID);
$query{NAME} = $name if (defined $name);
$query{DESCRIPTION} = $description if (defined $description);
# check time format
$query{TARGETED} = $targeted if (defined $targeted);
$query{EXPIRES} = $expires if (defined $expires);
$query{TIMELAG} = $timelag if (defined $timelag);
$query{STATUS} = 0 if (defined $enable);
$query{STATUS} = 7 if (defined $disable);


if ( exists $query{TARGETED} )
{
    if ( $query{TARGETED} !~ /^\d{4}-\d{2}-\d{2}([-_+ ]\d{2}:\d{2}(:\d{2})?)?$/ )
    {
        print __("Invalid time format for 'targeted' time.")."\n";
        exit 4;
    }
}
if ( exists $query{EXPIRES} )
{
    if ( $query{EXPIRES} !~ /^\d{4}-\d{2}-\d{2}([-_+ ]\d{2}:\d{2}(:\d{2})?)?$/ )
    {
        print __("Invalid time format for 'expires' time.")."\n";
        exit 4;
    }
}
if ( exists $query{TIMELAG} )
{
    if ( $query{TIMELAG} !~ /^\d{2}:\d{2}(:\d{2})?$/ )
    {
        print __("Invalid time format for 'timelag'.")."\n";
        exit 4;
    }
}


if (defined $finished)
{
    $query{'FINISHED+'} = 'NULL' if ($finished);
    $query{'FINISHED-'} = 'NULL' if (not $finished);
}
if (defined $upstream)
{
    $query{UPSTREAM} =  $upstream ? 1:0;
}
if (defined $cacheresult)
{
    $query{CACHERESULT} =  $cacheresult ? 1:0;
}

if (defined $verbosejob)
{
    $query{VERBOSE} =  $verbosejob ? 1:0;
}
if (defined $persistent)
{
    $query{PERSISTENT} = $persistent ? 1:0;
}
$query{PARENT_ID} = $parent if (defined $parent);
$query{PARENT_ID} = 'NULL' if (defined $parentreset);

my %para = ();
$para{EXECCOMMAND} = $EXECcommand if (defined $EXECcommand);
$para{WAITTIME} = $WAITtime if (defined $WAITtime);
$para{WAITSTATUS} = $WAITstatus if (defined $WAITstatus);
$para{ACTION} = $CDAction if (defined $CDAction);




#######################################################################################
#
# MAIN routine
#


my $JobQ = SMT::JobQueue->new({ 'dbh' => $dbh });
my $Job = SMT::Job->new({ 'dbh' => $dbh });
## my $Client = SMT::Client->new({'dbh' => $dbh});

my $opc = 0;
$opc++ if (defined $create);
$opc++ if (defined $edit);
$opc++ if (defined $delete);
$opc++ if (defined $list);
$opc++ if (defined $construct);
if ( $opc > 1 )
{
    print __("Please define only one operation mode.")."\n";
    exit 3;
}


if ( defined $create || defined $construct )
{
    ## CREATE ##

    if ( not defined $typeID )
    {
        print __("The job type is invalid.")."\n";
        exit 3;
    }
    if ( ((not defined $GUID) && (not @guid)) && (not defined $construct) )
    {
        print __("The guid is invalid.")."\n";
        exit 3;
    }

    # create empty job skeleton
    $Job->{guid} = (@guid > 0) ? undef : $GUID;
    $Job->{id} = undef;
    $Job->{type} = $typeID;

    # fill in parameters if they are specified - maybe overwritten in job type handling below
    my @CreateProps = qw(NAME DESCRIPTION VERBOSE PERSISTENT PARENT_ID TARGETED EXPIRES TIMELAG UPSTREAM CACHERESULT);

    foreach my $cprop (@CreateProps)
    {
        $Job->{lc($cprop)} = $query{uc($cprop)} if ( exists $query{uc($cprop)}  && defined $query{uc($cprop)} );
    }

    if ( defined $Job->{persistent}  &&  $Job->{persistent} == 1 )
    {
        $Job->{timelag} = defined $query{TIMELAG} ? $query{TIMELAG} : '23:00';
        if ( ! defined $Job->{timelag}  ||  $Job->{timelag} =~ /^00:00/ )
        {
            print __("Creating a persistent job with a timelag smaller than one minute is prohibited.")."\n";
            print __("If you really need a small interval first create the job with one minute and then edit its timelag.")."\n";
            exit 3;
        }
    }

    if ( $typeID == 1 )
    {
        ## PATCHSTATUS ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} : 'Patchstatus Job';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : ("Patchstatus Job" . ($GUID ? " for Client $GUID":''));
    }
    elsif ( $typeID == 2 )
    {
        ## SOFTWARE PUSH ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} : 'Software Push';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : sprintf("Software Push: %s", join(', ', @SWpackagelist));

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);
        my %options;
        $options{force} = 1 if defined $SWforce;
        $options{agreelicenses} = 1 if defined $SWagree;
        $options{reboot} = $SWreboot if defined $SWreboot;
        $w->startTag('arguments', %options);

        # new arguments/options format:
        if (%options)
        {
            $w->startTag('options');
            foreach my $optkey (keys %options) {
                $w->dataElement($optkey, $options{$optkey});
            }
            $w->endTag('options');
        }

        my $argumentslistok = 0;

        if ( (@SWpackagelist) && (@SWpackagelist > 0) )
        {
            $w->startTag('packages');
            foreach my $_p (@SWpackagelist) {
                $w->dataElement('package', $_p);
                $argumentslistok = 1;
            }
            $w->endTag('packages');
        }
        if ( (@SWpatchlist) && (@SWpatchlist > 0) )
        {
            $w->startTag('patches');
            foreach my $_p (@SWpatchlist) {
                $w->dataElement('patch', $_p);
                $argumentslistok = 1;
            }
            $w->endTag('patches');
        }
        if ( (@SWpatternlist) && (@SWpatternlist > 0) )
        {
            $w->startTag('patterns');
            foreach my $_p (@SWpatternlist) {
                $w->dataElement('pattern', $_p);
                $argumentslistok = 1;
            }
            $w->endTag('patterns');
        }
        if ( (@SWproductlist) && (@SWproductlist > 0) )
        {
            $w->startTag('products');
            foreach my $_p (@SWproductlist) {
                $w->dataElement('product', $_p);
                $argumentslistok = 1;
            }
            $w->endTag('products');
        }

        $w->endTag('arguments');
        $w->end();

        unless ( $argumentslistok )
        {
            print __("Please define at least one package for a software push job.")."\n";
            exit 3;
        }
        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 3 )
    {
        ## update ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} : 'Update Job';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Update Job';

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);

        my %options;
        $options{patchwithupdate} = 1 if $UPpatchwithupdate;
        $options{agreelicenses} = ($SWagree ? 1:0) if defined $SWagree;
        $options{reboot} = $SWreboot if defined $SWreboot;
        $w->startTag('arguments', %options);

        # new arguments/options format:
        if (%options)
        {
            $w->startTag('options');
            foreach my $optkey (keys %options) {
                $w->dataElement($optkey, $options{$optkey});
            }
            $w->endTag('options');
        }
        $w->endTag('arguments');
        $w->end();

        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 4 )
    {
        ## exec ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} : 'Execute';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Execute custom command';

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);
        my $cmd = (exists $para{EXECCOMMAND} && defined $para{EXECCOMMAND}) ? $para{EXECCOMMAND} : undef;
        die "Please define a command to be executed." unless $cmd;
        $w->startTag('arguments', (command => $cmd));
        $w->startTag('options');
        $w->dataElement('command', $cmd );
        $w->endTag('options');
        $w->endTag('arguments');
        $w->end();

        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 5 )
    {
        ## reboot ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} : 'Reboot';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Reboot now';
    }
    elsif ( $typeID == 6 )
    {
        ## configure ##
        print __("This job type is reserved for future use.")."\n";
        exit 1;
    }
    elsif ( $typeID == 7 )
    {
        ## wait ##
        my $sec = $para{WAITTIME} || '1';
        my $ret = $para{WAITSTATUS} || '0';

        $Job->{name}        = defined $query{NAME} ? $query{NAME} : sprintf("Wait %s sec. for exit %s.", $sec, $ret);
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : sprintf("Wait for %s seconds and return with value %s.", $sec, $ret);

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);

        my %options;
        $options{waittime} = $sec;
        $options{exitcode} = $ret;
        $w->startTag('arguments', %options);

        # new arguments/options format:
        if (%options)
        {
            $w->startTag('options');
            foreach my $optkey (keys %options) {
                $w->dataElement($optkey, $options{$optkey});
            }
            $w->endTag('options');
        }
        $w->endTag('arguments');
        $w->end();

        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 8 )
    {
        ## eject ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} : 'Eject job';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Job to eject the CD/DVD drawer';
        my $action = (exists $para{ACTION} && defined $para{ACTION}) ? $para{ACTION} : 'open';

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);
        $w->startTag('arguments', (action => $action));
        $w->startTag('options');
        $w->dataElement('action', $action);
        $w->endTag('options');
        $w->endTag('arguments');
        $w->end();

        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 51 )
    {
        ## createjob ##
        $Job->{name}        = defined $query{NAME} ?$query{NAME} : 'Createjob job';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Create a job in the clients SMT server';
        die "Please define a jobtemplate file via --jobtemplate parameter." unless $jobtemplate;
        my $filefull = $PWD.'/'.$jobtemplate;
        open (JTMPL, '<', $filefull) or die "Can not open file $filefull: $!";
        my $jobtemplXML = do { local $/; <JTMPL> };
        close JTMPL;
        die "The jobtemplate file does not contain valid content." unless $jobtemplXML;

        if (@targetguid < 1) {
            print __("Please enter one or more target GUIDs for this job.")."\n";
            exit 1;
        }

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2, UNSAFE => 1 );
        die "Unable to process the arguments for the new job." unless ($w);
        $w->startTag('arguments');
        $w->startTag('guids');
        foreach my $_g (@targetguid) {
            $w->dataElement('guid' , $_g) if defined $_g;
        }
        $w->endTag('guids');
        $w->raw("\n".$jobtemplXML);
        $w->endTag('arguments');
        $w->end();

        # we need do parse the XML because we were in unsafe mode because we needed to call raw()
        my $parser = new XML::Parser();
        die "Could not parse the arguments of the new job" unless ($parser);
        eval { $parser->parse($argXML) };
        die "Error when processing the arguments for the new job." if ($@);

        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 52 )
    {
        ## report ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} :'Report job';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Report';
        # if undefined then cacheresult by default is enabled in report job - otherwise report makes no sense
        $Job->{cacheresult} = defined $cacheresult ? $cacheresult : 1;
        if (@targetjob < 1) {
            print __("Please enter one or more target job IDs for this job.");
            exit 1;
        }

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);
        $w->startTag('arguments');
        foreach my $_j (@targetjob) {
            $w->dataElement('jobid' , $_j) if defined $_j;
        }
        $w->endTag('arguments');
        $w->end();

        $Job->{arguments} = $argXML || '';
    }
    elsif ( $typeID == 53 )
    {
        ## inventory ##
        $Job->{name}        = defined $query{NAME} ? $query{NAME} :'Inventory job';
        $Job->{description} = defined $query{DESCRIPTION} ? $query{DESCRIPTION} : 'Inventory';

        my $w = undef;
        my $argXML = '';
        $w = new XML::Writer( OUTPUT => \$argXML, DATA_MODE => 1, DATA_INDENT => 2 );
        die "Unable to process the arguments for the new job." unless ($w);
        $w->startTag('arguments');
        $w->emptyTag('refresh-software', (type => 'packages') ) if ($inventoryPackages);
        $w->emptyTag('refresh-software', (type => 'patches')  ) if ($inventoryPatches);
        if (@inventoryHWcommand)
        {
            $w->startTag('refresh-hardware');
            foreach my $cmdStr (@inventoryHWcommand) {
                $cmdStr =~ /^([a-zA-Z0-9_-]+)#(.*)$/;
                my $_id = $1;
                my $_cmd = $2;
                unless ($_id && $_cmd) {
                    print "Inventory command does not match required pattern: '<id-string>#<command>'"."\n";
                    exit 1;
                }
                $w->emptyTag('param', (id => $_id, description => $_id, command => $_cmd));
            }
            $w->endTag('refresh-hardware');
        }

        $w->endTag('arguments');
        $w->end();
        $Job->{arguments} = $argXML || '';
    }
    else
    {
        print __("This job type is unsupported.")."\n";
        exit 3;
    }

    my $newjobid = undef;

    if ( defined $create )
    {
        $newjobid = (@guid > 0) ? $JobQ->addJobForMultipleGUIDs($Job, @guid) : $Job->save();

        if ( $newjobid )
        {
            print sprintf(__("Successfully created new job. The job id is: %s"), $newjobid )."\n" unless $quiet;

            # for new softwarepush and update jobs automatcally add a patchstatus job to refresh the patchstatus immediately
            # can be disabled by --no-autopatchstatus / it is enabled by default
            if ( ( $typeID == 2  ||  $typeID == 3 )  &&  $autoPatchstatus )
            {
                my $PSjob = SMT::Job->new({ 'dbh' => $dbh });
                $PSjob->{guid}        = (@guid > 0) ? undef : $GUID;
                $PSjob->{id}          = undef;
                $PSjob->{type}        = $PSjob->jobTypeToID('patchstatus');
                $PSjob->{name}        = 'Automatic Patchstatus Job';
                $PSjob->{description} = sprintf("Automatic Patchstatus Job after job ID %s", $newjobid);
                $PSjob->{parent_id}   = $newjobid;

                my $newPSjobid = undef;
                $newPSjobid = (@guid > 0) ? $JobQ->addJobForMultipleGUIDs($PSjob, @guid) : $PSjob->save();

                if ( $newPSjobid )
                {
                    print sprintf(__("Successfully created an automatic patchstatus job. The job id is: %s"), $newPSjobid )."\n" unless $quiet;
                }
                else
                {
                    print  __("An error occurred when creating an automatic patchstatus job.")."\n";
                }
            }
        }
        else
        {
            print __("An error occurred when creating a new job.")."\n";
        }
    }
    elsif ( defined $construct )
    {
        $Job->{guid} = undef;
        print $Job->asXML({xmldecl => 0, arguments => 1, stdout => 0 , stderr => 0 });
    }

}
elsif ( defined $edit )
{
    ## EDIT  ##

    if ( (not defined $GUID) || ( not defined $jobid ) )
    {
        print __("Please define one job id and one guid to edit a job.")."\n";
        exit 3;
    }

    # get job data from database
    $Job->readJobFromDatabase($jobid, $GUID);

    # overwrite with new values
    my @EditProps = qw(NAME DESCRIPTION VERBOSE PERSISTENT PARENT_ID TARGETED EXPIRES TIMELAG UPSTREAM CACHERESULT STATUS);
    foreach my $eprop (@EditProps)
    {
        $Job->{lc($eprop)} = $query{uc($eprop)} if ( exists $query{uc($eprop)}  && defined $query{uc($eprop)} );
    }

    # save job
    my $result = $Job->save();
    if ($result)
    {
        print __("Successfully saved the edited job.")."\n" unless $quiet;
    }
    else
    {
        print __("An error occurred when saving the edited job.")."\n";
    }
}
elsif ( defined $delete )
{
    ##  DELETE  ##
    my $DELguid = undef;
    my $DELjobid = undef;

    if ( (not defined $jobid)  &&  (not defined $GUID) )
    {
        print __("To delete a job please define a job ID and the GUID of the client or either of these together with --deleteall.")."\n";
        exit 3;
    }
    elsif ( defined $jobid  &&  defined $GUID )
    {
        print sprintf(__("Deleting job %s for guid %s."), $jobid, $GUID )."\n" unless $quiet;
        $DELguid=$GUID;
        $DELjobid=$jobid;
    }
    else
    {
        if ( not defined $jobid )
        {
            if ( not $deleteall )
            {
                print __("The job id is invalid.")."\n";
                exit 3;
            }
            print sprintf(__("Deleting all jobs for guid %s."), $GUID )."\n" unless $quiet;
            $DELguid=$GUID;
            $DELjobid='ALL';
        }
        if ( not defined $GUID )
        {
            if ( not $deleteall )
            {
                print __("The guid is invalid.")."\n";
                exit 3;
            }
            print sprintf(__("Deleting all jobs with id  %s for all guids."), $jobid )."\n" unless $quiet;
            $DELguid='ALL';
            $DELjobid=$jobid;
        }
    }

    if ( not $JobQ->deleteJob($DELjobid, $DELguid) )
    {
        if ($deleteall)
        {
            print sprintf(__("An error occurred when deleting job (%s) for guid (%s)."), $DELjobid || '', $DELguid || '')."\n" unless $quiet;
        }
        else
        {
            print sprintf(__("An error occurred when deleting job %s for guid %s."), $DELjobid || '', $DELguid || '')."\n" unless $quiet;
        }
    }
    else
    {
        print __("Successfully deleted job.")."\n" unless $quiet;
    }
}
else
{
    ## LIST  ##

    if ( not defined $JobQ )
    {
        print __("Can not query the job queue.")."\n";
        exit 3;
    }

    # make sure we select all data
    $query{selectAll} = '';
    my $res = $JobQ->getJobsInfo(\%query);

    # reder the result
    my $t = new Text::ASCIITable;

    # basic job list info - without a --verbose switch
    my @DPROP = qw(ID PARENT_ID GUID TYPE NAME STATUS UPSTREAM);
    $verbose = 0 if (not $verbose);

    # set the level of verbosity
    if ($verbose == 1)
    {
        @DPROP = qw(ID PARENT_ID GUID TYPE NAME PERSISTENT STATUS UPSTREAM FINISHED);
    }
    elsif ($verbose == 2)
    {
        @DPROP = qw(ID PARENT_ID GUID TYPE NAME PERSISTENT TIMELAG STATUS UPSTREAM FINISHED DESCRIPTION);
    }
    elsif ($verbose == 3)
    {
        @DPROP = qw(ID PARENT_ID GUID TYPE NAME DESCRIPTION PERSISTENT STATUS CREATED TARGETED EXPIRES RETRIEVED FINISHED TIMELAG ARGUMENTS VERBOSE UPSTREAM CACHERESULT STDOUT STDERR EXITCODE MESSAGE);
    }

    # create the column titles
    my @cols = ();
    foreach my $prop (@DPROP)
    {
        my $copyprop = $prop;
        $copyprop =~ s/PARENT_ID/PARENT/;
        push( @cols, ucfirst(lc($copyprop)) );
    }
    $t->setCols(@cols);

    my $p = '';
    my $h = undef;
    my $onerow = [];

    # do not render an empty table if the result is empty
    exit 0 unless keys %{$res};
    # process job data, sort by client id and job id (bnc#514678)
    # rewrite JOB_TYPE and JOB_STATUS
    foreach my $xcid ( sort keys %{$res} )
    {
        foreach my $xjid ( sort keys %{${$res}{$xcid}} )
        {
            $onerow = [];
            $h = ${$res}{$xcid}{$xjid};

            foreach $p (@DPROP)
            {
                if ($p eq 'TYPE')   { ${$h}{$p} = ${$h}{$p} =~ /^\d+$/ ? SMT::Job::Constants::JOB_TYPE->{${$h}{$p}}   : ${$h}{$p} }
                if ($p eq 'STATUS') { ${$h}{$p} = ${$h}{$p} =~ /^\d+$/ ? SMT::Job::Constants::JOB_STATUS->{${$h}{$p}} : ${$h}{$p} }
                unless ($verbose == 3)
                {
                    if ($p eq 'VERBOSE' || $p eq 'PERSISTENT' || $p eq 'CACHERESULT' || $p eq 'UPSTREAM')
                    {
                        ${$h}{$p} = (defined ${$h}{$p} && ${$h}{$p} =~ /^1$/) ? 'X':'';
                    }
                }

                # collect the information or just print it
                if ($verbose == 3)
                {
                    print  $p eq 'PARENT_ID' ? 'Parent:   ':ucfirst(lc($p)).":";
                    print ' ' x (15 - length($p));
                    if ( defined ${$h}{$p}  &&  ${$h}{$p} =~ /\n/ )
                    {
                        # start multiline values in new line and indent them by 4 spaces and remove trailing newlines
                        chomp( ${$h}{$p} );
                        ${$h}{$p} =~ s/\n/\n    /g;
                        print "\n    ";
                    }
                    print ''.(defined ${$h}{$p} ?  ${$h}{$p}:'')."\n";
                }
                else
                {
                    push(@{$onerow}, defined ${$h}{$p} ?  ${$h}{$p}:'');
                }
            }

            # add one row to the table or print a separator
            if ($verbose == 3)
            {
                print '-' x 80 . "\n\n";
            }
            else
            {
                $t->addRow($onerow);
            }

        }
    }

    # set the heading
    # disable heading, as rendering takes too long then
    # $t->setOptions('headingText', __('Found jobs in SMT job queue'));
    print $t->draw() if ($verbose != 3);
    print "\n";
}


exit 0;

########################################################################################
#
# Manpage
#

=head1 NAME

smt-job

=head1 SYNOPSIS

smt-job <operation mode> [OPTION [OPTION ...]]

=head1 DESCRIPTION

C<smt-job> manages jobs for smt clients. It allows to list, create, edit and delete jobs like patchstatus or softwarepush.

=head1 OPTIONS

=head2 BASIC OPTIONS

=over

=item -h, --help

Shows the help screen.

=item -v, --verbose <level>

Shows detailed job information in list mode ( level supports values from 0 to 3 ).
Verbose level 0 shows minimal information. Level 1 and 2 each show some more details.
The result will be rendered as a table for levels 0, 1 and 2.
Level 3 lists all available information about a job as a block instead of a table.
Default verbosity level is 0.

=item -q, --quiet

Quiet mode, suppresses success messages.

=back

=head2 OPERATION MODES

=over

=item -l, --list

Lists jobs. This mode is the default if the operation mode is omitted.

=item -c, --create

Creates a new job.

=item -e, --edit

Edits a job.

=item -d, --delete

Deletes a job.

=item --construct

Like create, but only ouputs the job formatted in XML. This XML representation can be used to create a job of the type B<createjob>.

=back

=head2 MAIN PARAMETERS

=over

=item -g, --guid <guid>

Specifies the client's guid. This paramater can be used multiple times when
creating a job in order to create a job for more than one client.

=item -j, --jobid <id>

Specifies the job ID. This will be ignored when creating a job.
Note: Editing and deleting a job requires a job ID and client's guid, as the same job
for multiple clients has the same job ID.

=item -A, --deleteall

Allows to omit either the client's guid or the job ID parameter in delete operation, the missing parameter will
then match all clients resp. jobs.

=back

=head2 PARAMETERS FOR SEARCHING OR CHANGING

=over

=item -t, --type <type>

Specifies the job type (e.g.  C<patchstatus>, C<softwarepush>, C<update>, C<execute>, C<reboot>, C<wait>, C<eject>, C<createjob>, C<report>, C<inventory>).
B<Note:> Only C<patchstatus>, C<inventory>, C<softwarepush> and C<update> are enabled by default on the client. On a SMT server that has an upstream SMT server the following are also enabled by default: C<createjob>, C<report>, C<inventory>.

=item -n --name <name>

Specifies a job name

=item --decription <description>

Specifies a job description

=item --parent <id>

Specifies the job id of the parent job. This is used in order to describe a dependency.
A job won't be processed until its parent has successfully finished.

=item --parentreset

Reset the parent id of a existing job. This removes the dependency on another job that was created with the C<--parent> parameter.
Note: Useful only in editing mode. A job chain can be restarted with this, if one job in the chain failed.

=item --enable

Enable or reenable a job that is currently not queued. Jobs that are disabled or have already failed can be (re)enabled to add them to the queue. A job chain can be restarted with this, if one job in the chain failed.
Note: Only in editing mode. The timing constraints are not touched when enabling a job. A failed and reenabled job might not get delivered due to expiry settings.

=item --disable

Disable a job. This prevents the job from being delivered to the client. A job chain would also be stopped if another job is relying on the disabled one.
Note: Only in editing mode.

=item -V, --verbosejob

Specifies whether a job is verbose. Verbose jobs return stdout and stderr.

=item --no-verbosejob

Opposite of C<--verbosejob>.

=item --persistent

Specifies whether a job is persistent. Non-persistent jobs are procressed only once, while persistent
jobs are processed again and again. Please use C<--timelag> to define the time that elapses until the next run.

=item --no-persistent

Opposite of C<--persistent>.

=item --finished

Search option for finished jobs.

=item --no-finished

Opposite of C<--finished>.

=item --upstream

Search and set option for upstream jobs. An upstream job is a job which was either created from an upstream
SMT server or a job that was manually flagged as upstream by the admin to enable upstream control for that job.
The upstream control means, that the upstream SMT server is allowed to request the full job results of that job
and it may also delete it (persistent jobs wont be cleaned up automatically).

=item --no-upstream

Opposite of C<--upstream>.

=item --cacheresult

Search and set option for the cacheresult flag. The results of jobs with this flag will be cached (longer than
the job itself) and not be removed when the job is deleted or cleaned up. Jobs that are created from an upstream
SMT server are flagged with cacheresult in order to save the results until they are retrieved.

=item --no-cacheresult

Opposite of C<--cacheresult>.


=back

=head2 PARAMETERS FOR TIMING

=over

=item --targeted <time>

Specifies the earliest execution time of a job in the format C<'YYYY-MM-DD[-HH:MM[:SS]]>. Please note that
the job most likely won't run exactly at that point in time but probably some minutes/hours after, because
the client polls in a fixed interval for jobs.

=item --expires <time>

Defines when the job will no longer be executed anymore. Use the time format C<'YYYY-MM-DD[-HH:MM[:SS]]>.

=item --timelag <time>

Defines the interval for persistent jobs in the format C<HH:MM[:SS]>.

=back

=head2 JOB SPECIFIC PARAMETERS (only for creating a job)

=head3 softwarepush

=over

=item -P, --package <pack>

Defines a package name to be installed or updated. This paramater can be used multiple times and
it may contain a version instruction like "package>=2.13".

=item --patch <patch>

Defines a patch name to be installed. This paramater can be used multiple times and it may contain
a version instruction like "patch>=2.13".

=item --pattern <pattern>

Defines a pattern name to be installed. This paramater can be used multiple times and it may contain
a version instruction like "pattern>=2.13".

=item --product <product>

Defines a product name to be installed. This paramater can be used multiple times and it may contain
a version instruction like "product>=2.13".

=item -F, --forceinstall

Enforces installation of a packages, it is reinstalled even if the latest version is installed already.

=item --no-forceinstall

Opposite of C<--foreceinstall>. Default is not to enforce the installation.


=item --agreelicense

By using this option, you choose to agree with licenses of all third-party software that will be installed.

=item --no-agreelicense

Opposite of C<--agreelicense>. Default is not to agree to licenses.


=item --autopatchstatus

Enables to automatically add a B<patchstatus> job after a B<softwarepush> or B<update> job. This will update the patchstatus information immediately after a software installation on the client.
Otherwise there might be a gap of upto 26 hours until it gets refreshed. B<Note:> Default is to automatically add a patchstatus job.

=item --no-autopatchstatus

Opposite of C<--autopatchstatus>

=item --reboot <policy>

Set the reboot policy for this job. The policy may be one of C<never, ifneeded, enforce>. If a reboot is triggered via B<ifneeded> or B<enforce> the client will interrupt the job queue, no following jobs will be executed. The client will perform the reboot as part of the job. After booting cron will trigger the next SMT job after the regular interval. Thus a job chain will take longer to finish if one of its jobs performs a reboot.
Note: The reboot policy B<ifneeded> currently is supported for the job type B<update> only.

=back

=head3 createjob

=over

=item --jobtemplate <file>

Specify a file name containing a job template definition. This template needs to be combined with a set of GUIDs in the createjob job type on the downstream SMT server. The template itself does (or must) not contain a GUID and thus requires one or more C<--targetguid> parameters. Use the C<--construct> operation mode to create new job templates and adapt them by hand if needed.

=item --targetguid

Defines the GUID(s) for the job that is being created on the downstream SMT server. For all target GUIDs a job is created using the job template that is defined with C<--jobtemplate>. This paramater can be used multiple times.

=back

=head3 report

=over

=item --targetjob <id>

Specify the job ID(s) of the downstream SMT server whose results should be sent upstream to this SMT server. The job ID can be found in the message of the createjob job, that created the downstream job. This paramater can be used multiple times.

=back

=head3 update

=over

=item --patchwithupdate

This option will not only patch the system but also trigger a package update. In this case newer versions of packages will be installed if available even if no patch is requiring it.
Background information: The smt client by default only runs B<zypper patch> in an update job. With this option used it will run B<zypper patch ; zypper update>.

=item --agreelicenses

By using this option, you choose to agree with licenses of all third-party software that will be installed.

=item --no-agreelicense

Opposite of C<--agreelicense>. Default is not to agree to licenses.

=item --autopatchstatus

Enables to automatically add a B<patchstatus> job after a B<softwarepush> or B<update> job. This will update the patchstatus information immediately after a software installation on the client.
Otherwise there might be a gap of upto 26 hours until it gets refreshed. B<Note:> Default is to automatically add a patchstatus job.

=item --no-autopatchstatus

Opposite of C<--autopatchstatus>

=item --reboot <policy>

Set the reboot policy for this job. The policy may be one of C<never, ifneeded, enforce>. If a reboot is triggered via B<ifneeded> or B<enforce> the client will interrupt the job queue, no following jobs will be executed. The client will perform the reboot as part of the job. After booting cron will trigger the next SMT job after the regular interval. Thus a job chain will take longer to finish if one of its jobs performs a reboot
.
Note: The reboot policy B<ifneeded> currently is supported for the job type B<update> only.

=back

=head3 execute

=over

=item -X, -exec <command>

This paramater defines the commandline to execute on the client.

=back

=head3 wait

=over

=item --waittime <time>

Time in seconds the job should take.

=item --waitstatus <exit>

The exit code for the job to return with.

=back

=head3 eject

=over

=item --action

The action of CD or DVD tray: C<open>, C<close> or C<toggle>.

=back

=head3 createjob

=over

=item createjob job

A B<createjob> job is meant to be an internal job. It is generated by the Novell Customer Center mainly but can be used by the user as well. In a createjob a job template and a set of GUIDs are combined and assigned to the GUID of a downstream SMT server. On the downstream SMT server the job is created for all target GUIDs. The results of such jobs however are saved in the C<smt> database table C<JobResults>. There is no user interface to output this information nor is it planned. The main usage of this job is to distribute jobs from the Novell Customer Center down to the clients through the customers SMT server and later fetch the results of these jobs via a B<report> job.

=item --jobtemplate <file>

Specify the file containing the job template. The template can be created using the B<--contruct> operation mode.

=item --targetguid <guid>

Specify the target GUIDs for the job that is about to be created.

=back

=head3 report

=over

=item report job

A B<report> job is meant as to be an internal job. It is generated by the Novell Customer Center mainly but can be used by the user as well. It queries the results of a job from a downstream SMT server. Job results that are created on downstream SMT server are saved in the C<smt> database table C<JobResults>. A B<report> job currently is the only interface to this data. The report job collects all results of all target job IDs and sends them to the upstream SMT server. The report job must be flagged as C<cacheresult> otherwise the result data will be lost. It must also be flagged as C<upstream> to allow upstream control for this job. If flagged accordingly the data is saved in the C<smt> database table C<JobResults> on the upstream SMT server.

=item --targetjobid

Specify a target job ID. This parameter can be used multiple times.

=back

=head3 inventory

=over

=item inventory job

An B<inventory> job is meant to be an internal job. It is generated by the Novell Customer Center mainly but can be used by the user as well. The results of such jobs however are saved in the C<smt> database table C<JobResults>. There is no user interface to output this information nor is it planned. The main usage of these job is to query software and hardware information store it on the customers SMT server until it gets queried by the Novell Customer Center (or an upstream SMT server) via a B<report> job.

=item --inventorypackages

Query the client system for installed packages.

=item --inventorypackages

Query the client system for installed packages.

=item --inventorycommand

Query the client system for hardware information. The command must match the following pattern: C<<id-string>#<command-string>> (where id-string must only contain letters or digits). The command however is not blindly executed, but filtered on the client system by suseRegister. SuseRegister has a limited set of allowed commands like C<hwinfo>, C<uname>, C<lsb-release> and a few others. This parameter can be used multiple times.

=back

=head1 EXAMPLES

=over

=item List all finished jobs with a few details:

# smt-job --list --finished -v 1

=item Create a softwarepush job that installes C<xterm> and C<bash> on client C<12345> and C<67890>:

# smt-job --create -t softwarepush -P xterm -P bash -g 12345 -g 67890

=item Change the timing for a persistent job with job ID C<42> and guid C<12345>  to run every 6 hours:

It is necessary to update the targeted time as well as persistent jobs always add their timelag to the current targeted time.

# smt-job --edit -j 42 -g 12345 --targeted 0000-00-00 --timelag 06:00

=item Delete all jobs with job ID C<42>

# smt-job --delete -A -j 42

=item Show everything known about jobid C<42>:

# smt-job --list -j 42 -v 3

=back

=head1 AUTHORS and CONTRIBUTORS

J. Daniel Schmidt, Thomas Goettlicher

=cut


=head1 LICENSE

Copyright (c) 2009-2012 SUSE LINUX Products GmbH, Nuernberg, Germany.

This program is free software; you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by the Free Software
Foundation; either version 2 of the License, or (at your option) any later
version.

This program is distributed in the hope that it will be useful, but WITHOUT ANY
WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A
PARTICULAR PURPOSE. See the GNU General Public License for more details.

You should have received a copy of the GNU General Public License along with
this program; if not, write to the Free Software Foundation, Inc., 675 Mass
Ave, Cambridge, MA 02139, USA.

=cut
