Rework webhook trigger tasks checks and payload build (#1544)
This commit is contained in:
parent
61b7c2ec48
commit
3ade317010
5 changed files with 210 additions and 160 deletions
|
|
@ -11,6 +11,10 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
|
|||
|
||||
- Add filtering by escalation chain to alert groups page ([1535](https://github.com/grafana/oncall/pull/1535))
|
||||
|
||||
### Fixed
|
||||
|
||||
- Improve tasks checking/triggering webhooks in new backend
|
||||
|
||||
## v1.1.37 (2023-03-14)
|
||||
|
||||
### Fixed
|
||||
|
|
|
|||
|
|
@ -4,9 +4,7 @@ from celery.utils.log import get_task_logger
|
|||
from django.conf import settings
|
||||
|
||||
from apps.alerts.models import AlertGroup, AlertGroupLogRecord
|
||||
from apps.user_management.models import User
|
||||
from apps.webhooks.models import Webhook
|
||||
from apps.webhooks.utils import serialize_event
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
||||
from .trigger_webhook import send_webhook_event
|
||||
|
|
@ -17,6 +15,14 @@ logger.setLevel(logging.DEBUG)
|
|||
|
||||
MAX_RETRIES = 10
|
||||
|
||||
ACTION_TO_TRIGGER_TYPE = {
|
||||
AlertGroupLogRecord.TYPE_ACK: Webhook.TRIGGER_ACKNOWLEDGE,
|
||||
AlertGroupLogRecord.TYPE_RESOLVED: Webhook.TRIGGER_RESOLVE,
|
||||
AlertGroupLogRecord.TYPE_SILENCE: Webhook.TRIGGER_SILENCE,
|
||||
AlertGroupLogRecord.TYPE_UN_SILENCE: Webhook.TRIGGER_UNSILENCE,
|
||||
AlertGroupLogRecord.TYPE_UN_RESOLVED: Webhook.TRIGGER_UNRESOLVE,
|
||||
}
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task(
|
||||
bind=True, autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else MAX_RETRIES
|
||||
|
|
@ -28,15 +34,16 @@ def alert_group_created(self, alert_group_id):
|
|||
return
|
||||
|
||||
trigger_type = Webhook.TRIGGER_NEW
|
||||
event = {
|
||||
"type": "Firing",
|
||||
"time": alert_group.started_at,
|
||||
}
|
||||
data = serialize_event(event, alert_group, None)
|
||||
organization_id = alert_group.channel.organization_id
|
||||
team_id = alert_group.channel.team_id
|
||||
webhooks = Webhook.objects.filter(trigger_type=trigger_type, organization_id=organization_id, team_id=team_id)
|
||||
|
||||
# check if there are any webhooks before going on
|
||||
if not webhooks:
|
||||
return
|
||||
|
||||
send_webhook_event.apply_async(
|
||||
(trigger_type, data), kwargs={"organization_id": organization_id, "team_id": team_id}
|
||||
(trigger_type, alert_group_id), kwargs={"organization_id": organization_id, "team_id": team_id}
|
||||
)
|
||||
|
||||
|
||||
|
|
@ -46,46 +53,22 @@ def alert_group_created(self, alert_group_id):
|
|||
def alert_group_status_change(self, action_type, alert_group_id, user_id):
|
||||
try:
|
||||
alert_group = AlertGroup.unarchived_objects.get(pk=alert_group_id)
|
||||
user = User.objects.filter(pk=user_id).first()
|
||||
except (AlertGroup.DoesNotExist):
|
||||
return
|
||||
|
||||
# TODO: update mapping, maybe use a dict instead
|
||||
if action_type == AlertGroupLogRecord.TYPE_ACK:
|
||||
trigger_type = Webhook.TRIGGER_ACKNOWLEDGE
|
||||
event = {
|
||||
"type": "Acknowledge",
|
||||
"time": alert_group.acknowledged_at,
|
||||
}
|
||||
elif action_type == AlertGroupLogRecord.TYPE_RESOLVED:
|
||||
trigger_type = Webhook.TRIGGER_RESOLVE
|
||||
event = {
|
||||
"type": "Resolve",
|
||||
"time": alert_group.resolved_at,
|
||||
}
|
||||
elif action_type == AlertGroupLogRecord.TYPE_SILENCE:
|
||||
trigger_type = Webhook.TRIGGER_SILENCE
|
||||
event = {
|
||||
"type": "Silence",
|
||||
"time": alert_group.silenced_at,
|
||||
"until": alert_group.silenced_until,
|
||||
}
|
||||
elif action_type == AlertGroupLogRecord.TYPE_UN_SILENCE:
|
||||
trigger_type = Webhook.TRIGGER_UNSILENCE
|
||||
event = {
|
||||
"type": "Unsilence",
|
||||
}
|
||||
elif action_type == AlertGroupLogRecord.TYPE_UN_RESOLVED:
|
||||
trigger_type = Webhook.TRIGGER_UNRESOLVE
|
||||
event = {
|
||||
"type": "Unresolve",
|
||||
}
|
||||
else:
|
||||
trigger_type = ACTION_TO_TRIGGER_TYPE.get(action_type)
|
||||
if trigger_type is None:
|
||||
return
|
||||
|
||||
data = serialize_event(event, alert_group, user)
|
||||
organization_id = alert_group.channel.organization_id
|
||||
team_id = alert_group.channel.team_id
|
||||
webhooks = Webhook.objects.filter(trigger_type=trigger_type, organization_id=organization_id, team_id=team_id)
|
||||
|
||||
# check if there are any webhooks before going on
|
||||
if not webhooks:
|
||||
return
|
||||
|
||||
send_webhook_event.apply_async(
|
||||
(trigger_type, data), kwargs={"organization_id": organization_id, "team_id": team_id}
|
||||
(trigger_type, alert_group_id),
|
||||
kwargs={"organization_id": organization_id, "team_id": team_id, "user_id": user_id},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -7,8 +7,16 @@ from django.apps import apps
|
|||
from django.conf import settings
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.webhooks.models import WebhookLog
|
||||
from apps.webhooks.utils import InvalidWebhookData, InvalidWebhookHeaders, InvalidWebhookTrigger, InvalidWebhookUrl
|
||||
from apps.alerts.models import AlertGroup
|
||||
from apps.user_management.models import User
|
||||
from apps.webhooks.models import Webhook, WebhookLog
|
||||
from apps.webhooks.utils import (
|
||||
InvalidWebhookData,
|
||||
InvalidWebhookHeaders,
|
||||
InvalidWebhookTrigger,
|
||||
InvalidWebhookUrl,
|
||||
serialize_event,
|
||||
)
|
||||
from common.custom_celery_tasks import shared_dedicated_queue_retry_task
|
||||
|
||||
logger = get_task_logger(__name__)
|
||||
|
|
@ -18,20 +26,72 @@ logger.setLevel(logging.DEBUG)
|
|||
@shared_dedicated_queue_retry_task(
|
||||
autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None
|
||||
)
|
||||
def send_webhook_event(trigger_type, data, team_id=None, organization_id=None):
|
||||
def send_webhook_event(trigger_type, alert_group_id, team_id=None, organization_id=None, user_id=None):
|
||||
Webhooks = apps.get_model("webhooks", "Webhook")
|
||||
webhooks_qs = Webhooks.objects.filter(trigger_type=trigger_type, organization_id=organization_id, team_id=team_id)
|
||||
|
||||
for webhook in webhooks_qs:
|
||||
execute_webhook.apply_async((webhook.pk, data))
|
||||
execute_webhook.apply_async((webhook.pk, alert_group_id, user_id))
|
||||
|
||||
|
||||
def _isoformat_date(date_value):
|
||||
return date_value.isoformat() if date_value else None
|
||||
|
||||
|
||||
def _build_payload(trigger_type, alert_group_id, user_id):
|
||||
user = None
|
||||
try:
|
||||
alert_group = AlertGroup.unarchived_objects.get(pk=alert_group_id)
|
||||
if user_id is not None:
|
||||
user = User.objects.filter(pk=user_id).first()
|
||||
except AlertGroup.DoesNotExist:
|
||||
return
|
||||
|
||||
if trigger_type == Webhook.TRIGGER_NEW:
|
||||
event = {
|
||||
"type": "Firing",
|
||||
"time": _isoformat_date(alert_group.started_at),
|
||||
}
|
||||
elif trigger_type == Webhook.TRIGGER_ACKNOWLEDGE:
|
||||
event = {
|
||||
"type": "Acknowledge",
|
||||
"time": _isoformat_date(alert_group.acknowledged_at),
|
||||
}
|
||||
elif trigger_type == Webhook.TRIGGER_RESOLVE:
|
||||
event = {
|
||||
"type": "Resolve",
|
||||
"time": _isoformat_date(alert_group.resolved_at),
|
||||
}
|
||||
elif trigger_type == Webhook.TRIGGER_SILENCE:
|
||||
event = {
|
||||
"type": "Silence",
|
||||
"time": _isoformat_date(alert_group.silenced_at),
|
||||
"until": _isoformat_date(alert_group.silenced_until),
|
||||
}
|
||||
elif trigger_type == Webhook.TRIGGER_UNSILENCE:
|
||||
event = {
|
||||
"type": "Unsilence",
|
||||
}
|
||||
elif trigger_type == Webhook.TRIGGER_UNRESOLVE:
|
||||
event = {
|
||||
"type": "Unresolve",
|
||||
}
|
||||
data = serialize_event(event, alert_group, user)
|
||||
return data
|
||||
|
||||
|
||||
@shared_dedicated_queue_retry_task(
|
||||
autoretry_for=(Exception,), retry_backoff=True, max_retries=1 if settings.DEBUG else None
|
||||
)
|
||||
def execute_webhook(webhook_pk, data):
|
||||
def execute_webhook(webhook_pk, alert_group_id, user_id):
|
||||
Webhooks = apps.get_model("webhooks", "Webhook")
|
||||
try:
|
||||
webhook = Webhooks.objects.get(pk=webhook_pk)
|
||||
except Webhooks.DoesNotExist:
|
||||
logger.warn(f"Webhook {webhook_pk} does not exist")
|
||||
return
|
||||
|
||||
data = _build_payload(webhook.trigger_type, alert_group_id, user_id)
|
||||
status = {
|
||||
"last_run_at": timezone.now(),
|
||||
"input_data": data,
|
||||
|
|
@ -45,7 +105,6 @@ def execute_webhook(webhook_pk, data):
|
|||
|
||||
exception = None
|
||||
try:
|
||||
webhook = Webhooks.objects.get(pk=webhook_pk)
|
||||
triggered, status["trigger"] = webhook.check_trigger(data)
|
||||
if triggered:
|
||||
status["url"] = webhook.build_url(data)
|
||||
|
|
@ -66,9 +125,6 @@ def execute_webhook(webhook_pk, data):
|
|||
else:
|
||||
# do not add a log entry if the webhook is not triggered
|
||||
return
|
||||
except Webhooks.DoesNotExist:
|
||||
logger.warn(f"Webhook {webhook_pk} does not exist")
|
||||
return
|
||||
except InvalidWebhookUrl as e:
|
||||
status["url"] = e.message
|
||||
except InvalidWebhookTrigger as e:
|
||||
|
|
|
|||
|
|
@ -4,67 +4,53 @@ import pytest
|
|||
from django.utils import timezone
|
||||
|
||||
from apps.alerts.models import AlertGroup, AlertGroupLogRecord
|
||||
from apps.public_api.serializers import IncidentSerializer
|
||||
from apps.webhooks.models import Webhook
|
||||
from apps.webhooks.tasks import alert_group_created, alert_group_status_change
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_alert_group_created(make_organization, make_alert_receive_channel, make_alert_group):
|
||||
def test_alert_group_created(make_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook):
|
||||
organization = make_organization()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
# make sure there is a webhook setup
|
||||
make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_NEW)
|
||||
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event:
|
||||
alert_group_created(alert_group.pk)
|
||||
|
||||
assert mock_send_event.called
|
||||
expected_data = {
|
||||
"event": {
|
||||
"type": "Firing",
|
||||
"time": alert_group.started_at,
|
||||
},
|
||||
"user": None,
|
||||
"alert_group": IncidentSerializer(alert_group).data,
|
||||
"alert_group_id": alert_group.public_primary_key,
|
||||
"alert_payload": "",
|
||||
}
|
||||
|
||||
assert mock_send_event.call_args == call(
|
||||
(Webhook.TRIGGER_NEW, expected_data), kwargs={"organization_id": organization.pk, "team_id": None}
|
||||
(Webhook.TRIGGER_NEW, alert_group.pk), kwargs={"organization_id": organization.pk, "team_id": None}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_alert_group_created_for_team(make_organization, make_team, make_alert_receive_channel, make_alert_group):
|
||||
def test_alert_group_created_for_team(
|
||||
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
||||
):
|
||||
organization = make_organization()
|
||||
team = make_team(organization)
|
||||
alert_receive_channel = make_alert_receive_channel(organization, team=team)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
# make sure there is a webhook setup
|
||||
make_custom_webhook(organization=organization, team=team, trigger_type=Webhook.TRIGGER_NEW)
|
||||
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event:
|
||||
alert_group_created(alert_group.pk)
|
||||
|
||||
assert mock_send_event.called
|
||||
expected_data = {
|
||||
"event": {
|
||||
"type": "Firing",
|
||||
"time": alert_group.started_at,
|
||||
},
|
||||
"user": None,
|
||||
"alert_group": IncidentSerializer(alert_group).data,
|
||||
"alert_group_id": alert_group.public_primary_key,
|
||||
"alert_payload": "",
|
||||
}
|
||||
|
||||
assert mock_send_event.call_args == call(
|
||||
(Webhook.TRIGGER_NEW, expected_data), kwargs={"organization_id": organization.pk, "team_id": team.pk}
|
||||
(Webhook.TRIGGER_NEW, alert_group.pk), kwargs={"organization_id": organization.pk, "team_id": team.pk}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_alert_group_created_does_not_exist():
|
||||
def test_alert_group_created_does_not_exist(make_organization, make_custom_webhook):
|
||||
assert AlertGroup.all_objects.filter(pk=53).first() is None
|
||||
organization = make_organization()
|
||||
# make sure there is a webhook setup
|
||||
make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_NEW)
|
||||
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event:
|
||||
alert_group_created(53)
|
||||
|
|
@ -74,13 +60,13 @@ def test_alert_group_created_does_not_exist():
|
|||
|
||||
@pytest.mark.django_db
|
||||
@pytest.mark.parametrize(
|
||||
"action_type,event_type,webhook_type,time_field",
|
||||
"action_type,webhook_type",
|
||||
[
|
||||
(AlertGroupLogRecord.TYPE_ACK, "Acknowledge", Webhook.TRIGGER_ACKNOWLEDGE, "acknowledged_at"),
|
||||
(AlertGroupLogRecord.TYPE_RESOLVED, "Resolve", Webhook.TRIGGER_RESOLVE, "resolved_at"),
|
||||
(AlertGroupLogRecord.TYPE_SILENCE, "Silence", Webhook.TRIGGER_SILENCE, "silenced_at"),
|
||||
(AlertGroupLogRecord.TYPE_UN_SILENCE, "Unsilence", Webhook.TRIGGER_UNSILENCE, None),
|
||||
(AlertGroupLogRecord.TYPE_UN_RESOLVED, "Unresolve", Webhook.TRIGGER_UNRESOLVE, None),
|
||||
(AlertGroupLogRecord.TYPE_ACK, Webhook.TRIGGER_ACKNOWLEDGE),
|
||||
(AlertGroupLogRecord.TYPE_RESOLVED, Webhook.TRIGGER_RESOLVE),
|
||||
(AlertGroupLogRecord.TYPE_SILENCE, Webhook.TRIGGER_SILENCE),
|
||||
(AlertGroupLogRecord.TYPE_UN_SILENCE, Webhook.TRIGGER_UNSILENCE),
|
||||
(AlertGroupLogRecord.TYPE_UN_RESOLVED, Webhook.TRIGGER_UNRESOLVE),
|
||||
],
|
||||
)
|
||||
def test_alert_group_status_change(
|
||||
|
|
@ -88,40 +74,31 @@ def test_alert_group_status_change(
|
|||
make_user_for_organization,
|
||||
make_alert_receive_channel,
|
||||
make_alert_group,
|
||||
make_custom_webhook,
|
||||
action_type,
|
||||
event_type,
|
||||
webhook_type,
|
||||
time_field,
|
||||
):
|
||||
organization = make_organization()
|
||||
user = make_user_for_organization(organization)
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
# make sure there is a webhook setup
|
||||
make_custom_webhook(organization=organization, trigger_type=webhook_type)
|
||||
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event:
|
||||
alert_group_status_change(action_type, alert_group.pk, user.pk)
|
||||
|
||||
expected_data = {
|
||||
"event": {
|
||||
"type": event_type,
|
||||
},
|
||||
"user": user.username,
|
||||
"alert_group": IncidentSerializer(alert_group).data,
|
||||
"alert_group_id": alert_group.public_primary_key,
|
||||
"alert_payload": "",
|
||||
}
|
||||
if time_field is not None:
|
||||
expected_data["event"]["time"] = getattr(alert_group, time_field)
|
||||
if action_type == AlertGroupLogRecord.TYPE_SILENCE:
|
||||
expected_data["event"]["until"] = alert_group.silenced_until
|
||||
assert mock_send_event.call_args == call(
|
||||
(webhook_type, expected_data), kwargs={"organization_id": organization.pk, "team_id": None}
|
||||
(webhook_type, alert_group.pk), kwargs={"organization_id": organization.pk, "team_id": None, "user_id": user.pk}
|
||||
)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_alert_group_status_change_does_not_exist():
|
||||
def test_alert_group_status_change_does_not_exist(make_organization, make_custom_webhook):
|
||||
assert AlertGroup.all_objects.filter(pk=53).first() is None
|
||||
organization = make_organization()
|
||||
# make sure there is a webhook setup
|
||||
make_custom_webhook(organization=organization, trigger_type=Webhook.TRIGGER_ACKNOWLEDGE)
|
||||
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event:
|
||||
alert_group_status_change(AlertGroupLogRecord.TYPE_ACK, 53, None)
|
||||
|
|
@ -130,25 +107,20 @@ def test_alert_group_status_change_does_not_exist():
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_alert_group_status_change_for_team(make_organization, make_team, make_alert_receive_channel, make_alert_group):
|
||||
def test_alert_group_status_change_for_team(
|
||||
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
||||
):
|
||||
organization = make_organization()
|
||||
team = make_team(organization)
|
||||
alert_receive_channel = make_alert_receive_channel(organization, team=team)
|
||||
alert_group = make_alert_group(alert_receive_channel, resolved=True, resolved_at=timezone.now())
|
||||
# make sure there is a webhook setup
|
||||
make_custom_webhook(organization=organization, team=team, trigger_type=Webhook.TRIGGER_RESOLVE)
|
||||
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.send_webhook_event.apply_async") as mock_send_event:
|
||||
alert_group_status_change(AlertGroupLogRecord.TYPE_RESOLVED, alert_group.pk, None)
|
||||
|
||||
expected_data = {
|
||||
"event": {
|
||||
"type": "Resolve",
|
||||
"time": alert_group.resolved_at,
|
||||
},
|
||||
"user": None,
|
||||
"alert_group": IncidentSerializer(alert_group).data,
|
||||
"alert_group_id": alert_group.public_primary_key,
|
||||
"alert_payload": "",
|
||||
}
|
||||
assert mock_send_event.call_args == call(
|
||||
(Webhook.TRIGGER_RESOLVE, expected_data), kwargs={"organization_id": organization.pk, "team_id": team.pk}
|
||||
(Webhook.TRIGGER_RESOLVE, alert_group.pk),
|
||||
kwargs={"organization_id": organization.pk, "team_id": team.pk, "user_id": None},
|
||||
)
|
||||
|
|
|
|||
|
|
@ -2,7 +2,9 @@ import json
|
|||
from unittest.mock import call, patch
|
||||
|
||||
import pytest
|
||||
from django.utils import timezone
|
||||
|
||||
from apps.public_api.serializers import IncidentSerializer
|
||||
from apps.webhooks.models import Webhook
|
||||
from apps.webhooks.tasks import execute_webhook, send_webhook_event
|
||||
|
||||
|
|
@ -16,10 +18,14 @@ class MockResponse:
|
|||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_send_webhook_event_filters(make_organization, make_team, make_custom_webhook):
|
||||
def test_send_webhook_event_filters(
|
||||
make_organization, make_team, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
||||
):
|
||||
organization = make_organization()
|
||||
other_organization = make_organization()
|
||||
other_team = make_team(organization)
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
|
||||
webhooks = {}
|
||||
for trigger_type, _ in Webhook.TRIGGER_TYPES:
|
||||
|
|
@ -33,74 +39,93 @@ def test_send_webhook_event_filters(make_organization, make_team, make_custom_we
|
|||
)
|
||||
other_org_webhook = make_custom_webhook(organization=other_organization, trigger_type=Webhook.TRIGGER_NEW)
|
||||
|
||||
sample_data = {"field": "value"}
|
||||
for trigger_type, _ in Webhook.TRIGGER_TYPES:
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute:
|
||||
send_webhook_event(trigger_type, sample_data, organization_id=organization.pk)
|
||||
assert mock_execute.call_args == call((webhooks[trigger_type].pk, sample_data))
|
||||
send_webhook_event(trigger_type, alert_group.pk, organization_id=organization.pk)
|
||||
assert mock_execute.call_args == call((webhooks[trigger_type].pk, alert_group.pk, None))
|
||||
|
||||
# other team
|
||||
alert_receive_channel = make_alert_receive_channel(organization, team=other_team)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute:
|
||||
send_webhook_event(
|
||||
Webhook.TRIGGER_ACKNOWLEDGE, sample_data, organization_id=organization.pk, team_id=other_team.pk
|
||||
Webhook.TRIGGER_ACKNOWLEDGE, alert_group.pk, organization_id=organization.pk, team_id=other_team.pk
|
||||
)
|
||||
assert mock_execute.call_args == call((other_team_webhook.pk, sample_data))
|
||||
assert mock_execute.call_args == call((other_team_webhook.pk, alert_group.pk, None))
|
||||
|
||||
# other org
|
||||
alert_receive_channel = make_alert_receive_channel(other_organization)
|
||||
alert_group = make_alert_group(alert_receive_channel)
|
||||
with patch("apps.webhooks.tasks.trigger_webhook.execute_webhook.apply_async") as mock_execute:
|
||||
send_webhook_event(Webhook.TRIGGER_NEW, sample_data, organization_id=other_organization.pk)
|
||||
assert mock_execute.call_args == call((other_org_webhook.pk, sample_data))
|
||||
send_webhook_event(Webhook.TRIGGER_NEW, alert_group.pk, organization_id=other_organization.pk)
|
||||
assert mock_execute.call_args == call((other_org_webhook.pk, alert_group.pk, None))
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_execute_webhook_ok(make_organization, make_custom_webhook):
|
||||
# set trigger, build_url, build_requests_args, check status/log
|
||||
def test_execute_webhook_ok(
|
||||
make_organization, make_user_for_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
||||
):
|
||||
organization = make_organization()
|
||||
user = make_user_for_organization(organization)
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(
|
||||
alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True, acknowledged_by=user.pk
|
||||
)
|
||||
webhook = make_custom_webhook(
|
||||
organization=organization,
|
||||
url="https://something/{{ alert_id }}/",
|
||||
url="https://something/{{ alert_group_id }}/",
|
||||
http_method="POST",
|
||||
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
||||
trigger_template="{{ integration_id == 'the-integration' }}",
|
||||
headers='{"some-header": "{{ alert_id }}"}',
|
||||
data='{"value": "{{ value }}"}',
|
||||
trigger_template="{{{{ alert_group.integration_id == '{}' }}}}".format(
|
||||
alert_receive_channel.public_primary_key
|
||||
),
|
||||
headers='{"some-header": "{{ alert_group_id }}"}',
|
||||
data='{"value": "{{ alert_group_id }}"}',
|
||||
forward_all=False,
|
||||
)
|
||||
data = {
|
||||
"integration_id": "the-integration",
|
||||
"alert_id": "ID123",
|
||||
"value": "42",
|
||||
}
|
||||
|
||||
mock_response = MockResponse()
|
||||
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
||||
mock_gethostbyname.return_value = "8.8.8.8"
|
||||
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
||||
mock_requests.post.return_value = mock_response
|
||||
execute_webhook(webhook.pk, data)
|
||||
execute_webhook(webhook.pk, alert_group.pk, user.pk)
|
||||
|
||||
assert mock_requests.post.called
|
||||
expected_call = call(
|
||||
"https://something/ID123/",
|
||||
"https://something/{}/".format(alert_group.public_primary_key),
|
||||
timeout=10,
|
||||
headers={"some-header": "ID123"},
|
||||
json={"value": "42"},
|
||||
headers={"some-header": alert_group.public_primary_key},
|
||||
json={"value": alert_group.public_primary_key},
|
||||
)
|
||||
assert mock_requests.post.call_args == expected_call
|
||||
# check logs
|
||||
log = webhook.logs.all()[0]
|
||||
assert log.response_status == "200"
|
||||
assert log.response == json.dumps(mock_response.json())
|
||||
assert log.input_data == data
|
||||
assert log.data == json.dumps({"value": "42"})
|
||||
assert log.headers == json.dumps({"some-header": "ID123"})
|
||||
assert log.url == "https://something/ID123/"
|
||||
expected_data = {
|
||||
"event": {
|
||||
"type": "Acknowledge",
|
||||
"time": alert_group.acknowledged_at.isoformat(),
|
||||
},
|
||||
"user": user.username,
|
||||
"alert_group": IncidentSerializer(alert_group).data,
|
||||
"alert_group_id": alert_group.public_primary_key,
|
||||
"alert_payload": "",
|
||||
}
|
||||
assert log.input_data == expected_data
|
||||
assert log.data == json.dumps({"value": alert_group.public_primary_key})
|
||||
assert log.headers == json.dumps({"some-header": alert_group.public_primary_key})
|
||||
assert log.url == "https://something/{}/".format(alert_group.public_primary_key)
|
||||
|
||||
|
||||
@pytest.mark.django_db
|
||||
def test_execute_webhook_trigger_false(make_organization, make_custom_webhook):
|
||||
# set trigger, build_url, build_requests_args, check status/log
|
||||
def test_execute_webhook_trigger_false(
|
||||
make_organization, make_alert_receive_channel, make_alert_group, make_custom_webhook
|
||||
):
|
||||
organization = make_organization()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel, acknowledged_at=timezone.now(), acknowledged=True)
|
||||
webhook = make_custom_webhook(
|
||||
organization=organization,
|
||||
url="https://something/{{ alert_id }}/",
|
||||
|
|
@ -108,14 +133,9 @@ def test_execute_webhook_trigger_false(make_organization, make_custom_webhook):
|
|||
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
||||
trigger_template="{{ integration_id == 'the-integration' }}",
|
||||
)
|
||||
data = {
|
||||
"integration_id": "other-integration",
|
||||
"alert_id": "ID123",
|
||||
"value": "42",
|
||||
}
|
||||
|
||||
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
||||
execute_webhook(webhook.pk, data)
|
||||
execute_webhook(webhook.pk, alert_group.pk, None)
|
||||
|
||||
assert not mock_requests.post.called
|
||||
# check no logs
|
||||
|
|
@ -128,9 +148,9 @@ def test_execute_webhook_trigger_false(make_organization, make_custom_webhook):
|
|||
[
|
||||
(
|
||||
"url",
|
||||
"https://myserver/{{ alert_payload.id }}/triggered",
|
||||
"https://myserver/{{ }}/triggered",
|
||||
"url",
|
||||
"URL - Template Warning: 'alert_payload' is undefined",
|
||||
"URL - Template Error: Expected an expression, got 'end of print statement'",
|
||||
),
|
||||
(
|
||||
"trigger_template",
|
||||
|
|
@ -143,35 +163,50 @@ def test_execute_webhook_trigger_false(make_organization, make_custom_webhook):
|
|||
],
|
||||
)
|
||||
def test_execute_webhook_errors(
|
||||
make_organization, make_custom_webhook, field_name, value, log_field_name, expected_error
|
||||
make_organization,
|
||||
make_alert_receive_channel,
|
||||
make_alert_group,
|
||||
make_custom_webhook,
|
||||
field_name,
|
||||
value,
|
||||
log_field_name,
|
||||
expected_error,
|
||||
):
|
||||
organization = make_organization()
|
||||
alert_receive_channel = make_alert_receive_channel(organization)
|
||||
alert_group = make_alert_group(alert_receive_channel, resolved_at=timezone.now(), resolved=True)
|
||||
|
||||
extra_kwargs = {field_name: value}
|
||||
if "url" not in extra_kwargs:
|
||||
extra_kwargs["url"] = "https://something.cool/"
|
||||
webhook = make_custom_webhook(
|
||||
organization=organization,
|
||||
http_method="POST",
|
||||
trigger_type=Webhook.TRIGGER_ACKNOWLEDGE,
|
||||
trigger_type=Webhook.TRIGGER_RESOLVE,
|
||||
forward_all=False,
|
||||
**extra_kwargs,
|
||||
)
|
||||
data = {
|
||||
"integration_id": "other-integration",
|
||||
"alert_id": "ID123",
|
||||
"value": "42",
|
||||
}
|
||||
|
||||
with patch("apps.webhooks.utils.socket.gethostbyname") as mock_gethostbyname:
|
||||
# make it a valid URL when resolving name
|
||||
mock_gethostbyname.return_value = "8.8.8.8"
|
||||
with patch("apps.webhooks.models.webhook.requests") as mock_requests:
|
||||
execute_webhook(webhook.pk, data)
|
||||
execute_webhook(webhook.pk, alert_group.pk, None)
|
||||
|
||||
assert not mock_requests.post.called
|
||||
log = webhook.logs.all()[0]
|
||||
assert log.response_status is None
|
||||
assert log.response is None
|
||||
assert log.input_data == data
|
||||
expected_data = {
|
||||
"event": {
|
||||
"type": "Resolve",
|
||||
"time": alert_group.resolved_at.isoformat(),
|
||||
},
|
||||
"user": None,
|
||||
"alert_group": IncidentSerializer(alert_group).data,
|
||||
"alert_group_id": alert_group.public_primary_key,
|
||||
"alert_payload": "",
|
||||
}
|
||||
assert log.input_data == expected_data
|
||||
error = getattr(log, log_field_name)
|
||||
assert error == expected_error
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue