Help us learn about your current experience with the documentation. Take the survey.

集成开发指南

本页提供了实现 GitLab 集成 的开发指南, 这些集成是我们 主 Rails 项目 的一部分。

另请参阅我们的 方向页面,了解我们围绕集成的策略概述。

本指南仍在完善中。如果您需要澄清或发现任何过时信息,欢迎联系 @gitlab-org/foundations/import-and-integrate

添加新集成

定义集成

  1. app/models/integrations 中添加一个继承自 Integration 的新模型。

    • 例如,在 app/models/integrations/foo_bar.rb 中创建 Integrations::FooBar
    • 对于某些类型的集成,您可以包含这些基础模块:
      • Integrations::Base::ChatNotification
      • Integrations::Base::Ci
      • Integrations::Base::IssueTracker
      • Integrations::Base::Monitoring
      • Integrations::Base::SlashCommands
      • Integrations::Base::ThirdPartyWiki
    • 对于主要触发对外部服务 HTTP 调用的集成,您也可以使用 Integrations::HasWebHook concern。这通过关联的 ServiceHook 模型重用 GitLab 中的 webhook 功能,并自动记录请求日志,可在集成设置中查看。
  2. 将集成的下划线名称 ('foo_bar') 添加到 Integration::INTEGRATION_NAMES

  3. Project 上添加集成关联:

    has_one :foo_bar_integration, class_name: 'Integrations::FooBar'

定义字段

集成可以使用类方法 Integration.field 定义任意字段来存储其配置。这些值以加密的 JSON 哈希形式存储在 integrations.encrypted_properties 列中。

例如:

module Integrations
  class FooBar < Integration
    field :url
    field :tags
  end
end

Integration.field 会在类上安装访问器方法。在这里,我们将拥有 #url#url=#url_changed? 来管理 url 字段。这些访问器应该直接访问模型上存储在 Integration#properties 中的字段,就像其他 ActiveRecord 属性一样。

您应该始终通过它们的 getters 访问字段,而不是直接与 properties 哈希交互。您必须不写入 properties 哈希,而必须使用生成的 setter 方法。对此哈希的直接写入不会被持久化。

要了解这些字段如何在集成的前端表单中展示, 请参阅 自定义前端表单

其他方法包括使用 Integration.prop_accessorIntegration.data_field,您可能在早期版本的集成中看到过。对于新集成,不应使用这些方法。

定义验证

您应该为所有字段定义 Rails 验证。

验证仅在集成启用时应用,通过测试 #activated? 方法。

任何具有 required: 属性 的字段都应有相应的 presence 验证,因为 required: 字段属性仅用于前端。

例如:

module Integrations
  class FooBar < Integration
    with_options if: :activated? do
      validates :key, presence: true, format: { with: KEY_REGEX }
      validates :bar, inclusion: [true, false]
    end

    field :key, required: true
    field :bar, type: :checkbox
  end
end

定义触发事件

集成通过在 GitLab 事件中调用其 #execute 方法来触发,该方法会传递一个包含事件详细信息的 payload 哈希。

支持的事件与 webhook 事件 有一些重叠,并接收相同的 payload。您可以通过在模型中重写类方法 Integration.supported_events 来指定您感兴趣的事件。

集成支持以下事件:

事件类型 默认 触发
Alert event alert 记录新的、唯一的警报。
Commit event commit 创建或更新提交。
Deployment event deployment 部署开始或完成。
Work item event issue 创建、更新或关闭问题。
Confidential issue event confidential_issue 创建、更新或关闭机密问题。
Job event job
Merge request event merge_request 创建、更新或合并合并请求。
Comment event comment 添加新评论。
Confidential comment event confidential_note 在机密问题上添加新评论。
Pipeline event pipeline 管道状态更改。
Push event push 推送到仓库。
Tag push event tag_push 将新标签推送到仓库。
Vulnerability event vulnerability 记录新的、唯一的漏洞。仅限 Ultimate 版本。
Wiki page event wiki_page 创建或更新 wiki 页面。

事件示例

此示例定义了一个响应 commitmerge_request 事件的集成:

module Integrations
  class FooBar < Integration
    def self.supported_events
      %w[commit merge_request]
    end
  end
end

集成也可以不响应事件,而是以其他方式实现自定义功能:

module Integrations
  class FooBar < Integration
    def self.supported_events
      []
    end
  end
end

定义事件属性默认值

