From ac0550a2bd9c3ddb1bfdaf0b7f749bbcc0562398 Mon Sep 17 00:00:00 2001 From: tdruez Date: Wed, 24 Jul 2024 10:55:37 +0400 Subject: [PATCH] Add support for new Webhook model in API #1325 Signed-off-by: tdruez --- scanpipe/api/serializers.py | 25 ++++++++++++++++++++++--- scanpipe/models.py | 29 ++++++++++++++++++----------- scanpipe/tests/test_models.py | 11 ++++++----- 3 files changed, 46 insertions(+), 19 deletions(-) diff --git a/scanpipe/api/serializers.py b/scanpipe/api/serializers.py index fc8e75c1f..4d4126172 100644 --- a/scanpipe/api/serializers.py +++ b/scanpipe/api/serializers.py @@ -35,6 +35,7 @@ from scanpipe.models import InputSource from scanpipe.models import Project from scanpipe.models import ProjectMessage +from scanpipe.models import WebhookSubscription from scanpipe.models import Run from scanpipe.pipes import count_group_by @@ -143,6 +144,18 @@ class Meta: ] +class WebhookSubscriptionSerializer(serializers.ModelSerializer): + class Meta: + model = WebhookSubscription + fields = [ + "target_url", + "trigger_on_each_run", + "include_summary", + "include_results", + "is_active", + ] + + class ProjectSerializer( ExcludeFromListViewMixin, SerializerExcludeFieldsMixin, @@ -167,6 +180,7 @@ class ProjectSerializer( style={"base_template": "textarea.html"}, ) webhook_url = serializers.CharField(write_only=True, required=False) + webhooks = WebhookSubscriptionSerializer(many=True, write_only=True) next_run = serializers.CharField(source="get_next_run", read_only=True) runs = RunSerializer(many=True, read_only=True) input_sources = InputSourceSerializer( @@ -192,6 +206,7 @@ class Meta: "upload_file_tag", "input_urls", "webhook_url", + "webhooks", "created_date", "is_archived", "notes", @@ -289,6 +304,7 @@ def create(self, validated_data): pipeline = validated_data.pop("pipeline", []) execute_now = validated_data.pop("execute_now", False) webhook_url = validated_data.pop("webhook_url", None) + webhooks = validated_data.pop("webhooks", []) project = super().create(validated_data) @@ -298,12 +314,15 @@ def create(self, validated_data): for url in input_urls: project.add_input_source(download_url=url) - if webhook_url: - project.add_webhook_subscription(webhook_url) - for pipeline_name in pipeline: project.add_pipeline(pipeline_name, execute_now) + if webhook_url: + project.add_webhook_subscription(target_url=webhook_url) + + for webhook_data in webhooks: + project.add_webhook_subscription(**webhook_data) + return project diff --git a/scanpipe/models.py b/scanpipe/models.py index 844deac6b..598158c91 100644 --- a/scanpipe/models.py +++ b/scanpipe/models.py @@ -699,35 +699,33 @@ def clone( execute_now=False, ): """Clone this project using the provided ``clone_name`` as new project name.""" - cloned_project = Project.objects.create( + new_project = Project.objects.create( name=clone_name, settings=self.settings if copy_settings else {}, ) if labels := self.labels.names(): - cloned_project.labels.add(*labels) + new_project.labels.add(*labels) if copy_inputs: # Clone the InputSource instances for input_source in self.inputsources.all(): - input_source.pk = None - input_source.project = cloned_project - input_source.save() + input_source.clone(to_project=new_project) # Copy the files from the input work directory for input_location in self.inputs(): - cloned_project.copy_input_from(input_location) + new_project.copy_input_from(input_location) if copy_pipelines: for run in self.runs.all(): - cloned_project.add_pipeline( + new_project.add_pipeline( run.pipeline_name, execute_now, selected_groups=run.selected_groups ) if copy_subscriptions: for subscription in self.webhooksubscriptions.all(): - cloned_project.add_webhook_subscription(subscription.target_url) + subscription.clone(to_project=new_project) - return cloned_project + return new_project def _raise_if_run_in_progress(self): """ @@ -1184,12 +1182,12 @@ def add_pipeline(self, pipeline_name, execute_now=False, selected_groups=None): return run - def add_webhook_subscription(self, target_url): + def add_webhook_subscription(self, **kwargs): """ Create a new WebhookSubscription instance with the provided `target_url` for the current project. """ - return WebhookSubscription.objects.create(project=self, target_url=target_url) + return WebhookSubscription.objects.create(project=self, **kwargs) @cached_property def can_start_pipelines(self): @@ -1497,6 +1495,15 @@ class Meta: def model_fields(cls): return [field.name for field in cls._meta.get_fields()] + def clone(self, to_project): + """Clone this instance as a new instance of the provided ``to_project``.""" + if to_project == self.project: + raise ValueError("Cannot clone instance into the same project.") + + self.pk = None + self.project = to_project + self.save() + class ProjectMessage(UUIDPKModel, ProjectRelatedModel): """Stores messages such as errors and exceptions raised during a pipeline run.""" diff --git a/scanpipe/tests/test_models.py b/scanpipe/tests/test_models.py index b3d149d95..72a51a476 100644 --- a/scanpipe/tests/test_models.py +++ b/scanpipe/tests/test_models.py @@ -235,7 +235,7 @@ def test_scanpipe_project_model_clone(self): new_file_path1.touch() run1 = self.project1.add_pipeline("analyze_docker_image", selected_groups=["g"]) run2 = self.project1.add_pipeline("find_vulnerabilities") - subscription1 = self.project1.add_webhook_subscription("http://domain.url") + subscription1 = self.project1.add_webhook_subscription(target_url="http://domain.url") cloned_project = self.project1.clone("cloned project") self.assertIsInstance(cloned_project, Project) @@ -535,7 +535,7 @@ def test_scanpipe_project_model_add_uploads(self): def test_scanpipe_project_model_add_webhook_subscription(self): self.assertEqual(0, self.project1.webhooksubscriptions.count()) - self.project1.add_webhook_subscription("https://localhost") + self.project1.add_webhook_subscription(target_url="https://localhost") self.assertEqual(1, self.project1.webhooksubscriptions.count()) def test_scanpipe_project_model_get_next_run(self): @@ -1199,7 +1199,7 @@ def test_scanpipe_run_model_append_to_log(self): @mock.patch("scanpipe.models.WebhookSubscription.deliver") def test_scanpipe_run_model_deliver_project_subscriptions(self, mock_deliver): - self.project1.add_webhook_subscription("https://localhost") + self.project1.add_webhook_subscription(target_url="https://localhost") run1 = self.create_run() run1.deliver_project_subscriptions() mock_deliver.assert_called_once_with(pipeline_run=run1) @@ -1912,7 +1912,8 @@ def test_scanpipe_codebase_resource_model_walk_method_problematic_filenames(self @mock.patch("requests.post") def test_scanpipe_webhook_subscription_model_deliver_method(self, mock_post): - webhook = self.project1.add_webhook_subscription("https://localhost") + webhook = self.project1.add_webhook_subscription(target_url="https://localhost") + self.assertFalse(webhook.delivered) run1 = self.create_run() @@ -1939,7 +1940,7 @@ def test_scanpipe_webhook_subscription_model_deliver_method(self, mock_post): self.assertEqual("text", webhook.response_text) def test_scanpipe_webhook_subscription_model_get_payload(self): - webhook = self.project1.add_webhook_subscription("https://localhost") + webhook = self.project1.add_webhook_subscription(target_url="https://localhost") run1 = self.create_run() payload = webhook.get_payload(run1)