InfoGrab DocsInfoGrab Docs

웹훅 개발자 가이드

요약

이 페이지는 GitLab 웹훅에 대한 개발자 가이드입니다. 웹훅은 GitLab에서 발생한 이벤트 또는 변경 사항에 대한 JSON 데이터를 웹훅 수신자에게 POST합니다. 다음은 웹훅이 트리거되고 실행될 때 발생하는 일에 대한 상위 수준의 설명입니다.

이 페이지는 GitLab 웹훅에 대한 개발자 가이드입니다.

웹훅은 GitLab에서 발생한 이벤트 또는 변경 사항에 대한 JSON 데이터를 웹훅 수신자에게 POST합니다. 웹훅을 사용하면 API를 폴링할 필요 없이 특정 변경 사항이 발생했을 때 고객에게 알림을 보낼 수 있습니다.

웹훅 흐름#

다음은 웹훅이 트리거되고 실행될 때 발생하는 일에 대한 상위 수준의 설명입니다.

sequenceDiagram Web or API node->>+Database: Fetch data for payload Database-->>-Web or API node: Build payload Note over Web or API node,Database: Webhook triggered Web or API node->>Sidekiq: Queue webhook execution Sidekiq->>+Remote webhook receiver: POST webhook payload Remote webhook receiver-)-Database: Save response in WebHookLog Note over Database,Remote webhook receiver: Webhook executed

새 웹훅 추가#

웹훅은 리소스 중심으로 설계되어 있습니다. 예를 들어, "emoji" 웹훅은 이모지가 부여되거나 취소될 때마다 트리거됩니다.

리소스에 대한 웹훅 지원을 추가하려면:

web_hooks 테이블에 새 칼럼을 추가합니다. 새 칼럼은 다음 조건을 만족해야 합니다:

Boolean 타입이어야 함

  • Not null

  • <resource>_events 형식으로 이름을 지정

  • 기본값을 false로 설정.

마이그레이션에서 #change 메서드의 예시:

def change
  add_column :web_hooks, :emoji_events, :boolean, null: false, default: false
end

TriggerableHooks.available_triggers에 새 웹훅 지원을 추가합니다.

웹훅을 프로젝트, 그룹 또는 GitLab 인스턴스에 대해 구성할 수 있어야 하는지에 따라 ProjectHook, GroupHook, 또는 SystemHooktriggerable_hooks 목록에 추가합니다. 지침은 프로젝트, 그룹 및 시스템 훅을 참조하세요.

app/views/shared/web_hooks/_form.html.haml의 웹훅 설정 양식에서 새 체크박스에 대한 프론트엔드 지원을 추가합니다.

TestHooks::ProjectServiceTestHooks::SystemService에서 새 웹훅 테스트 지원을 추가합니다. TestHooks::GroupService는 업데이트할 필요가 없습니다. ProjectService만 실행하기 때문입니다.

웹훅 페이로드를 정의합니다.

GitLab이 웹훅을 트리거하도록 업데이트합니다.

웹훅 문서를 추가합니다.

REST API 지원을 추가합니다:

API::ProjectHooks, API::GroupHooks, API::SystemHooks를 업데이트하여 해당 인수를 지원합니다.

  • API::Entities::ProjectHookAPI::Entities::GroupHook을 업데이트하여 새 필드를 지원합니다. (시스템 훅은 일반적인 API::Entities::Hook을 사용합니다.)

  • 프로젝트 웹훅, 그룹 웹훅, 시스템 훅에 대한 API 문서를 업데이트합니다.

결정: 프로젝트, 그룹 및 시스템 웹훅#

웹훅을 프로젝트, 그룹 또는 GitLab 인스턴스에 대해 구성할 수 있어야 하는지 결정하는 데 다음 내용을 참고하세요.

  • 해당 수준에 속하는 리소스와 관련된 웹훅은 해당 수준에서 구성할 수 있어야 합니다. 예시: 이슈 웹훅은 프로젝트에 대해 구성할 수 있고, 그룹 멤버십 웹훅은 그룹에 대해 구성할 수 있으며, 사용자 로그인 실패 웹훅은 GitLab 인스턴스에 대해 구성할 수 있습니다.

  • 프로젝트에 대해 구성할 수 있는 웹훅은 일반적으로 그룹에 대해서도 구성할 수 있어야 합니다. 그룹 웹훅은 그룹 Owner가 해당 그룹의 프로젝트에서 발생하는 모든 이벤트를 수신하도록 구성하는 경우가 많으며, 프로젝트 웹훅이 트리거될 때 그룹 웹훅도 자동으로 실행되기 때문입니다.

  • 일반적으로 프로젝트 또는 그룹(또는 둘 다)에 대해 구성할 수 있는 웹훅은 인스턴스 관리자가 이를 수신해야 한다는 명확한 기능 요청이 있을 경우에만 인스턴스에서 구성할 수 있어야 합니다. 현재 많은 프로젝트 및 그룹 웹훅은 인스턴스 수준에서 구성할 수 없습니다.

