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