From 4de64d783e881cd90ba9df6603feaa399f8a6408 Mon Sep 17 00:00:00 2001 From: jeremystretch Date: Tue, 28 Feb 2023 16:21:01 -0500 Subject: [PATCH] Add trigger_webhooks() to JobResult --- netbox/extras/constants.py | 10 ++++++- netbox/extras/models/models.py | 47 +++++++++++++++++++++++++++----- netbox/extras/webhooks_worker.py | 11 +++++--- 3 files changed, 56 insertions(+), 12 deletions(-) diff --git a/netbox/extras/constants.py b/netbox/extras/constants.py index 12ff21b31..d64f02d6b 100644 --- a/netbox/extras/constants.py +++ b/netbox/extras/constants.py @@ -1,8 +1,16 @@ from django.contrib.contenttypes.models import ContentType -# Webhook content types +# Webhooks HTTP_CONTENT_TYPE_JSON = 'application/json' +WEBHOOK_EVENT_TYPES = { + 'create': 'created', + 'update': 'updated', + 'delete': 'deleted', + 'job_start': 'job_started', + 'job_end': 'job_ended', +} + # Dashboard DEFAULT_DASHBOARD = [ { diff --git a/netbox/extras/models/models.py b/netbox/extras/models/models.py index 4de0e3ef2..e2a40a8ce 100644 --- a/netbox/extras/models/models.py +++ b/netbox/extras/models/models.py @@ -26,7 +26,7 @@ from netbox.constants import RQ_QUEUE_DEFAULT from netbox.models import ChangeLoggedModel from netbox.models.features import ( CloningMixin, CustomFieldsMixin, CustomLinksMixin, ExportTemplatesMixin, JobResultsMixin, SyncedDataMixin, - TagsMixin, + TagsMixin, WebhooksMixin, ) from utilities.querysets import RestrictedQuerySet from utilities.utils import render_jinja2 @@ -689,10 +689,16 @@ class JobResult(models.Model): """ Record the job's start time and update its status to "running." """ - if self.started is None: - self.started = timezone.now() - self.status = JobResultStatusChoices.STATUS_RUNNING - JobResult.objects.filter(pk=self.pk).update(started=self.started, status=self.status) + if self.started is not None: + return + + # Start the job + self.started = timezone.now() + self.status = JobResultStatusChoices.STATUS_RUNNING + JobResult.objects.filter(pk=self.pk).update(started=self.started, status=self.status) + + # Handle webhooks + self.trigger_webhooks(event='job_start') def terminate(self, status=JobResultStatusChoices.STATUS_COMPLETED): """ @@ -701,10 +707,15 @@ class JobResult(models.Model): valid_statuses = JobResultStatusChoices.TERMINAL_STATE_CHOICES if status not in valid_statuses: raise ValueError(f"Invalid status for job termination. Choices are: {', '.join(valid_statuses)}") + + # Mark the job as completed self.status = status self.completed = timezone.now() JobResult.objects.filter(pk=self.pk).update(status=self.status, completed=self.completed) + # Handle webhooks + self.trigger_webhooks(event='job_end') + @classmethod def enqueue_job(cls, func, name, obj_type, user, schedule_at=None, interval=None, *args, **kwargs): """ @@ -738,6 +749,28 @@ class JobResult(models.Model): return job_result + def trigger_webhooks(self, event): + rq_queue_name = get_config().QUEUE_MAPPINGS.get('webhook', RQ_QUEUE_DEFAULT) + rq_queue = django_rq.get_queue(rq_queue_name, is_async=False) + + # Fetch any webhooks matching this object type and action + webhooks = Webhook.objects.filter( + **{f'type_{event}': True}, + content_types=self.obj_type, + enabled=True + ) + + for webhook in webhooks: + rq_queue.enqueue( + "extras.webhooks_worker.process_webhook", + webhook=webhook, + model_name=self.obj_type.model, + event=event, + data=self.data, + timestamp=str(timezone.now()), + username=self.user.username + ) + class ConfigRevision(models.Model): """ @@ -780,7 +813,7 @@ class ConfigRevision(models.Model): # Custom scripts & reports # -class Script(JobResultsMixin, models.Model): +class Script(JobResultsMixin, WebhooksMixin, models.Model): """ Dummy model used to generate permissions for custom scripts. Does not exist in the database. """ @@ -792,7 +825,7 @@ class Script(JobResultsMixin, models.Model): # Reports # -class Report(JobResultsMixin, models.Model): +class Report(JobResultsMixin, WebhooksMixin, models.Model): """ Dummy model used to generate permissions for reports. Does not exist in the database. """ diff --git a/netbox/extras/webhooks_worker.py b/netbox/extras/webhooks_worker.py index 7e8965182..438231b7e 100644 --- a/netbox/extras/webhooks_worker.py +++ b/netbox/extras/webhooks_worker.py @@ -5,8 +5,8 @@ from django.conf import settings from django_rq import job from jinja2.exceptions import TemplateError -from .choices import ObjectChangeActionChoices from .conditions import ConditionSet +from .constants import WEBHOOK_EVENT_TYPES from .webhooks import generate_signature logger = logging.getLogger('netbox.webhooks_worker') @@ -28,7 +28,7 @@ def eval_conditions(webhook, data): @job('default') -def process_webhook(webhook, model_name, event, data, snapshots, timestamp, username, request_id): +def process_webhook(webhook, model_name, event, data, timestamp, username, request_id=None, snapshots=None): """ Make a POST request to the defined Webhook """ @@ -38,14 +38,17 @@ def process_webhook(webhook, model_name, event, data, snapshots, timestamp, user # Prepare context data for headers & body templates context = { - 'event': dict(ObjectChangeActionChoices)[event].lower(), + 'event': WEBHOOK_EVENT_TYPES[event], 'timestamp': timestamp, 'model': model_name, 'username': username, 'request_id': request_id, 'data': data, - 'snapshots': snapshots, } + if snapshots: + context.update({ + 'snapshots': snapshots + }) # Build the headers for the HTTP request headers = {