1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
|
# frozen_string_literal: true
module API
class Integrations < ::API::Base
feature_category :integrations
INTEGRATIONS_TAGS = %w[integrations].freeze
integrations = Helpers::IntegrationsHelpers.integrations
integration_classes = Helpers::IntegrationsHelpers.integration_classes
if Gitlab.dev_or_test_env?
integrations['mock-ci'] = [
{
required: true,
name: :mock_service_url,
type: String,
desc: 'URL to the mock integration'
}
]
integrations['mock-deployment'] = []
integrations['mock-monitoring'] = []
integration_classes += Helpers::IntegrationsHelpers.development_integration_classes
end
INTEGRATIONS = integrations.freeze
integration_classes.each do |integration|
event_names = integration.try(:event_names) || next
event_names.each do |event_name|
INTEGRATIONS[integration.to_param.tr("_", "-")] << {
required: false,
name: event_name.to_sym,
type: ::Grape::API::Boolean,
desc: IntegrationsHelper.integration_event_description(integration, event_name)
}
end
end
SLASH_COMMAND_INTEGRATIONS = {
'mattermost-slash-commands' => [
{
name: :token,
type: String,
desc: 'The Mattermost token'
}
],
'slack-slash-commands' => [
{
name: :token,
type: String,
desc: 'The Slack token'
}
]
}.freeze
helpers do
def integration_attributes(integration)
integration.fields.inject([]) do |arr, hash|
arr << hash[:name].to_sym
end
end
end
# The API officially documents only the `:id/integrations` API paths.
# We support the older `id:/services` path for backwards-compatibility in API V4.
# The support for `:id/services` can be dropped if we create an API V5.
[':id/services', ':id/integrations'].each do |path|
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
before { authenticate! }
before { authorize_admin_project }
desc 'List all active integrations' do
detail 'Get a list of all active project integrations.'
success Entities::ProjectIntegrationBasic
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
is_array true
tags INTEGRATIONS_TAGS
end
get path do
integrations = user_project.integrations.active
present integrations, with: Entities::ProjectIntegrationBasic
end
INTEGRATIONS.each do |slug, settings|
desc "Create/Edit #{slug.titleize} integration" do
detail "Set #{slug.titleize} integration for a project."
success Entities::ProjectIntegrationBasic
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
tags INTEGRATIONS_TAGS
end
params do
settings.each do |setting|
if setting[:required]
requires setting[:name], type: setting[:type], desc: setting[:desc]
else
optional setting[:name], type: setting[:type], desc: setting[:desc]
end
end
end
put "#{path}/#{slug}" do
integration = user_project.find_or_initialize_integration(slug.underscore)
params = declared_params(include_missing: false).merge(active: true)
if integration.update(params)
present integration, with: Entities::ProjectIntegration
else
render_api_error!('400 Bad Request', 400)
end
end
end
desc "Disable an integration" do
detail "Disable the integration for a project. Integration settings are preserved."
success code: 204
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
tags INTEGRATIONS_TAGS
end
params do
requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
end
delete "#{path}/:slug" do
integration = user_project.find_or_initialize_integration(params[:slug].underscore)
destroy_conditionally!(integration) do
attrs = integration_attributes(integration).index_with do |attr|
column = if integration.attribute_present?(attr)
integration.column_for_attribute(attr)
elsif integration.data_fields_present?
integration.data_fields.column_for_attribute(attr)
end
case column
when nil, ActiveRecord::ConnectionAdapters::NullColumn
nil
else
column.default
end
end.merge(active: false)
render_api_error!('400 Bad Request', 400) unless integration.update(attrs)
end
end
desc "Get an integration settings" do
detail "Get the integration settings for a project."
success Entities::ProjectIntegration
failure [
{ code: 400, message: 'Bad request' },
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
tags INTEGRATIONS_TAGS
end
params do
requires :slug, type: String, values: INTEGRATIONS.keys, desc: 'The name of the integration'
end
get "#{path}/:slug" do
integration = user_project.find_or_initialize_integration(params[:slug].underscore)
not_found!('Integration') unless integration&.persisted?
present integration, with: Entities::ProjectIntegration
end
end
SLASH_COMMAND_INTEGRATIONS.each do |integration_slug, settings|
helpers do
def slash_command_integration(project, integration_slug, params)
project.integrations.active.find do |integration|
integration.try(:token) == params[:token] && integration.to_param == integration_slug.underscore
end
end
end
params do
requires :id, types: [String, Integer], desc: 'The ID or URL-encoded path of the project'
end
resource :projects, requirements: API::NAMESPACE_OR_PROJECT_REQUIREMENTS do
desc "Trigger a slash command for #{integration_slug}" do
detail 'Added in GitLab 8.13'
failure [
{ code: 401, message: 'Unauthorized' },
{ code: 404, message: 'Not found' }
]
tags INTEGRATIONS_TAGS
end
params do
settings.each do |setting|
requires setting[:name], type: setting[:type], desc: setting[:desc]
end
end
post "#{path}/#{integration_slug.underscore}/trigger", urgency: :low do
project = find_project(params[:id])
# This is not accurate, but done to prevent leakage of the project names
not_found!('Integration') unless project
integration = slash_command_integration(project, integration_slug, params)
result = integration.try(:trigger, params)
if result
status result[:status] || 200
present result
else
not_found!('Integration')
end
end
end
end
end
desc "Trigger a global slack command" do
detail 'Added in GitLab 9.4'
failure [
{ code: 401, message: 'Unauthorized' }
]
end
params do
requires :text, type: String, desc: 'Text of the slack command'
end
post 'slack/trigger' do
if result = Gitlab::SlashCommands::GlobalSlackHandler.new(params).trigger
status result[:status] || 200
present result
else
not_found!
end
end
end
end
# Added for JiHu
# https://gitlab.com/gitlab-org/gitlab/-/merge_requests/118289#note_1379334692
API::Integrations.prepend_mod
|