EE 전용 고려 사항#

그룹 웹훅은 Premium 라이선스 기능입니다. 그룹 웹훅을 트리거하거나, 그룹에 대해서만 구성 가능한 웹훅의 페이로드를 빌드하는 것과 관련된 모든 코드는 ee/ 디렉터리에 있어야 합니다.

웹훅 트리거#

프로젝트 및 그룹 웹훅 트리거#

프로젝트 및 그룹 웹훅은 프로젝트 또는 그룹에서 #execute_hooks를 호출하여 트리거됩니다.

#execute_hooks 메서드에는 다음이 전달됩니다:

예시:

project.execute_hooks(payload, :emoji_hooks)

단일 프로젝트 또는 그룹에서 #execute_hooks가 호출되면, 트리거는 자동으로 상위 그룹으로 전파되어 상위 그룹도 실행됩니다. 이를 통해 그룹은 하위 그룹 또는 프로젝트에서 발생하는 이벤트에 대한 웹훅을 수신하도록 구성할 수 있습니다.

메서드가 다음에서 호출될 때:

  • 프로젝트: 해당 프로젝트에 대해 구성된 해당 유형의 웹훅이 실행되는 것 외에도, 프로젝트의 그룹 및 상위 그룹에 대해 구성된 해당 유형의 웹훅도 실행됩니다. 해당 유형에 대해 구성된 인스턴스(시스템) 웹훅도 실행됩니다.

  • 그룹: 해당 그룹에 대해 구성된 해당 유형의 웹훅이 실행되는 것 외에도, 그룹의 상위 그룹에 대해 구성된 웹훅도 실행됩니다.

페이로드를 빌드하면 일반적으로 데이터베이스에서 더 많은 레코드를 로드해야 하므로 비용이 많이 들 수 있습니다. 따라서 웹훅을 트리거하기 전에 프로젝트에서 #has_active_hooks?를 확인하세요. (그룹에 대한 유사한 메서드 지원은 이슈 #517890에서 추적 중입니다.)

이 메서드는 다음 중 하나에 해당하면 true를 반환합니다:

  • 프로젝트 또는 그룹, 또는 상위 그룹에 주어진 유형의 웹훅이 구성되어 있어 하나 이상의 웹훅이 실행되어야 하는 경우.

  • 프로젝트에서 호출될 때 주어진 유형에 대해 시스템 웹훅이 구성된 경우.

예시:

def execute_emoji_hooks
  return unless project.has_active_hooks?(:emoji_hooks)

  payload = Gitlab::DataBuilder::Emoji.build(emoji)
  project.execute_hooks(payload, :emoji_hooks)
end

인스턴스(시스템) 웹훅 트리거#

프로젝트에 대한 웹훅이 트리거되면, 해당 웹훅 유형에 대해 구성된 시스템 웹훅도 자동으로 실행됩니다.

웹훅이 프로젝트에 대해 구성할 수 없는 경우에는 SystemHooksService를 통해 시스템 훅을 트리거할 수도 있습니다.

예시:

SystemHooksService.new.execute_hooks_for(user, :create)

리소스에 대한 데이터를 빌드하도록 SystemHooksService를 업데이트해야 합니다.

정확한 페이로드로 트리거#

웹훅 페이로드는 이벤트 발생 시점의 데이터 상태를 정확하게 나타내야 합니다. 경쟁 조건(race condition)이나 데이터 상태를 변경하는 동시 프로세스로 인해 발생하는 문제를 방지하는 데 주의해야 합니다. 그렇지 않으면 웹훅 수신자에게 부정확한 페이로드가 전송될 수 있습니다.

이를 위한 몇 가지 팁:

  • 페이로드를 빌드하기 전에 객체를 다시 로드하지 않도록 하세요. 다른 프로세스가 객체의 상태를 변경했을 수 있습니다.

  • 이벤트 발생 직후 페이로드를 빌드하세요. 그러면 페이로드를 위해 로드되는 추가 데이터도 이벤트 시점의 상태를 반영합니다.

이 두 가지 사항 모두 페이로드를 일반적으로 Sidekiq를 사용하는 비동기 방식이 아니라 요청 내에서 빌드해야 함을 의미합니다.

페이로드가 항상 불변 데이터만 포함하는 경우라면 예외가 될 수 있지만, 일반적으로 그렇지 않습니다.

웹훅 페이로드#

웹훅 페이로드는 웹훅 수신자에게 POST되는 JSON 데이터입니다.

기존 웹훅 페이로드는 웹훅 이벤트 문서에서 확인하세요.

웹훅 페이로드에 포함하지 않아야 할 것은?#

