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