8715443ec13cfc10eb5560a429859d0a146671e4
[usit-rt.git] / local / bin / rt-crontool
1 #!/usr/bin/perl
2 # BEGIN BPS TAGGED BLOCK {{{
3 #
4 # COPYRIGHT:
5 #
6 # This software is Copyright (c) 1996-2013 Best Practical Solutions, LLC
7 #                                          <sales@bestpractical.com>
8 #
9 # (Except where explicitly superseded by other copyright notices)
10 #
11 #
12 # LICENSE:
13 #
14 # This work is made available to you under the terms of Version 2 of
15 # the GNU General Public License. A copy of that license should have
16 # been provided with this software, but in any event can be snarfed
17 # from www.gnu.org.
18 #
19 # This work is distributed in the hope that it will be useful, but
20 # WITHOUT ANY WARRANTY; without even the implied warranty of
21 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
22 # General Public License for more details.
23 #
24 # You should have received a copy of the GNU General Public License
25 # along with this program; if not, write to the Free Software
26 # Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
27 # 02110-1301 or visit their web page on the internet at
28 # http://www.gnu.org/licenses/old-licenses/gpl-2.0.html.
29 #
30 #
31 # CONTRIBUTION SUBMISSION POLICY:
32 #
33 # (The following paragraph is not intended to limit the rights granted
34 # to you to modify and distribute this software under the terms of
35 # the GNU General Public License and is only of importance to you if
36 # you choose to contribute your changes and enhancements to the
37 # community by submitting them to Best Practical Solutions, LLC.)
38 #
39 # By intentionally submitting any modifications, corrections or
40 # derivatives to this work, or any other work intended for use with
41 # Request Tracker, to Best Practical Solutions, LLC, you confirm that
42 # you are the copyright holder for those contributions and you grant
43 # Best Practical Solutions,  LLC a nonexclusive, worldwide, irrevocable,
44 # royalty-free, perpetual, license to use, copy, create derivative
45 # works based on those contributions, and sublicense and distribute
46 # those contributions and any derivatives thereof.
47 #
48 # END BPS TAGGED BLOCK }}}
49 use strict;
50 use warnings;
51 use Carp;
52
53 # fix lib paths, some may be relative
54 BEGIN { # BEGIN RT CMD BOILERPLATE
55     require File::Spec;
56     require Cwd;
57     use lib '/www/data/rt/perl/share/perl5';
58     use lib '/www/data/rt/perl/lib/perl5';
59     use lib '/www/data/rt/perl/lib64/perl5';
60     my @libs = ("lib", "local/lib");
61     my $bin_path;
62
63     for my $lib (@libs) {
64         unless ( File::Spec->file_name_is_absolute($lib) ) {
65             $bin_path ||= ( File::Spec->splitpath(Cwd::abs_path(__FILE__)) )[1];
66             $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
67         }
68         unshift @INC, $lib;
69     }
70
71 }
72
73 use RT;
74
75 use Getopt::Long;
76
77 use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
78
79 my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
80      $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
81 GetOptions(
82     "search=s"           => \$search,
83     "search-arg=s"       => \$search_arg,
84     "condition=s"        => \$condition,
85     "condition-arg=s"    => \$condition_arg,
86     "action-arg=s"       => \$action_arg,
87     "action=s"           => \$action,
88     "template=s"         => \$template,
89     "template-id=s"      => \$template_id,
90     "transaction=s"      => \$transaction,
91     "transaction-type=s" => \$transaction_type,
92     "log=s"              => \$log,
93     "verbose|v"          => \$verbose,
94     "help"               => \$help,
95 );
96
97 # Load the config file
98 RT::LoadConfig();
99
100 # adjust logging to the screen according to options
101 RT->Config->Set( LogToSTDERR => $log ) if $log;
102
103 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
104 RT::Init();
105
106 # Clean out all the nasties from the environment
107 CleanEnv();
108
109 require RT::Tickets;
110 require RT::Template;
111
112 #Get the current user all loaded
113 my $CurrentUser = GetCurrentUser();
114
115 # show help even if there is no current user
116 help() if $help;
117
118 unless ( $CurrentUser->Id ) {
119     print loc("No RT user found. Please consult your RT administrator.");
120     exit(1);
121 }
122
123 help() unless $search && $action;
124
125 $transaction = lc( $transaction||'' );
126 if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
127     print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
128     exit 1;
129 }
130
131 if ( $template && $template_id ) {
132     print STDERR loc("--template-id is deprecated argument and can not be used with --template");
133     exit 1;
134 }
135 elsif ( $template_id ) {
136 # don't warn
137     $template = $template_id;
138 }
139
140 # We _must_ have a search object
141 load_module($search);
142 load_module($action)    if ($action);
143 load_module($condition) if ($condition);
144
145 my $void_scrip = RT::Scrip->new( $CurrentUser );
146 my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
147
148 #At the appointed time:
149
150 #find a bunch of tickets
151 my $tickets = RT::Tickets->new($CurrentUser);
152 $search  = $search->new(
153     TicketsObj  => $tickets,
154     Argument    => $search_arg,
155     CurrentUser => $CurrentUser
156 );
157 $search->Prepare();
158
159 #for each ticket we've found
160 while ( my $ticket = $tickets->Next() ) {
161     print $ticket->Id() . ":\n" if ($verbose);
162
163     my $template_obj = get_template( $ticket );
164
165     if ( $transaction ) {
166         my $txns = get_transactions($ticket);
167         my $found = 0;
168         while ( my $txn = $txns->Next ) {
169             print "\t".loc("Using transaction #[_1]...", $txn->id)."\n"
170                 if $verbose;
171             process($ticket, $txn, $template_obj);
172             $found = 1;
173         }
174         print "\t".loc("Couldn't find suitable transaction, skipping")."\n"
175             if $verbose && !$found;
176     } else {
177         print "\t".loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument")."\n"
178             if $verbose;
179
180         process($ticket, undef, $template_obj);
181     }
182 }
183
184 sub process {
185     my $ticket = shift;
186     my $transaction = shift;
187     my $template_obj = shift;
188
189     # perform some more advanced check
190     if ($condition) {
191         my $condition_obj = $condition->new(
192             TransactionObj => $transaction,
193             TicketObj      => $ticket,
194             ScripObj       => $void_scrip,
195             TemplateObj    => $template_obj,
196             Argument       => $condition_arg,
197             CurrentUser    => $CurrentUser,
198         );
199
200         # if the condition doesn't apply, get out of here
201
202         return unless $condition_obj->IsApplicable;
203         print "\t".loc("Condition matches...")."\n" if $verbose;
204     }
205
206     #prepare our action
207     my $action_obj = $action->new(
208         TicketObj      => $ticket,
209         TransactionObj => $transaction,
210         TemplateObj    => $template_obj,
211         Argument       => $action_arg,
212         ScripObj       => $void_scrip,
213         ScripActionObj => $void_scrip_action,
214         CurrentUser    => $CurrentUser,
215     );
216
217     #if our preparation, move onto the next ticket
218     return unless $action_obj->Prepare;
219     print "\t".loc("Action prepared...")."\n" if $verbose;
220
221     #commit our action.
222     return unless $action_obj->Commit;
223     print "\t".loc("Action committed.")."\n" if $verbose;
224 }
225
226 # =head2 get_transactions
227
228 # Takes ticket and returns L<RT::Transactions> object with transactions
229 # of the ticket according to command line arguments C<--transaction>
230 # and <--transaction-type>.
231
232 # =cut
233
234 sub get_transactions {
235     my $ticket = shift;
236     my $txns = $ticket->Transactions;
237     my $order = $transaction eq 'last'? 'DESC': 'ASC';
238     $txns->OrderByCols(
239         { FIELD => 'Created', ORDER => $order },
240         { FIELD => 'id', ORDER => $order },
241     );
242     if ( $transaction_type ) {
243         $transaction_type =~ s/^\s+//;
244         $transaction_type =~ s/\s+$//;
245         foreach my $type ( split /\s*,\s*/, $transaction_type ) {
246             $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' );
247         }
248     }
249     $txns->RowsPerPage(1) unless $transaction eq 'all';
250     return $txns;
251 }
252
253 # =head2 get_template
254
255 # Takes a ticket and returns a template according to command line options.
256
257 # =cut
258
259 { my $cache = undef;
260 sub get_template {
261     my $ticket = shift;
262     return undef unless $template;
263
264     unless ( $template =~ /\D/ ) {
265         # by id
266         return $cache if $cache;
267
268         my $cache = RT::Template->new( RT->SystemUser );
269         $cache->Load( $template );
270         die "Failed to load template '$template'"
271             unless $cache->id;
272         return $cache;
273     }
274
275     my $queue = $ticket->Queue;
276     return $cache->{ $queue } if $cache->{ $queue };
277
278     my $res = RT::Template->new( RT->SystemUser );
279     $res->LoadQueueTemplate( Queue => $queue, Name => $template );
280     unless ( $res->id ) {
281         $res->LoadGlobalTemplate( $template );
282         die "Failed to load template '$template', either for queue #$queue or global"
283             unless $res->id;
284     }
285     return $cache->{ $queue } = $res;
286 } }
287
288
289 # =head2 load_module
290
291 # Loads a perl module, dying nicely if it can't find it.
292
293 # =cut
294
295 sub load_module {
296     my $modname = shift;
297     eval "require $modname";
298     if ($@) {
299         die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
300     }
301
302 }
303
304
305 sub help {
306
307     print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
308       . "\n";
309     print loc("It takes several arguments:") . "\n\n";
310
311     print "        "
312       . loc( "[_1] - Specify the search module you want to use", "--search" )
313       . "\n";
314     print "        "
315       . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
316       . "\n";
317
318     print "        "
319       . loc( "[_1] - Specify the condition module you want to use", "--condition" )
320       . "\n";
321     print "        "
322       . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
323       . "\n";
324     print "        "
325       . loc( "[_1] - Specify the action module you want to use", "--action" )
326       . "\n";
327     print "        "
328       . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" )
329       . "\n";
330     print "        "
331       . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
332       . "\n";
333     print "        "
334       . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
335       . "\n";
336     print "        "
337       . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
338       . "\n";
339     print "        "
340       . loc( "[_1] - Adjust LogToSTDERR config option", "--log" ) . "\n";
341     print "        "
342       . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
343     print "\n";
344     print "\n";
345     print loc("Security:")."\n";
346     print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ". 
347         loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
348         loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " . 
349         loc("It is suggested that you create a non-privileged unix user with the correct group membership and RT access to run this tool.")."\n";
350     print "\n";
351     print loc("Example:");
352     print "\n";
353     print " "
354       . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:"
355       )
356       . "\n\n";
357
358     print " bin/rt-crontool \\\n";
359     print "  --search RT::Search::ActiveTicketsInQueue  --search-arg general \\\n";
360     print "  --condition RT::Condition::Overdue \\\n";
361     print "  --action RT::Action::SetPriority --action-arg 99 \\\n";
362     print "  --verbose\n";
363
364     print "\n";
365     print loc("Escalate tickets"). "\n";
366     print " bin/rt-crontool \\\n";
367     print "  --search RT::Search::ActiveTicketsInQueue  --search-arg general \\\n";
368     print"  --action RT::Action::EscalatePriority\n";
369  
370  
371  
372
373
374
375     exit(0);
376 }
377
378 __END__
379
380 =head1 NAME
381
382 rt-crontool - a tool to act on tickets from an external scheduling tool
383
384 =head1 SYNOPSIS
385
386     # find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:
387     rt-crontool \
388       --search RT::Search::ActiveTicketsInQueue  --search-arg general \
389       --condition RT::Condition::Overdue \
390       --action RT::Action::SetPriority --action-arg 99 \
391       --verbose
392
393     # Escalate tickets
394       rt-crontool \
395         --search RT::Search::ActiveTicketsInQueue  --search-arg general \
396         --action RT::Action::EscalatePriority
397
398 =head1 DESCRIPTION
399
400 This script is a tool to act on tickets from an external scheduling tool, such
401 as cron.
402
403 Security:
404
405 This tool allows the user to run arbitrary perl modules from within RT. If
406 this tool were setgid, a hostile local user could use this tool to gain
407 administrative access to RT. It is incredibly important that nonprivileged
408 users not be allowed to run this tool. It is suggested that you create a
409 non-privileged unix user with the correct group membership and RT access to
410 run this tool.
411
412
413 =head1 OPTIONS
414
415 =over
416
417 =item search 
418
419 Specify the search module you want to use
420
421 =item search-arg 
422
423 An argument to pass to --search
424
425 =item condition
426
427 Specify the condition module you want to use
428
429 =item condition-arg
430
431 An argument to pass to --condition
432
433 =item action 
434
435 Specify the action module you want to use
436
437 =item action-arg
438
439 An argument to pass to --action
440
441 =item template
442
443 Specify name or id of template(s) you want to use
444
445 =item transaction
446
447 Specify if you want to use either 'first', 'last' or 'all' transactions
448
449
450 =item transaction-type
451
452 Specify the comma separated list of transactions' types you want to use
453
454 =item log
455
456 Adjust LogToSTDERR config option
457
458 =item verbose
459
460 Output status updates to STDOUT
461
462 =back
463