集成开发指南
本页提供了实现 GitLab 集成 的开发指南, 这些集成是我们 主 Rails 项目 的一部分。
另请参阅我们的 方向页面,了解我们围绕集成的策略概述。
本指南仍在完善中。如果您需要澄清或发现任何过时信息,欢迎联系 @gitlab-org/foundations/import-and-integrate。
添加新集成
定义集成
-
在
app/models/integrations中添加一个继承自Integration的新模型。- 例如,在
app/models/integrations/foo_bar.rb中创建Integrations::FooBar。 - 对于某些类型的集成,您可以包含这些基础模块:
Integrations::Base::ChatNotificationIntegrations::Base::CiIntegrations::Base::IssueTrackerIntegrations::Base::MonitoringIntegrations::Base::SlashCommandsIntegrations::Base::ThirdPartyWiki
- 对于主要触发对外部服务 HTTP 调用的集成,您也可以使用
Integrations::HasWebHookconcern。这通过关联的ServiceHook模型重用 GitLab 中的 webhook 功能,并自动记录请求日志,可在集成设置中查看。
- 例如,在
-
将集成的下划线名称 (
'foo_bar') 添加到Integration::INTEGRATION_NAMES。 -
在
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
endIntegration.field 会在类上安装访问器方法。在这里,我们将拥有 #url、#url= 和 #url_changed? 来管理 url 字段。这些访问器应该直接访问模型上存储在 Integration#properties 中的字段,就像其他 ActiveRecord 属性一样。
您应该始终通过它们的 getters 访问字段,而不是直接与 properties 哈希交互。您必须不写入 properties 哈希,而必须使用生成的 setter 方法。对此哈希的直接写入不会被持久化。
要了解这些字段如何在集成的前端表单中展示, 请参阅 自定义前端表单。
其他方法包括使用 Integration.prop_accessor 或 Integration.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 页面。 |
事件示例
此示例定义了一个响应 commit 和 merge_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,
则需要数据迁移来为旧记录填充该属性值。
定义指标
每个新集成都应有五个 指标:
- 使用给定集成的活跃项目数
- 继承给定集成的活跃项目数
- 使用给定集成的活跃群组数
- 继承给定集成的活跃群组数
- 给定集成的活跃实例级集成数
指标需要集成模型的类才能工作。您只能在模型创建时或之后添加指标。
要创建指标定义:
- 复制为现有活跃集成创建的指标。
- 将所有先前集成名称的出现替换为新集成名称。
- 将
milestone替换为当前里程碑,将introduced_by_url替换为合并请求链接。 - 通过检查 指标指南 验证所有其他属性具有正确的值。
例如,要为 Slack 集成创建指标定义,您复制这些指标,然后
将 Slack 替换为新集成的名称:
20210216180122_projects_slack_active.yml20210216180124_groups_slack_active.yml20210216180127_instances_slack_active.yml20210216180131_groups_inheriting_slack_active.yml20210216180129_projects_inheriting_slack_active.yml
安全要求
所有 HTTP 调用必须使用 Integrations::Clients::HTTP
集成必须始终使用 Integrations::Clients::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:包含连接到集成并进行身份验证所需的基本字段,如url、username、password。SECTION_TYPE_CONFIGURATION:包含更高级的配置和关于集成工作方式的可选设置。SECTION_TYPE_TRIGGER:包含将触发集成的事件列表。
SECTION_TYPE_CONNECTION 和 SECTION_TYPE_CONFIGURATION 在内部渲染 dynamic-field 组件。
dynamic-field 组件为集成渲染 checkbox、number、input、select 或 textarea 类型的字段。
例如:
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 定制的要求,您可以创建新的自定义部分:
-
通过添加新的常量
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 -
在
~/integrations/constants.js中更新前端常量integrationFormSections和integrationFormSectionComponents。 -
在
app/assets/javascripts/integrations/edit/components/sections/*中添加您的新部分组件。 -
在
app/assets/javascripts/integrations/edit/components/integration_forms/section.vue中包含并渲染新部分。
前端表单示例
此示例定义了一个必需的 url 字段,以及可选的 username 和 password 字段,所有这些都位于 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 中暴露集成:
-
将集成的类 (
::Integrations::FooBar) 添加到API::Helpers::IntegrationsHelpers.integration_classes。 -
将集成的 API 参数添加到
API::Helpers::IntegrationsHelpers.integrations,例如:'foo-bar' => ::Integrations::FooBar.api_arguments -
更新
doc/api/project_integrations.md和doc/api/group_integrations.md中的参考文档,为您的集成添加新部分,并记录所有属性。
您也可以参考我们的 REST API 风格指南。
敏感字段不会通过 API 暴露。敏感字段是其名称包含以下任何内容的字段:
keypassphrasepasswordsecrettokenwebhook
集成的可用性
默认情况下,集成可以应用于特定项目或群组,或 整个实例。大多数集成仅在项目上下文中运行,但仍可以 为群组和实例配置。
对于某些集成,仅在特定级别(项目、群组或实例)上启用它可能是有意义的。为此,集成必须从 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 中的 功能标志 之后。
文档
为集成添加文档:
您也可以参考我们的通用 文档指南。
您可以在集成表单中提供帮助文本,包括对外部文档的链接, 如上所述在 自定义前端表单 中。请参考 我们的 可用性指南 了解帮助文本。
测试
测试不应与 定义配置测试 混淆。
通常在 spec/models/integrations 中为集成模型添加测试,
以及在 spec/factories/integrations.rb 中添加带有示例设置的工厂就足够了。
每个集成也作为通用测试的一部分进行测试。例如,有功能规范 验证所有集成的设置表单是否正确渲染。
如果您的集成实现任何自定义行为,特别是在前端,这应该 通过额外的测试来覆盖。
您也可以参考我们的通用 测试指南。
国际化
所有 UI 字符都应通过遵循我们的 国际化指南 为翻译做准备。
字符串应使用集成名称作为 命名空间,例如 s_('FooBarIntegration|My string')。
弃用和移除集成
要移除集成,您必须先弃用该集成。有关更多信息, 请参阅 功能弃用指南。
弃用集成
您必须在计划移除前的第三个里程碑之前宣布任何弃用。要弃用集成:
- 添加弃用条目。
- 将集成文档标记为已弃用。
- 可选。为防止创建任何新的项目级记录,
将集成添加到
Project#disabled_integrations(参见 示例合并请求)。
移除集成
要安全地移除集成,您必须在两个里程碑中分阶段移除。
在计划移除的主要里程碑(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 问题集成。