Преглед изворни кода

Add type_job_start & type_job_end to Webhook

jeremystretch пре 3 година
родитељ
комит
697feed257

+ 11 - 5
docs/models/extras/webhook.md

@@ -22,11 +22,13 @@ If not selected, the webhook will be inactive.
 
 
 The events which will trigger the webhook. At least one event type must be selected.
 The events which will trigger the webhook. At least one event type must be selected.
 
 
-| Name      | Description                          |
-|-----------|--------------------------------------|
-| Creations | A new object has been created        |
-| Updates   | An existing object has been modified |
-| Deletions | An object has been deleted           |
+| Name       | Description                          |
+|------------|--------------------------------------|
+| Creations  | A new object has been created        |
+| Updates    | An existing object has been modified |
+| Deletions  | An object has been deleted           |
+| Job starts | A job for an object starts           |
+| Job ends   | A job for an object terminates       |
 
 
 ### URL
 ### URL
 
 
@@ -58,6 +60,10 @@ Jinja2 template for a custom request body, if desired. If not defined, NetBox wi
 
 
 A secret string used to prove authenticity of the request (optional). This will append a `X-Hook-Signature` header to the request, consisting of a HMAC (SHA-512) hex digest of the request body using the secret as the key.
 A secret string used to prove authenticity of the request (optional). This will append a `X-Hook-Signature` header to the request, consisting of a HMAC (SHA-512) hex digest of the request body using the secret as the key.
 
 
+### Conditions
+
+A set of [prescribed conditions](../../reference/conditions.md) against which the triggering object will be evaluated. If the conditions are defined but not met by the object, the webhook will not be sent. A webhook that does not define any conditions will _always_ trigger.
+
 ### SSL Verification
 ### SSL Verification
 
 
 Controls whether validation of the receiver's SSL certificate is enforced when HTTPS is used.
 Controls whether validation of the receiver's SSL certificate is enforced when HTTPS is used.

+ 4 - 3
netbox/extras/api/serializers.py

@@ -68,9 +68,10 @@ class WebhookSerializer(ValidatedModelSerializer):
     class Meta:
     class Meta:
         model = Webhook
         model = Webhook
         fields = [
         fields = [
-            'id', 'url', 'display', 'content_types', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url',
-            'enabled', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
-            'conditions', 'ssl_verification', 'ca_file_path', 'created', 'last_updated',
+            'id', 'url', 'display', 'content_types', 'name', 'type_create', 'type_update', 'type_delete',
+            'type_job_start', 'type_job_end', 'payload_url', 'enabled', 'http_method', 'http_content_type',
+            'additional_headers', 'body_template', 'secret', 'conditions', 'ssl_verification', 'ca_file_path',
+            'created', 'last_updated',
         ]
         ]
 
 
 
 

+ 2 - 2
netbox/extras/filtersets.py

@@ -48,8 +48,8 @@ class WebhookFilterSet(BaseFilterSet):
     class Meta:
     class Meta:
         model = Webhook
         model = Webhook
         fields = [
         fields = [
-            'id', 'name', 'type_create', 'type_update', 'type_delete', 'payload_url', 'enabled', 'http_method',
-            'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
+            'id', 'name', 'type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end', 'payload_url',
+            'enabled', 'http_method', 'http_content_type', 'secret', 'ssl_verification', 'ca_file_path',
         ]
         ]
 
 
     def search(self, queryset, name, value):
     def search(self, queryset, name, value):

+ 8 - 0
netbox/extras/forms/bulk_edit.py

@@ -140,6 +140,14 @@ class WebhookBulkEditForm(BulkEditForm):
         required=False,
         required=False,
         widget=BulkEditNullBooleanSelect()
         widget=BulkEditNullBooleanSelect()
     )
     )
