2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2015 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 }}}
53 # fix lib paths, some may be relative
56 my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
60 unless ( File::Spec->file_name_is_absolute($lib) ) {
62 if ( File::Spec->file_name_is_absolute(__FILE__) ) {
63 $bin_path = ( File::Spec->splitpath(__FILE__) )[1];
68 $bin_path = $FindBin::Bin;
71 $lib = File::Spec->catfile( $bin_path, File::Spec->updir, $lib );
82 use RT::Interface::CLI qw(CleanEnv GetCurrentUser GetMessageContent loc);
84 #Clean out all the nasties from the environment
87 my ( $search, $condition, $action, $search_arg, $condition_arg, $action_arg,
88 $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
90 "search=s" => \$search,
91 "search-arg=s" => \$search_arg,
92 "condition=s" => \$condition,
93 "condition-arg=s" => \$condition_arg,
94 "action-arg=s" => \$action_arg,
95 "action=s" => \$action,
96 "template=s" => \$template,
97 "template-id=s" => \$template_id,
98 "transaction=s" => \$transaction,
99 "transaction-type=s" => \$transaction_type,
101 "verbose|v" => \$verbose,
105 # Load the config file
108 # adjust logging to the screen according to options
109 RT->Config->Set( LogToScreen => $log ) if $log;
111 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
115 require RT::Template;
117 #Get the current user all loaded
118 my $CurrentUser = GetCurrentUser();
120 # show help even if there is no current user
123 unless ( $CurrentUser->Id ) {
124 print loc("No RT user found. Please consult your RT administrator.");
128 help() unless $search && $action;
130 $transaction = lc( $transaction||'' );
131 if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
132 print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
136 if ( $template && $template_id ) {
137 print STDERR loc("--template-id is deprecated argument and can not be used with --template");
140 elsif ( $template_id ) {
142 $template = $template_id;
145 # We _must_ have a search object
146 load_module($search);
147 load_module($action) if ($action);
148 load_module($condition) if ($condition);
150 my $void_scrip = RT::Scrip->new( $CurrentUser );
151 my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
153 #At the appointed time:
155 #find a bunch of tickets
156 my $tickets = RT::Tickets->new($CurrentUser);
157 $search = $search->new(
158 TicketsObj => $tickets,
159 Argument => $search_arg,
160 CurrentUser => $CurrentUser
164 #for each ticket we've found
165 while ( my $ticket = $tickets->Next() ) {
166 print $ticket->Id() . ":\n" if ($verbose);
168 my $template_obj = get_template( $ticket );
170 if ( $transaction ) {
171 my $txns = get_transactions($ticket);
173 while ( my $txn = $txns->Next ) {
174 print "\t".loc("Using transaction #[_1]...", $txn->id)."\n"
176 process($ticket, $txn, $template_obj);
179 print "\t".loc("Couldn't find suitable transaction, skipping")."\n"
180 if $verbose && !$found;
182 print "\t".loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument")."\n"
185 process($ticket, undef, $template_obj);
191 my $transaction = shift;
192 my $template_obj = shift;
194 # perform some more advanced check
196 my $condition_obj = $condition->new(
197 TransactionObj => $transaction,
198 TicketObj => $ticket,
199 ScripObj => $void_scrip,
200 TemplateObj => $template_obj,
201 Argument => $condition_arg,
202 CurrentUser => $CurrentUser,
205 # if the condition doesn't apply, get out of here
207 return unless $condition_obj->IsApplicable;
208 print "\t".loc("Condition matches...")."\n" if $verbose;
212 my $action_obj = $action->new(
213 TicketObj => $ticket,
214 TransactionObj => $transaction,
215 TemplateObj => $template_obj,
216 Argument => $action_arg,
217 ScripObj => $void_scrip,
218 ScripActionObj => $void_scrip_action,
219 CurrentUser => $CurrentUser,
222 #if our preparation, move onto the next ticket
223 return unless $action_obj->Prepare;
224 print "\t".loc("Action prepared...")."\n" if $verbose;
227 return unless $action_obj->Commit;
228 print "\t".loc("Action committed.")."\n" if $verbose;
231 # =head2 get_transactions
233 # Takes ticket and returns L<RT::Transactions> object with transactions
234 # of the ticket according to command line arguments C<--transaction>
235 # and <--transaction-type>.
239 sub get_transactions {
241 my $txns = $ticket->Transactions;
242 my $order = $transaction eq 'last'? 'DESC': 'ASC';
244 { FIELD => 'Created', ORDER => $order },
245 { FIELD => 'id', ORDER => $order },
247 if ( $transaction_type ) {
248 $transaction_type =~ s/^\s+//;
249 $transaction_type =~ s/\s+$//;
250 foreach my $type ( split /\s*,\s*/, $transaction_type ) {
251 $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' );
254 $txns->RowsPerPage(1) unless $transaction eq 'all';
258 # =head2 get_template
260 # Takes a ticket and returns a template according to command line options.
267 return undef unless $template;
269 unless ( $template =~ /\D/ ) {
271 return $cache if $cache;
273 my $cache = RT::Template->new( RT->SystemUser );
274 $cache->Load( $template );
275 die "Failed to load template '$template'"
280 my $queue = $ticket->Queue;
281 return $cache->{ $queue } if $cache->{ $queue };
283 my $res = RT::Template->new( RT->SystemUser );
284 $res->LoadQueueTemplate( Queue => $queue, Name => $template );
285 unless ( $res->id ) {
286 $res->LoadGlobalTemplate( $template );
287 die "Failed to load template '$template', either for queue #$queue or global"
290 return $cache->{ $queue } = $res;
296 # Loads a perl module, dying nicely if it can't find it.
302 eval "require $modname";
304 die loc( "Failed to load module [_1]. ([_2])", $modname, $@ );
312 print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
314 print loc("It takes several arguments:") . "\n\n";
317 . loc( "[_1] - Specify the search module you want to use", "--search" )
320 . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
324 . loc( "[_1] - Specify the condition module you want to use", "--condition" )
327 . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
330 . loc( "[_1] - Specify the action module you want to use", "--action" )
333 . loc( "[_1] - An argument to pass to [_2]", "--action-arg", "--action" )
336 . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
339 . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
342 . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
345 . loc( "[_1] - Adjust LogToScreen config option", "--log" ) . "\n";
347 . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
350 print loc("Security:")."\n";
351 print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ".
352 loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
353 loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " .
354 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";
356 print loc("Example:");
359 . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:"
363 print " bin/rt-crontool \\\n";
364 print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
365 print " --condition RT::Condition::Overdue \\\n";
366 print " --action RT::Action::SetPriority --action-arg 99 \\\n";
367 print " --verbose\n";
370 print loc("Escalate tickets"). "\n";
371 print " bin/rt-crontool \\\n";
372 print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
373 print" --action RT::Action::EscalatePriority\n";
387 rt-crontool - a tool to act on tickets from an external scheduling tool
391 # find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:
393 --search RT::Search::ActiveTicketsInQueue --search-arg general \
394 --condition RT::Condition::Overdue \
395 --action RT::Action::SetPriority --action-arg 99 \
400 --search RT::Search::ActiveTicketsInQueue --search-arg general \
401 --action RT::Action::EscalatePriority
405 This script is a tool to act on tickets from an external scheduling tool, such
410 This tool allows the user to run arbitrary perl modules from within RT. If
411 this tool were setgid, a hostile local user could use this tool to gain
412 administrative access to RT. It is incredibly important that nonprivileged
413 users not be allowed to run this tool. It is suggested that you create a
414 non-privileged unix user with the correct group membership and RT access to
424 Specify the search module you want to use
428 An argument to pass to --search
432 Specify the condition module you want to use
436 An argument to pass to --condition
440 Specify the action module you want to use
444 An argument to pass to --action
448 Specify name or id of template(s) you want to use
452 Specify if you want to use either 'first', 'last' or 'all' transactions
455 =item transaction-type
457 Specify the comma separated list of transactions' types you want to use
461 Adjust LogToScreen config option
465 Output status updates to STDOUT