集成存在一个问题,记录在 issue #382999 中, 由于大多数 事件属性 的默认值为 true, 我们比必要的更频繁地加载集成。在我们解决该问题之前,集成必须以以下方式定义所有事件 attribute 属性:

  • 对于通知集成(包含 Integrations::Base::ChatNotification 的集成),将所有事件属性设置为 false。 这会呈现一个带有复选框的表单,每个事件触发器默认未选中。
  • 对于其他集成:
    • 将与集成 触发事件 匹配的事件属性设置为 true
    • 将所有其他事件 attributes 设置为 false

例如,一个仅响应提交和合并请求 触发事件 的集成应将其事件属性设置如下:

attribute :commit_events, default: true
attribute :merge_requests_events, default: true

attribute :alert_events, default: false
attribute :incident_events, default: false
attribute :confidential_issues_events, default: false
attribute :confidential_note_events, default: false
attribute :issues_events, default: false
attribute :job_events, default: false
attribute :note_events, default: false
attribute :pipeline_events, default: false
attribute :push_events, default: false
attribute :tag_push_events, default: false
attribute :wiki_page_events, default: false

更改事件属性默认值

如果现有集成的事件属性更改为 true, 则需要数据迁移来为旧记录填充该属性值。

定义指标

每个新集成都应有五个 指标

  • 使用给定集成的活跃项目数
  • 继承给定集成的活跃项目数
  • 使用给定集成的活跃群组数
  • 继承给定集成的活跃群组数
  • 给定集成的活跃实例级集成数

指标需要集成模型的类才能工作。您只能在模型创建时或之后添加指标。

要创建指标定义:

  1. 复制为现有活跃集成创建的指标。
  2. 将所有先前集成名称的出现替换为新集成名称。
  3. milestone 替换为当前里程碑,将 introduced_by_url 替换为合并请求链接。
  4. 通过检查 指标指南 验证所有其他属性具有正确的值。

例如,要为 Slack 集成创建指标定义,您复制这些指标,然后 将 Slack 替换为新集成的名称:

安全要求

所有 HTTP 调用必须使用 Integrations::Clients::HTTP

集成必须始终使用 Integrations::Clients::HTTP 进行 HTTP 调用,该类:

  • 确保 网络设置 对 HTTP 调用强制执行。
  • 具有额外的 安全加固 功能。
  • 是我们进行安全 HTTP 调用的单一事实来源。
  • 确保所有响应大小都经过验证。

屏蔽通道值

Integrations::Base::ChatNotification 包含的集成 可以隐藏其通道输入字段的值。当字段包含敏感信息(如身份验证令牌)时,集成应隐藏这些值。

默认情况下,#mask_configurable_channels? 返回 false。要屏蔽通道值,请在集成中重写 #mask_configurable_channels? 方法以返回 true

override :mask_configurable_channels?
def mask_configurable_channels?
  true
end

不允许使用进行 HTTP 调用的 Ruby gem

GitLab 集成不得添加进行 HTTP 调用的 Ruby gem。也不应添加添加小型抽象的其他 gem。

如果需要,可以使用来自官方来源的类似工具的 gem,如 atlassian-jwt gem。

包装与第三方服务交互的 gem 初看起来可能很方便,但与涉及的成本相比,它们提供的最小好处:

  • 它们增加了潜在安全问题的范围以及修复它们所需的努力。
  • 通常这些 gem 代表您进行 HTTP 调用。由于集成可以对用户配置的远程服务器进行 HTTP 调用,我们 完全控制网络调用 至关重要。
  • 存在管理 gem 升级的维护成本。
  • 它们可能会阻止我们使用新功能。

定义配置测试

可选地,您可以定义集成设置的配置测试。测试从集成表单的 测试 按钮执行,并将结果返回给用户。

一个好的配置测试:

  • 不会更改服务上的数据。例如,它不应触发 CI 构建。发送消息是可以的。
  • 有意义且尽可能全面。

如果无法遵循上述指南,请考虑不添加配置测试。

要添加配置测试,请为集成模型定义一个 #test 方法。

该方法接收 data,这是一个测试推送事件 payload。 它应返回一个哈希,包含以下键:

  • success(必需):一个布尔值,指示配置测试是否通过。
  • result(可选):如果配置测试失败,返回给用户的消息。

例如:

module Integrations
  class FooBar < Integration
    def test(data)
      success = test_api_key(data)

      { success: success, result: 'API key is invalid' }
    end
  end
end

