iwink/gitlab-webhook-bundle

A Symfony bundle to process GitLab webhooks.

Installs: 327

Dependents: 0

Suggesters: 0

Security: 0

Stars: 6

Watchers: 5

Forks: 3

Open Issues: 3

Type:symfony-bundle

v1.2.2 2023-07-26 12:45 UTC

This package is auto-updated.

Last update: 2024-04-26 14:16:56 UTC


README

License Tag Build Status

Symfony bundle to process GitLab webhooks.

Installation

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.

Usage

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.

Secured webhooks

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.

Caveat

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

Multiple webhooks

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.

Supported webhooks

The following webhooks are supported: