[Bps-public-commit] rt-extension-pagerduty branch master updated. 2819d95e6c552b3f56fefd1adf7c81f1120d16bd

BPS Git Server git at git.bestpractical.com
Wed Mar 16 03:35:58 UTC 2022


This is an automated email from the git hooks/post-receive script. It was
generated because a ref change was pushed to the repository containing
the project "rt-extension-pagerduty".

The branch, master has been updated
  discards  0826025822cf2a26d378ea110adb2b34b7669c89 (commit)
  discards  598d2074042255a0afd1ac42db2904fb51b0873f (commit)
  discards  1be5c5e17db207de6db9afee8cdcfc6ed4ad884e (commit)
  discards  e7b0d4bac97bc9ed7f2dd17f2b16f596ce8b4308 (commit)
       via  2819d95e6c552b3f56fefd1adf7c81f1120d16bd (commit)
       via  1b8a6d251c85c8c3fd9a6359fb36587924e3bf5f (commit)
       via  69d61cbb29a838fdeea0ae4179c182eda6096ce6 (commit)
       via  3476e6818b4db60453b7d4a864ac97b3b29389ed (commit)

This update added new revisions after undoing existing revisions.  That is
to say, the old revision is not a strict subset of the new revision.  This
situation occurs when you --force push a change and generate a repository
containing something like this:

 * -- * -- B -- O -- O -- O (0826025822cf2a26d378ea110adb2b34b7669c89)
            \
             N -- N -- N (2819d95e6c552b3f56fefd1adf7c81f1120d16bd)

When this happens we assume that you've already had alert emails for all
of the O revisions, and so we here report only the revisions in the N
branch from the common base, B.

Those revisions listed above that are new to this repository have
not appeared on any other notification email; so we list those
revisions in full, below.

- Log -----------------------------------------------------------------
commit 2819d95e6c552b3f56fefd1adf7c81f1120d16bd
Author: Brad Embree <brad at bestpractical.com>
Date:   Wed Feb 16 11:08:27 2022 -0800

    Prep for first release: version 0.01