민감한 데이터는 웹훅 페이로드에 절대 포함해서는 안 됩니다. 여기에는 시크릿과 비공개 사용자 이메일이 포함됩니다. (비공개 사용자 이메일은 User#hook_attrs를 통해 자동으로 삭제됩니다.)

웹훅 페이로드를 빌드하는 것은 매우 효율적이어야 하므로, 웹훅 페이로드에 추가되는 모든 새로운 속성은 데이터베이스에서 검색하는 오버헤드에 비해 정당성이 있어야 합니다.

더 작은 웹훅을 수신한 후 소수의 고객이 API에서 객체에 대한 일부 데이터를 가져와야 하는 것과, 모든 고객이 웹훅 페이로드에서 해당 데이터를 수신하는 것 중 어느 것이 더 나은지 균형 있게 고려하세요. 이 시나리오에서는 웹훅이 빌드되는 시점과 고객이 추가 데이터를 검색하는 시점 사이에 시간 차이가 있습니다. 이 지연으로 인해 API 데이터와 웹훅 데이터가 서로 다른 시점의 상태를 나타낼 수 있습니다.

페이로드 정의#

객체는 웹훅 페이로드에 대한 객체 속성을 반환하기 위해 #hook_attrs 메서드를 정의해야 합니다.

#hook_attrs의 속성은 정적 키로 정의되어야 합니다. 이 메서드는 #attributes 또는 #as_json이 반환하는 속성이 아니라 특정 속성 집합을 반환해야 합니다. 그렇지 않으면 모델의 모든 미래 속성이 웹훅 페이로드에 포함됩니다 (이슈 440384 참조).

Gitlab::DataBuilder:: 내의 모듈 또는 클래스가 전체 페이로드를 구성해야 합니다. 전체 페이로드는 일반적으로 연관된 객체를 포함합니다.

전체 페이로드의 구조는 페이로드 스키마를 참조하세요.

예시:

# An object defines #hook_attrs:
class Car < ApplicationRecord
  def hook_attrs
    {
      make: make,
      color: color
    }
  end
end

# A Gitlab::DataBuilder module or class composes the full webhook payload:
module Gitlab
  module DataBuilder
    module Car
      extend self

      def build(car, action)
        {
          object_kind: 'car',
          action: action,
          object_attributes: car.hook_attrs,
          driver: car.driver.hook_attrs # Calling #hook_attrs on associated data
        }
      end
    end
  end
end

# Building the payload:
Gitlab::DataBuilder::Car.build(car, 'start')

페이로드 스키마#

역사적으로 다양한 유형의 웹훅 간에 페이로드 스키마에 많은 불일치가 있었습니다.

앞으로는 일관성을 위해 새 유형의 웹훅 페이로드가 기존 페이로드와 유사해야 하는 경우(예: 새로운 issuable에 대한 웹훅)를 제외하고, 새 웹훅의 스키마는 다음 규칙을 따라야 합니다:

  • 새 웹훅의 스키마에는 다음 필수 속성이 있어야 합니다:

"object_kind", 스네이크 케이스(snake case)로 표현한 객체의 종류. 예시: "merge_request".

  • "action", 현재 시제를 사용하는 발생한 일에 대한 도메인별 동사. 예시: "create", "assign", "update" 또는 "revoke". 이는 웹훅이 객체의 수명 주기의 다른 시점에서 트리거될 때 발생하는 다양한 종류의 변경 사항을 수신자가 식별하고 처리하는 데 도움이 됩니다.

  • "object_attributes", 이벤트 후 객체의 속성을 포함합니다. 이러한 속성은 #hook_attrs에서 생성됩니다.

  • 연관된 데이터는 "object_attributes" 안에 중첩되지 않고 페이로드에서 최상위 수준이어야 합니다.

  • 페이로드에 변경된 속성 값의 기록이 포함되는 경우, 이는 최상위 "changes" 객체에 있어야 합니다.

위 내용에 대한 JSON 스키마 설명:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "description": "Recommended GitLab webhook payload schema",
  "type": "object",
  "properties": {
    "object_kind": {
      "type": "string",
      "description": "Kind of object in snake case. Example: merge_request",
      "pattern": "^([a-zA-Z]+(_[a-zA-Z]+)*)$"
    },
    "action": {
      "type": "string",
      "description": "A domain-specific verb of what just happened to the object, using present tense. Examples: create, revoke",
    },
    "object_attributes": {
      "type": "object",
      "description": "Attributes of the object after the event"
    },
    "changes": {
      "type": "object",
      "description": "Optional object attributes that were changed during the event",
      "patternProperties": {
        ".+" : {
          "type" : "object",
          "properties": {
            "previous": {
              "description": "Value of attribute before the event"
            },
            "current": {
              "description": "Value of attribute after the event"
            }
          },
          "required": ["previous", "current"]
        }
      }
    }
  },
  "required": ["object_kind", "action", "object_attributes"]
}

