如何为CircleCI的webhooks建立一个Laravel API

249 阅读8分钟

软件应用由相互连接的系统组成--每个系统都为满足商业需求的共同目标提供专门服务。与任何网络一样,有效的数据交换机制是其功能、有效性和响应性的关键。

在过去,数据交换是通过轮询请求进行的。每隔一段时间,系统就会发出请求,以获得最新的信息或找出是否有更新的信息需要处理。这种技术被证明是低效的,因为大多数请求被退回时没有新的信息可供操作。如果有新的东西需要处理,信息很可能是陈旧的,使得应用程序无法实时响应。

这导致了一种新的通信形式:webhooks。如果一个预先约定的事件发生,就会发出请求,通知需要该信息的系统,使该系统能够立即作出反应。使用webhooks的好处是降低了系统的开销,因为所发出的请求较少。它还可以确保数据的实时交换。CircleCI提供的webhooks允许您实时接收有关您的管道的信息。这可以帮助你避免轮询API或手动检查CircleCI网络应用程序以获得你需要的数据。

在本教程中,我将带领你建立一个Laravel API,它将被用作CircleCI管道的webhook,我们也将创建。

前提条件

要从本教程中获得最大的收获,你需要一些东西。

我们的教程是与平台无关的,但使用CircleCI作为一个例子。如果你没有CircleCI账户,请**在这里注册一个免费账户。**

CircleCI webhooks的使用案例

如果您仍然想知道CircleCI webhooks如何使用,无论您选择何种编程语言,这里有一些用例。

  • 聚集来自单个或多个项目的事件,并发送数据到一个通信渠道,如Slack
  • 当工作流程/工作被取消或完成时,自动向开发团队发送通知。
  • 通过一个自定义的仪表板可视化和分析工作流程/工作事件。
  • 将有价值的数据发送到数据记录和事件管理工具。

开始工作

创建一个新的项目来开始。

laravel new circle_ci_webhook_api

cd circle_ci_webhook_api

现在,为了设置webhook,你需要创建一个基本的CircleCI配置,并为CI管道设置一个GitHub仓库。在项目的根目录下,创建一个名为.circleci 的新文件夹。在.circleci 文件夹中,创建一个名为config.yml 的新文件。把这个文件添加到文件中。

version: 2
jobs:
  build:
    docker:
      - image: cimg/php:8.0-browsers

    steps:
      - checkout

      - run:
          name: "Prepare Environment"
          command: |
            sudo apt update

      # Download and cache dependencies
      - restore_cache:
          keys:
            # "composer.lock" can be used if it is committed to the repo
            - v1-dependencies-{{ checksum "composer.json" }}
            # fallback to using the latest cache if no exact match is found
            - v1-dependencies-

      - run:
          name: "Install Dependencies"
          command: composer install -n --prefer-dist

      - save_cache:
          key: v1-dependencies-{{ checksum "composer.json" }}
          paths:
            - ./vendor

下一步是创建一个控制器。这个控制器将被用来处理来自CircleCI的请求,并根据你保存在数据库中的内容检索请求的历史。要创建一个控制器,运行这个artisan 命令。

php artisan make:controller CircleCIController

在新创建的app/Http/Controllers/CircleCIController.php 文件中,添加以下内容。

<?php

namespace App\Http\Controllers;

use App\Models\WebhookNotification;
use Illuminate\Http\{JsonResponse, Response};

class CircleCIController extends Controller {

    public function getAllNotifications()
    : JsonResponse {

        return response()->json();
    }

    public function handleNotification()
    : JsonResponse {

        return response()
            ->json(null, Response::HTTP_NO_CONTENT);
    }

}

这个代码片断声明了两个函数。目前,它们只分别返回状态为200204 的JSON响应。

接下来,为你的API添加两条路由。打开routes/api.php ,添加这个。

Route::post('circleci', [CircleCIController::class, 'handleNotification']);
Route::get('circleci', [CircleCIController::class, 'getAllNotifications']);

第一个路由是webhook路由。它将处理来自CircleCI的POST请求。第二个路由将被用来获取CircleCI发送给webhook的通知。因为这些路由已经被注册为API路由,完整的路由URI将以api:api/circleci 为前缀。

导入CircleCIController

use App\Http\Controllers\CircleCIController;

现在你已经准备好为你的应用程序提供服务。使用这个命令运行应用程序。

php artisan serve

设置ngrok