+    type_job_start = forms.NullBooleanField(
+        required=False,
+        widget=BulkEditNullBooleanSelect()
+    )
+    type_job_end = forms.NullBooleanField(
+        required=False,
+        widget=BulkEditNullBooleanSelect()
+    )
     http_method = forms.ChoiceField(
     http_method = forms.ChoiceField(
         choices=add_blank_choice(WebhookHttpMethodChoices),
         choices=add_blank_choice(WebhookHttpMethodChoices),
         required=False,
         required=False,

+ 3 - 3
netbox/extras/forms/bulk_import.py

@@ -116,9 +116,9 @@ class WebhookImportForm(CSVModelForm):
     class Meta:
     class Meta:
         model = Webhook
         model = Webhook
         fields = (
         fields = (
-            'name', 'enabled', 'content_types', 'type_create', 'type_update', 'type_delete', 'payload_url',
-            'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret', 'ssl_verification',
-            'ca_file_path'
+            'name', 'enabled', 'content_types', 'type_create', 'type_update', 'type_delete', 'type_job_start',
+            'type_job_end', 'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template',
+            'secret', 'ssl_verification', 'ca_file_path'
         )
         )
 
 
 
 

+ 21 - 4
netbox/extras/forms/filtersets.py

@@ -222,7 +222,7 @@ class WebhookFilterForm(SavedFiltersMixin, FilterForm):
     fieldsets = (
     fieldsets = (
         (None, ('q', 'filter_id')),
         (None, ('q', 'filter_id')),
         ('Attributes', ('content_type_id', 'http_method', 'enabled')),
         ('Attributes', ('content_type_id', 'http_method', 'enabled')),
-        ('Events', ('type_create', 'type_update', 'type_delete')),
+        ('Events', ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
     )
     )
     content_type_id = ContentTypeMultipleChoiceField(
     content_type_id = ContentTypeMultipleChoiceField(
         queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()),
         queryset=ContentType.objects.filter(FeatureQuery('webhooks').get_query()),
@@ -244,19 +244,36 @@ class WebhookFilterForm(SavedFiltersMixin, FilterForm):
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
-        )
+        ),
+        label=_('Object creations')
     )
     )
     type_update = forms.NullBooleanField(
     type_update = forms.NullBooleanField(
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
-        )
+        ),
+        label=_('Object updates')
     )
     )
     type_delete = forms.NullBooleanField(
     type_delete = forms.NullBooleanField(
         required=False,
         required=False,
         widget=forms.Select(
         widget=forms.Select(
             choices=BOOLEAN_WITH_BLANK_CHOICES
             choices=BOOLEAN_WITH_BLANK_CHOICES
-        )
+        ),
+        label=_('Object deletions')
+    )
+    type_job_start = forms.NullBooleanField(
+        required=False,
+        widget=forms.Select(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        ),
+        label=_('Job starts')
+    )
+    type_job_end = forms.NullBooleanField(
+        required=False,
+        widget=forms.Select(
+            choices=BOOLEAN_WITH_BLANK_CHOICES
+        ),
+        label=_('Job terminations')
     )
     )
 
 
 
 

+ 3 - 1
netbox/extras/forms/model_forms.py

@@ -154,7 +154,7 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
 
 
     fieldsets = (
     fieldsets = (
         ('Webhook', ('name', 'content_types', 'enabled')),
         ('Webhook', ('name', 'content_types', 'enabled')),
-        ('Events', ('type_create', 'type_update', 'type_delete')),
+        ('Events', ('type_create', 'type_update', 'type_delete', 'type_job_start', 'type_job_end')),
         ('HTTP Request', (
         ('HTTP Request', (
             'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
             'payload_url', 'http_method', 'http_content_type', 'additional_headers', 'body_template', 'secret',
         )),
         )),
@@ -169,6 +169,8 @@ class WebhookForm(BootstrapMixin, forms.ModelForm):
             'type_create': 'Creations',
             'type_create': 'Creations',
             'type_update': 'Updates',
             'type_update': 'Updates',
             'type_delete': 'Deletions',
             'type_delete': 'Deletions',
+            'type_job_start': 'Job executions',
+            'type_job_end': 'Job terminations',
         }
         }
         widgets = {
         widgets = {
             'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),
             'additional_headers': forms.Textarea(attrs={'class': 'font-monospace'}),

+ 38 - 0
netbox/extras/migrations/0088_jobresult_webhooks.py

@@ -0,0 +1,38 @@
+# Generated by Django 4.1.7 on 2023-02-28 19:11
+
+from django.db import migrations, models
+
+
+class Migration(migrations.Migration):
+
+    dependencies = [
+        ('extras', '0087_dashboard'),
+    ]
+
+    operations = [
+        migrations.AddField(
+            model_name='webhook',
+            name='type_job_end',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AddField(
+            model_name='webhook',
+            name='type_job_start',
+            field=models.BooleanField(default=False),
+        ),
+        migrations.AlterField(
+            model_name='webhook',
+            name='type_create',
+            field=models.BooleanField(default=True),
+        ),
+        migrations.AlterField(
+            model_name='webhook',
+            name='type_delete',
+            field=models.BooleanField(default=True),
+        ),
+        migrations.AlterField(
+            model_name='webhook',
+            name='type_update',
+            field=models.BooleanField(default=True),
+        ),
+    ]

+ 19 - 8
netbox/extras/models/models.py

@@ -5,7 +5,6 @@ from django.conf import settings
 from django.contrib import admin
 from django.contrib import admin
 from django.contrib.auth.models import User
 from django.contrib.auth.models import User
 from django.contrib.contenttypes.fields import GenericForeignKey
 from django.contrib.contenttypes.fields import GenericForeignKey
-from django.contrib.contenttypes.models import ContentType
 from django.core.cache import cache
 from django.core.cache import cache
 from django.core.validators import MinValueValidator, ValidationError
 from django.core.validators import MinValueValidator, ValidationError
 from django.db import models
 from django.db import models
@@ -64,16 +63,24 @@ class Webhook(ExportTemplatesMixin, ChangeLoggedModel):
         unique=True
         unique=True
     )
     )
     type_create = models.BooleanField(
     type_create = models.BooleanField(
-        default=False,
-        help_text=_("Call this webhook when a matching object is created.")
+        default=True,
+        help_text=_("Triggers when a matching object is created.")
     )
     )
     type_update = models.BooleanField(
     type_update = models.BooleanField(
-        default=False,
-        help_text=_("Call this webhook when a matching object is updated.")
+        default=True,
+        help_text=_("Triggers when a matching object is updated.")
     )
     )
     type_delete = models.BooleanField(
     type_delete = models.BooleanField(
+        default=True,
+        help_text=_("Triggers when a matching object is deleted.")
+    )
+    type_job_start = models.BooleanField(
+        default=False,
+        help_text=_("Triggers when a job for a matching object is started.")
+    )
+    type_job_end = models.BooleanField(
         default=False,
         default=False,
-        help_text=_("Call this webhook when a matching object is deleted.")
+        help_text=_("Triggers when a job for a matching object terminates.")
     )
     )
     payload_url = models.CharField(
     payload_url = models.CharField(
         max_length=500,
         max_length=500,
@@ -159,8 +166,12 @@ class Webhook(ExportTemplatesMixin, ChangeLoggedModel):
         super().clean()
         super().clean()
 
 
         # At least one action type must be selected
         # At least one action type must be selected
-        if not self.type_create and not self.type_delete and not self.type_update:
-            raise ValidationError("At least one type must be selected: create, update, and/or delete.")
+        if not any([
+            self.type_create, self.type_update, self.type_delete, self.type_job_start, self.type_job_end
+        ]):
+            raise ValidationError(
+                "At least one event type must be selected: create, update, delete, job_start, and/or job_end."
+            )
 
 
         if self.conditions:
         if self.conditions:
             try:
             try:

+ 11 - 4
netbox/extras/tables/tables.py

@@ -146,6 +146,12 @@ class WebhookTable(NetBoxTable):
     type_delete = columns.BooleanColumn(
     type_delete = columns.BooleanColumn(
         verbose_name='Delete'
         verbose_name='Delete'
     )
     )
+    type_job_start = columns.BooleanColumn(
+        verbose_name='Job start'
+    )
+    type_job_end = columns.BooleanColumn(
+        verbose_name='Job end'
+    )
     ssl_validation = columns.BooleanColumn(
     ssl_validation = columns.BooleanColumn(
         verbose_name='SSL Validation'
         verbose_name='SSL Validation'
     )
     )
@@ -153,12 +159,13 @@ class WebhookTable(NetBoxTable):
     class Meta(NetBoxTable.Meta):
     class Meta(NetBoxTable.Meta):
         model = Webhook
         model = Webhook
         fields = (
         fields = (
-            'pk', 'id', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
-            'payload_url', 'secret', 'ssl_validation', 'ca_file_path', 'created', 'last_updated',
+            'pk', 'id', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete',
+            'type_job_start', 'type_job_end', 'http_method', 'payload_url', 'secret', 'ssl_validation', 'ca_file_path',
+            'created', 'last_updated',
         )
         )
         default_columns = (
         default_columns = (
-            'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'http_method',
-            'payload_url',
+            'pk', 'name', 'content_types', 'enabled', 'type_create', 'type_update', 'type_delete', 'type_job_start',
+            'type_job_end', 'http_method', 'payload_url',
         )
         )
 
 
 
 

+ 49 - 3
netbox/extras/tests/test_filtersets.py

@@ -89,12 +89,16 @@ class WebhookTestCase(TestCase, BaseFilterSetTests):
 
 
     @classmethod
     @classmethod
     def setUpTestData(cls):
     def setUpTestData(cls):
-        content_types = ContentType.objects.filter(model__in=['site', 'rack', 'device'])
+        content_types = ContentType.objects.filter(model__in=['region', 'site', 'rack', 'location', 'device'])
 
 
         webhooks = (
         webhooks = (
             Webhook(
             Webhook(
                 name='Webhook 1',
                 name='Webhook 1',
                 type_create=True,
                 type_create=True,
+                type_update=False,
+                type_delete=False,
+                type_job_start=False,
+                type_job_end=False,
                 payload_url='http://example.com/?1',
                 payload_url='http://example.com/?1',
                 enabled=True,
                 enabled=True,
                 http_method='GET',
                 http_method='GET',
@@ -102,7 +106,11 @@ class WebhookTestCase(TestCase, BaseFilterSetTests):
             ),
             ),
             Webhook(
             Webhook(
                 name='Webhook 2',
                 name='Webhook 2',
+                type_create=False,
                 type_update=True,
                 type_update=True,
+                type_delete=False,
+                type_job_start=False,
+                type_job_end=False,
                 payload_url='http://example.com/?2',
                 payload_url='http://example.com/?2',
                 enabled=True,
                 enabled=True,
                 http_method='POST',
                 http_method='POST',
@@ -110,26 +118,56 @@ class WebhookTestCase(TestCase, BaseFilterSetTests):
             ),
             ),
             Webhook(
             Webhook(
                 name='Webhook 3',
                 name='Webhook 3',
+                type_create=False,
+                type_update=False,
                 type_delete=True,
                 type_delete=True,
+                type_job_start=False,
+                type_job_end=False,
                 payload_url='http://example.com/?3',
                 payload_url='http://example.com/?3',
                 enabled=False,
                 enabled=False,
                 http_method='PATCH',
                 http_method='PATCH',
                 ssl_verification=False,
                 ssl_verification=False,
             ),
             ),
+            Webhook(
+                name='Webhook 4',
+                type_create=False,
+                type_update=False,
+                type_delete=False,
+                type_job_start=True,
+                type_job_end=False,
+                payload_url='http://example.com/?4',
+                enabled=False,
+                http_method='PATCH',
+                ssl_verification=False,
+            ),
+            Webhook(
+                name='Webhook 5',
+                type_create=False,
+                type_update=False,
+                type_delete=False,
+                type_job_start=False,
+                type_job_end=True,
+                payload_url='http://example.com/?5',
+                enabled=False,
+                http_method='PATCH',
+                ssl_verification=False,
+            ),
         )
         )
         Webhook.objects.bulk_create(webhooks)
         Webhook.objects.bulk_create(webhooks)
         webhooks[0].content_types.add(content_types[0])
         webhooks[0].content_types.add(content_types[0])
         webhooks[1].content_types.add(content_types[1])
         webhooks[1].content_types.add(content_types[1])
         webhooks[2].content_types.add(content_types[2])
         webhooks[2].content_types.add(content_types[2])
+        webhooks[3].content_types.add(content_types[3])
+        webhooks[4].content_types.add(content_types[4])
 
 
     def test_name(self):
     def test_name(self):
         params = {'name': ['Webhook 1', 'Webhook 2']}
         params = {'name': ['Webhook 1', 'Webhook 2']}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
 
 
     def test_content_types(self):
     def test_content_types(self):
-        params = {'content_types': 'dcim.site'}
+        params = {'content_types': 'dcim.region'}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
-        params = {'content_type_id': [ContentType.objects.get_for_model(Site).pk]}
+        params = {'content_type_id': [ContentType.objects.get_for_model(Region).pk]}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
     def test_type_create(self):
     def test_type_create(self):
@@ -144,6 +182,14 @@ class WebhookTestCase(TestCase, BaseFilterSetTests):
         params = {'type_delete': True}
         params = {'type_delete': True}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
 
 
+    def test_type_job_start(self):
+        params = {'type_job_start': True}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
+    def test_type_job_end(self):
+        params = {'type_job_end': True}
+        self.assertEqual(self.filterset(params, self.queryset).qs.count(), 1)
+
     def test_enabled(self):
     def test_enabled(self):
         params = {'enabled': True}
         params = {'enabled': True}
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)
         self.assertEqual(self.filterset(params, self.queryset).qs.count(), 2)

+ 8 - 0
netbox/templates/extras/webhook.html

@@ -40,6 +40,14 @@
             <th scope="row">Delete</th>
             <th scope="row">Delete</th>
             <td>{% checkmark object.type_delete %}</td>
             <td>{% checkmark object.type_delete %}</td>
           </tr>
           </tr>
+          <tr>
+            <th scope="row">Job start</th>
+            <td>{% checkmark object.type_job_start %}</td>
+          </tr>
+          <tr>
+            <th scope="row">Job end</th>
+            <td>{% checkmark object.type_job_end %}</td>
+          </tr>
         </table>
         </table>
       </div>
       </div>
     </div>
     </div>