위의 페이로드 스키마를 따르는 가상의 Car 객체에 대한 웹훅 페이로드 예시:

{
  "object_kind": "car",
  "action": "start",
  "object_attributes": {
    "make": "Toyota",
    "color": "grey"
  },
  "driver": {
    "name": "Kaya",
    "age": 18
  }
}

변경 객체 포함#

페이로드에 객체의 속성 변경 목록을 포함해야 하는 경우, ReportableChanges 모듈을 모델에 추가하세요. 이 모듈은 객체가 로드된 시점부터 이후의 모든 저장(save)에 이르기까지 속성 값에 대한 모든 변경 사항을 수집합니다. 이는 주어진 요청 컨텍스트에서 객체에 대해 여러 저장 작업이 있고 최종 훅이 가장 최근 저장의 델타가 아닌 누적 델타에 접근해야 하는 경우 유용할 수 있습니다.

페이로드에 속성 변경 사항을 포함하는 방법은 페이로드 스키마를 참조하세요.

데이터베이스 요청 최소화#

일부 유형의 웹훅은 GitLab.com에서 하루에 수백만 번 트리거됩니다.

웹훅 페이로드에 대한 추가 데이터 로드는 요청 내에서 페이로드를 빌드해야 하며 Sidekiq에서 빌드하지 않으므로 성능이 우수해야 합니다. GitLab.com에서는 웹훅이 데이터베이스 쓰기 후 트리거되므로 페이로드에 대한 추가 데이터도 PostgreSQL 프라이머리에서 로드됩니다.

웹훅 페이로드를 빌드할 때 데이터 요청을 최소화하려면:

  • 웹훅 페이로드에 추가 데이터를 추가하는 것의 중요성을 따져보세요.

  • N+1 문제를 방지하기 위해 추가 데이터를 미리 로드하세요.

  • 회귀를 방지하기 위해 웹훅 페이로드를 빌드하기 위한 데이터베이스 호출 수를 테스트에서 검증하세요.

이미 로드된 레코드에 데이터를 미리 로드해야 할 수도 있습니다. 이 경우 ActiveRecord::Associations::Preloader를 사용할 수 있습니다.

연관 데이터가 웹훅 페이로드를 빌드하는 데만 필요한 경우, #has_active_hooks? 확인이 통과된 후에만 해당 연관 데이터를 미리 로드하세요.

코드베이스에서 이에 대한 좋은 작업 예시는 Gitlab::DataBuilder::Pipeline입니다.

예시:

# Working with an issue that has been loaded
issue = Issue.first

# Imagine we have performed the #has_active_hooks? check and now are building the webhook payload.
# Doing this will perform N+1 database queries:
# issue.notes.map(&:author).map(&:name)
#
# Instead, first preload the associations to avoid the N+1
ActiveRecord::Associations::Preloader.new(records: [issue], associations: { notes: :author }).call;
issue.notes.map(&:author).map(&:name)

호환성을 깨는 변경#

웹훅 페이로드에 호환성을 깨는 변경(breaking change)을 할 수 없습니다.

웹훅 수신자가 웹훅 페이로드의 변경으로 인해 오류가 발생할 수 있다면, 그 변경은 호환성을 깨는 것입니다.

새 속성이 추가되는 추가적(additive) 변경만 허용됩니다.

호환성을 깨는 변경에는 다음이 포함됩니다:

  • 속성 제거.

  • 속성 이름 변경.

  • "object_kind" 속성 값 변경.

  • "action" 속성 값 변경.

기능 제거 등의 이유로 "object_kind" 또는 "action" 이외의 속성 값이 변경되어야 하는 경우, 속성을 제거하는 대신 값을 null, {}, 또는 []로 설정하세요.

테스트#

DataBuilder 클래스에 대한 단위 테스트를 작성할 때는 다음을 검증하세요:

  • QueryRecorder를 사용하여 특정 수의 데이터베이스 요청이 이루어지는지 확인합니다. QueryRecorder를 사용하여 쿼리 수를 측정한 다음 스펙에서 해당 수와 비교하여 의식적인 선택 없이 쿼리 수가 변경되지 않도록 합니다. 연관 데이터의 미리 로드도 참조하세요.

  • 페이로드에 예상 속성이 있는지 확인합니다.

또한 웹훅이 트리거되어야 하는(또는 트리거되어서는 안 되는) 시나리오를 테스트하여 올바르게 트리거되는지 확인하세요.

QA로 변경 사항 확인#

웹훅 URL을 https://webhook.site에서 제공하는 URL로 구성하면 웹훅이 트리거될 때 생성되는 전체 웹훅 헤더와 페이로드를 확인할 수 있습니다.

변경 사항 검토 요청#

코드 리뷰를 위한 일반적인 검토자 외에도, 웹훅에 대한 변경 사항은 Import & Integrate의 백엔드 팀원이 검토해야 합니다.

웹훅 개발자 가이드

