NAME
    Plack::App::GitHub::WebHook - GitHub WebHook receiver as Plack
    application

SYNOPSIS
        use Plack::App::GitHub::WebHook;

        # 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 <http://developer.github.com/webhooks/>, for instance
    PushEvents
    <http://developer.github.com/v3/activity/events/types/#pushevent>.

    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
        <https://developer.github.com/webhooks/#events>, 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
        <http://developer.github.com/v3/activity/events/types/> expected to
        be send with the "X-GitHub-Event" header (e.g. "['pull']").

    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 stream "psgi.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 logging object as fourth parameter. It 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 Plack::App::GitHub::WebHook;
        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->{info}, $log->{error};
                    1;
                },
                # sub { ...optional action after each pull... } 
            ],
        )->to_app;

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/>.

    *   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::App::GitHub::WebHook;
            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.