为了开发的目的,我们需要一种方法,将我们的本地应用程序作为一个webhook暴露给CircleCI。我们可以使用ngrok将我们正在运行的Laravel应用程序暴露在互联网上。

下载ngrok的可执行文件并解压。要在你的本地机器上暴露一个端口, 使用ngrok http 命令, 指定你想要暴露的端口号.我们的Laravel应用程序将在8000端口运行, 所以使用这个命令:

ngrok http 8000

当你启动ngrok时,它将在你的终端显示一个用户界面,显示你的隧道的公共URL和其他状态以及通过你的隧道连接的指标信息。

ngrok Running

设置一个CircleCI管道

接下来,在GitHub上建立一个仓库,并将该项目链接到CircleCI。

登录你的CircleCI账户。如果你用GitHub账户注册,你的所有仓库将显示在你的项目仪表板上。

在你的circle_ci_webhook_api 项目旁边,点击Set Up Project

CircleCI将检测项目中的config.yml 文件。点击使用现有的配置,然后点击开始构建。您的第一个构建过程将开始运行并成功完成。

为项目配置一个webhook

circle_ci_webhook_api 项目的CircleCI仪表板上,点击项目设置。在侧边栏,点击Webhooks,然后点击Add Webhook。填写表格。

Configure Webhook

Receiver URL字段中指定ngrok给出的公共URL。添加一个秘密令牌来验证对Webhook的传入请求,并确保只接受来自CircleCI的请求。记下秘密令牌,因为你将用它来验证进入接收器URL的请求。

点击添加Webhook来保存Webhook的细节。当一个工作流程或工作完成后,一个POST请求将被发送到我们Laravel应用程序的api/circleci

设置环境变量

在本教程中, 我们将使用MySQL来管理我们的数据库.在.env 文件中, 用这个来更新数据库相关的参数:

DB_CONNECTION=mysql
DB_HOST=127.0.0.1
DB_PORT=3306
DB_DATABASE=circle_ci_webhook_api
DB_USERNAME=YOUR_DATABASE_USERNAME
DB_PASSWORD=YOUR_DATABASE_PASSWORD

使用你喜欢的数据库管理应用程序,创建一个名为circle_ci_webhook_api 的新数据库。

你还需要一个环境变量来保存webhook的秘密令牌。在.env 文件中包括以下内容。

CIRCLE_CI_WEBHOOK_SECRET="YOUR_CIRCLE_CI_WEBHOOK_SECRET"

创建WebhookNotification 模型

在我们的应用程序中,我们需要一个模型,它将表示由我们的webhook接收的通知的细节。将这个模型命名为WebhookNotification 。它将有这些字段。

  • id 是数据库中的主键。
  • notification_id 是webhook提供的id,用于唯一地识别来自CircleCI的每一个通知。
  • type 的值是workflow-completedjob-completed
  • happened_at 是代表事件发生时间的ISO 8601时间戳。
  • has_vcs_info 让你知道该通知是否有一个版本控制图,用于访问与触发该事件的git提交相关的元数据。
  • commit_subject 代表提交的主题,如果通知有一个版本控制图。否则该值为空。
  • commit_author 代表提交的作者。如果 ,该值可以为空。has_vcs_info
  • event_status 对应于工作流或工作达到终端状态时的状态。其值可以是 , , , , 或 。success failed error canceled unauthorized
  • workflow_url 包含工作流或工作在CircleCI仪表板上的URL。

使用此命令创建模型。

php artisan make:model WebhookNotification -m