GitLab v19.1
원문 보기
요약

이 페이지는 GitLab 웹훅에 대한 개발자 가이드입니다. 웹훅은 GitLab에서 발생한 이벤트 또는 변경 사항에 대한 JSON 데이터를 웹훅 수신자에게 POST합니다. 다음은 웹훅이 트리거되고 실행될 때 발생하는 일에 대한 상위 수준의 설명입니다.

이 페이지는 GitLab 웹훅에 대한 개발자 가이드입니다.

웹훅은 GitLab에서 발생한 이벤트 또는 변경 사항에 대한 JSON 데이터를 웹훅 수신자에게 POST합니다. 웹훅을 사용하면 API를 폴링할 필요 없이 특정 변경 사항이 발생했을 때 고객에게 알림을 보낼 수 있습니다.

웹훅 흐름#

다음은 웹훅이 트리거되고 실행될 때 발생하는 일에 대한 상위 수준의 설명입니다.

sequenceDiagram Web or API node->>+Database: Fetch data for payload Database-->>-Web or API node: Build payload Note over Web or API node,Database: Webhook triggered Web or API node->>Sidekiq: Queue webhook execution Sidekiq->>+Remote webhook receiver: POST webhook payload Remote webhook receiver-)-Database: Save response in WebHookLog Note over Database,Remote webhook receiver: Webhook executed

새 웹훅 추가#

웹훅은 리소스 중심으로 설계되어 있습니다. 예를 들어, "emoji" 웹훅은 이모지가 부여되거나 취소될 때마다 트리거됩니다.

리소스에 대한 웹훅 지원을 추가하려면:

web_hooks 테이블에 새 칼럼을 추가합니다. 새 칼럼은 다음 조건을 만족해야 합니다:

Boolean 타입이어야 함

  • Not null

  • <resource>_events 형식으로 이름을 지정

  • 기본값을 false로 설정.

마이그레이션에서 #change 메서드의 예시:

def change
  add_column :web_hooks, :emoji_events, :boolean, null: false, default: false
end

TriggerableHooks.available_triggers에 새 웹훅 지원을 추가합니다.

웹훅을 프로젝트, 그룹 또는 GitLab 인스턴스에 대해 구성할 수 있어야 하는지에 따라 ProjectHook, GroupHook, 또는 SystemHooktriggerable_hooks 목록에 추가합니다. 지침은 프로젝트, 그룹 및 시스템 훅을 참조하세요.

app/views/shared/web_hooks/_form.html.haml의 웹훅 설정 양식에서 새 체크박스에 대한 프론트엔드 지원을 추가합니다.

TestHooks::ProjectServiceTestHooks::SystemService에서 새 웹훅 테스트 지원을 추가합니다. TestHooks::GroupService는 업데이트할 필요가 없습니다. ProjectService만 실행하기 때문입니다.

웹훅 페이로드를 정의합니다.

GitLab이 웹훅을 트리거하도록 업데이트합니다.

웹훅 문서를 추가합니다.

REST API 지원을 추가합니다:

API::ProjectHooks, API::GroupHooks, API::SystemHooks를 업데이트하여 해당 인수를 지원합니다.

  • API::Entities::ProjectHookAPI::Entities::GroupHook을 업데이트하여 새 필드를 지원합니다. (시스템 훅은 일반적인 API::Entities::Hook을 사용합니다.)

  • 프로젝트 웹훅, 그룹 웹훅, 시스템 훅에 대한 API 문서를 업데이트합니다.

결정: 프로젝트, 그룹 및 시스템 웹훅#

웹훅을 프로젝트, 그룹 또는 GitLab 인스턴스에 대해 구성할 수 있어야 하는지 결정하는 데 다음 내용을 참고하세요.

  • 해당 수준에 속하는 리소스와 관련된 웹훅은 해당 수준에서 구성할 수 있어야 합니다. 예시: 이슈 웹훅은 프로젝트에 대해 구성할 수 있고, 그룹 멤버십 웹훅은 그룹에 대해 구성할 수 있으며, 사용자 로그인 실패 웹훅은 GitLab 인스턴스에 대해 구성할 수 있습니다.

  • 프로젝트에 대해 구성할 수 있는 웹훅은 일반적으로 그룹에 대해서도 구성할 수 있어야 합니다. 그룹 웹훅은 그룹 Owner가 해당 그룹의 프로젝트에서 발생하는 모든 이벤트를 수신하도록 구성하는 경우가 많으며, 프로젝트 웹훅이 트리거될 때 그룹 웹훅도 자동으로 실행되기 때문입니다.

  • 일반적으로 프로젝트 또는 그룹(또는 둘 다)에 대해 구성할 수 있는 웹훅은 인스턴스 관리자가 이를 수신해야 한다는 명확한 기능 요청이 있을 경우에만 인스턴스에서 구성할 수 있어야 합니다. 현재 많은 프로젝트 및 그룹 웹훅은 인스턴스 수준에서 구성할 수 없습니다.

