Commit 529fb7f7 authored by Peter Leitzen's avatar Peter Leitzen

Merge branch '229125-validate-pagerduty-payload-using-json-schemer' into 'master'

Validate PagerDuty payload using json_schemer

Closes #229125

See merge request gitlab-org/gitlab!42862
parents f576abbd e529930a
...@@ -34,7 +34,7 @@ module IncidentManagement ...@@ -34,7 +34,7 @@ module IncidentManagement
strong_memoize(:pager_duty_processable_events) do strong_memoize(:pager_duty_processable_events) do
::PagerDuty::WebhookPayloadParser ::PagerDuty::WebhookPayloadParser
.call(params.to_h) .call(params.to_h)
.filter { |msg| msg['event'].in?(PAGER_DUTY_PROCESSABLE_EVENT_TYPES) } .filter { |msg| msg['event'].to_s.in?(PAGER_DUTY_PROCESSABLE_EVENT_TYPES) }
end end
end end
......
{
"type": "object",
"required": ["event", "incident"],
"properties": {
"event": { "type": "string" },
"incident": {
"type": "object",
"required": [
"html_url",
"incident_number",
"title",
"status",
"created_at",
"urgency",
"incident_key"
],
"properties": {
"html_url": { "type": "string" },
"incindent_number": { "type": "integer" },
"title": { "type": "string" },
"status": { "type": "string" },
"created_at": { "type": "string" },
"urgency": { "type": "string", "enum": ["high", "low"] },
"incident_key": { "type": ["string", "null"] },
"assignments": {
"type": "array",
"items": {
"assignee": {
"type": "array",
"items": {
"summary": { "type": "string" },
"html_url": { "type": "string" }
}
}
}
},
"impacted_services": {
"type": "array",
"items": {
"summary": { "type": "string" },
"html_url": { "type": "string" }
}
}
}
}
}
}
...@@ -2,6 +2,8 @@ ...@@ -2,6 +2,8 @@
module PagerDuty module PagerDuty
class WebhookPayloadParser class WebhookPayloadParser
SCHEMA_PATH = File.join('lib', 'pager_duty', 'validator', 'schemas', 'message.json')
def initialize(payload) def initialize(payload)
@payload = payload @payload = payload
end end
...@@ -11,7 +13,7 @@ module PagerDuty ...@@ -11,7 +13,7 @@ module PagerDuty
end end
def call def call
Array(payload['messages']).map { |msg| parse_message(msg) } Array(payload['messages']).map { |msg| parse_message(msg) }.reject(&:empty?)
end end
private private
...@@ -19,6 +21,8 @@ module PagerDuty ...@@ -19,6 +21,8 @@ module PagerDuty
attr_reader :payload attr_reader :payload
def parse_message(message) def parse_message(message)
return {} unless valid_message?(message)
{ {
'event' => message['event'], 'event' => message['event'],
'incident' => parse_incident(message['incident']) 'incident' => parse_incident(message['incident'])
...@@ -26,8 +30,6 @@ module PagerDuty ...@@ -26,8 +30,6 @@ module PagerDuty
end end
def parse_incident(incident) def parse_incident(incident)
return {} if incident.blank?
{ {
'url' => incident['html_url'], 'url' => incident['html_url'],
'incident_number' => incident['incident_number'], 'incident_number' => incident['incident_number'],
...@@ -62,5 +64,9 @@ module PagerDuty ...@@ -62,5 +64,9 @@ module PagerDuty
def reject_empty(entities) def reject_empty(entities)
Array(entities).reject { |e| e['summary'].blank? && e['url'].blank? } Array(entities).reject { |e| e['summary'].blank? && e['url'].blank? }
end end
def valid_message?(message)
::JSONSchemer.schema(Pathname.new(SCHEMA_PATH)).valid?(message)
end
end end
end end
# frozen_string_literal: true # frozen_string_literal: true
require 'fast_spec_helper' require 'fast_spec_helper'
require 'json_schemer'
RSpec.describe PagerDuty::WebhookPayloadParser do RSpec.describe PagerDuty::WebhookPayloadParser do
describe '.call' do describe '.call' do
...@@ -8,36 +9,36 @@ RSpec.describe PagerDuty::WebhookPayloadParser do ...@@ -8,36 +9,36 @@ RSpec.describe PagerDuty::WebhookPayloadParser do
File.read(File.join(File.dirname(__FILE__), '../../fixtures/pager_duty/webhook_incident_trigger.json')) File.read(File.join(File.dirname(__FILE__), '../../fixtures/pager_duty/webhook_incident_trigger.json'))
end end
let(:triggered_event) do
{
'event' => 'incident.trigger',
'incident' => {
'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
'incident_number' => 33,
'title' => 'My new incident',
'status' => 'triggered',
'created_at' => '2017-09-26T15:14:36Z',
'urgency' => 'high',
'incident_key' => nil,
'assignees' => [{
'summary' => 'Laura Haley',
'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
}],
'impacted_services' => [{
'summary' => 'Production XDB Cluster',
'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
}]
}
}
end
subject(:parse) { described_class.call(payload) } subject(:parse) { described_class.call(payload) }
context 'when payload is a correct PagerDuty payload' do context 'when payload is a correct PagerDuty payload' do
let(:payload) { Gitlab::Json.parse(fixture_file) } let(:payload) { Gitlab::Json.parse(fixture_file) }
it 'returns parsed payload' do it 'returns parsed payload' do
is_expected.to eq( is_expected.to eq([triggered_event])
[
{
'event' => 'incident.trigger',
'incident' => {
'url' => 'https://webdemo.pagerduty.com/incidents/PRORDTY',
'incident_number' => 33,
'title' => 'My new incident',
'status' => 'triggered',
'created_at' => '2017-09-26T15:14:36Z',
'urgency' => 'high',
'incident_key' => nil,
'assignees' => [{
'summary' => 'Laura Haley',
'url' => 'https://webdemo.pagerduty.com/users/P553OPV'
}],
'impacted_services' => [{
'summary' => 'Production XDB Cluster',
'url' => 'https://webdemo.pagerduty.com/services/PN49J75'
}]
}
}
]
)
end end
context 'when assignments summary and html_url are blank' do context 'when assignments summary and html_url are blank' do
...@@ -69,11 +70,42 @@ RSpec.describe PagerDuty::WebhookPayloadParser do ...@@ -69,11 +70,42 @@ RSpec.describe PagerDuty::WebhookPayloadParser do
end end
end end
context 'when payload has no incidents' do context 'when payload schema is invalid' do
let(:payload) { { 'messages' => [{ 'event' => 'incident.trigger' }] } } let(:payload) { { 'messages' => [{ 'event' => 'incident.trigger' }] } }
it 'returns payload with blank incidents' do it 'returns payload with blank incidents' do
is_expected.to eq([{ 'event' => 'incident.trigger', 'incident' => {} }]) is_expected.to eq([])
end
end
context 'when payload consists of two messages' do
context 'when one of the messages has no incident data' do
let(:payload) do
valid_payload = Gitlab::Json.parse(fixture_file)
event = { 'event' => 'incident.trigger' }
valid_payload['messages'] = valid_payload['messages'].append(event)
valid_payload
end
it 'returns parsed payload with valid events only' do
is_expected.to eq([triggered_event])
end
end
context 'when one of the messages has unknown event' do
let(:payload) do
valid_payload = Gitlab::Json.parse(fixture_file)
event = { 'event' => 'incident.unknown', 'incident' => valid_payload['messages'].first['incident'] }
valid_payload['messages'] = valid_payload['messages'].append(event)
valid_payload
end
it 'returns parsed payload' do
unknown_event = triggered_event.dup
unknown_event['event'] = 'incident.unknown'
is_expected.to contain_exactly(triggered_event, unknown_event)
end
end end
end 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