On 07/02/2013 04:07 AM, Bryon Baker wrote: > Is there a library of RT-scrips? If so where can I find it
Personally I think any useful set of scrips should generally be turned into an extension and published on CPAN. That's what I've been doing with the ones I've written so far, like RT::Extension::SMSNotify and RT::Extension::CustomerGroups . I have another in the pipeline that's more of a collection of generally useful small scrips, I just need to untangle it from some in-house-only code. I'll reproduce my notes on extension creation in case they're useful to anyone. I really need to convert them to wikia syntax or integrate them into the current guide. The following uses Redmine wiki markup. Details of paths are specific to Debian installs of RT. h1. RT development notes To test an action without installing it to the DB and hooking it up to a queue, you can use rt-crontool. Searches can be any TicketSQL, but sometimes a simple ticket id is useful: rt-crontool --transaction last --search RT::Search::FromSQL --search-arg "(id = 1033)" --action RT::Action::ConvertRequestorCustomersToGroups The error messages for compilation and module load errors are not very informative. If you get errors about I18N or Locale, it' probably actually your module failing to compile. Test with: PERL5LIB=/usr/local/share/request-tracker4/lib:/usr/share/request-tracker4/lib perl -mRT::Action::ConvertRequestorCustomersToGroups h2. Test script The following code starts up an RT script you can run arbitrary RT code in. <pre> #!/usr/bin/perl use Modern::Perl; BEGIN { push(@INC, '/usr/share/request-tracker4/lib'); push(@INC, '/usr/local/share/request-tracker4/lib'); } use strict; use lib "/opt/rt3/lib"; use RT; use RT::Interface::CLI qw( CleanEnv GetCurrentUser ); use RT::ScripCondition; use RT::Utils2ndQ; CleanEnv( ); RT::LoadConfig( ); RT::Init( ); my $user = GetCurrentUser( ); unless( $user->Id ) { print "No RT user found. Please consult your RT administrator.\n"; exit 1; } say "Current RT::User is in \$user variable. Entering interactive shell."; </pre> Good for testing scripts and snippets. You might find it useful to load this in `re.pl` from Perl's "`Devel::REPL` module":http://search.cpan.org/~ether/Devel-REPL-1.003015/lib/Devel/REPL.pm (in CPAN) so you can interactively work with RT's API. Just point re.pl to the file, eg: <pre> bash$ re.pl --rcfile /path/to/rtcmdline.pl $_ $user->Id 149 $_ $user->Name SomeUserName $_ </pre> You'll need the full explicit path; re.pl silently ignores files it can't find and doesn't search the current directory for rcfiles. I like to alias this to rtrepl in my bashrc: <pre> alias rtrepl='re.pl --rcfile ~/rtcmdline.pl' </pre> h2. Manually adding actions to the DB You can use the following code in an RT script like the above test script to create an action in the DB: <pre> $sa->Create( Name => 'ConvertRequestorCustomersToGroups', Description => 'Convert all customer accounts added as a requestor into the associated customer group', ExecModule => 'ConvertRequestorCustomersToGroups', Argument => 'Requestor' ); </pre> Argument is optional, though some actions require it. The meaning varies from action to action. h2. Packaging RT extensions The packaging process for RT extensions is rather underdocumented. Most of it is standard Perl module stuff, though: h3. Packaging a Perl module Create a Makefile.PL according to Perl's "Module::Install":http://search.cpan.org/~adamk/Module-Install/lib/Module/Install.pod or use the example below, which adds use of <pre> use inc::Module::Install 0.77; use Module::Install::AutoManifest; use Module::Install::ReadmeFromPod; name 'Example-Module'; all_from 'lib/Example/Module.pm'; requires 'Carp' => 0; # add additional dependencies here test_requires 'Test::More' => '0.47'; readme_from 'lib/Example/Module.pm'; auto_manifest; homepage 'https://github.com/yours/module-example-perl'; bugtracker 'https://rt.cpan.org/Dist/Display.html?Status=Active&Queue=Module-Example'; repository 'git://github.com/yours/module-example-perl.git'; WriteAll; </pre> Create a MANIFEST.SKIP like: <pre> \.git/ \.gitignore .*\.o pm_to_blib blib/ README.GIT MANIFEST.SKIP Module-Example-.* \..*\.swp </pre> Make sure your main module's POD begins like this, replacing Example::Module with your module package and name: <pre> package Example::Module; =pod =head1 NAME Example::Module - Sample module with package Example::Module =head1 SYNOPSIS ... code sample here ... =head1 DESCRIPTION ... descriptive text ... =head1 LICENSE The same as for Perl itself =cut use 5.010; use strict; use vars qw{$VERSION @ISA}; BEGIN { $VERSION = '1.04'; @ISA = 'Example::Module'; } </pre> Add this to .gitignore: <pre> blib/ pm_to_blib Makefile Makefile.old *.tar.gz inc/ MANIFEST META.yml README Module-Example-* .*.swp </pre> h2. Additions for RT support To turn the above into an RT extension module, add the following to Makefile.PL after the 'use' statements and before the 'name' statement, like: <pre> requires 'Module::Install::RTx' => '0.30'; use Module::Install::RTx; RTx ('RT-Extension-SMSNotify'); requires_rt('4.0.7'); </pre> This allows you to auto-detect the RT installation and install the module to it. You can create a file etc/initialdata that sets up database contents like scrips and scrip actions. This appears to be undocumented, but examples can be found in RT::Extension::SLA among others. The initialdata is applied with "make initdb". h1. Extension mechanisms See "Customizing":http://requesttracker.wikia.com/wiki/Customizing on the RT wiki h2. Scrip Actions and Scrip Conditions RT's scrips are the most common way to extend RT. They can be run when certain events occur on tickets or on a timed basis using rt-crontool and can act on tickets. Actions are somewhat documented and there are lots of sample modules you can use to learn how it works. See "Scrips":http://requesttracker.wikia.com/wiki/Scrip and "Extensions":http://requesttracker.wikia.com/wiki/Extensions . scrip actions and conditions go in the lib/RT/Actions/ and lib/RT/Conditions directories in extensions. Scrip actions and conditions can be added directly as Perl modules in the local lib directory, but are better added by packaged plugin extensions. h2. Mail plugins See the "Customization" section of "the rt-mailgate man page":http://linux.die.net/man/1/rt-mailgate where it mentions the @MailPlugins parameter. MailPlugins appears to be for user authentication. The docs aren't very clear, but the default Auth::MailFrom plugin is only added to the list automatically if @MailPlugins isn't defined in the config. There isn't a plug or hook mechanism for other parts of the mail import; the <tt>rt-mailgate</tt> program invokes <tt>RT::Interface::Email::Gateway</tt>, which is a rather monolithic method. To hook it you have to replace it with an overlay, which means the whole method is copied. The lookup of sender address to user is handled by GetAuthenticationLevel . The comment on this is considerably more useful than rt-mailgate's docs. Looking at the code we can see: <pre> # Authentication Level # -1 - Get out. this user has been explicitly declined # 0 - User may not do anything (Not used at the moment) # 1 - Normal user # 2 - User is allowed to specify status updates etc. a la enhanced-mailgate </pre> and in the loop over plugins, you can see that the auth level of the result is the greatest auth level returned by any plugin a plugin returns -1 to deny this user explicitly. The user returned is whatever the last plugin executed returned. Each plugin gets the previous plugin's CurrentUser as its input; it's free to return this, modify it, or return a different CurrentUser. The default plugin RT::Interface::Email::Auth::MailFrom does *not* use the input CurrentUser argument, it presumes it is `undef` and ignores it. This is raised in RT bug 23089. It can therefore not be used as the last entry for user lookup since it'll clobber whatever came first. It can be used BEFORE another plugin that overrides the input user, or you can just rip the simple user lookup out of it and use your own module instead of it. h2. Callbacks Callbacks allow you to customise the RT web UI's Mason pages. They're fairly critically under-documented, but there's a "useful article here":http://www.runpcrun.com/node/272. There's some information on the "CustomizingWithCallbacks section of the RT wiki":http://requesttracker.wikia.com/wiki/CustomizingWithCallbacks too. Callbacks are placed in the html/ directory of extensions. RT callbacks are written with Perl's Mason templating language. See "the documentation for mason":http://www.masonbook.com/book/chapter-2.mhtml You'll also need to read the docs on the <%init> and <%args> blocks. Callbacks are "Components called with content" in Mason terms, as covered in the "advanced features chapter":http://www.masonbook.com/book/chapter-5.mhtml. You can find available callbacks in the RT sources. Look in the share/html/ directory; in say Ticket/Create.html you'll find ->callback invocations like: <pre> $m->callback( CallbackName => 'BeforeCreate', ARGSRef => \%ARGS, skip_create => \$skip_create, checks_failure => $checks_failure, results => \@results ); </pre> Sometimes the logic is a bit hard to trace. For example, ticket creation starts in the <%init> methods of SelfService/Create.html (unpriv) and Ticket/Create.html (priv) where, in response to form data submission, the BeforeCreate callback is invoked. These components then invoke the corresponding Display.html code in the same directories to do the actual ticket creation in their <%init> methods. Both of these appear to go via lib/RT/Interface/Web.pm's CreateTicket method, but email submission doesn't; it looks like lib/RT/Interface/Email.pm calls RT::Ticket->new(...) directly from its Gateway method. h2. Overlays Overlays may be used to fully override methods in RT's core modules or to insert new methods. They're not usually recommended for use in extensions because there's no good way to handle multiple extensions overriding the same method. See "Overlays":http://requesttracker.wikia.com/wiki/CustomizingWithOverlays in the RT wiki. h1. The RT database The RT DB is notably lacking in foreign keys and documentation. Here are a few notes. It seems like a good idea to interact with it from Perl most of the time. h2. Finding the Privileged group The system internal group Privileged can be found with: <pre> select id from groups were where type = 'Privileged' and domain = 'SystemInternal'; </pre> or from within RT to get the RT::Group ($priv) and then get the RT::User members at all nested levels. <pre> my $priv = RT::Group->new( RT::SystemUser ); $priv->LoadSystemInternalGroup('Privileged'); @{$priv->UserMembersObj->ItemsArrayRef} </pre> h2. Finding privileged users <pre> select u.id, u.name from groups g INNER JOIN groupmembers gm ON (g.id = gm.groupid) INNER JOIN users u ON (u.id = gm.memberid) where g.type = 'Privileged' and g.domain = 'SystemInternal'; </pre> You can not use this approach to find group memberships to non-SystemInternal groups because non-SystemInternal groups can have other groups as members. You have to use a recursive query or query via the cachedgroupmembers table. It's fine for the SystemInternal groups 'Everyone', 'Privileged' and 'Unprivileged'. Don't query the DB directly from RT, use the RT::Group methods. Querying the DB can mainly be useful for reporting. >From within RT: <pre> my $u = RT::Users->new( RT::SystemUser ); $u->LimitToPrivileged(); @{$u->ItemsArrayRef} </pre> to list users you can do something like: <pre> foreach my $uid (@{$u->ItemsArrayRef}) {say $uid->Id, ',', $uid->Name, ',', $uid->EmailAddress; } </pre> or more usefully: <pre> use Text::CSV; my $csv = Text::CSV->new(); foreach my $uid (@{$u->ItemsArrayRef}) { $csv->print(\*STDOUT, [$uid->Id, $uid->Name]); say ''; } </pre> -- Craig Ringer http://www.2ndQuadrant.com/ PostgreSQL Development, 24x7 Support, Training & Services