EE 전용 고려 사항#

그룹 웹훅은 Premium 라이선스 기능입니다. 그룹 웹훅을 트리거하거나, 그룹에 대해서만 구성 가능한 웹훅의 페이로드를 빌드하는 것과 관련된 모든 코드는 ee/ 디렉터리에 있어야 합니다.

웹훅 트리거#

프로젝트 및 그룹 웹훅 트리거#

프로젝트 및 그룹 웹훅은 프로젝트 또는 그룹에서 #execute_hooks를 호출하여 트리거됩니다.

#execute_hooks 메서드에는 다음이 전달됩니다:

예시:

project.execute_hooks(payload, :emoji_hooks)

단일 프로젝트 또는 그룹에서 #execute_hooks가 호출되면, 트리거는 자동으로 상위 그룹으로 전파되어 상위 그룹도 실행됩니다. 이를 통해 그룹은 하위 그룹 또는 프로젝트에서 발생하는 이벤트에 대한 웹훅을 수신하도록 구성할 수 있습니다.

메서드가 다음에서 호출될 때:

  • 프로젝트: 해당 프로젝트에 대해 구성된 해당 유형의 웹훅이 실행되는 것 외에도, 프로젝트의 그룹 및 상위 그룹에 대해 구성된 해당 유형의 웹훅도 실행됩니다. 해당 유형에 대해 구성된 인스턴스(시스템) 웹훅도 실행됩니다.

  • 그룹: 해당 그룹에 대해 구성된 해당 유형의 웹훅이 실행되는 것 외에도, 그룹의 상위 그룹에 대해 구성된 웹훅도 실행됩니다.

페이로드를 빌드하면 일반적으로 데이터베이스에서 더 많은 레코드를 로드해야 하므로 비용이 많이 들 수 있습니다. 따라서 웹훅을 트리거하기 전에 프로젝트에서 #has_active_hooks?를 확인하세요. (그룹에 대한 유사한 메서드 지원은 이슈 #517890에서 추적 중입니다.)

이 메서드는 다음 중 하나에 해당하면 true를 반환합니다:

  • 프로젝트 또는 그룹, 또는 상위 그룹에 주어진 유형의 웹훅이 구성되어 있어 하나 이상의 웹훅이 실행되어야 하는 경우.

  • 프로젝트에서 호출될 때 주어진 유형에 대해 시스템 웹훅이 구성된 경우.

예시:

def execute_emoji_hooks
  return unless project.has_active_hooks?(:emoji_hooks)

  payload = Gitlab::DataBuilder::Emoji.build(emoji)
  project.execute_hooks(payload, :emoji_hooks)
end

인스턴스(시스템) 웹훅 트리거#

프로젝트에 대한 웹훅이 트리거되면, 해당 웹훅 유형에 대해 구성된 시스템 웹훅도 자동으로 실행됩니다.

웹훅이 프로젝트에 대해 구성할 수 없는 경우에는 SystemHooksService를 통해 시스템 훅을 트리거할 수도 있습니다.

예시:

SystemHooksService.new.execute_hooks_for(user, :create)

리소스에 대한 데이터를 빌드하도록 SystemHooksService를 업데이트해야 합니다.

정확한 페이로드로 트리거#

웹훅 페이로드는 이벤트 발생 시점의 데이터 상태를 정확하게 나타내야 합니다. 경쟁 조건(race condition)이나 데이터 상태를 변경하는 동시 프로세스로 인해 발생하는 문제를 방지하는 데 주의해야 합니다. 그렇지 않으면 웹훅 수신자에게 부정확한 페이로드가 전송될 수 있습니다.

이를 위한 몇 가지 팁:

  • 페이로드를 빌드하기 전에 객체를 다시 로드하지 않도록 하세요. 다른 프로세스가 객체의 상태를 변경했을 수 있습니다.

  • 이벤트 발생 직후 페이로드를 빌드하세요. 그러면 페이로드를 위해 로드되는 추가 데이터도 이벤트 시점의 상태를 반영합니다.

이 두 가지 사항 모두 페이로드를 일반적으로 Sidekiq를 사용하는 비동기 방식이 아니라 요청 내에서 빌드해야 함을 의미합니다.

페이로드가 항상 불변 데이터만 포함하는 경우라면 예외가 될 수 있지만, 일반적으로 그렇지 않습니다.

웹훅 페이로드#

웹훅 페이로드는 웹훅 수신자에게 POST되는 JSON 데이터입니다.

기존 웹훅 페이로드는 웹훅 이벤트 문서에서 확인하세요.

웹훅 페이로드에 포함하지 않아야 할 것은?#

