GitLab Webhook設計
概要
Issue Outsource PlatformがGitLab (gitlab.ethan-tech.jp) から受信するWebhookイベントの設計。 Webhookを通じてGitLab上の変更をリアルタイムにPF側へ反映する。
受信するWebhookイベント一覧
| イベント種別 | GitLabイベント名 | トリガー条件 | PF側の処理 |
|---|---|---|---|
| Issue | issue | Issue作成・更新・クローズ・再オープン | PF側Issue同期、ステータス更新 |
| Merge Request | merge_request | MR作成・更新・マージ・クローズ | レビューステータス反映、納品判定 |
| Note (コメント) | note | Issue/MRへのコメント追加 | コメント同期、レビューフィードバック取得 |
| Pipeline | pipeline | CI/CDパイプライン完了・失敗 | テスト結果取得、品質チェック反映 |
Webhookエンドポイント
POST /api/v1/webhooks/gitlabリクエストヘッダ
| ヘッダ名 | 説明 |
|---|---|
X-Gitlab-Event | イベント種別(例: Issue Hook, Merge Request Hook) |
X-Gitlab-Token | シークレットトークン(署名検証用) |
X-Gitlab-Event-UUID | イベントの一意識別子(冪等性に使用) |
Content-Type | application/json |
シークレットトークンによる署名検証
検証フロー
1. GitLabプロジェクト設定でWebhookシークレットトークンを登録
2. GitLabがWebhook送信時に `X-Gitlab-Token` ヘッダにトークンを付与
3. PF側で受信時にヘッダ値とサーバー保存値を比較
4. 不一致の場合は 403 Forbidden を返却しリクエストを破棄実装方針
python
import hmac
from fastapi import Request, HTTPException
WEBHOOK_SECRET = os.environ["GITLAB_WEBHOOK_SECRET"] # Keychainから取得
async def verify_gitlab_webhook(request: Request):
token = request.headers.get("X-Gitlab-Token")
if not token or not hmac.compare_digest(token, WEBHOOK_SECRET):
raise HTTPException(status_code=403, detail="Invalid webhook token")冪等性の担保(イベントID重複排除)
方針
GitLabは同一イベントを再送する可能性がある(ネットワーク障害時のリトライ等)。 X-Gitlab-Event-UUID を使い、処理済みイベントの重複実行を防ぐ。
実装方針
1. Webhook受信時に X-Gitlab-Event-UUID を取得
2. Redis (TTL: 24時間) または DBテーブルで処理済みUUIDを管理
3. 既に処理済みの場合は 200 OK を返却(再処理しない)
4. 未処理の場合はUUIDを記録し、イベント処理を実行データ構造
sql
CREATE TABLE webhook_events (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
gitlab_event_uuid VARCHAR(255) UNIQUE NOT NULL,
event_type VARCHAR(100) NOT NULL,
payload JSONB NOT NULL,
status VARCHAR(20) DEFAULT 'received', -- received, processing, completed, failed
created_at TIMESTAMP DEFAULT NOW(),
processed_at TIMESTAMP
);
CREATE INDEX idx_webhook_events_uuid ON webhook_events(gitlab_event_uuid);
CREATE INDEX idx_webhook_events_status ON webhook_events(status);各イベントの処理フロー
Issue Hook
mermaid
sequenceDiagram
participant GL as GitLab
participant WH as Webhook Handler
participant DB as Database
participant Sync as SyncService
GL->>WH: POST /webhooks/gitlab (Issue Hook)
WH->>WH: 署名検証 (X-Gitlab-Token)
WH->>DB: 冪等性チェック (X-Gitlab-Event-UUID)
alt 処理済み
WH-->>GL: 200 OK (skip)
else 未処理
WH->>DB: イベント記録 (status: received)
WH-->>GL: 200 OK (accepted)
WH->>Sync: 非同期処理キュー投入
Sync->>DB: GitLab Issueデータ取得
alt action: open
Sync->>DB: PF側Issue作成 or 更新
else action: update
Sync->>DB: タイトル・説明・ラベル同期
else action: close
Sync->>DB: PF側Issueステータス → closed
Sync->>DB: アサイン状態チェック・報酬処理トリガー
else action: reopen
Sync->>DB: PF側Issueステータス → open
end
Sync->>DB: イベントstatus → completed
endMerge Request Hook
mermaid
sequenceDiagram
participant GL as GitLab
participant WH as Webhook Handler
participant DB as Database
participant Review as ReviewService
participant Reward as RewardService
GL->>WH: POST /webhooks/gitlab (MR Hook)
WH->>WH: 署名検証
WH->>DB: 冪等性チェック
WH-->>GL: 200 OK
WH->>Review: 非同期処理
alt action: open
Review->>DB: レビューレコード作成 (status: pending)
Review->>DB: 対応Issue紐付け
else action: update
Review->>DB: MR情報更新(コミット、差分)
else action: merge
Review->>DB: レビューstatus → approved
Review->>Reward: 報酬確定トリガー
Reward->>DB: 報酬レコード作成 (status: confirmed)
Reward->>DB: Issue status → completed
else action: close (マージなし)
Review->>DB: レビューstatus → rejected
Review->>DB: Issue status → open (再アサイン可能)
endNote Hook (コメント)
mermaid
sequenceDiagram
participant GL as GitLab
participant WH as Webhook Handler
participant DB as Database
participant Notify as NotificationService
GL->>WH: POST /webhooks/gitlab (Note Hook)
WH->>WH: 署名検証・冪等性チェック
WH-->>GL: 200 OK
alt noteable_type: Issue
WH->>DB: Issueコメント保存
WH->>Notify: 担当エンジニアへ通知
else noteable_type: MergeRequest
WH->>DB: レビューコメント保存
alt レビューア承認コメント
WH->>DB: レビューstatus更新
end
WH->>Notify: MR関係者へ通知
endPipeline Hook
mermaid
sequenceDiagram
participant GL as GitLab
participant WH as Webhook Handler
participant DB as Database
participant Quality as QualityService
GL->>WH: POST /webhooks/gitlab (Pipeline Hook)
WH->>WH: 署名検証・冪等性チェック
WH-->>GL: 200 OK
WH->>DB: パイプライン結果保存
alt status: success
WH->>Quality: テスト結果OK記録
WH->>DB: MR品質チェック → passed
else status: failed
WH->>Quality: 失敗内容記録
WH->>DB: MR品質チェック → failed
WH->>DB: 担当エンジニアへ通知
endWebhookペイロード例
Issue Hook ペイロード(抜粋)
json
{
"object_kind": "issue",
"event_type": "issue",
"user": {
"id": 1,
"username": "engineer1"
},
"project": {
"id": 78,
"name": "issueoutsourcing",
"web_url": "https://gitlab.ethan-tech.jp/aieo/issueoutsourcing"
},
"object_attributes": {
"id": 100,
"iid": 5,
"title": "GitLab連携API設計",
"description": "...",
"state": "opened",
"action": "open",
"labels": [
{ "title": "reward:10000" },
{ "title": "difficulty:medium" }
],
"assignee_ids": [2]
}
}Merge Request Hook ペイロード(抜粋)
json
{
"object_kind": "merge_request",
"event_type": "merge_request",
"user": {
"id": 2,
"username": "engineer1"
},
"project": {
"id": 78
},
"object_attributes": {
"id": 200,
"iid": 10,
"title": "feat: Issue #5 GitLab連携API実装",
"state": "merged",
"action": "merge",
"source_branch": "feature/issue-5",
"target_branch": "main",
"merge_status": "can_be_merged"
}
}エラーハンドリング
| 状況 | レスポンス | 備考 |
|---|---|---|
| 署名検証失敗 | 403 Forbidden | ログに警告記録 |
| 不明なイベント種別 | 200 OK | 無視(ログに記録) |
| ペイロードパースエラー | 400 Bad Request | エラーログ記録 |
| 内部処理エラー | 200 OK + キュー投入 | 非同期リトライで対応 |
| 重複イベント | 200 OK | 処理スキップ |
リトライ戦略
- Webhook受信は即座に 200 OK を返却(GitLab側リトライを防止)
- 実処理は非同期キュー(Celery / asyncio task)で実行
- 失敗時は指数バックオフでリトライ(最大5回)
- 1回目: 10秒後
- 2回目: 30秒後
- 3回目: 90秒後
- 4回目: 270秒後
- 5回目: 810秒後
- 5回失敗後は status: failed としてアラート通知GitLabプロジェクト設定
Webhook登録
GitLab Project > Settings > Webhooks
URL: https://<PF_DOMAIN>/api/v1/webhooks/gitlab
Secret Token: <GITLAB_WEBHOOK_SECRET>
Trigger:
✅ Push events (無効)
✅ Issues events
✅ Merge request events
✅ Note events
✅ Pipeline events
SSL verification: ✅ Enable