Commit e88d909d authored by Robert Speicher's avatar Robert Speicher

Merge branch 'ee-create-issue-from-generic-alert-notification' into 'master'

Create issue from generic alert notification

See merge request gitlab-org/gitlab-ee!16021
parents 95330a13 0a05e9d6
# frozen_string_literal: true
module Projects
module Alerting
class NotifyService < BaseService
include Gitlab::Utils::StrongMemoize
# Prevents users to use WIP feature on private GitLab instances
# by enabling 'generic_alert_endpoint' feature manually.
# TODO: https://gitlab.com/gitlab-org/gitlab-ee/issues/14792
DEV_TOKEN = :development_token
def execute(token)
return unauthorized unless valid_token?(token)
return forbidden unless create_issue?
process_incident_issues
ServiceResponse.success
end
private
def generic_alert_endpoint_enabled?
Feature.enabled?(:generic_alert_endpoint, project)
end
def incident_management_available?
project.feature_available?(:incident_management)
end
def create_issue?
incident_management_available? && generic_alert_endpoint_enabled?
end
def process_incident_issues
IncidentManagement::ProcessAlertWorker
.perform_async(project.id, parsed_payload)
end
def parsed_payload
Gitlab::Alerting::NotificationPayloadParser.call(params.to_h)
end
def valid_token?(token)
token == DEV_TOKEN
end
def unauthorized
ServiceResponse.error(message: 'Unauthorized', http_status: 401)
end
def forbidden
ServiceResponse.error(message: 'Forbidden', http_status: 403)
end
end
end
end
# frozen_string_literal: true
module Gitlab
module Alerting
class NotificationPayloadParser
DEFAULT_TITLE = 'New: Incident'
def initialize(payload)
@payload = payload.to_h.with_indifferent_access
end
def self.call(payload)
new(payload).call
end
def call
{
'annotations' => annotations,
'startsAt' => starts_at
}.compact
end
private
attr_reader :payload
def title
payload[:title].presence || DEFAULT_TITLE
end
def annotations
{
'title' => title,
'description' => payload[:description].presence,
'monitoring_tool' => payload[:monitoring_tool].presence,
'service' => payload[:service].presence,
'hosts' => hosts
}.compact
end
def hosts
Array(payload[:hosts]).reject(&:blank?).presence
end
def current_time
Time.current.change(usec: 0).rfc3339
end
def starts_at
Time.parse(payload[:start_time].to_s).rfc3339
rescue ArgumentError
current_time
end
end
end
end
# frozen_string_literal: true
require 'fast_spec_helper'
describe Gitlab::Alerting::NotificationPayloadParser do
describe '.call' do
let(:starts_at) { Time.current.change(usec: 0) }
let(:payload) do
{
'title' => 'alert title',
'start_time' => starts_at.rfc3339,
'description' => 'Description',
'monitoring_tool' => 'Monitoring tool name',
'service' => 'Service',
'hosts' => ['gitlab.com']
}
end
subject { described_class.call(payload) }
it 'returns Prometheus-like payload' do
is_expected.to eq(
{
'annotations' => {
'title' => 'alert title',
'description' => 'Description',
'monitoring_tool' => 'Monitoring tool name',
'service' => 'Service',
'hosts' => ['gitlab.com']
},
'startsAt' => starts_at.rfc3339
}
)
end
context 'when title is blank' do
before do
payload[:title] = ''
end
it 'sets a predefined title' do
expect(subject.dig('annotations', 'title')).to eq('New: Incident')
end
end
context 'when hosts attribute is a string' do
before do
payload[:hosts] = 'gitlab.com'
end
it 'returns hosts as an array of one element' do
expect(subject.dig('annotations', 'hosts')).to eq(['gitlab.com'])
end
end
context 'when the time is in unsupported format' do
before do
payload[:start_time] = 'invalid/date/format'
end
it 'sets startsAt to a current time in RFC3339 format' do
expect(subject['startsAt']).to eq(starts_at.rfc3339)
end
end
context 'when payload is blank' do
let(:payload) { {} }
it 'returns default parameters' do
is_expected.to eq(
'annotations' => { 'title' => 'New: Incident' },
'startsAt' => starts_at.rfc3339
)
end
end
context 'when payload attributes have blank lines' do
let(:payload) do
{
'title' => '',
'start_time' => '',
'description' => '',
'monitoring_tool' => '',
'service' => '',
'hosts' => ['']
}
end
it 'returns default parameters' do
is_expected.to eq(
'annotations' => { 'title' => 'New: Incident' },
'startsAt' => starts_at.rfc3339
)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Projects::Alerting::NotifyService do
set(:project) { create(:project) }
before do
# We use `set(:project)` so we make sure to clear caches
project.clear_memoization(:licensed_feature_available)
end
shared_examples 'processes incident issues' do |amount|
let(:create_incident_service) { spy }
it 'processes issues', :sidekiq do
expect(IncidentManagement::ProcessAlertWorker)
.to receive(:perform_async)
.with(project.id, kind_of(Hash))
.exactly(amount).times
Sidekiq::Testing.inline! do
expect(subject.status).to eq(:success)
end
end
end
shared_examples 'does not process incident issues' do |http_status:|
it 'does not process issues' do
expect(IncidentManagement::ProcessAlertWorker)
.not_to receive(:perform_async)
expect(subject.status).to eq(:error)
expect(subject.http_status).to eq(http_status)
end
end
describe '#execute' do
let(:token) { :development_token }
let(:starts_at) { Time.now.change(usec: 0) }
let(:service) { described_class.new(project, nil, payload) }
let(:payload_raw) do
{
'title' => 'alert title',
'start_time' => starts_at.rfc3339
}
end
let(:payload) { ActionController::Parameters.new(payload_raw).permit! }
subject { service.execute(token) }
context 'with license' do
before do
stub_licensed_features(incident_management: true)
end
context 'with Generic Alert Endpoint feature enabled' do
before do
stub_feature_flags(generic_alert_endpoint: true)
end
context 'with valid token' do
it_behaves_like 'processes incident issues', 1
end
context 'with invalid token' do
let(:token) { 'invalid-token' }
it_behaves_like 'does not process incident issues', http_status: 401
end
end
context 'with Generic Alert Endpoint feature disabled' do
before do
stub_feature_flags(generic_alert_endpoint: false)
end
it_behaves_like 'does not process incident issues', http_status: 403
end
end
context 'without license' do
before do
stub_licensed_features(incident_management: false)
end
it_behaves_like 'does not process incident issues', http_status: 403
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