민감한 데이터는 웹훅 페이로드에 절대 포함해서는 안 됩니다. 여기에는 시크릿과 비공개 사용자 이메일이 포함됩니다. (비공개 사용자 이메일은 User#hook_attrs를 통해 자동으로 삭제됩니다.)

웹훅 페이로드를 빌드하는 것은 매우 효율적이어야 하므로, 웹훅 페이로드에 추가되는 모든 새로운 속성은 데이터베이스에서 검색하는 오버헤드에 비해 정당성이 있어야 합니다.

더 작은 웹훅을 수신한 후 소수의 고객이 API에서 객체에 대한 일부 데이터를 가져와야 하는 것과, 모든 고객이 웹훅 페이로드에서 해당 데이터를 수신하는 것 중 어느 것이 더 나은지 균형 있게 고려하세요. 이 시나리오에서는 웹훅이 빌드되는 시점과 고객이 추가 데이터를 검색하는 시점 사이에 시간 차이가 있습니다. 이 지연으로 인해 API 데이터와 웹훅 데이터가 서로 다른 시점의 상태를 나타낼 수 있습니다.

페이로드 정의#

객체는 웹훅 페이로드에 대한 객체 속성을 반환하기 위해 #hook_attrs 메서드를 정의해야 합니다.

#hook_attrs의 속성은 정적 키로 정의되어야 합니다. 이 메서드는 #attributes 또는 #as_json이 반환하는 속성이 아니라 특정 속성 집합을 반환해야 합니다. 그렇지 않으면 모델의 모든 미래 속성이 웹훅 페이로드에 포함됩니다 (이슈 440384 참조).

Gitlab::DataBuilder:: 내의 모듈 또는 클래스가 전체 페이로드를 구성해야 합니다. 전체 페이로드는 일반적으로 연관된 객체를 포함합니다.

전체 페이로드의 구조는 페이로드 스키마를 참조하세요.

예시:

# An object defines #hook_attrs:
class Car < ApplicationRecord
  def hook_attrs
    {
      make: make,
      color: color
    }
  end
end

# A Gitlab::DataBuilder module or class composes the full webhook payload:
module Gitlab
  module DataBuilder
    module Car
      extend self

      def build(car, action)
        {
          object_kind: 'car',
          action: action,
          object_attributes: car.hook_attrs,
          driver: car.driver.hook_attrs # Calling #hook_attrs on associated data
        }
      end
    end
  end
end

# Building the payload:
Gitlab::DataBuilder::Car.build(car, 'start')

페이로드 스키마#

역사적으로 다양한 유형의 웹훅 간에 페이로드 스키마에 많은 불일치가 있었습니다.

앞으로는 일관성을 위해 새 유형의 웹훅 페이로드가 기존 페이로드와 유사해야 하는 경우(예: 새로운 issuable에 대한 웹훅)를 제외하고, 새 웹훅의 스키마는 다음 규칙을 따라야 합니다:

  • 새 웹훅의 스키마에는 다음 필수 속성이 있어야 합니다:

"object_kind", 스네이크 케이스(snake case)로 표현한 객체의 종류. 예시: "merge_request".

  • "action", 현재 시제를 사용하는 발생한 일에 대한 도메인별 동사. 예시: "create", "assign", "update" 또는 "revoke". 이는 웹훅이 객체의 수명 주기의 다른 시점에서 트리거될 때 발생하는 다양한 종류의 변경 사항을 수신자가 식별하고 처리하는 데 도움이 됩니다.

  • "object_attributes", 이벤트 후 객체의 속성을 포함합니다. 이러한 속성은 #hook_attrs에서 생성됩니다.

  • 연관된 데이터는 "object_attributes" 안에 중첩되지 않고 페이로드에서 최상위 수준이어야 합니다.

  • 페이로드에 변경된 속성 값의 기록이 포함되는 경우, 이는 최상위 "changes" 객체에 있어야 합니다.

위 내용에 대한 JSON 스키마 설명:

{
  "$schema": "http://json-schema.org/draft-07/schema#",
  "description": "Recommended GitLab webhook payload schema",
  "type": "object",
  "properties": {
    "object_kind": {
      "type": "string",
      "description": "Kind of object in snake case. Example: merge_request",
      "pattern": "^([a-zA-Z]+(_[a-zA-Z]+)*)$"
    },
    "action": {
      "type": "string",
      "description": "A domain-specific verb of what just happened to the object, using present tense. Examples: create, revoke",
    },
    "object_attributes": {
      "type": "object",
      "description": "Attributes of the object after the event"
    },
    "changes": {
      "type": "object",
      "description": "Optional object attributes that were changed during the event",
      "patternProperties": {
        ".+" : {
          "type" : "object",
          "properties": {
            "previous": {
              "description": "Value of attribute before the event"
            },
            "current": {
              "description": "Value of attribute after the event"
            }
          },
          "required": ["previous", "current"]
        }
      }
    }
  },
  "required": ["object_kind", "action", "object_attributes"]
}

위의 페이로드 스키마를 따르는 가상의 Car 객체에 대한 웹훅 페이로드 예시:

{
  "object_kind": "car",
  "action": "start",
  "object_attributes": {
    "make": "Toyota",
    "color": "grey"
  },
  "driver": {
    "name": "Kaya",
    "age": 18
  }
}

변경 객체 포함#

페이로드에 객체의 속성 변경 목록을 포함해야 하는 경우, ReportableChanges 모듈을 모델에 추가하세요. 이 모듈은 객체가 로드된 시점부터 이후의 모든 저장(save)에 이르기까지 속성 값에 대한 모든 변경 사항을 수집합니다. 이는 주어진 요청 컨텍스트에서 객체에 대해 여러 저장 작업이 있고 최종 훅이 가장 최근 저장의 델타가 아닌 누적 델타에 접근해야 하는 경우 유용할 수 있습니다.

페이로드에 속성 변경 사항을 포함하는 방법은 페이로드 스키마를 참조하세요.

데이터베이스 요청 최소화#

일부 유형의 웹훅은 GitLab.com에서 하루에 수백만 번 트리거됩니다.

웹훅 페이로드에 대한 추가 데이터 로드는 요청 내에서 페이로드를 빌드해야 하며 Sidekiq에서 빌드하지 않으므로 성능이 우수해야 합니다. GitLab.com에서는 웹훅이 데이터베이스 쓰기 후 트리거되므로 페이로드에 대한 추가 데이터도 PostgreSQL 프라이머리에서 로드됩니다.

웹훅 페이로드를 빌드할 때 데이터 요청을 최소화하려면:

  • 웹훅 페이로드에 추가 데이터를 추가하는 것의 중요성을 따져보세요.

  • N+1 문제를 방지하기 위해 추가 데이터를 미리 로드하세요.

  • 회귀를 방지하기 위해 웹훅 페이로드를 빌드하기 위한 데이터베이스 호출 수를 테스트에서 검증하세요.

이미 로드된 레코드에 데이터를 미리 로드해야 할 수도 있습니다. 이 경우 ActiveRecord::Associations::Preloader를 사용할 수 있습니다.

연관 데이터가 웹훅 페이로드를 빌드하는 데만 필요한 경우, #has_active_hooks? 확인이 통과된 후에만 해당 연관 데이터를 미리 로드하세요.

코드베이스에서 이에 대한 좋은 작업 예시는 Gitlab::DataBuilder::Pipeline입니다.

예시:

# Working with an issue that has been loaded
issue = Issue.first

# Imagine we have performed the #has_active_hooks? check and now are building the webhook payload.
# Doing this will perform N+1 database queries:
# issue.notes.map(&:author).map(&:name)
#
# Instead, first preload the associations to avoid the N+1
ActiveRecord::Associations::Preloader.new(records: [issue], associations: { notes: :author }).call;
issue.notes.map(&:author).map(&:name)

호환성을 깨는 변경#

웹훅 페이로드에 호환성을 깨는 변경(breaking change)을 할 수 없습니다.

웹훅 수신자가 웹훅 페이로드의 변경으로 인해 오류가 발생할 수 있다면, 그 변경은 호환성을 깨는 것입니다.

새 속성이 추가되는 추가적(additive) 변경만 허용됩니다.

호환성을 깨는 변경에는 다음이 포함됩니다:

  • 속성 제거.

  • 속성 이름 변경.

  • "object_kind" 속성 값 변경.

  • "action" 속성 값 변경.

기능 제거 등의 이유로 "object_kind" 또는 "action" 이외의 속성 값이 변경되어야 하는 경우, 속성을 제거하는 대신 값을 null, {}, 또는 []로 설정하세요.

테스트#

DataBuilder 클래스에 대한 단위 테스트를 작성할 때는 다음을 검증하세요:

  • QueryRecorder를 사용하여 특정 수의 데이터베이스 요청이 이루어지는지 확인합니다. QueryRecorder를 사용하여 쿼리 수를 측정한 다음 스펙에서 해당 수와 비교하여 의식적인 선택 없이 쿼리 수가 변경되지 않도록 합니다. 연관 데이터의 미리 로드도 참조하세요.

  • 페이로드에 예상 속성이 있는지 확인합니다.

또한 웹훅이 트리거되어야 하는(또는 트리거되어서는 안 되는) 시나리오를 테스트하여 올바르게 트리거되는지 확인하세요.

QA로 변경 사항 확인#

웹훅 URL을 https://webhook.site에서 제공하는 URL로 구성하면 웹훅이 트리거될 때 생성되는 전체 웹훅 헤더와 페이로드를 확인할 수 있습니다.

변경 사항 검토 요청#

코드 리뷰를 위한 일반적인 검토자 외에도, 웹훅에 대한 변경 사항은 Import & Integrate의 백엔드 팀원이 검토해야 합니다.