Commit 25f0847f authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '227202-create-gitlab-issue-from-pagerduty-incident-part-2' into 'master'

Create GitLab issues from PagerDuty webhook payload (Part 2)

See merge request gitlab-org/gitlab!36603
parents e89b0d15 72b83885
......@@ -11,11 +11,7 @@ module Projects
prepend_before_action :project_without_auth
def create
result = ServiceResponse.success(http_status: :accepted)
unless Feature.enabled?(:pagerduty_webhook, @project)
result = ServiceResponse.error(message: 'Unauthorized', http_status: :unauthorized)
end
result = webhook_processor.execute(params[:token])
head result.http_status
end
......@@ -26,6 +22,14 @@ module Projects
@project ||= Project
.find_by_full_path("#{params[:namespace_id]}/#{params[:project_id]}")
end
def webhook_processor
::IncidentManagement::PagerDuty::ProcessWebhookService.new(project, nil, payload)
end
def payload
@payload ||= params.permit![:pager_duty_incident].to_h
end
end
end
end
......@@ -12,7 +12,7 @@ module IncidentManagement
attr_encrypted :pagerduty_token,
mode: :per_attribute_iv,
key: Settings.attr_encrypted_db_key_base_truncated,
key: ::Settings.attr_encrypted_db_key_base_truncated,
algorithm: 'aes-256-gcm',
encode: false, # No need to encode for binary column https://github.com/attr-encrypted/attr_encrypted#the-encode-encode_iv-encode_salt-and-default_encoding-options
encode_iv: false
......
# frozen_string_literal: true
module IncidentManagement
module PagerDuty
class ProcessWebhookService < BaseService
include Gitlab::Utils::StrongMemoize
include IncidentManagement::Settings
# https://developer.pagerduty.com/docs/webhooks/webhook-behavior/#size-limit
PAGER_DUTY_PAYLOAD_SIZE_LIMIT = 55.kilobytes
# https://developer.pagerduty.com/docs/webhooks/v2-overview/#webhook-types
PAGER_DUTY_PROCESSABLE_EVENT_TYPES = %w(incident.trigger).freeze
def execute(token)
return forbidden unless webhook_setting_active?
return unauthorized unless valid_token?(token)
return bad_request unless valid_payload_size?
process_incidents
accepted
end
private
def process_incidents
pager_duty_processable_events.each do |event|
::IncidentManagement::PagerDuty::ProcessIncidentWorker.perform_async(project.id, event['incident'])
end
end
def pager_duty_processable_events
strong_memoize(:pager_duty_processable_events) do
::PagerDuty::WebhookPayloadParser
.call(params.to_h)
.filter { |msg| msg['event'].in?(PAGER_DUTY_PROCESSABLE_EVENT_TYPES) }
end
end
def webhook_setting_active?
Feature.enabled?(:pagerduty_webhook, project) &&
incident_management_setting.pagerduty_active?
end
def valid_token?(token)
token && incident_management_setting.pagerduty_token == token
end
def valid_payload_size?
Gitlab::Utils::DeepSize.new(params, max_size: PAGER_DUTY_PAYLOAD_SIZE_LIMIT).valid?
end
def accepted
ServiceResponse.success(http_status: :accepted)
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: :forbidden)
end
def unauthorized
ServiceResponse.error(message: 'Unauthorized', http_status: :unauthorized)
end
def bad_request
ServiceResponse.error(message: 'Bad Request', http_status: :bad_request)
end
end
end
end
This diff is collapsed.
......@@ -189,7 +189,7 @@
:tags: []
- :name: cronjob:iterations_update_status
:feature_category: :issue_tracking
:has_external_dependencies:
:has_external_dependencies:
:urgency: :low
:resource_boundary: :unknown
:weight: 1
......
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe Projects::IncidentManagement::PagerDutyIncidentsController do
let_it_be(:project) { create(:project) }
describe 'POST #create' do
let(:payload) { { messages: [] } }
def make_request
post :create, params: project_params, body: payload.to_json, as: :json
end
context 'when pagerduty_webhook feature enabled' do
before do
stub_feature_flags(pagerduty_webhook: project)
end
it 'responds with 202 Accepted' do
make_request
expect(response).to have_gitlab_http_status(:accepted)
end
end
context 'when pagerduty_webhook feature disabled' do
before do
stub_feature_flags(pagerduty_webhook: false)
end
it 'responds with 401 Unauthorized' do
make_request
expect(response).to have_gitlab_http_status(:unauthorized)
end
end
end
private
def project_params(opts = {})
opts.reverse_merge(namespace_id: project.namespace, project_id: project)
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe 'PagerDuty webhook' do
let_it_be(:project) { create(:project) }
describe 'POST /incidents/pagerduty' do
let(:payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
let(:webhook_processor_class) { ::IncidentManagement::PagerDuty::ProcessWebhookService }
let(:webhook_processor) { instance_double(webhook_processor_class) }
def make_request
headers = { 'Content-Type' => 'application/json' }
post project_incidents_pagerduty_url(project, token: 'VALID-TOKEN'), params: payload.to_json, headers: headers
end
before do
allow(webhook_processor_class).to receive(:new).and_return(webhook_processor)
allow(webhook_processor).to receive(:execute).and_return(ServiceResponse.success(http_status: :accepted))
end
it 'calls PagerDuty webhook processor with correct parameters' do
make_request
expect(webhook_processor_class).to have_received(:new).with(project, nil, payload)
expect(webhook_processor).to have_received(:execute).with('VALID-TOKEN')
end
it 'responds with 202 Accepted' do
make_request
expect(response).to have_gitlab_http_status(:accepted)
end
end
end
# frozen_string_literal: true
require 'spec_helper'
RSpec.describe IncidentManagement::PagerDuty::ProcessWebhookService do
let_it_be(:project, reload: true) { create(:project) }
describe '#execute' do
shared_examples 'does not process incidents' do
it 'does not process incidents' do
expect(::IncidentManagement::PagerDuty::ProcessIncidentWorker).not_to receive(:perform_async)
execute
end
end
let(:webhook_payload) { Gitlab::Json.parse(fixture_file('pager_duty/webhook_incident_trigger.json')) }
let(:token) { nil }
subject(:execute) { described_class.new(project, nil, webhook_payload).execute(token) }
context 'when pagerduty_webhook feature is enabled' do
before do
stub_feature_flags(pagerduty_webhook: project)
end
context 'when PagerDuty webhook setting is active' do
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: true) }
context 'when token is valid' do
let(:token) { incident_management_setting.pagerduty_token }
context 'when webhook payload has acceptable size' do
it 'responds with Accepted' do
result = execute
expect(result).to be_success
expect(result.http_status).to eq(:accepted)
end
it 'processes issues' do
incident_payload = ::PagerDuty::WebhookPayloadParser.call(webhook_payload).first['incident']
expect(::IncidentManagement::PagerDuty::ProcessIncidentWorker)
.to receive(:perform_async)
.with(project.id, incident_payload)
.once
execute
end
end
context 'when webhook payload is too big' do
let(:deep_size) { instance_double(Gitlab::Utils::DeepSize, valid?: false) }
before do
allow(Gitlab::Utils::DeepSize)
.to receive(:new)
.with(webhook_payload, max_size: described_class::PAGER_DUTY_PAYLOAD_SIZE_LIMIT)
.and_return(deep_size)
end
it 'responds with Bad Request' do
result = execute
expect(result).to be_error
expect(result.http_status).to eq(:bad_request)
end
it_behaves_like 'does not process incidents'
end
context 'when webhook payload is blank' do
let(:webhook_payload) { nil }
it 'responds with Accepted' do
result = execute
expect(result).to be_success
expect(result.http_status).to eq(:accepted)
end
it_behaves_like 'does not process incidents'
end
end
context 'when token is invalid' do
let(:token) { 'invalid-token' }
it 'responds with Unauthorized' do
result = execute
expect(result).to be_error
expect(result.http_status).to eq(:unauthorized)
end
it_behaves_like 'does not process incidents'
end
end
context 'when both tokens are nil' do
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
let(:token) { nil }
before do
incident_management_setting.update_column(:pagerduty_active, true)
end
it 'responds with Unauthorized' do
result = execute
expect(result).to be_error
expect(result.http_status).to eq(:unauthorized)
end
it_behaves_like 'does not process incidents'
end
context 'when PagerDuty webhook setting is not active' do
let_it_be(:incident_management_setting) { create(:project_incident_management_setting, project: project, pagerduty_active: false) }
it 'responds with Forbidden' do
result = execute
expect(result).to be_error
expect(result.http_status).to eq(:forbidden)
end
it_behaves_like 'does not process incidents'
end
end
context 'when pagerduty_webhook feature is disabled' do
before do
stub_feature_flags(pagerduty_webhook: false)
end
it 'responds with Forbidden' do
result = execute
expect(result).to be_error
expect(result.http_status).to eq(:forbidden)
end
it_behaves_like 'does not process incidents'
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment