Symfony bundle to process GitLab webhooks.
To use this bundle, install it using Composer: composer require iwink/gitlab-webhook-bundle
. If
your project uses Symfony Flex, you are done. If not, make sure to enable the bundle
in your project's config/bundles.php
.
To mark a controller as a GitLab webhook, you can use the @Webhook(event="event")
annotation above your controller and
define a Iwink\GitLabWebhookBundle\Event\WebhookEvent
argument in your method:
<?php
namespace App\Controller;
use Iwink\GitLabWebhookBundle\Annotation\Webhook;
use Iwink\GitLabWebhookBundle\Event\PipelineEvent;
use Iwink\GitLabWebhookBundle\Scheduler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/webhook", name="webhook_")
*/
class WebhookController {
/**
* @Route("/pipeline", name="pipeline")
* @Webhook("pipeline")
*/
public function pipeline(PipelineEvent $event, Scheduler $scheduler): JsonResponse {
$status = $event->getObjectAttributes()['status'];
if ('success' === $status) {
$scheduler->schedule([$this, 'expensiveOperation'], ['one', true]);
}
return new JsonResponse();
}
public function expensiveOperation(string $name, bool $valid): void {
// Does something expensive
}
}
The example above annotates the pipeline
method as a webhook which receives a
Pipeline Hook
event by using
the @Webhook("pipeline")
annotation. The event is injected into the method's $event
argument. The injection is based
on the typehint (PipelineEvent
) of the argument, the argument's name doesn't matter. Because GitLab expects a response
as soon as possible, the
expensive part of the webhook is scheduled and executed after a response has been sent by the
Iwink\GitLabWebhookBundle\Scheduler::schedule()
method.
GitLab has the option to secure a webhook with a secret token. You can define these secret tokens in a webhook annotation:
<?php
namespace App\Controller;
use Iwink\GitLabWebhookBundle\Annotation\Webhook;
use Iwink\GitLabWebhookBundle\Event\PipelineEvent;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/webhook", name="webhook_")
*/
class WebhookController {
/**
* @Route("/pipeline", name="pipeline")
* @Webhook("pipeline", tokens={"secret_token"})
*/
public function pipeline(PipelineEvent $event): JsonResponse {
// Handle request
}
}
The received Pipeline Hook
request should now contain the secret token (provided by the X-GitLab-Token
header), otherwise the request fails. The tokens
should be defined as an array because it's possible to define multiple tokens for the same annotation since multiple
GitLab projects might trigger the same webhook. The tokens can also be defined as a
configuration parameter using the
%parameter.name%
format: @Webhook("pipeline", tokens={"%gitlab.secret_token%"})
. Since parameters can contain
environment variables,
configuring the secrets is very flexible.
Because Symfony caches the annotations, and the file defining the annotation is not changed when updating a parameter,
you should manually clear the cache after changing a secret parameter's value. This will only ever happen in the dev
environment because in the prod
environment the container is always cached (everytime your code changes, you will need
to clear the cache).
It's possible to register multiple webhooks to a single controller by using multiple @Webhook
annotations:
<?php
namespace App\Controller;
use Iwink\GitLabWebhookBundle\Annotation\Webhook;
use Iwink\GitLabWebhookBundle\Event\MergeRequestEvent;
use Iwink\GitLabWebhookBundle\Event\PushEvent;
use Iwink\GitLabWebhookBundle\Event\WebhookEvent;
use Iwink\GitLabWebhookBundle\Scheduler;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\Routing\Annotation\Route;
/**
* @Route("/webhook", name="webhook_")
*/
class WebhookController {
/**
* @Route("/pipeline", name="pipeline")
* @Webhook("push")
* @Webhook(event="merge request")
*/
public function pipeline(WebhookEvent $event, Scheduler $scheduler): JsonResponse {
if (
($event instanceof PushEvent && 'some/project' === $event->getProject()['name'])
|| ($event instanceof MergeRequestEvent && 'success' === $event->getObjectAttributes()['status'])
) {
$scheduler->schedule([$this, 'expensiveOperation']);
}
return new JsonResponse();
}
public function expensiveOperation(): void {
// Does something expensive
}
}
The injected $event
is now either a Iwink\GitLabWebhookBundle\Event\PushEvent
or a
Iwink\GitLabWebhookBundle\Event\MergeRequestEvent
. Notice the difference between the 2 @Webhook
annotations, both
the short (@Webhook("push")
) and the long (@Webhook(event="merge request")
) syntax give the same result so it
doesn't matter which syntax you use.
The following webhooks are supported:
- comment (
Iwink\GitLabWebhookBundle\Event\CommentEvent
) - deployment (
Iwink\GitLabWebhookBundle\Event\DeploymentEvent
) - feature flag (
Iwink\GitLabWebhookBundle\Event\FeatureFlagEvent
) - issue (
Iwink\GitLabWebhookBundle\Event\IssueEvent
) - job (
Iwink\GitLabWebhookBundle\Event\JobEvent
) - merge request (
Iwink\GitLabWebhookBundle\Event\MergeRequestEvent
) - pipeline (
Iwink\GitLabWebhookBundle\Event\PipelineEvent
) - push (
Iwink\GitLabWebhookBundle\Event\PushEvent
) - release (
Iwink\GitLabWebhookBundle\Event\ReleaseEvent
) - tag (
Iwink\GitLabWebhookBundle\Event\TagEvent
) - wiki page (
Iwink\GitLabWebhookBundle\Event\WikiPageEvent
)