2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2012 Best Practical Solutions, LLC
7 # <sales@bestpractical.com>
9 # (Except where explicitly superseded by other copyright notices)
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
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.
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.
31 # CONTRIBUTION SUBMISSION POLICY:
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.)
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.
48 # END BPS TAGGED BLOCK }}}
52 use lib '/www/data/rt/rt-perl/current-perl10/share/perl5';
53 use lib '/www/data/rt/rt-perl/current-perl10/lib/perl5';
54 use lib '/www/data/rt/rt-perl/current-perl10/lib64/perl5';
56 # fix lib paths, some may be relative
59 my @libs = ("lib", "local/lib");
63 unless ( File::Spec->file_name_is_absolute($lib) ) {
65 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
66 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
71 $bin_path = $FindBin::Bin;
74 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
85 use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
87 #Clean out all the nasties from the environment
90 my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
91 $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
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,
104 "verbose|v" => \$verbose,
108 # Load the config file
111 # adjust logging to the screen according to options
112 RT->Config->Set( LogToScreen => $log ) if $log;
114 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
118 require RT::Template;
120 #Get the current user all loaded
121 my $CurrentUser = GetCurrentUser();
123 # show help even if there is no current user
126 unless ( $CurrentUser->Id ) {
127 print loc("No RT user found. Please consult your RT administrator.");
131 help() unless $search && $action;
133 $transaction = lc( $transaction||'' );
134 if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
135 print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
139 if ( $template && $template_id ) {
140 print STDERR loc("--template-id is deprecated argument and can not be used with --template");
143 elsif ( $template_id ) {
145 $template = $template_id;
148 # We _must_ have a search object
149 load_module($search);
150 load_module($action) if ($action);
151 load_module($condition) if ($condition);
153 my $void_scrip = RT::Scrip->new( $CurrentUser );
154 my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
156 #At the appointed time:
158 #find a bunch of tickets
159 my $tickets = RT::Tickets->new($CurrentUser);
160 my $search = $search->new(
161 TicketsObj => $tickets,
162 Argument => $search_arg,
163 CurrentUser => $CurrentUser
168 # TicketsFound is an RT::Tickets object
169 my $tickets = $search->TicketsObj;
171 #for each ticket we've found
172 while ( my $ticket = $tickets->Next() ) {
173 print $ticket->Id() . ":\n" if ($verbose);
175 my $template_obj = get_template( $ticket );
177 if ( $transaction ) {
178 my $txns = get_transactions($ticket);
180 while ( my $txn = $txns->Next ) {
181 print "\t".loc("Using transaction #[_1]...", $txn->id)."\n"
183 process($ticket, $txn, $template_obj);
186 print "\t".loc("Couldn't find suitable transaction, skipping")."\n"
187 if $verbose && !$found;
189 print "\t".loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument")."\n"
192 process($ticket, undef, $template_obj);
198 my $transaction = shift;
199 my $template_obj = shift;
201 # perform some more advanced check
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,
212 # if the condition doesn't apply, get out of here
214 return unless $condition_obj->IsApplicable;
215 print "\t".loc("Condition matches...")."\n" if $verbose;
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,
229 #if our preparation, move onto the next ticket
230 return unless $action_obj->Prepare;
231 print "\t".loc("Action prepared...")."\n" if $verbose;
234 return unless $action_obj->Commit;
235 print "\t".loc("Action committed.")."\n" if $verbose;
238 # =head2 get_transactions
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>.
246 sub get_transactions {
248 my $txns = $ticket->Transactions;
249 my $order = $transaction eq 'last'? 'DESC': 'ASC';
251 { FIELD => 'Created', ORDER => $order },
252 { FIELD => 'id', ORDER => $order },
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' );
261 $txns->RowsPerPage(1) unless $transaction eq 'all';
265 # =head2 get_template
267 # Takes a ticket and returns a template according to command line options.
274 return undef unless $template;
276 unless ( $template =~ /\D/ ) {
278 return $cache if $cache;
280 my $cache = RT::Template->new( RT->SystemUser );
281 $cache->Load( $template );
282 die "Failed to load template '$template'"
287 my $queue = $ticket->Queue;
288 return $cache->{ $queue } if $cache->{ $queue };
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"
297 return $cache->{ $queue } = $res;
303 # Loads a perl module, dying nicely if it can't find it.
309 eval "require $modname";
311 die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
320 # Localize this string, with the current user's currentuser object
325 $CurrentUser->loc(@_);
331 print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
333 print loc("It takes several arguments:") . "\n\n";
336 . loc( "[_1] - Specify the search module you want to use", "--search" )
339 . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
343 . loc( "[_1] - Specify the condition module you want to use", "--condition" )
346 . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
349 . loc( "[_1] - Specify the action module you want to use", "--action" )
352 . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" )
355 . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
358 . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
361 . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
364 . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n";
366 . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\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";
375 print loc("Example:");
378 . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:"
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";
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";
406 rt-crontool - a tool to act on tickets from an external scheduling tool
410 # find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:
412 --search RT::Search::ActiveTicketsInQueue --search-arg general \
413 --condition RT::Condition::Overdue \
414 --action RT::Action::SetPriority --action-arg 99 \
419 --search RT::Search::ActiveTicketsInQueue --search-arg general \
420 --action RT::Action::EscalatePriority
424 This script is a tool to act on tickets from an external scheduling tool, such
429 This tool allows the user to run arbitrary perl modules from within RT. If
430 this tool were setgid, a hostile local user could use this tool to gain
431 administrative access to RT. It is incredibly important that nonprivileged
432 users not be allowed to run this tool. It is suggested that you create a
433 non-privileged unix user with the correct group membership and RT access to
443 Specify the search module you want to use
447 An argument to pass to --search
451 Specify the condition module you want to use
455 An argument to pass to --condition
459 Specify the action module you want to use
463 An argument to pass to --action
467 Specify name or id of template(s) you want to use
471 Specify if you want to use either 'first', 'last' or 'all' transactions
474 =item transaction-type
476 Specify the comma separated list of transactions' types you want to use
480 Adjust LogToScreen config option
484 Output status updates to STDOUT