自定义前端表单

前端表单是根据模型中定义的元数据动态生成的。

默认情况下,集成表单提供:

  • 一个复选框用于启用或禁用集成。
  • 每个从 Integration#configurable_events 返回的触发事件的复选框。

您还可以通过重写 Integration#help 或在 app/views/shared/integrations/$INTEGRATION_NAME/_help.html.haml 中提供模板,在表单顶部添加帮助文本。

要将自定义属性添加到表单,您可以在 Integration#fields 中为它们定义元数据。

此方法应返回一个哈希数组,每个字段一个,其中键可以是:

类型 必需 默认值 描述
type: symbol true :text 表单字段的类型。可以是 :text:number:textarea:password:checkbox:string_array:select
section: symbol false 指定字段所属的部分。
name: string true 表单字段的属性名称。
required: boolean false false 指定表单字段是必需还是可选。注意仍需要 后端验证 来验证存在性。
title: string false name: 的大写值 表单字段的标签。
placeholder: string false 表单字段的占位符。
help: string false 显示在表单字段下方的帮助文本。
api_only: boolean false false 指定字段是否只能通过 API 可用,并从前端表单中排除。
description string false API 字段的描述。
if: boolean or lambda false true 指定字段是否可用。值可以是布尔值或 lambda。

type: :checkbox 的额外键

类型 必需 默认值 描述
checkbox_label: string false title: 的值 显示在复选框旁边的自定义标签。

type: :select 的额外键

类型 必需 默认 描述
choices: array true 嵌套的 [label, value] 元组数组。

type: :password 的额外键

类型 必需 默认值 描述
non_empty_password_title: string false title: 的值 当已有值存储时显示的替代标签。
non_empty_password_help: string false help: 的值 当已有值存储时显示的替代帮助文本。

定义部分

所有集成都应定义 Integration#sections,将表单分成较小的部分, 使用户更容易设置集成。

最常用的部分是预定义的,并且已经包含一些 UI:

  • SECTION_TYPE_CONNECTION:包含连接到集成并进行身份验证所需的基本字段,如 urlusernamepassword
  • SECTION_TYPE_CONFIGURATION:包含更高级的配置和关于集成工作方式的可选设置。
  • SECTION_TYPE_TRIGGER:包含将触发集成的事件列表。

SECTION_TYPE_CONNECTIONSECTION_TYPE_CONFIGURATION 在内部渲染 dynamic-field 组件。 dynamic-field 组件为集成渲染 checkboxnumberinputselecttextarea 类型的字段。 例如:

module Integrations
  class FooBar < Integration
    def sections
      [
        {
          type: SECTION_TYPE_CONNECTION,
          title: s_('Integrations|Connection details'),
          description: help
        },
        {
          type: SECTION_TYPE_CONFIGURATION,
          title: _('Configuration'),
          description: s_('Advanced configuration for integration')
        }
      ]
    end
  end
end

要将字段添加到特定部分,您可以将 section: 键添加到字段元数据中。

新的自定义部分