diff --git a/Changes b/Changes
new file mode 100644
index 0000000..6a334cb
--- /dev/null
+++ b/Changes
@@ -0,0 +1,4 @@
+Revision history for RT-Extension-PagerDuty
+
+0.01 [Release Date]
+ - Initial version
diff --git a/META.yml b/META.yml
new file mode 100644
index 0000000..1a45cc6
--- /dev/null
+++ b/META.yml
@@ -0,0 +1,30 @@
+---
+abstract: 'RT-Extension-PagerDuty Extension'
+author:
+  - 'Best Practical Solutions, LLC <modules at bestpractical.com>'
+build_requires:
+  ExtUtils::MakeMaker: 6.59
+configure_requires:
+  ExtUtils::MakeMaker: 6.59
+distribution_type: module
+dynamic_config: 1
+generated_by: 'Module::Install version 1.19'
+license: gpl_2
+meta-spec:
+  url: http://module-build.sourceforge.net/META-spec-v1.4.html
+  version: 1.4
+name: RT-Extension-PagerDuty
+no_index:
+  directory:
+    - etc
+    - html
+    - inc
+requires:
+  perl: 5.10.1
+resources:
+  license: http://opensource.org/licenses/gpl-license.php
+  repository: https://github.com/bestpractical/rt-extension-pagerduty
+version: '0.01'
+x_module_install_rtx_version: '0.43'
+x_requires_rt: 5.0.0
+x_rt_too_new: 5.2.0
diff --git a/Makefile.PL b/Makefile.PL
new file mode 100644
index 0000000..772fea1
--- /dev/null
+++ b/Makefile.PL
@@ -0,0 +1,12 @@
+use lib '.';
+use inc::Module::Install;
+
+RTx     'RT-Extension-PagerDuty';
+license 'gpl_2';
+repository 'https://github.com/bestpractical/rt-extension-pagerduty';
+
+requires_rt '5.0.0';
+rt_too_new '5.2.0';
+
+sign;
+WriteAll;
diff --git a/README b/README
new file mode 100644
index 0000000..0a04766
--- /dev/null
+++ b/README
@@ -0,0 +1,162 @@
+NAME
+    RT-Extension-PagerDuty - Two way integration with PagerDuty
+
+DESCRIPTION
+    This RT extension allows for two way integration with the PagerDuty
+    incident response platform.
+
+    On ticket creation in RT trigger an incident in PagerDuty. When a ticket
+    is acknowledged or resolved in RT update the incident in PagerDuty.
+
+    Configure a PagerDuty webhook to push noticications to RT from
+    PagerDuty. When a new incident is triggered in PagerDuty have it create
+    a ticket in RT. If an incident is acknowledged or resolved in PagerDuty
+    update the corresponding ticket in RT.
+
+RT VERSION
+    Works with RT 5.
+
+INSTALLATION
+    perl Makefile.PL
+    make
+    make install
+        May need root permissions
+
+    make initdb
+        Only run this the first time you install this module.
+
+        If you run this twice, you may end up with duplicate data in your
+        database.
+
+        If you are upgrading this module, check for upgrading instructions
+        in case changes need to be made to your database.
+
+    Edit your /opt/rt5/etc/RT_SiteConfig.pm
+        Add this line:
+
+            Plugin('RT::Extension::PagerDuty');
+
+    Clear your mason cache
+            rm -rf /opt/rt5/var/mason_data/obj
+
+    Restart your webserver
+
+CONFIGURATION
+    To define the interactions between RT and PagerDuty use the $PagerDuty
+    config option. This option takes the form of:
+
+        Set(
+            $PagerDuty,
+            {
+                services => {
+                    'PagerDuty Service ID' => {
+                        api_token => 'PagerDuty API Token',
+                        api_user => 'PagerDuty User',
+                        create_queue => 'General',
+                    }
+                },
+                queues => {
+                    'RT Queue Name' => {
+                        service => 'PagerDuty Service ID',
+                        acknowledged => 'open',
+                        resolved => 'resolved',
+                    }
+                }
+            }
+        );
+
+    The services section maps a PagerDuty service id to the token and user
+    to use for API access. The api_token and api_user values are required.
+    The optional create_queue value is the RT queue name where new RT
+    tickets should be created if a PagerDuty incident creates a new RT
+    ticket. create_queue defaults to the General queue if not specified. Use
+    '*' as the PagerDuty service id to apply to multiple PagerDuty services.
+
+    The queues section maps an RT queue name to the PagerDuty service where
+    it should trigger new incidents when an RT ticket is created. The
+    service value is required and must be a PagerDuty service id. The
+    acknowledged and resolved optional values indicate what RT ticket status
+    to use when an incident is acknowledged or resolved on PagerDuty. If not
+    specified they default to acknowledged => 'open' and resolved =>
+    'resolved'. Use '*' as the RT queue name to apply to multiple RT queues.
+
+    To get the PagerDuty Service ID login to your PagerDuty account and go
+    to Services -> Service Directory. Click on the Service you want the ID
+    for and the the ID will be at the end of the URL. For example:
+
+        pagerduty.com/service-directory/P3AFFQR
+
+    the Service ID is P3AFFQR.
+
+    To create an api_token login to your PagerDuty account and go to
+    Integrations -> API Access Keys. Click the Create New API Key button.
+    Add a description and click Create Key. Copy the key and paste it into
+    the $PagerDuty config as the api_token. You will not be able to view the
+    key again but you can generate a new one if you lose the key.
+
+    The api_user is the email address for a valid PagerDuty user that has
+    access to the PagerDuty Service you are integrating with.
+
+Scrips
+    This extension will install three new Scrips that do not apply to any
+    queues: On Acknowledge PagerDuty Acknowledge, On Create PagerDuty
+    Trigger, and On Resolve PagerDuty Resolve.
+
+    Once you have added the configuration you can apply these Scrips to the
+    queues you want to integrate with PagerDuty.
+
+CUSTOM FIELDS
+    This extension adds two ticket custom fields: PagerDuty ID and PagerDuty
+    URL.
+
+    When an RT ticket creates an incident on PagerDuty or an incident on
+    PagerDuty creates an RT ticket the custom fields are automatically
+    filled in. The PagerDuty URL links directly to the incident on
+    PagerDuty.
+
+    If you would like to group the new custom fields in their own PagerDuty
+    group you can use the CustomFieldGroupings config option:
+
+        Set(
+            %CustomFieldGroupings,
+            'RT::Ticket' => [
+                'PagerDuty' => [ 'PagerDuty ID', 'PagerDuty URL' ],
+            ],
+        );
+
+WEBHOOK USAGE
+    To call the webhook from PagerDuty:
+
+    1. Create an auth token for a user with permissions to create tickets in
+    the PagerDuty create queue. To create an auth token go to Logged in as
+    -> Settings -> Auth Tokens and create a new token.
+    2. Go to the PagerDuty Service Integrations Webhooks
+    3. Add a new webhook, using:
+    https://your.rt.example/PagerDuty/WebHook.html as the webhook URL. Add a
+    custom header with the name Authorization and value 'token #-#-abc123'
+    where '#-#-abc123' is the value for the auth token you created in step
+    one. Currently the only event subscriptions supported are
+    incident.acknowledged, incident.resolved, and incident.triggered.
+
+WEBHOOK WITH $RestrictReferrer ENABLED
+    If the RT config setting $RestrictReferrer is enabled then the webhook
+    will not work without allowing it in the config:
+
+        Set( %ReferrerComponents,
+            '/PagerDuty/WebHook.html' => 1,
+        );
+
+AUTHOR
+    Best Practical Solutions, LLC <modules at bestpractical.com>
+
+    All bugs should be reported via email to
+        bug-RT-Extension-PagerDuty at rt.cpan.org
+    or via the web at
+        http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-PagerDuty
+LICENSE AND COPYRIGHT
+    This software is Copyright (c) 2022 by BPS
+
+    This is free software, licensed under:
+
+      The GNU General Public License, Version 2, June 1991
+
commit 1b8a6d251c85c8c3fd9a6359fb36587924e3bf5f
Author: Brad Embree <brad at bestpractical.com>
Date:   Wed Feb 16 11:07:54 2022 -0800

    Add PagerDuty webhook

