Fixed relative path for rt-libs.
[usit-rt.git] / local / bin / rt-crontool
CommitLineData
1a70ad06
MKG
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 }}}
49use strict;
50use warnings;
51use Carp;
52
53# fix lib paths, some may be relative
54BEGIN { # 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';
ae75ea76 60 my @libs = ("../lib", "../local/lib");
1a70ad06
MKG
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
73use RT;
74
75use Getopt::Long;
76
77use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
78
79my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
80 $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
81GetOptions(
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
98RT::LoadConfig();
99
100# adjust logging to the screen according to options
101RT->Config->Set( LogToSTDERR => $log ) if $log;
102
103#Connect to the database and get RT::SystemUser and RT::Nobody loaded
104RT::Init();
105
106# Clean out all the nasties from the environment
107CleanEnv();
108
109require RT::Tickets;
110require RT::Template;
111
112#Get the current user all loaded
113my $CurrentUser = GetCurrentUser();
114
115# show help even if there is no current user
116help() if $help;
117
118unless ( $CurrentUser->Id ) {
119 print loc("No RT user found. Please consult your RT administrator.");
120 exit(1);
121}
122
123help() unless $search && $action;
124
125$transaction = lc( $transaction||'' );
126if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
127 print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
128 exit 1;
129}
130
131if ( $template && $template_id ) {
132 print STDERR loc("--template-id is deprecated argument and can not be used with --template");
133 exit 1;
134}
135elsif ( $template_id ) {
136# don't warn
137 $template = $template_id;
138}
139
140# We _must_ have a search object
141load_module($search);
142load_module($action) if ($action);
143load_module($condition) if ($condition);
144
145my $void_scrip = RT::Scrip->new( $CurrentUser );
146my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
147
148#At the appointed time:
149
150#find a bunch of tickets
151my $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
160while ( 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
184sub 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
234sub 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;
260sub 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
295sub 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
305sub 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
382rt-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
400This script is a tool to act on tickets from an external scheduling tool, such
401as cron.
402
403Security:
404
405This tool allows the user to run arbitrary perl modules from within RT. If
406this tool were setgid, a hostile local user could use this tool to gain
407administrative access to RT. It is incredibly important that nonprivileged
408users not be allowed to run this tool. It is suggested that you create a
409non-privileged unix user with the correct group membership and RT access to
410run this tool.
411
412
413=head1 OPTIONS
414
415=over
416
417=item search
418
419Specify the search module you want to use
420
421=item search-arg
422
423An argument to pass to --search
424
425=item condition
426
427Specify the condition module you want to use
428
429=item condition-arg
430
431An argument to pass to --condition
432
433=item action
434
435Specify the action module you want to use
436
437=item action-arg
438
439An argument to pass to --action
440
441=item template
442
443Specify name or id of template(s) you want to use
444
445=item transaction
446
447Specify if you want to use either 'first', 'last' or 'all' transactions
448
449
450=item transaction-type
451
452Specify the comma separated list of transactions' types you want to use
453
454=item log
455
456Adjust LogToSTDERR config option
457
458=item verbose
459
460Output status updates to STDOUT
461
462=back
463