RT – Request Tracker

Aus gegebenen Anlass und zu Dokumentationszwecken:
Meiner Meinung nach das beste Ticketsystem im Vergleich zu z.B. BMCs Remedy oder Atlassians JIRA und Co.
Neben diversen Rollen und sehr fein granulierbaren Berechtigungen kann man auch scripte zwecks automatisiertem zuweisen(assign), beantworten, kommentieren, zusammenfüheren(merge) von Tickets sowie auch notifizierungen (auch extern via IRC/Pager) bei Statusänderung durchführen. Die primäre Kommunikation erfolgt jedoch per Mail. Eigene Felder (CustomFields) lassen sich ebenso selbstverständlich einrichten wie FAQ- und Default-Antworten.
Einen Fehler hat das ganze jedoch: die Dokumentation hat noch sehr viel Potential zum wachsen – um es freundlich auszudrücken. Gut, Best Practical Solutions, die macher von RT, will ja mit Support Geld verdienen… daher gibt es hier auch noch einige scripting Beispiele die über das eigentliche installieren hinaus gehen.

 

HP: http://bestpractical.squarespace.com/request-tracker

Dependencys:
Apace2 mit mod_perl
MySQL
CPAN
Perl: Crypt::SSLeay, LWP + Bundle::LWP und ein ganzen haufen Perlmodule die via „make fixdeps“ abgehandelt werden.

Dependencys die schwierigkeiten gemacht haben:

DateTime: (Da dieses Modul ./Build benutzt und nicht make hat CPAN da Probleme)
cd /usr/local/src/
/usr/local/src # wget http://search.cpan.org/CPAN/authors/id/D/DR/DROLSKY/DateTime-0.53.tar.gz
/usr/local/src # tar xzf DateTime-0.53.tar.gz
/usr/local/src # cd DateTime-0.53
/usr/local/src/DateTime-0.53 # perl Build.PL && ./Build installdeps && perl Build.PL && ./Build install

XML::Parser: (Wegen fehlenden expat header dateien)
1. yum install expat-devel oder yast -i expat-devel
2. force install (test will fail with „syntax error“ of test – Einmal nur mit Profis…)

 

cd /usr/local/src/
/usr/local/src # wget https://download.bestpractical.com/pub/rt/release/rt-3.8.7.tar.gz
/usr/local/src # tar xzf rt-3.8.7.tar.gz
/usr/local/src # cd rt-3.8.7
/usr/local/src/rt-3.8.7 # ./configure –prefix=/usr/local/rt3 –enable-gpg –with-db-database=rt3_company_instance –with-db-rt-user=rtdbuser_instance
/usr/local/src/rt-3.8.7 # make testdeps
/usr/local/src/rt-3.8.7 # make fixdeps; make testdeps
=> Solange bis keine fehlenden dependencys mehr angezeigt werden

/usr/local/src/rt-3.8.7 # make install

 

/usr/local/src/rt-3.8.7 # mysql -p
mysql> GRANT ALL PRIVILEGES ON rt3_company_instance.* TO ‚rtdbuser_instance’@’localhost‘ IDENTIFIED BY ‚rt_password_CHANGE_ME!‘;
mysql> flush privileges;
mysql> quit

/usr/local/src/rt-3.8.7 # vim /usr/local/rt3/etc/RT_SiteConfig.pm

Set($rtname, ‚RT.Futzelnet‘);

Set($Organization , „rt.futzelnet.de“);
Set($Timezone , ‚Europe/Berlin‘);
Set($DatabaseType , ‚mysql‘);
Set($DatabaseHost , ‚localhost‘);
Set($DatabaseRTHost , ‚localhost‘);
Set($DatabaseUser , ‚rtdbuser_instance‘);
Set($DatabasePassword , ‚rt_password_CHANGE_ME!‘);
Set($DatabaseName , ‚rt3_company_instance.‘);

