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