在新创建的database/migrations/*YYYY_MM_DD_HHMMSS*_create_webhook_notifications_table.php ,更新up 函数以匹配这个代码块。

public function up() {

        Schema::create('webhook_notifications', function (Blueprint $table) {

            $table->id();
            $table->string('notification_id');
            $table->string('type');
            $table->string('happened_at');
            $table->boolean('has_vcs_info');
            $table->string('commit_subject')->nullable();
            $table->string('commit_author')->nullable();
            $table->string('event_status');
            $table->text('workflow_url');
            $table->timestamps();
        });
    }

接下来,打开WebhookNotification 的模型文件:app/Models/WebhookNotification.php 。使用这段代码更新它,将字段添加为$fillable 属性。

<?php

namespace App\Models;

use Illuminate\Database\Eloquent\Factories\HasFactory;
use Illuminate\Database\Eloquent\Model;

class WebhookNotification extends Model
{
    use HasFactory;

    protected $fillable = [
        'notification_id', 'type', 'happened_at', 'has_vcs_info', 'commit_subject','commit_author',
        'event_status','workflow_url'
    ];
}

运行你的迁移程序,创建webhook_notifications 表和它的列。

php artisan migrate

创建一个辅助类来管理CircleCI请求

为了分离关注点,使我们的控制器更加精简,你可以创建一个辅助类。这个帮助类验证传入的请求,并解析请求中用于填充WebhookNotification 模型的信息。

在项目的app 文件夹中,创建一个名为Helpers 的新文件夹。在该文件夹中,创建一个名为CircleCINotificationHelper.php 的新文件。用这段代码更新该文件的内容。

<?php

namespace App\Helpers;

use App\Models\WebhookNotification;
use Illuminate\Http\{Request, Response};

class CircleCINotificationHelper {

    public static function handle(Request $request)
    : void {

        $circleCISignature = $request->headers->get('circleci-signature');

        self::validate($circleCISignature, $request->getContent());
        $requestContent = $request->toArray();
        $hasVCSInfo = isset($requestContent['pipeline']['vcs']);

        $notificationType = $requestContent['type'];

        $notificationDetails = [
            'notification_id' => $requestContent['id'],
            'type'            => $notificationType,
            'happened_at'     => $requestContent['happened_at'],
            'workflow_url'    => $requestContent['workflow']['url'],
            'has_vcs_info'    => $hasVCSInfo,
        ];

        if ($hasVCSInfo) {
            $commitDetails = $requestContent['pipeline']['vcs']['commit'];
            $notificationDetails['commit_subject'] = $commitDetails['subject'];
            $notificationDetails['commit_author'] = $commitDetails['author']['name'];
        }

        $notificationDetails['event_status'] = $notificationType === 'job-completed' ?
            $requestContent['job']['status'] :
            $requestContent['workflow']['status'];

        $webhookNotification = new WebhookNotification($notificationDetails);

        $webhookNotification->save();
    }

    private static function validate(string $signature, string $requestContent)
    : void {

        $receivedSignature = explode('=', $signature)[1];

        $generatedSignature = hash_hmac(
            'sha256',
            $requestContent,
            env('CIRCLE_CI_WEBHOOK_SECRET')
        );

        abort_if(
            $receivedSignature !== $generatedSignature,
            Response::HTTP_UNAUTHORIZED,
            'Invalid Signature Provided'
        );
    }
}

CircleCINotificationHelper 只有一个名为handle 的公共方法,它接收一个Request 对象。

在解析请求的内容之前,首先使用validate 函数进行验证检查。这确保只有来自CircleCI的请求被处理。为了验证请求,circleci-signature 头与请求正文的HMAC-SHA256摘要进行比较,使用配置的签署秘密作为秘密密钥。如果数值不匹配,该过程将被终止,并返回一个401 响应。

如果请求签名检查出来,handle 方法检索WebhookNotification 模型的值,创建一个,并将其保存到数据库。

为CircleCIController添加功能

有了模型和助手,我们可以在CircleCIController中为先前定义的函数添加功能。打开app/Http/Controllers/CircleCIController.php ,并更新它以与之匹配。

<?php

namespace App\Http\Controllers;

use App\Helpers\CircleCINotificationHelper;
use App\Models\WebhookNotification;
use Illuminate\Http\{JsonResponse, Request, Response};

class CircleCIController extends Controller {

    public function getAllNotifications()
    : JsonResponse {

        return response()->json(WebhookNotification::all());
    }

    public function handleNotification(Request $request)
    : JsonResponse {

        CircleCINotificationHelper::handle($request);

        return response()
            ->json(null, Response::HTTP_NO_CONTENT);
    }

}

现在这些改动已经完成,你可以通过提交和推送你的改动到GitHub仓库来触发一个新事件。

git add .

git commit -m 'Implement Webhook for CircleCI'

git push origin main

这就触发了一个构建过程。当这个过程完成后,会向我们指定的ngrok公共URL发送一个请求。使用ngrok创建的隧道,应用程序接收请求并将通知保存到数据库中。你可以在你的ngrok终端查看HTTP Requests 部分。

ngrok build process

总结

在本教程中,我们设置了一个Laravel API来与CircleCI webhook进行通信。虽然我们只是将数据保存在数据库中,用于未来的可视化/分析,但webhooks为许多依赖实时数据的操作打开了大门,如事件检测和响应/管理。与你的团队分享这个教程,用你自己的项目来扩展你所学到的东西。