Set($CorrespondAddress , ‚rt@futzelnet.de‘);
Set($CommentAddress , ‚rt@futzelnet.de‘);
Set($OwnerEmail , ‚rt-admin@futzelnet.de‘);

Set($LoopsToRTOwner , 1);
Set($StoreLoops , undef);
Set($MaxAttachmentSize , 10000000);
# Abschneiden zu langer Attachments
Set($TruncateLongAttachments , undef);
# Loeschen zu langer Attachments
Set($DropLongAttachments , undef);
# Wenn die Ticketaddresse auch im CC sein kann.
Set($ParseNewMessageForTicketCcs , 1);
# RTAddressRegexp sollte man setzen wenn ParseNewMessageForTicketCcs true ist.
Set($RTAddressRegexp , ‚(rt|zweite-queue|weitere-rt-queue)(.*)\@.*(futzelnet.de|futzelticket.de)$‘);
Set($RejectOnMissingPrivateKey , 0);
Set($keyserver, ‚hkp://subkeys.pgp.net‘);
Set($auto-key-locate , ‚keyserver‘);
Set($auto-key-retrieve , 1);
# if set to true, enables ‚take‘ and ‚resolve‘ as possible actions via the mail gateway
Set($UnsafeEmailCommands , 0);
# Vertraue HTML-Attachments
Set($TrustHTMLAttachments , 0);
# Um Tickets linken/mergen zu koennen benoetigt man modify-rechte bei beiden tickets
#Set($StrictLinkACL, 1);
# Transaktionen werden in einem batch zusammengefasst und nicht einzeln durch- und aufgefuehrt
Set($UseTransactionBatch, 1);

Set($WebPath , „“);
Set($WebBaseURL , „https://rt.futzelnet.de“);
Set($WebURL , $WebBaseURL . $WebPath . „/“);
# Aendern des default Stylesheets: web2, 3.5-default oder 3.4-compat
#Set($WebDefaultStylesheet , ‚3.4-compat‘);
# Benutze auch externe (basic-auth vom Apache z.B. via LDAP, MySQL oder .htpasswd)
Set($WebExternalAuth , 1);
# Wenn das nicht klappt benutze die interne Authentifikation
Set($WebFallbackToInternalAuth , 1);
# „match ‚gecos‘ field as the user identity“
Set($WebExternalGecos , undef);
# Automatisches erstellen von usern anhand der REMOTE_USER Variablen wenn der User noch nicht existiert
Set($WebExternalAuto , 1);

/usr/local/src/rt-3.8.7 # vim /usr/local/apache2/conf.d/rt.conf

<VirtualHost 123.123.123.123:443>
        ServerName rt.futzelnet.de
        ServerAdmin rt-admin@rt.futzelnet.de

        SSLEngine on
        SSLProtocol all
        SSLCipherSuite HIGH:MEDIUM
        SSLCertificateFile /usr/local/rt3/ssl/rt.futzelnet.de.crt
        SSLCertificateKeyFile /usr/local/rt3/ssl/rt.futzelnet.de.key
        SSLCACertificateFile /usr/local/rt3/ssl/futzelnet_ca.pem

        DocumentRoot "/usr/local/rt3/share/html"

        ErrorLog /usr/local/rt3/logs/error.log
        TransferLog /usr/local/rt3/logs/access.log

        AddDefaultCharset UTF-8
        PerlRequire /usr/local/rt3/bin/webmux.pl

        #<Location />
        #       AuthType Basic
        #       require valid-user
        #       AuthUserFile /dev/null
        #       AuthGroupFile /dev/null
        #       # LDAP
        #       AuthBasicProvider ldap
        #       AuthName "Futzenet RT"
        #       AuthLDAPBindDN  "cn=binduser,ou=futzelnet,o=futzelnet.de"
        #       AuthLDAPBindPassword "ldap_pw"
        #       AuthLDAPURL "ldap://ldap.futzelnet.de:389/o=futzelnet.de?uid?sub"
        #       AuthzLDAPAuthoritative Off
        #       # MySQL
        #       AuthMySQLEnable on
        #       AuthMySQLHost localhost
        #       AuthMySQLUser db_user
        #       AuthMySQLPassword db_password
        #       AuthMySQLDB database_name
        #       AuthMysqlUserTable http_auth
        #       AuthMySQLPwEncryption none
        #</Location>

        <Directory /usr/local/rt3/share/html>
                Order allow,deny
                Allow from all

                SetHandler perl-script
                PerlHandler RT::Mason
        </Directory>
        <Directory /usr/local/rt3/share/html/REST>
                Order deny,allow
                Deny from all
                Allow from 127.0.0.1 SERVER_IP_ADDRESS
                Satisfy any
        </Directory>
</VirtualHost>


„Manpage“: https://rt-wiki.bestpractical.com/wiki/ManualApacheConfig

vim /etc/aliases

rt: „|/usr/local/rt3/bin/rt-mailgate –queue General –action correspond –url https://rt.futzelnet.de/“
rt-comment: „|/usr/local/rt3/bin/rt-mailgate –queue General –action comment –url https://rt.futzelnet.de/“

weitere-rt-queue: „|/usr/local/rt3/bin/rt-mailgate –queue weitere-tr-queue –action correspond –url https://rt.futzelnet.de/“
weitere-rt-queue-comment: „|/usr/local/rt3/bin/rt-mailgate –queue weitere-tr-queue –action comment –url https://rt.futzelnet.de/“

Hier gibt es noch eine kleine Stolperfalle:
„If you are using the default sendmail included in most redhat distro’s you will most likely need to copy (cp) rt-mailgate to /etc/smrsh or create a symbolic link and change /opt/rt3/bin above to /etc/smrsh or sendmail will complain. These sendmails will only execute programs as alias targets which have been blessed by placing them in a special directory. This is not necessary for Exim.“
Lösung: cp /usr/local/rt3/bin/rt-mailgate /etc/smrsh/
Darf man bei update dann nur nicht vergessen…
Postfix hat dieses „Problem“, im übrigen, ebenfalls nicht.

Für z.B. eine „Support-Queue“, in der jeder Tickets erzeugen darf, legt man nun eine entsprechende Queue an und gibt „everyone“ das Recht Tickets zu erzeugen:
Configuration->Queues->Die jeweilige queue->Group Rights->System Groups->Everyone = „CreateTicket“
Für jede Queue müssen die Rechte jeweils erst vergeben werden. Ausser man stellt sie Global ein.

 

/usr/local/src/rt-3.8.7 # make initialize-database
/usr/local/src/rt-3.8.7 # mkdir -p /usr/local/rt3/ssl
/usr/local/src/rt-3.8.7 # mkdir -p /usr/local/rt3/logs && chown www:www /usr/local/rt3/logs

Zertifikate erzeugen und nach /usr/local/rt3/ssl schieben.

 

/usr/local/src/rt-3.8.7 # /etc/init.d/apache2 restart

Im Browser: https://rt.futzelnet.de/
Defaultuser und Password: root / password
!!! SOFORT ÄNDERN !!!

Und Fertig ist das Default-RT.

Neue Benutzer, die auch Rechte für queues bekommen sollen, müssen beim anlegen einen Haken bei „Let this user be granted rights“ (default: disabled) bekommen!

Mittels sogenannter „scrip“-scripte kann man das RT noch nahezu unendlich erweitern und auf eigene Bedürfnisse anpassen. (Notifyer, Autoresponder, Autom. Ticketzuweisung und noch sehr viel mehr.) Siehe weiter unten.

 

Eigene Felder, CustomFields, (z.B. für externe Ticketnummern/Kundennummern/ob es ein security vorfall ist o.ä.) für Tickets anlegen:
Configuration->Custom Fields->Create
Anschließend über Configuration->Custom Fields->Name_des_neuen_feldes->Applies to den queues zuweisen die das Feld brauchen.

 

Weitere abweichungen vom Default die, meiner Meinung nach, Sinnvoll sind:
Configuration->Global->Group Rights->Privileged = add ModifySelf

 

Admin Manual:
https://rt-wiki.bestpractical.com/wiki/ManualAdministration

RT via RSS:
https://rt-wiki.bestpractical.com/wiki/RssFeed

Extensions (z.B.):
SLA: http://search.cpan.org/dist/RT-Extension-SLA/
Workflow: http://search.cpan.org/dist/RTx-WorkflowBuilder/
SpawnChildTicket: http://search.cpan.org/dist/RT-Extension-SpawnLinkedTicketInQueue/
CannedReplies: https://rt-wiki.bestpractical.com/wiki/CannedReplies

„Contributions“: https://rt-wiki.bestpractical.com/wiki/Contributions

Eigene „scrip“-scripte einbauen:
https://localhost/rt/Admin/Global/Scrip.html?create=1&Queue=0

 

Beispiel um neu erzeugte Tickets automagisch an z.B. einen pager zu notifyen:
Im Webfrontend: Configuration->Global->Scrips->New

Description: Pager Notify
Condition: On Create
Action: User defined
Template: Global template: Blank
Stage: TransactionCreate

Custom action preparation code:

1;

Custom action cleanup code:

my $pager='/usr/local/bin/pager';
my $QueueObj = $self->TicketObj->QueueObj;
my $TicketObj = $self->TicketObj;

my $id=$TicketObj->Id;
my $subject=$TicketObj->Subject||'(No subject)';
my $queue=$QueueObj->Name;
my $owner=$TicketObj->OwnerObj->Name;

# Exit if Ticket is not in the HighPrioAlertQueue-Queue or BossQueue-Queue we want to be notified about
return 1 if ($queue ne 'HighPrioAlertQueue' && $queue ne 'BossQueue');

# Log as warning
$RT::Logger->warning("DEBUG: New Ticket in $queue: RT\#$id \'$subject\'");

# Execute systemcall wit the actual paging
system("$pager --target=target --message=\"New Ticket in $queue: RT\#$id \'$subject\'\" ");

1;


(Hint: der „pager“ kann auch ein IRC/Twitter/$sonstwas Bot sein.)

Der Actor (wer das Ticket erstellt/zuweist/bearbeitet) ist wie folgt ermittelbar:

my $UserObj=RT::User->new($self->CurrentUser);
$UserObj->Load($self->TransactionObj->Creator);
my $actor=$UserObj->Name;
my $actorid=$UserObj->Id;

 

Beispiel um neu erzeugte Tickets automagisch die Priorität, auf z.B. 10, zu setzen wenn die Tickets in einer, oder mehreren, speziellen queues erstellt wird:

Description: Set higer prio
Condition: On Create
Action: User defined
Template: Global template: Blank
Stage: TransactionCreate

Custom action preparation code:

1;

Custom action cleanup code:

my %queues=(
        'queue1' => 1,
        'queue2' => 1,
);
# Get the final priority a Ticket could get or set to 10 if none set
my $defaultprio=$self->TicketObj->QueueObj->FinalPriority || '10';

# Set the priority to 90% of the final priority - reserve the last 10% of priority for very special tickets
$self->TicketObj->SetPriority( int( $defaultprio * 0.9 ) ) if ($queues{$self->TicketObj->QueueObj->Name});

1;

 

Beispiel um Antworten, die per mail kommen, die mit AW, Antwort Re oder ähnlichem beginnen oder das selbe Subject haben automatisch in das Ursprungsticket zu mergen:

Description: AutoMerge
Condition: On Create
Action: User defined
Template: Global template: Blank
Stage: TransactionCreate

Custom action preparation code:

1;

Custom action cleanup code:

my $QueueObj=$self->TicketObj->QueueObj;
my $queue=$QueueObj->Name;

my $TicketObj=$self->TicketObj;
my $subject=$TicketObj->Subject;

my $TicketsObj = RT::Tickets->new($RT::SystemUser);
$TicketsObj->LimitStatus(VALUE => 'open');
$TicketsObj->LimitStatus(VALUE => 'new');
$TicketsObj->LimitStatus(VALUE => 'stalled');
$TicketsObj->LimitQueue(VALUE  => $queue);

# Exit if there is no Ticket found by the search
return 1 if ($TicketsObj->Count == 0);

while (my $ticket = $TicketsObj->Next) {
        # Ignore the ticket that triggered this action
        next if ($self->TicketObj->Id == $ticket->Id);
        my $merge=0;

        # Merge Tickets if the new ticket would be a reply to an existing ticket
        $merge=1 if ($ticket->Subject =~ /^\W*(re:\W*|aw:\W*|\!aw:\W*|antwort:\W*|fw:\W*)+\W*$subject\W*$/i);

        # Merge Tickets if the subject of the new ticket is the same as an existing ticket
        $merge=1 if ($ticket->Subject =~ /^$subject$/);

        # Do the actual merge
        if ($merge==1) {
                my $tid=$ticket->Id;
                my $mid=$self->TicketObj->Id;
                RT::Logger->warning("Merging Ticket $mid into $tid\n");
                $self->TicketObj->MergeInto($tid) if ($merge==1 && $tid != $mid);
        }
}

1;

 

Noch ein paar schnipsel:

# Search for Tickets where an CustomField $numeric_custom_field_id with $TicketID as value exists.
my $TicketsObj = RT::Tickets->new($RT::SystemUser);
$TicketsObj->LimitCustomField(CUSTOMFIELD => $numeric_custom_field_id, OPERATOR => '=', VALUE => $ExternalTicketID);

# Add Value to CustomField.
$self->TicketObj->AddCustomFieldValue(Field => $numeric_custom_field_id, Value => 'Text for fieldvalue', RecordTransaction => 1);

# Go through found tickets and link them together
while (my $ticket = $TicketsObj->Next) {
        # Ignore the ticket that triggered this action
        next if ($self->TicketObj->Id == $ticket->Id);

        $id = $ticket->Id;
        $self->TicketObj->AddLink(Type => 'RefersTo', Target => $id);
}

# If creator is $caddr then delete watcher/Requestor $daddr and set watcher/Requestor to $aaddr
my $caddr='requstor\@futzelnet.de';
my $daddr='user\@futzelnet.de';
my $aaddr='requstor\@futzelnet.de';
if ($self->TicketObj->IsWatcher(Type => 'Requestor', Email => $caddr)) {
        $self->TicketObj->AddWatcher( Type => 'Requestor', Email => $aaddr);
        $self->TicketObj->DeleteWatcher( Type => 'Requestor', Email => $daddr);
}

# Get all mailaddresses of the requestors
my @addresses;
push( @addresses, $self->TicketObj->Requestors->MemberEmailAddresses );

# Write a comment to the actual ticket
$self->TicketObj->Comment(Content => 'comment text');

# Get the status of the actual ticket
my $state=$self->TicketObj->Status();

# Set the status of the ticket to "rejected"
$self->TicketObj->SetStatus(Status => 'rejected', Force => 1);



# Now magically turn myself into a Requestor Notify object...
require RT::Action::Notify; bless($self, 'RT::Action::Notify');
$self->{Argument} = 'Requestor'; $self->Prepare;

return 1;

 

Weitere Beispiele für „scrip“-scripte:
https://rt-wiki.bestpractical.com/wiki/OnCreateFromEmail
https://rt-wiki.bestpractical.com/wiki/SendNagiosAlert
http://archives.free.net.ph/message/20040319.180325.27528377.en.html
http://lists.fsck.com/pipermail/rt-users/2009-August/060941.html

… das wars erstmal.