如果现有部分不满足您对 UI 定制的要求,您可以创建新的自定义部分:

  1. 通过添加新的常量 SECTION_TYPE_* 并将其添加到 #sections 方法中来添加新部分:

    module Integrations
      class FooBar < Integration
        SECTION_TYPE_SUPER = :my_custom_section
    
        def sections
          [
            {
              type: SECTION_TYPE_SUPER,
              title: s_('Integrations|Custom section'),
              description: s_('Integrations|Help')
            }
          ]
        end
      end
    end
  2. ~/integrations/constants.js 中更新前端常量 integrationFormSectionsintegrationFormSectionComponents

  3. app/assets/javascripts/integrations/edit/components/sections/* 中添加您的新部分组件。

  4. app/assets/javascripts/integrations/edit/components/integration_forms/section.vue 中包含并渲染新部分。

前端表单示例

此示例定义了一个必需的 url 字段,以及可选的 usernamepassword 字段,所有这些都位于 Connection details 部分下:

module Integrations
  class FooBar < Integration
    field :url,
      section: SECTION_TYPE_CONNECTION,
      type: :text,
      title: s_('FooBarIntegration|Server URL'),
      placeholder: 'https://example.com/',
      required: true

    field :username,
      section: SECTION_TYPE_CONNECTION,
      type: :text,
      title: s_('FooBarIntegration|Username')

    field :password,
      section: SECTION_TYPE_CONNECTION,
      type: 'password',
      title: s_('FoobarIntegration|Password'),
      non_empty_password_title: s_('FooBarIntegration|Enter new password')

    def sections
      [
        {
          type: SECTION_TYPE_CONNECTION,
          title: s_('Integrations|Connection details'),
          description: s_('Integrations|Help')
        }
      ]
    end
  end
end

在 REST API 中暴露集成

要在 REST API 中暴露集成:

  1. 将集成的类 (::Integrations::FooBar) 添加到 API::Helpers::IntegrationsHelpers.integration_classes

  2. 将集成的 API 参数添加到 API::Helpers::IntegrationsHelpers.integrations,例如:

    'foo-bar' => ::Integrations::FooBar.api_arguments
  3. 更新 doc/api/project_integrations.mddoc/api/group_integrations.md 中的参考文档,为您的集成添加新部分,并记录所有属性。

您也可以参考我们的 REST API 风格指南

敏感字段不会通过 API 暴露。敏感字段是其名称包含以下任何内容的字段:

  • key
  • passphrase
  • password
  • secret
  • token
  • webhook

集成的可用性

默认情况下,集成可以应用于特定项目或群组,或 整个实例。大多数集成仅在项目上下文中运行,但仍可以 为群组和实例配置。

对于某些集成,仅在特定级别(项目、群组或实例)上启用它可能是有意义的。为此,集成必须从 Integration::INTEGRATION_NAMES 中移除,而是添加到:

  • Integration::PROJECT_LEVEL_ONLY_INTEGRATION_NAMES 仅允许在项目级别启用。
  • Integration::INSTANCE_LEVEL_ONLY_INTEGRATION_NAMES 仅允许在实例级别启用。
  • Integration::PROJECT_AND_GROUP_LEVEL_ONLY_INTEGRATION_NAMES 防止在实例级别启用。

在开发新集成时,我们还建议您将其可用性限制在 Integration.available_integration_names 中的 功能标志 之后。

文档

为集成添加文档:

  • doc/user/project/integrations 中添加一个页面。
  • 集成概述 链接它。
  • 文档合并后,添加条目集成 下的文档导航。

您也可以参考我们的通用 文档指南

您可以在集成表单中提供帮助文本,包括对外部文档的链接, 如上所述在 自定义前端表单 中。请参考 我们的 可用性指南 了解帮助文本。

测试

测试不应与 定义配置测试 混淆。

通常在 spec/models/integrations 中为集成模型添加测试, 以及在 spec/factories/integrations.rb 中添加带有示例设置的工厂就足够了。

每个集成也作为通用测试的一部分进行测试。例如,有功能规范 验证所有集成的设置表单是否正确渲染。

如果您的集成实现任何自定义行为,特别是在前端,这应该 通过额外的测试来覆盖。

您也可以参考我们的通用 测试指南

国际化

所有 UI 字符都应通过遵循我们的 国际化指南 为翻译做准备。

字符串应使用集成名称作为 命名空间,例如 s_('FooBarIntegration|My string')

弃用和移除集成

要移除集成,您必须先弃用该集成。有关更多信息, 请参阅 功能弃用指南

弃用集成

您必须在计划移除前的第三个里程碑之前宣布任何弃用。要弃用集成:

移除集成

要安全地移除集成,您必须在两个里程碑中分阶段移除。

在计划移除的主要里程碑(M.0)中,禁用集成并从数据库中删除记录:

  • Integration::INTEGRATION_NAMES 中移除集成。
  • 删除集成模型的 #execute#test 方法(如果已定义),但保留模型。
  • 添加后迁移以从 PostgreSQL 中删除集成记录(参见 示例合并请求)。
  • 将集成文档标记为已移除
  • 更新 项目群组 集成 API 页面。

在下一个次要版本(M.1)中:

  • 移除集成的模型和任何剩余代码。
  • 关闭任何带有集成标签 (~Integration::<name>) 的问题、合并请求和史诗。
  • gitlab-org 中删除集成的标签 (~Integration::<name>)。

持续的迁移和重构

开发人员应该意识到,集成团队正在 统一集成属性的定义方式

集成示例

您可以参考这些问题以了解添加新集成的示例:

  • Datadog:指标收集器,类似于 Prometheus 集成。
  • EWM/RTC:外部问题跟踪器。
  • Webex Teams:聊天通知。
  • ZenTao:外部问题跟踪器,带有自定义问题视图,类似于 Jira 问题集成。