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