diff --git a/html/PagerDuty/WebHook.html b/html/PagerDuty/WebHook.html
new file mode 100644
index 0000000..521b55d
--- /dev/null
+++ b/html/PagerDuty/WebHook.html
@@ -0,0 +1,132 @@
+<%init>
+use JSON;
+use Data::Dumper;
+
+if ( !$ARGS{'POSTDATA'} ) {
+    RT->Logger->error('PagerDuty webhook failed no POSTDATA');
+    $m->abort(400);
+}
+
+# payload example: https://developer.pagerduty.com/docs/ZG9jOjExMDI5NTkw-v3-overview#webhook-payload
+my $data;
+eval { $data = decode_json( $ARGS{'POSTDATA'} ); };
+
+if ($@) {
+    RT->Logger->error("PagerDuty webhook failed to decode json data: $@");
+    $m->abort(400);
+}
+
+RT->Logger->debug( 'PagerDuty webhook got data: ' . Dumper($data) );
+
+# this webhook only handles incident events
+if ( $data->{event}{data}{type} ne 'incident' ) {
+    RT->Logger->debug('PagerDuty webhook ignoring non incident event');
+    $m->abort(400);
+}
+
+my $config  = RT::Config->Get('PagerDuty') // {};
+my $service = $config->{services}{ $data->{event}{data}{service}{id} }
+    // $config->{services}{'*'};
+
+unless ( defined $service ) {
+    RT->Logger->error( "PagerDuty webhook no service config found for id: "
+            . $data->{event}{data}{service}{id} );
+    $m->abort(400);
+}
+
+my $user_obj = $session{CurrentUser};
+unless ($user_obj) {
+    RT->Logger->error('PagerDuty webhook failed no current user');
+    $m->abort(401);
+}
+
+# need to see if a ticket already exists for this incident
+my $tickets = RT::Tickets->new($user_obj);
+$tickets->LimitCustomField(
+    CUSTOMFIELD => 'PagerDuty ID',
+    OPERATOR    => '=',
+    VALUE       => $data->{event}{data}{id},
+);
+
+# if there is a ticket already we ignore the triggered event to avoid a loop between RT and PagerDuty
+# XXX - should we log an error if there is more than 1 ticket for this incident?
+my $pd_event = $data->{event}{event_type};
+if ( ( $tickets->Count > 0 ) && ( $pd_event ne 'incident.triggered' ) ) {
+    my $ticket = $tickets->First;
+    my $queue  = $config->{queues}{ $ticket->QueueObj->Name }
+        // $config->{queues}{'*'};
+
+    unless ( defined $queue ) {
+        RT->Logger->error( "PagerDuty webhook no queue config found for: "
+                . $ticket->QueueObj->Name );
+        $m->abort(400);
+    }
+
+    if ( $pd_event eq 'incident.acknowledged' ) {
+        if ( $ticket->LifecycleObj->IsInitial( $ticket->Status ) ) {
+            my ( $ret, $msg )
+                = $ticket->SetStatus( $queue->{acknowledged} // 'open' );
+            unless ($ret) {
+                RT->Logger->error(
+                    "Pagerduty webhook could not set ticket status: $msg");
+            }
+            ( $ret, $msg )
+                = $ticket->Comment( Content => 'acknowledged on PagerDuty' );
+            unless ($ret) {
+                RT->Logger->error(
+                    "Pagerduty webhook could not add comment: $msg");
+            }
+        }
+    } elsif ( $pd_event eq 'incident.resolved' ) {
+        my $resolved_status = $queue->{resolved} // 'resolved';
+        if ( $ticket->Status ne $resolved_status ) {
+            my ( $ret, $msg ) = $ticket->SetStatus($resolved_status);
+            unless ($ret) {
+                RT->Logger->error(
+                    "Pagerduty webhook could not set ticket status: $msg");
+            }
+            ( $ret, $msg )
+                = $ticket->Comment( Content => 'resolved on PagerDuty' );
+            unless ($ret) {
+                RT->Logger->error(
+                    "Pagerduty webhook could not add comment: $msg");
+            }
+        }
+    }
+}
+
+# if there is not a ticket already we only handle the triggered event
+elsif ( ( $tickets->Count == 0 ) && ( $pd_event eq 'incident.triggered' ) ) {
+    my $queue = $service->{create_queue} // 'General';
+
+    # look up ids for PagerDuty related custom fields
+    my $CF = RT::CustomField->new($user_obj);
+    $CF->LoadByName( Name => 'PagerDuty ID' );
+    my $cf_id = $CF->id;
+    $CF->LoadByName( Name => 'PagerDuty URL' );
+    my $cf_url = $CF->id;
+
+    my $Ticket = RT::Ticket->new($user_obj);
+    my ( $id, $Trans, $ErrMsg ) = $Ticket->Create(
+        Type    => 'ticket',
+        Queue   => $queue,
+        Subject => 'PagerDuty Incident: '
+            . $data->{event}{data}{service}{summary} . ' - '
+            . $data->{event}{data}{title},
+        "CustomField-$cf_id"  => $data->{event}{data}{id}       // '',
+        "CustomField-$cf_url" => $data->{event}{data}{html_url} // '',
+    );
+
+    if ($id) {
+        RT::Logger->debug(
+            'PagerDuty webhook created new ticket with custom field values: '
+                . $data->{event}{data}{id} . ' - '
+                . $data->{event}{data}{html_url} );
+    } else {
+        RT::Logger->error(
+            "PagerDuty webhook failed to create new ticket: $ErrMsg");
+    }
+}
+
+</%init>
+% $m->abort;
commit 69d61cbb29a838fdeea0ae4179c182eda6096ce6
Author: Brad Embree <brad at bestpractical.com>
Date:   Wed Feb 16 11:07:04 2022 -0800

    Add RT::Extension::PagerDuty POD

diff --git a/lib/RT/Extension/PagerDuty.pm b/lib/RT/Extension/PagerDuty.pm
new file mode 100644
index 0000000..5b11832
--- /dev/null
+++ b/lib/RT/Extension/PagerDuty.pm
@@ -0,0 +1,203 @@
+use strict;
+use warnings;
+
+package RT::Extension::PagerDuty;
+
+our $VERSION = '0.01';
+
+=head1 NAME
+
+RT-Extension-PagerDuty - Two way integration with PagerDuty
+
+=head1 DESCRIPTION
+
+This RT extension allows for two way integration with the PagerDuty incident
+response platform.
+
+On ticket creation in RT trigger an incident in PagerDuty. When a ticket is
+acknowledged or resolved in RT update the incident in PagerDuty.
+
+Configure a PagerDuty webhook to push noticications to RT from PagerDuty. When
+a new incident is triggered in PagerDuty have it create a ticket in RT. If an
+incident is acknowledged or resolved in PagerDuty update the corresponding
+ticket in RT.
+
+=head1 RT VERSION
+
+Works with RT 5.
+
+=head1 INSTALLATION
+
+=over
+
+=item C<perl Makefile.PL>
+
+=item C<make>
+
+=item C<make install>
+
+May need root permissions
+
+=item C<make initdb>
+
+Only run this the first time you install this module.
+
+If you run this twice, you may end up with duplicate data
+in your database.
+
+If you are upgrading this module, check for upgrading instructions
+in case changes need to be made to your database.
+
+=item Edit your F</opt/rt5/etc/RT_SiteConfig.pm>
+
+Add this line:
+
+    Plugin('RT::Extension::PagerDuty');
+
+=item Clear your mason cache
+
+    rm -rf /opt/rt5/var/mason_data/obj
+
+=item Restart your webserver
+
+=back
+
+=head1 CONFIGURATION
+
+To define the interactions between RT and PagerDuty use the C<$PagerDuty> config
+option. This option takes the form of:
+
+    Set(
+        $PagerDuty,
+        {
+            services => {
+                'PagerDuty Service ID' => {
+                    api_token => 'PagerDuty API Token',
+                    api_user => 'PagerDuty User',
+                    create_queue => 'General',
+                }
+            },
+            queues => {
+                'RT Queue Name' => {
+                    service => 'PagerDuty Service ID',
+                    acknowledged => 'open',
+                    resolved => 'resolved',
+                }
+            }
+        }
+    );
+
+The services section maps a PagerDuty service id to the token and user to use
+for API access. The C<api_token> and C<api_user> values are required. The
+optional C<create_queue> value is the RT queue name where new RT tickets should
+be created if a PagerDuty incident creates a new RT ticket. C<create_queue>
+defaults to the General queue if not specified. Use '*' as the PagerDuty service
+id to apply to multiple PagerDuty services.
+
+The queues section maps an RT queue name to the PagerDuty service where it should
+trigger new incidents when an RT ticket is created. The C<service> value is
+required and must be a PagerDuty service id. The C<acknowledged> and C<resolved>
+optional values indicate what RT ticket status to use when an incident is
+acknowledged or resolved on PagerDuty. If not specified they default to
+acknowledged => 'open' and resolved => 'resolved'. Use '*' as the RT queue name
+to apply to multiple RT queues.
+
+To get the PagerDuty Service ID login to your PagerDuty account and go to
+Services -> Service Directory. Click on the Service you want the ID for and the
+the ID will be at the end of the URL. For example:
+
+    pagerduty.com/service-directory/P3AFFQR
+
+the Service ID is P3AFFQR.
+
+To create an api_token login to your PagerDuty account and go to
+Integrations -> API Access Keys. Click the Create New API Key button. Add a
+description and click Create Key. Copy the key and paste it into the $PagerDuty
+config as the api_token. You will not be able to view the key again but you can generate a new
+one if you lose the key.
+
+The api_user is the email address for a valid PagerDuty user that has access to
+the PagerDuty Service you are integrating with.
+
+=head1 Scrips
+
+This extension will install three new Scrips that do not apply to any queues:
+C<On Acknowledge PagerDuty Acknowledge>, C<On Create PagerDuty Trigger>, and
+C<On Resolve PagerDuty Resolve>.
+
+Once you have added the configuration you can apply these Scrips to the queues
+you want to integrate with PagerDuty.
+
+=head1 CUSTOM FIELDS
+
+This extension adds two ticket custom fields: PagerDuty ID and PagerDuty URL.
+
+When an RT ticket creates an incident on PagerDuty or an incident on PagerDuty
+creates an RT ticket the custom fields are automatically filled in. The PagerDuty
+URL links directly to the incident on PagerDuty.
+
+If you would like to group the new custom fields in their own PagerDuty group
+you can use the CustomFieldGroupings config option:
+
+    Set(
+        %CustomFieldGroupings,
+        'RT::Ticket' => [
+            'PagerDuty' => [ 'PagerDuty ID', 'PagerDuty URL' ],
+        ],
+    );
+
+=head1 WEBHOOK USAGE
+
+To call the webhook from PagerDuty:
+
+=over 4
+
+=item 1. Create an auth token for a user with permissions to create tickets in
+the PagerDuty create queue. To create an auth token go to
+Logged in as -> Settings -> Auth Tokens and create a new token.
+
+=item 2. Go to the PagerDuty Service Integrations Webhooks
+
+=item 3. Add a new webhook, using: C<https://your.rt.example/PagerDuty/WebHook.html>
+as the webhook URL. Add a custom header with the name Authorization and value
+'token #-#-abc123' where '#-#-abc123' is the value for the auth token you
+created in step one. Currently the only event subscriptions supported are
+incident.acknowledged, incident.resolved, and incident.triggered.
+
+=back
+
+=head1 WEBHOOK WITH $RestrictReferrer ENABLED
+
+If the RT config setting $RestrictReferrer is enabled then the webhook will not
+work without allowing it in the config:
+
+    Set( %ReferrerComponents,
+        '/PagerDuty/WebHook.html' => 1,
+    );
+
+=head1 AUTHOR
+
+Best Practical Solutions, LLC E<lt>modules at bestpractical.comE<gt>
+
+=for html <p>All bugs should be reported via email to <a
+href="mailto:bug-RT-Extension-PagerDuty at rt.cpan.org">bug-RT-Extension-PagerDuty at rt.cpan.org</a>
+or via the web at <a
+href="http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-PagerDuty">rt.cpan.org</a>.</p>
+
+=for text
+    All bugs should be reported via email to
+        bug-RT-Extension-PagerDuty at rt.cpan.org
+    or via the web at
+        http://rt.cpan.org/Public/Dist/Display.html?Name=RT-Extension-PagerDuty
+
+=head1 LICENSE AND COPYRIGHT
+
+This software is Copyright (c) 2022 by BPS
+
+This is free software, licensed under:
+
+  The GNU General Public License, Version 2, June 1991
+
+=cut
+
+1;
commit 3476e6818b4db60453b7d4a864ac97b3b29389ed
Author: Brad Embree <brad at bestpractical.com>
Date:   Tue Feb 15 09:15:23 2022 -0800

    Add Scrips and Custom Fields

diff --git a/etc/initialdata b/etc/initialdata
new file mode 100644
index 0000000..282fcb3
--- /dev/null
+++ b/etc/initialdata
@@ -0,0 +1,68 @@
+use strict;
+use warnings;
+
+our @ScripActions = (
+    {   Name        => 'PagerDuty Trigger',
+        Description => 'Trigger an incident on PagerDuty',
+        ExecModule  => 'UpdatePagerDuty',
+        Argument    => 'trigger',
+    },
+    {   Name        => 'PagerDuty Acknowledge',
+        Description => 'Acknowledge an incident on PagerDuty',
+        ExecModule  => 'UpdatePagerDuty',
+        Argument    => 'acknowledge',
+    },
+    {   Name        => 'PagerDuty Resolve',
+        Description => 'Resolve an incident on PagerDuty',
+        ExecModule  => 'UpdatePagerDuty',
+        Argument    => 'resolve',
+    },
+);
+
+our @ScripConditions = (
+    {   Name        => 'On Acknowledge',
+        Description => 'When a ticket is changed from an initial status',
+        ExecModule  => 'OnAcknowledge',
+        Argument    => '',
+        ApplicableTransTypes => 'Status',
+    },
+);
+
+our @Scrips = (
+    {   NoAutoGlobal   => 1,
+        Description    => 'On Create PagerDuty Trigger',
+        ScripCondition => 'On Create',
+        ScripAction    => 'PagerDuty Trigger',
+        Template       => 'Blank',
+    },
+    {   NoAutoGlobal   => 1,
+        Description    => 'On Acknowledge PagerDuty Acknowledge',
+        ScripCondition => 'On Acknowledge',
+        ScripAction    => 'PagerDuty Acknowledge',
+        Template       => 'Blank',
+    },
+    {   NoAutoGlobal   => 1,
+        Description    => 'On Resolve PagerDuty Resolve',
+        ScripCondition => 'On Resolve',
+        ScripAction    => 'PagerDuty Resolve',
+        Template       => 'Blank',
+    },
+);
+
+our @CustomFields = (
+    {   Name        => 'PagerDuty ID',
+        Description => 'ID for the related incident on PagerDuty',
+        Type        => 'FreeformSingle',
+        LookupType  => 'RT::Queue-RT::Ticket',
+        ApplyTo     => [],
+    },
+    {   Name        => 'PagerDuty URL',
+        Description => 'URL to view the incident on PagerDuty',
+        Type        => 'FreeformSingle',
+        LookupType  => 'RT::Queue-RT::Ticket',
+        ApplyTo     => [],
+        LinkValueTo => '__CustomField__ ',
+    },
+);
+
+1;
diff --git a/lib/RT/Action/UpdatePagerDuty.pm b/lib/RT/Action/UpdatePagerDuty.pm
new file mode 100644
index 0000000..ef04768
--- /dev/null
+++ b/lib/RT/Action/UpdatePagerDuty.pm
@@ -0,0 +1,212 @@
+package RT::Action::UpdatePagerDuty;
+
+use strict;
+use warnings;
+use base 'RT::Action';
+
+use HTTP::Request::Common qw(POST PUT);
+use LWP::UserAgent;
+
+sub Prepare {
+    my $self = shift;
+
+    return 1;
+}
+
+sub Commit {
+    my $self = shift;
+
+    my $config = RT::Config->Get('PagerDuty');
+    unless ($config) {
+        RT->Logger->error('PagerDuty config not set');
+        return 0;
+    }
+
+    my $service_id
+        = $config->{queues}{ $self->TicketObj->QueueObj->Name }{service}
+        // $config->{queues}{'*'}{service};
+    unless ($service_id) {
+        RT->Logger->error( 'PagerDuty no service id found for queue: '
+                . $self->TicketObj->QueueObj->Name );
+        return 0;
+    }
+
+    my $service = $config->{services}{$service_id}
+        // $config->{services}{'*'};
+    unless ($service) {
+        RT->Logger->error(
+            "PagerDuty no service config found for id: $service_id");
+        return 0;
+    }
+
+    my $token = $service->{api_token};
+    my $user  = $service->{api_user};
+    unless ( $token && $user ) {
+        RT->Logger->error(
+            "PagerDuty service config missing token or user for service id: $service_id"
+        );
+        return 0;
+    }
+
+    my $arg = lc( $self->Argument );
+    if ( $arg eq 'trigger' ) {
+        return $self->_trigger( $service_id, $token, $user );
+    } else {
+        return $self->_update( $arg, $token, $user );
+    }
+}
+
+sub _trigger {
+    my ( $self, $service_id, $token, $user ) = @_;
+
+    if ( $self->TicketObj->FirstCustomFieldValue('PagerDuty ID') ) {
+
+        # this ticket already has a PagerDuty ID filled in
+        # that means it was created by PagerDuty webhook
+        # so do not need to create incident
+        RT->Logger->debug(
+            'PagerDuty skipping ticket trigger as it was created by PagerDuty webhook'
+        );
+        return 1;
+    }
+
+    my $ua = LWP::UserAgent->new;
+    $ua->timeout(15);
+
+    my $id      = $self->TicketObj->id;
+    my $tag     = $self->TicketObj->SubjectTag;
+    my $subject = $self->TicketObj->Subject;
+    my $content = $self->TransactionObj->Content( Type => 'text/plain' );
+
+    my %post_content = (
+        incident => {
+            type    => "incident",
+            title   => "$tag New Ticket $subject",
+            service => {
+                id   => $service_id,
+                type => "service_reference"
+            },
+            incident_key => "[$tag #$id]",
+            body         => {
+                type    => "incident_body",
+                details => $content
+            }
+        }
+    );
+
+    my $post_content = JSON::to_json( \%post_content );
+
+    RT->Logger->debug("PagerDuty POST: $post_content");
+
+# https://developer.pagerduty.com/api-reference/b3A6Mjc0ODE0MA-create-an-incident
+    my $req = POST(
+        'https://api.pagerduty.com/incidents',
+        'Accept',
+        'application/vnd.pagerduty+json;version=2',
+        'Authorization',
+        "Token token=$token",
+        'Content-Type',
+        'application/json',
+        'From',
+        $user,
+        CONTENT => $post_content
+    );
+
+    my $resp = $ua->request($req);
+
+    RT->Logger->debug( 'PagerDuty got response: '
+            . $resp->status_line . ' '
+            . $resp->decoded_content() );
+
+    unless ( $resp->is_success ) {
+        RT->Logger->error(
+            'PagerDuty request failed: ' . $resp->status_line );
+        return 0;
+    }
+
+# update PagerDuty custom fields
+# XXX - allow arbitrary PagerDuty custom fields and parse name for /PagerDuty\s*($field_name)/
+#       where $field_name matches a field returned from the API
+#       would want to make field names "prettier" so maybe map _ => - and uppercase first letter of each word?
+    my $return = JSON::from_json( $resp->decoded_content );
+    my ( $status, $msg ) = $self->TicketObj->AddCustomFieldValue(
+        Field => 'PagerDuty ID',
+        Value => $return->{incident}->{id} // ''
+    );
+    unless ($msg) {
+        RT->Logger->error("PagerDuty could not set PagerDuty ID field: $msg");
+    }
+    ( $status, $msg ) = $self->TicketObj->AddCustomFieldValue(
+        Field => 'PagerDuty URL',
+        Value => $return->{incident}->{html_url} // ''
+    );
+    unless ($msg) {
+        RT->Logger->error(
+            "PagerDuty could not set PagerDuty URL field: $msg");
+    }
+
+    return 1;
+}
+
+sub _update {
+    my ( $self, $status, $token, $user ) = @_;
+
+    $status //= '';
+
+    # try to allow some flexibility in the status parameter
+    if ( $status =~ /^resolve/i ) {
+        $status = 'resolved';
+    } elsif ( $status =~ /^ack/i ) {
+        $status = 'acknowledged';
+    }
+
+    my $ua = LWP::UserAgent->new;
+    $ua->timeout(15);
+
+    my $pd_id = $self->TicketObj->CustomFieldValuesAsString('PagerDuty ID');
+
+    # XXX - should it fail here? any way to show an error to user from here?
+    #       or do we just assume no pd id means no incident to worry about?
+    return 1 unless $pd_id;
+
+    my %content = (
+        incident => {
+            type   => "incident",
+            status => $status
+        }
+    );
+
+    my $content = JSON::to_json( \%content );
+
+    RT->Logger->debug("PagerDuty PUT: $content");
+
+# https://developer.pagerduty.com/api-reference/b3A6Mjc0ODE0Mg-update-an-incident
+    my $req = PUT(
+        'https://api.pagerduty.com/incidents/' . $pd_id,
+        'Accept',
+        'application/vnd.pagerduty+json;version=2',
+        'Authorization',
+        "Token token=$token",
+        'Content-Type',
+        'application/json',
+        'From',
+        $user,
+        CONTENT => $content
+    );
+
+    my $resp = $ua->request($req);
+
+    RT->Logger->debug( 'PagerDuty got response: '
+            . $resp->status_line . ' '
+            . $resp->decoded_content() );
+
+    unless ( $resp->is_success ) {
+        RT->Logger->error(
+            'PagerDuty request failed: ' . $resp->status_line );
+        return 0;
+    }
+
+    return 1;
+}
+
+1;
diff --git a/lib/RT/Condition/OnAcknowledge.pm b/lib/RT/Condition/OnAcknowledge.pm
new file mode 100644
index 0000000..0e1eb88
--- /dev/null
+++ b/lib/RT/Condition/OnAcknowledge.pm
@@ -0,0 +1,19 @@
+package RT::Condition::OnAcknowledge;
+
+use strict;
+use warnings;
+use base 'RT::Condition';
+
+sub IsApplicable {
+    my $self = shift;
+
+    my $txn = $self->TransactionObj;
+
+    # only applicable if status was changed from initial status
+    return 0
+        unless $self->TicketObj->LifecycleObj->IsInitial( $txn->OldValue );
+
+    return 1;
+}
+
+1;
-----------------------------------------------------------------------

Summary of changes:
 README                           | 57 ++++++++++++++++++++++++----------------
 html/PagerDuty/WebHook.html      | 53 +++++++++++++++++++++++--------------
 lib/RT/Action/UpdatePagerDuty.pm | 32 +++++++++++++---------
 lib/RT/Extension/PagerDuty.pm    | 26 ++++++++++++++++++
 4 files changed, 114 insertions(+), 54 deletions(-)


hooks/post-receive
-- 
rt-extension-pagerduty


More information about the Bps-public-commit mailing list