NAME
Plack::App::GitHub::WebHook - GitHub WebHook receiver as Plack application
SYNOPSIS
# Basic Usage
Plack::App::GitHub::WebHook->new(
hook
=>
sub
{
my
$payload
=
shift
;
...
},
events
=> [
'pull'
],
# optional
secret
=>
$secret
,
# optional
access
=>
'github'
,
# default
)->to_app;
# Multiple hooks
use
IPC::Run3;
Plack::App::GitHub::WebHook->new(
hook
=> [
sub
{
$_
[0]->{repository}{name} eq
'foo'
},
sub
{
my
(
$payload
,
$event
,
$delivery
,
$logger
) =
@_
;
run3 \
@cmd
,
undef
,
$logger
->{info},
$logger
->{error};
},
sub
{ ... },
# some more action
]
)->to_app;
DESCRIPTION
This PSGI application receives HTTP POST requests with body parameter payload
set to a JSON object. The default use case is to receive GitHub WebHooks, for instance PushEvents.
The response of a HTTP request to this application is one of:
- HTTP 403 Forbidden
-
If access was not granted (for instance because it did not origin from GitHub).
- HTTP 405 Method Not Allowed
-
If the request was no HTTP POST.
- HTTP 400 Bad Request
-
If the payload was no well-formed JSON or the
X-GitHub-Event
header did not match configured events. - HTTP 200 OK
-
Otherwise, if the hook was called and returned a true value.
- HTTP 202 Accepted
-
Otherwise, if the hook was called and returned a false value.
- HTTP 500 Internal Server Error
-
If a hook died with an exception, the error is returned as content body. Use configuration parameter
safe
to disable HTTP 500 errors.
This module requires at least Perl 5.10.
CONFIGURATION
- hook
-
A hook can be any of a code reference, an object instance with method
code
, a class name, or a class name mapped to parameters. You can also pass a list of hooks as array reference. Class names are prepended by GitHub::WebHook unless prepended by+
.hook
=>
sub
{
my
(
$payload
,
$event
,
$delivery
,
$logger
) =
@_
;
...
}
hook
=>
'Foo'
hook
=>
'+GitHub::WebHook::Foo'
hook
=> GitHub::WebHook::Foo->new
hook
=> {
Bar
=> [
doz
=>
'baz'
] }
hook
=> GitHub::WebHook::Bar->new(
doz
=>
'baz'
)
Each hook gets passed the encoded payload, the type of webhook event, a unique delivery ID, and a logger object. If the hook returns a true value, the next the hook is called or HTTP status code 200 is returned. If a hook returns a false value (or if no hook was given), HTTP status code 202 is returned immediately. Information can be passed from one hook to the next by modifying the payload.
- events
-
A list of event types expected to be send with the
X-GitHub-Event
header (e.g.['pull']
). - logger
-
Object or function reference to hande logging events. An object must implement method
log
that is called with named arguments:$logger
->
log
(
level
=>
$level
,
message
=>
$message
);
For instance Log::Dispatch can be used as logger this way. A function reference is called with hash reference arguments:
$logger
->({
level
=>
$level
,
message
=>
$message
});
By default PSGI::Extensions is used as logger (if set).
- secret
-
Secret token set at GitHub Webhook setting to validate payload. See https://developer.github.com/webhooks/securing/ for details. Requires Plack::Middleware::HubSignature.
- access
-
Access restrictions, as passed to Plack::Middleware::Access. A recent list of official GitHub WebHook IPs is vailable at https://api.github.com/meta. The default value
access
=>
'github'
is a shortcut for these official IP ranges
access
=> [
allow
=>
"204.232.175.64/27"
,
allow
=>
"192.30.252.0/22"
,
deny
=>
'all'
]
and
access
=> [
allow
=>
'github'
,
...
]
is a shortcut for
access
=> [
allow
=>
"204.232.175.64/27"
,
allow
=>
"192.30.252.0/22"
,
...
]
To disable access control via IP ranges use any of
access
=>
'all'
access
=> []
- safe
-
Wrap all hooks in
eval { ... }
blocks to catch exceptions. Error messages are send to the PSGI error streampsgi.errors
. A dying hook in safe mode is equivalent to a hook that returns a false value, so it will result in a HTTP 202 response.If you want errors to result in a HTTP 500 response, don't use this option but wrap the application in an eval block such as this:
sub
{
eval
{
$app
->(
@_
) } ||
do
{
my
$msg
= $@ ||
'Server Error'
;
[ 500, [
'Content-Length'
=>
length
$msg
], [
$msg
] ];
};
};
LOGGING
Each hook is passed a logger object to facilitate logging to PSGI::Extensions. The logger provides logging methods for each log level and a general log method:
sub
sample_hook {
my
(
$payload
,
$event
,
$delivery
,
$log
) =
@_
;
$log
->debug(
'message'
);
$log
->{debug}->(
'message'
);
$log
->info(
'message'
);
$log
->{info}->(
'message'
);
$log
->
warn
(
'message'
);
$log
->{
warn
}->(
'message'
);
$log
->error(
'message'
);
$log
->{error}->(
'message'
);
$log
->fatal(
'message'
);
$log
->{fatal}->(
'message'
);
$log
->
log
(
warn
=>
'message'
);
run3 \
@system_command
,
undef
,
$log
->{info},
# STDOUT to log level info
$log
->{error};
# STDERR to log level error
}
Trailing newlines on log messages are trimmed.
EXAMPLES
Synchronize with a GitHub repository
The following application automatically pulls the master branch of a GitHub repository into a local working directory.
use
IPC::Run3;
my
$branch
=
"master"
;
my
$work_tree
=
"/some/path"
;
Plack::App::GitHub::WebHook->new(
events
=> [
'push'
,
'ping'
],
hook
=> [
sub
{
my
(
$payload
,
$event
,
$delivery
,
$log
) =
@_
;
$log
->info(
"$event $delivery"
);
$event
eq
'ping'
or
$payload
->{
ref
} eq
"refs/heads/$branch"
;
},
sub
{
my
(
$payload
,
$event
,
$delivery
,
$log
) =
@_
;
my
$origin
=
$payload
->{repository}->{clone_url}
or
die
"missing clone_url\n"
;
my
$cmd
;
if
( -d
"$work_tree/.git"
) {
chdir
$work_tree
;
$cmd
= [
'git'
,
'pull'
,
$origin
,
$branch
];
}
else
{
$cmd
= [
'git'
,
'clone'
,
$origin
,
'-b'
,
$branch
,
$work_tree
];
}
$log
->info(
join
' '
,
'$'
,
@$cmd
);
run3
$cmd
,
undef
,
$log
->{debug},
$log
->{
warn
};
1;
},
# sub { ...optional action after each pull... }
],
)->to_app;
See GitHub::WebHook::Clone for before copy and pasting this code.
DEPLOYMENT
Many deployment methods exist. An easy option might be to use Apache webserver with mod_cgi and Plack::Handler::CGI. First install Apache, Plack and Plack::App::GitHub::WebHook:
sudo apt-get install apache2
sudo apt-get install cpanminus libplack-perl
sudo cpanm Plack::App::GitHub::WebHook
Then add this section to /etc/apache2/sites-enabled/default
(or another host configuration) and restart Apache.
<Directory /var/www/webhooks>
Options +ExecCGI -Indexes +SymLinksIfOwnerMatch
AddHandler cgi-script .cgi
</Directory>
You can now put webhook applications in directory /var/www/webhooks
as long as they are executable, have file extension .cgi
and shebang line #!/usr/bin/env plackup
. You might further want to run webhooks scripts as another user instead of www-data
by using Apache module SuExec.
SEE ALSO
GitHub WebHooks are documented at http://developer.github.com/webhooks/.
See GitHub::WebHook for a collection of handlers for typical tasks.
WWW::GitHub::PostReceiveHook uses Web::Simple to receive GitHub web hooks. A listener as exemplified by the module can also be created like this:
use
Plack::Builder;
build {
mount
'/myProject'
=>
Plack::App::GitHub::WebHook->new(
hook
=>
sub
{
my
$payload
=
shift
; }
);
mount
'/myOtherProject'
=>
Plack::App::GitHub::WebHook->new(
hook
=>
sub
{ run3 \
@cmd
... }
);
};
Net::GitHub and Pithub provide access to GitHub APIs.
Github::Hooks::Receiver and App::GitHubWebhooks2Ikachan are alternative application that receive GitHub WebHooks.
COPYRIGHT AND LICENSE
Copyright Jakob Voss, 2014-
This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself.