2 # BEGIN BPS TAGGED BLOCK {{{
6 # This software is Copyright (c) 1996-2017 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
54 BEGIN { # BEGIN RT CMD BOILERPLATE
57 my @libs = ("@RT_LIB_PATH@", "@LOCAL_LIB_PATH@");
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 );
74 use RT::Interface::CLI qw(GetCurrentUser loc);
76 my ( $search, $condition, $actions, $search_arg, $condition_arg, $actions_arg,
77 $template, $template_id, $transaction, $transaction_type, $help, $log, $verbose );
79 "search=s" => \$search,
80 "search-arg=s" => \$search_arg,
81 "condition=s" => \$condition,
82 "condition-arg=s" => \$condition_arg,
83 "action-arg=s@" => \$actions_arg,
84 "action=s@" => \$actions,
85 "template=s" => \$template,
86 "template-id=s" => \$template_id,
87 "transaction=s" => \$transaction,
88 "transaction-type=s" => \$transaction_type,
90 "verbose|v" => \$verbose,
94 # Load the config file
97 # adjust logging to the screen according to options
98 RT->Config->Set( LogToSTDERR => $log ) if $log;
100 #Connect to the database and get RT::SystemUser and RT::Nobody loaded
104 require RT::Template;
106 #Get the current user all loaded
107 my $CurrentUser = GetCurrentUser();
109 # show help even if there is no current user
112 unless ( $CurrentUser->Id ) {
113 print loc("No RT user found. Please consult your RT administrator.") . "\n";
117 help() unless $search && $actions;
119 $transaction = lc( $transaction||'' );
120 if ( $transaction && $transaction !~ /^(first|all|last)$/i ) {
121 print STDERR loc("--transaction argument could be only 'first', 'last' or 'all'");
125 if ( $template && $template_id ) {
126 print STDERR loc("--template-id is deprecated argument and can not be used with --template");
129 elsif ( $template_id ) {
131 $template = $template_id;
134 # We _must_ have a search object
135 load_module($search);
136 for my $action (@$actions) {
137 load_module($action);
139 load_module($condition) if ($condition);
141 my $void_scrip = RT::Scrip->new( $CurrentUser );
142 my $void_scrip_action = RT::ScripAction->new( $CurrentUser );
144 #At the appointed time:
146 #find a bunch of tickets
147 my $tickets = RT::Tickets->new($CurrentUser);
148 $search = $search->new(
149 TicketsObj => $tickets,
150 Argument => $search_arg,
151 CurrentUser => $CurrentUser
155 #for each ticket we've found
156 while ( my $ticket = $tickets->Next() ) {
157 print $ticket->Id() . ":\n" if ($verbose);
159 my $template_obj = get_template( $ticket );
161 if ( $transaction ) {
162 my $txns = get_transactions($ticket);
164 while ( my $txn = $txns->Next ) {
165 print "\t".loc("Using transaction #[_1]...", $txn->id)."\n"
167 process($ticket, $txn, $template_obj);
170 print "\t".loc("Couldn't find suitable transaction, skipping")."\n"
171 if $verbose && !$found;
173 print "\t".loc("Processing without transaction, some conditions and actions may fail. Consider using --transaction argument")."\n"
176 process($ticket, undef, $template_obj);
182 my $transaction = shift;
183 my $template_obj = shift;
185 # perform some more advanced check
187 my $condition_obj = $condition->new(
188 TransactionObj => $transaction,
189 TicketObj => $ticket,
190 ScripObj => $void_scrip,
191 TemplateObj => $template_obj,
192 Argument => $condition_arg,
193 CurrentUser => $CurrentUser,
196 # if the condition doesn't apply, get out of here
198 return unless $condition_obj->IsApplicable;
199 print "\t".loc("Condition matches...")."\n" if $verbose;
203 for my $action (@$actions) {
205 # Given the current index of the actions array, see
206 # if there is still a corresponding element in the
207 # actions_arg array. If so, then use that argument,
209 my $action_argument = ($i <= $#$actions_arg) ? $actions_arg->[$i] : undef;
213 my $action_obj = $action->new(
214 TicketObj => $ticket,
215 TransactionObj => $transaction,
216 TemplateObj => $template_obj,
217 Argument => $action_argument,
218 ScripObj => $void_scrip,
219 ScripActionObj => $void_scrip_action,
220 CurrentUser => $CurrentUser,
223 # if our preparation failed, move onto the next action
224 next unless $action_obj->Prepare;
225 print "\t".loc("Action prepared...")."\n" if $verbose;
228 next unless $action_obj->Commit;
229 print "\t".loc("Action committed.")."\n" if $verbose;
233 # =head2 get_transactions
235 # Takes ticket and returns L<RT::Transactions> object with transactions
236 # of the ticket according to command line arguments C<--transaction>
237 # and <--transaction-type>.
241 sub get_transactions {
243 my $txns = $ticket->Transactions;
244 my $order = $transaction eq 'last'? 'DESC': 'ASC';
246 { FIELD => 'Created', ORDER => $order },
247 { FIELD => 'id', ORDER => $order },
249 if ( $transaction_type ) {
250 $transaction_type =~ s/^\s+//;
251 $transaction_type =~ s/\s+$//;
252 foreach my $type ( split /\s*,\s*/, $transaction_type ) {
253 $txns->Limit( FIELD => 'Type', VALUE => $type, ENTRYAGGREGATOR => 'OR' );
256 $txns->RowsPerPage(1) unless $transaction eq 'all';
260 # =head2 get_template
262 # Takes a ticket and returns a template according to command line options.
268 return undef unless $template;
270 unless ( $template =~ /\D/ ) {
272 my $template_obj = RT::Template->new( RT->SystemUser );
273 $template_obj->Load( $template );
274 die "Failed to load template '$template'"
275 unless $template_obj->id;
276 return $template_obj;
279 my $queue = $ticket->Queue;
281 my $res = RT::Template->new( RT->SystemUser );
282 $res->LoadQueueTemplate( Queue => $queue, Name => $template );
283 unless ( $res->id ) {
284 $res->LoadGlobalTemplate( $template );
285 die "Failed to load template '$template', either for queue #$queue or global"
294 # Loads a perl module, dying nicely if it can't find it.
300 unless ($modname->require) {
302 die loc( "Failed to load module [_1]. ([_2])", $modname, $error );
310 print loc( "[_1] is a tool to act on tickets from an external scheduling tool, such as cron.", $0 )
312 print loc("It takes several arguments:") . "\n\n";
315 . loc( "[_1] - Specify the search module you want to use", "--search" )
318 . loc( "[_1] - An argument to pass to [_2]", "--search-arg", "--search" )
322 . loc( "[_1] - Specify the condition module you want to use", "--condition" )
325 . loc( "[_1] - An argument to pass to [_2]", "--condition-arg", "--condition" )
328 . loc( "[_1] - Specify the action module you want to use. This option may be repeated to apply multiple actions to found tickets.", "--action" )
331 . loc( "[_1] - An argument to pass to [_2]. This option may be repeated to pass corresponding arguments to multiple calls of [_2].", "--action-arg", "--action" )
334 . loc( "[_1] - Specify name or id of template(s) you want to use", "--template" )
337 . loc( "[_1] - Specify if you want to use either 'first', 'last' or 'all' transactions", "--transaction" )
340 . loc( "[_1] - Specify the comma separated list of transactions' types you want to use", "--transaction-type" )
343 . loc( "[_1] - Adjust LogToSTDERR config option", "--log" ) . "\n";
345 . loc( "[_1] - Output status updates to STDOUT", "--verbose" ) . "\n";
348 print loc("Security:")."\n";
349 print loc("This tool allows the user to run arbitrary perl modules from within RT.")." ".
350 loc("If this tool were setgid, a hostile local user could use this tool to gain administrative access to RT.")." ".
351 loc("It is incredibly important that nonprivileged users not be allowed to run this tool."). " " .
352 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";
354 print loc("Example:");
357 . loc( "The following command will find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:"
361 print " bin/rt-crontool \\\n";
362 print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
363 print " --condition RT::Condition::Overdue \\\n";
364 print " --action RT::Action::SetPriority --action-arg 99 \\\n";
365 print " --verbose\n";
368 print loc("Escalate tickets"). "\n";
369 print " bin/rt-crontool \\\n";
370 print " --search RT::Search::ActiveTicketsInQueue --search-arg general \\\n";
371 print" --action RT::Action::EscalatePriority\n";
380 rt-crontool - a tool to act on tickets from an external scheduling tool
384 # find all active tickets in the queue 'general' and set their priority to 99 if they are overdue:
386 --search RT::Search::ActiveTicketsInQueue --search-arg general \
387 --condition RT::Condition::Overdue \
388 --action RT::Action::SetPriority --action-arg 99 \
393 --search RT::Search::ActiveTicketsInQueue --search-arg general \
394 --action RT::Action::EscalatePriority
398 This script is a tool to act on tickets from an external scheduling tool, such
403 This tool allows the user to run arbitrary perl modules from within RT. If
404 this tool were setgid, a hostile local user could use this tool to gain
405 administrative access to RT. It is incredibly important that nonprivileged
406 users not be allowed to run this tool. It is suggested that you create a
407 non-privileged unix user with the correct group membership and RT access to
417 Specify the search module you want to use
421 An argument to pass to --search
425 Specify the condition module you want to use
429 An argument to pass to --condition
433 Specify the action module you want to use. This option may be repeated to apply multiple actions to found tickets.
437 An argument to pass to --action. This option may be repeated to pass corresponding arguments to multiple calls of --action.
441 Specify name or id of template(s) you want to use
445 Specify if you want to use either 'first', 'last' or 'all' transactions
448 =item transaction-type
450 Specify the comma separated list of transactions' types you want to use
454 Adjust LogToSTDERR config option
458 Output status updates to STDOUT