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 # fix lib paths, some may be relative
55 my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
59 unless ( File::Spec->file_name_is_absolute($lib) ) {
61 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
62 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
67 $bin_path = $FindBin::Bin;
70 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
81 use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
83 #Clean out all the nasties from the environment
86 my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
87 $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
89 "search=s" => \$search,
90 "search-arg=s" => \$search_arg,
91 "condition=s" => \$condition,
92 "condition-arg=s" => \$condition_arg,
93 "action-arg=s" => \$action_arg,
94 "action=s" => \$action,
95 "template=s" => \$template,
96 "template-id=s" => \$template_id,
97 "transaction=s" => \$transaction,
98 "transaction-type=s" => \$transaction_type,
100 "verbose|v" => \$verbose,
104 # Load the config file
107 # adjust logging to the screen according to options
108 RT->Config->Set( LogToScreen => $log ) if $log;
110 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
114 require RT::Template;
116 #Get the current user all loaded
117 my $CurrentUser = GetCurrentUser();
119 # show help even if there is no current user
122 unless ( $CurrentUser->Id ) {
123 print loc("No RT user found. Please consult your RT administrator.");
127 help() unless $search && $action;
129 $transaction = lc( $transaction||'' );
130 if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
131 print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
135 if ( $template && $template_id ) {
136 print STDERR loc("--template-id is deprecated argument and can not be used with --template");
139 elsif ( $template_id ) {
141 $template = $template_id;
144 # We _must_ have a search object
145 load_module($search);
146 load_module($action) if ($action);
147 load_module($condition) if ($condition);
149 my $void_scrip = RT::Scrip->new( $CurrentUser );
150 my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
152 #At the appointed time:
154 #find a bunch of tickets
155 my $tickets = RT::Tickets->new($CurrentUser);
156 my $search = $search->new(
157 TicketsObj => $tickets,
158 Argument => $search_arg,
159 CurrentUser => $CurrentUser
164 # TicketsFound is an RT::Tickets object
165 my $tickets = $search->TicketsObj;
167 #for each ticket we've found
168 while ( my $ticket = $tickets->Next() ) {
169 print $ticket->Id() . ":\n" if ($verbose);
171 my $template_obj = get_template( $ticket );
173 if ( $transaction ) {
174 my $txns = get_transactions($ticket);
176 while ( my $txn = $txns->Next ) {
177 print "\t".loc("Using transaction #[_1]...", $txn->id)."\n"
179 process($ticket, $txn, $template_obj);
182 print "\t".loc("Couldn't find suitable transaction, skipping")."\n"
183 if $verbose && !$found;
185 print "\t".loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument")."\n"
188 process($ticket, undef, $template_obj);
194 my $transaction = shift;
195 my $template_obj = shift;
197 # perform some more advanced check
199 my $condition_obj = $condition->new(
200 TransactionObj => $transaction,
201 TicketObj => $ticket,
202 ScripObj => $void_scrip,
203 TemplateObj => $template_obj,
204 Argument => $condition_arg,
205 CurrentUser => $CurrentUser,
208 # if the condition doesn't apply, get out of here
210 return unless $condition_obj->IsApplicable;
211 print "\t".loc("Condition matches...")."\n" if $verbose;
215 my $action_obj = $action->new(
216 TicketObj => $ticket,
217 TransactionObj => $transaction,
218 TemplateObj => $template_obj,
219 Argument => $action_arg,
220 ScripObj => $void_scrip,
221 ScripActionObj => $void_scrip_action,
222 CurrentUser => $CurrentUser,
225 #if our preparation, move onto the next ticket
226 return unless $action_obj->Prepare;
227 print "\t".loc("Action prepared...")."\n" if $verbose;
230 return unless $action_obj->Commit;
231 print "\t".loc("Action committed.")."\n" if $verbose;
234 # =head2 get_transactions
236 # Takes ticket and returns L<RT::Transactions> object with transactions
237 # of the ticket according to command line arguments C<--transaction>
238 # and <--transaction-type>.
242 sub get_transactions {
244 my $txns = $ticket->Transactions;
245 my $order = $transaction eq 'last'? 'DESC': 'ASC';
247 { FIELD => 'Created', ORDER => $order },
248 { FIELD => 'id', ORDER => $order },
250 if ( $transaction_type ) {
251 $transaction_type =~ s/^\s+//;
252 $transaction_type =~ s/\s+$//;
253 foreach my $type ( split /\s*,\s*/, $transaction_type ) {
254 $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' );
257 $txns->RowsPerPage(1) unless $transaction eq 'all';
261 # =head2 get_template
263 # Takes a ticket and returns a template according to command line options.
270 return undef unless $template;
272 unless ( $template =~ /\D/ ) {
274 return $cache if $cache;
276 my $cache = RT::Template->new( RT->SystemUser );
277 $cache->Load( $template );
278 die "Failed to load template '$template'"
283 my $queue = $ticket->Queue;
284 return $cache->{ $queue } if $cache->{ $queue };
286 my $res = RT::Template->new( RT->SystemUser );
287 $res->LoadQueueTemplate( Queue => $queue, Name => $template );
288 unless ( $res->id ) {
289 $res->LoadGlobalTemplate( $template );
290 die "Failed to load template '$template', either for queue #$queue or global"
293 return $cache->{ $queue } = $res;
299 # Loads a perl module, dying nicely if it can't find it.
305 eval "require $modname";
307 die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
316 # Localize this string, with the current user's currentuser object
321 $CurrentUser->loc(@_);
327 print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
329 print loc("It takes several arguments:") . "\n\n";
332 . loc( "[_1] - Specify the search module you want to use", "--search" )
335 . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
339 . loc( "[_1] - Specify the condition module you want to use", "--condition" )
342 . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
345 . loc( "[_1] - Specify the action module you want to use", "--action" )
348 . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" )
351 . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
354 . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
357 . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
360 . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n";
362 . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
365 print loc("Security:")."\n";
366 print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ".
367 loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
368 loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " .
369 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";
371 print loc("Example:");
374 . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:"
378 print " bin/rt-crontool \\\n";
379 print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
380 print " --condition RT::Condition::Overdue \\\n";
381 print " --action RT::Action::SetPriority --action-arg 99 \\\n";
382 print " --verbose\n";
385 print loc("Escalate tickets"). "\n";
386 print " bin/rt-crontool \\\n";
387 print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
388 print" --action RT::Action::EscalatePriority\n";
402 rt-crontool - a tool to act on tickets from an external scheduling tool
406 # find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:
408 --search RT::Search::ActiveTicketsInQueue --search-arg general \
409 --condition RT::Condition::Overdue \
410 --action RT::Action::SetPriority --action-arg 99 \
415 --search RT::Search::ActiveTicketsInQueue --search-arg general \
416 --action RT::Action::EscalatePriority
420 This script is a tool to act on tickets from an external scheduling tool, such
425 This tool allows the user to run arbitrary perl modules from within RT. If
426 this tool were setgid, a hostile local user could use this tool to gain
427 administrative access to RT. It is incredibly important that nonprivileged
428 users not be allowed to run this tool. It is suggested that you create a
429 non-privileged unix user with the correct group membership and RT access to
439 Specify the search module you want to use
443 An argument to pass to --search
447 Specify the condition module you want to use
451 An argument to pass to --condition
455 Specify the action module you want to use
459 An argument to pass to --action
463 Specify name or id of template(s) you want to use
467 Specify if you want to use either 'first', 'last' or 'all' transactions
470 =item transaction-type
472 Specify the comma separated list of transactions' types you want to use
476 Adjust LogToScreen config option
480 Output status updates to STDOUT