diff --git a/core/tasks/common.py b/core/tasks/common.py new file mode 100644 index 00000000..57fe3801 --- /dev/null +++ b/core/tasks/common.py @@ -0,0 +1 @@ +DISPATCHERD_DEFAULT_CHANNEL = "pattern-service-tasks" diff --git a/core/tasks/demo.py b/core/tasks/demo.py index 338ba0f8..e5fd943c 100644 --- a/core/tasks/demo.py +++ b/core/tasks/demo.py @@ -1,7 +1,7 @@ from dispatcherd.publish import submit_task from dispatcherd.publish import task -DISPATCHERD_DEFAULT_CHANNEL = "pattern-service-tasks" +from core.tasks.common import DISPATCHERD_DEFAULT_CHANNEL @task(queue=DISPATCHERD_DEFAULT_CHANNEL, decorate=False) diff --git a/core/task_runner.py b/core/tasks/pattern.py similarity index 89% rename from core/task_runner.py rename to core/tasks/pattern.py index 57a164ce..8ff77493 100644 --- a/core/task_runner.py +++ b/core/tasks/pattern.py @@ -3,6 +3,12 @@ import os from contextlib import closing +from dispatcherd.publish import task + +from core.models import Pattern +from core.models import PatternInstance +from core.models import Task +from core.tasks.common import DISPATCHERD_DEFAULT_CHANNEL from core.utils.controller import assign_execute_roles from core.utils.controller import build_collection_uri from core.utils.controller import create_execution_environment @@ -13,14 +19,11 @@ from core.utils.controller import get_http_session from core.utils.controller import save_instance_state -from .models import Pattern -from .models import PatternInstance -from .models import Task - logger = logging.getLogger(__name__) -def run_pattern_task(pattern_id: int, task_id: int) -> None: +@task(queue=DISPATCHERD_DEFAULT_CHANNEL, decorate=False) +def pattern_create(pattern_id: int, task_id: int) -> None: """ Orchestrates downloading a collection and saving a pattern definition. @@ -66,7 +69,8 @@ def run_pattern_task(pattern_id: int, task_id: int) -> None: task.mark_failed({"error": error_message}) -def run_pattern_instance_task(instance_id: int, task_id: int) -> None: +@task(queue=DISPATCHERD_DEFAULT_CHANNEL, decorate=False) +def pattern_instance_create(instance_id: int, task_id: int) -> None: task = Task.objects.get(id=task_id) try: instance = PatternInstance.objects.select_related("pattern").get(id=instance_id) diff --git a/core/tests/conftest.py b/core/tests/conftest.py index 110f6825..b4916eb7 100644 --- a/core/tests/conftest.py +++ b/core/tests/conftest.py @@ -13,6 +13,7 @@ def client(): @pytest.fixture() def automation(db, pattern_instance) -> models.Automation: + models.Automation.objects.all().delete() automation = models.Automation.objects.create( automation_type=api_examples.automation_get_response.value["automation_type"], automation_id=api_examples.automation_get_response.value["automation_id"], @@ -24,6 +25,7 @@ def automation(db, pattern_instance) -> models.Automation: @pytest.fixture() def controller_label(db) -> models.ControllerLabel: + models.ControllerLabel.objects.all().delete() controller_label = models.ControllerLabel.objects.create( label_id=api_examples.controller_label_get_response.value["label_id"] ) @@ -32,6 +34,7 @@ def controller_label(db) -> models.ControllerLabel: @pytest.fixture() def pattern(db) -> models.Pattern: + models.Pattern.objects.all().delete() pattern = models.Pattern.objects.create( collection_name=api_examples.pattern_post_request.value["collection_name"], collection_version=api_examples.pattern_post_request.value[ @@ -44,6 +47,7 @@ def pattern(db) -> models.Pattern: @pytest.fixture() def pattern_instance(db, pattern) -> models.PatternInstance: + models.PatternInstance.objects.all().delete() pattern_instance = models.PatternInstance.objects.create( credentials=api_examples.pattern_instance_post_request.value["credentials"], executors=api_examples.pattern_instance_post_request.value["executors"], @@ -55,6 +59,7 @@ def pattern_instance(db, pattern) -> models.PatternInstance: @pytest.fixture() def task(db) -> models.Task: + models.Task.objects.all().delete() task = models.Task.objects.create( status=api_examples.task_get_response.value["status"], details=api_examples.task_get_response.value["details"], diff --git a/core/tests/integration/collections/cloud-testing-4.3.2.tar.gz b/core/tests/integration/collections/cloud-testing-4.3.2.tar.gz new file mode 100644 index 00000000..655cc3c6 Binary files /dev/null and b/core/tests/integration/collections/cloud-testing-4.3.2.tar.gz differ diff --git a/core/tests/integration/test_pattern.py b/core/tests/integration/test_pattern.py new file mode 100644 index 00000000..cd72396e --- /dev/null +++ b/core/tests/integration/test_pattern.py @@ -0,0 +1,22 @@ +import pytest +from rest_framework import status +from rest_framework.test import APIClient + + +@pytest.mark.django_db +def test_create_pattern(): + client = APIClient() + data = { + "collection_name": "cloud.testing", + "collection_version": "4.3.2", + "pattern_name": "new_pattern", + } + response = client.post("/api/pattern-service/v1/patterns/", data, format="json") + + assert response.status_code == status.HTTP_202_ACCEPTED + assert "task_id" in response.data + assert "message" in response.data + assert ( + response.data["message"] + == "Pattern creation initiated. Check task status for progress." + ) diff --git a/core/tests/test_api_v1.py b/core/tests/test_api_v1.py index b1a646f2..051c7fa8 100644 --- a/core/tests/test_api_v1.py +++ b/core/tests/test_api_v1.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + import pytest from freezegun import freeze_time from rest_framework import status @@ -15,14 +17,50 @@ def test_retrieve_automation_success(client, automation): url = f"/api/pattern-service/v1/automations/{automation.pk}/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == api_examples.automation_get_response.value + expected = api_examples.automation_get_response.value + pattern_instance_id = response.json().get("pattern_instance") + expected.update( + { + "url": f"/api/pattern-service/v1/automations/{automation.pk}/", + "id": automation.pk, + "pattern_instance": pattern_instance_id, + "related": { + "pattern_instance": ( + f"/api/pattern-service/v1/pattern_instances/{pattern_instance_id}/" + ), + }, + "summary_fields": {"pattern_instance": {"id": 1}}, + } + ) + assert response.json() == expected def test_list_automations_success(client, automation): url = "/api/pattern-service/v1/automations/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == [api_examples.automation_get_response.value] + expected = api_examples.automation_get_response.value + expected.update( + { + "url": f"/api/pattern-service/v1/automations/{automation.pk}/", + "id": automation.pk, + } + ) + pattern_instance_id = response.json()[0].get("pattern_instance") + expected.update( + { + "url": f"/api/pattern-service/v1/automations/{automation.pk}/", + "id": automation.pk, + "pattern_instance": pattern_instance_id, + "related": { + "pattern_instance": ( + f"/api/pattern-service/v1/pattern_instances/{pattern_instance_id}/" + ), + }, + "summary_fields": {"pattern_instance": {"id": pattern_instance_id}}, + } + ) + assert response.json() == [expected] def test_retrieve_controller_label_success(client, controller_label): @@ -36,21 +74,36 @@ def test_list_controller_labels_success(client, controller_label): url = "/api/pattern-service/v1/controller_labels/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == [api_examples.controller_label_get_response.value] - - -def test_create_pattern_success(client, db): + expected = api_examples.controller_label_get_response.value + expected.update( + { + "url": f"/api/pattern-service/v1/controller_labels/{controller_label.pk}/", + "id": controller_label.pk, + } + ) + assert response.json() == [expected] + + +@patch("core.views.submit_task", return_value=False) +def test_create_pattern_success(mock_submit_task, client, db): url = "/api/pattern-service/v1/patterns/" data = api_examples.pattern_post_request.value response = client.post(url, data, format="json") assert response.status_code == status.HTTP_202_ACCEPTED - assert response.json() == api_examples.pattern_post_response.value + assert response.json().get( + "message" + ) == api_examples.pattern_post_response.value.get("message") + assert isinstance(response.json().get("task_id"), int) def test_retrieve_pattern_success(client, pattern): url = f"/api/pattern-service/v1/patterns/{pattern.pk}/" response = client.get(url) assert response.status_code == status.HTTP_200_OK + expected = api_examples.pattern_get_response.value + expected.update( + {"url": f"/api/pattern-service/v1/patterns/{pattern.pk}/", "id": pattern.pk} + ) assert response.json() == api_examples.pattern_get_response.value @@ -58,16 +111,24 @@ def test_list_patterns_success(client, pattern): url = "/api/pattern-service/v1/patterns/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == [api_examples.pattern_get_response.value] + expected = api_examples.pattern_get_response.value + expected.update( + {"url": f"/api/pattern-service/v1/patterns/{pattern.pk}/", "id": pattern.pk} + ) + assert response.json() == [expected] -def test_create_pattern_instance_success(client, pattern): +@patch("core.views.submit_task", return_value=False) +def test_create_pattern_instance_success(mock_submit_task, client, pattern): url = "/api/pattern-service/v1/pattern_instances/" data = api_examples.pattern_instance_post_request.value data["pattern"] = pattern.pk response = client.post(url, data, format="json") assert response.status_code == status.HTTP_202_ACCEPTED - assert response.json() == api_examples.pattern_instance_post_response.value + assert response.json().get( + "message" + ) == api_examples.pattern_instance_post_response.value.get("message") + assert isinstance(response.json().get("task_id"), int) def test_retrieve_pattern_instance_success(client, controller_label, pattern_instance): @@ -75,7 +136,20 @@ def test_retrieve_pattern_instance_success(client, controller_label, pattern_ins url = f"/api/pattern-service/v1/pattern_instances/{pattern_instance.pk}/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == api_examples.pattern_instance_get_response.value + pattern_id = response.json().get("pattern") + assert isinstance(pattern_id, int) + expected = api_examples.pattern_instance_get_response.value + expected.update( + { + "url": f"/api/pattern-service/v1/pattern_instances/{pattern_instance.pk}/", + "id": pattern_instance.pk, + "controller_labels": [controller_label.id], + "summary_fields": {"pattern": {"id": pattern_id}}, + "related": {"pattern": f"/api/pattern-service/v1/patterns/{pattern_id}/"}, + "pattern": pattern_id, + } + ) + assert response.json() == expected def test_list_pattern_instances_success(client, controller_label, pattern_instance): @@ -83,13 +157,28 @@ def test_list_pattern_instances_success(client, controller_label, pattern_instan url = "/api/pattern-service/v1/pattern_instances/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == [api_examples.pattern_instance_get_response.value] + pattern_id = response.json()[0].get("pattern") + assert isinstance(pattern_id, int) + expected = api_examples.pattern_instance_get_response.value + expected.update( + { + "url": f"/api/pattern-service/v1/pattern_instances/{pattern_instance.pk}/", + "id": pattern_instance.pk, + "controller_labels": [controller_label.id], + "summary_fields": {"pattern": {"id": pattern_id}}, + "related": {"pattern": f"/api/pattern-service/v1/patterns/{pattern_id}/"}, + "pattern": pattern_id, + } + ) + assert response.json() == [expected] def test_retrieve_task_success(client, task): url = f"/api/pattern-service/v1/tasks/{task.pk}/" response = client.get(url) assert response.status_code == status.HTTP_200_OK + expected = api_examples.task_get_response.value + expected.update({"url": url, "id": task.pk}) assert response.json() == api_examples.task_get_response.value @@ -97,4 +186,6 @@ def test_list_tasks_success(client, task): url = "/api/pattern-service/v1/tasks/" response = client.get(url) assert response.status_code == status.HTTP_200_OK - assert response.json() == [api_examples.task_get_response.value] + expected = api_examples.task_get_response.value + expected.update({"url": f"/api/pattern-service/v1/tasks/{task.pk}/", "id": task.pk}) + assert response.json() == [expected] diff --git a/core/tests/test_tasks.py b/core/tests/test_pattern_tasks.py similarity index 82% rename from core/tests/test_tasks.py rename to core/tests/test_pattern_tasks.py index 1158d166..c4f92c1f 100644 --- a/core/tests/test_tasks.py +++ b/core/tests/test_pattern_tasks.py @@ -14,8 +14,8 @@ from core.models import Pattern from core.models import PatternInstance from core.models import Task -from core.task_runner import run_pattern_instance_task -from core.task_runner import run_pattern_task +from core.tasks.pattern import pattern_create +from core.tasks.pattern import pattern_instance_create class SharedDataMixin: @@ -82,12 +82,12 @@ def tearDown(self): class PatternTaskTest(SharedDataMixin, TestCase): @patch("core.models.Task.set_status", autospec=True, wraps=Task.set_status) - @patch("core.task_runner.download_collection") - def test_run_pattern_task_success(self, mock_download, mock_update_status): + @patch("core.tasks.pattern.download_collection") + def test_pattern_create_success(self, mock_download, mock_update_status): temp_dir_path = self.create_temp_collection_dir() mock_download.return_value.__enter__.return_value = temp_dir_path - run_pattern_task(self.pattern.id, self.task.id) + pattern_create(self.pattern.id, self.task.id) expected_calls = [ (self.task, "Running", {"info": "Processing pattern"}), @@ -115,8 +115,8 @@ def test_run_pattern_task_success(self, mock_download, mock_update_status): self.assertEqual(self.pattern.pattern_definition, {"mock_key": "mock_value"}) @patch("core.models.Task.set_status", autospec=True) - @patch("core.task_runner.download_collection", side_effect=FileNotFoundError) - def test_run_pattern_task_file_not_found(self, mock_download, mock_update_status): + @patch("core.tasks.pattern.download_collection", side_effect=FileNotFoundError) + def test_pattern_create_file_not_found(self, mock_download, mock_update_status): pattern = Pattern.objects.create( collection_name="demo.collection", collection_version="1.0.0", @@ -124,30 +124,31 @@ def test_run_pattern_task_file_not_found(self, mock_download, mock_update_status ) task = Task.objects.create(status="Initiated", details={}) - run_pattern_task(pattern.id, task.id) + pattern_create(pattern.id, task.id) mock_update_status.assert_called_with( task, "Failed", {"error": "Pattern definition not found."} ) @patch( - "core.task_runner.download_collection", side_effect=Exception("Download failed") + "core.tasks.pattern.download_collection", + side_effect=Exception("Download failed"), ) - def test_run_pattern_task_handles_download_failure(self, mock_download): - run_pattern_task(self.pattern.id, self.task.id) + def test_pattern_create_handles_download_failure(self, mock_download): + pattern_create(self.pattern.id, self.task.id) self.task.refresh_from_db() self.assertEqual(self.task.status, "Failed") self.assertIn("Download failed", self.task.details.get("error", "")) class PatternInstanceTaskTest(SharedDataMixin, TestCase): - @patch("core.task_runner.get_http_session") - @patch("core.task_runner.assign_execute_roles") - @patch("core.task_runner.save_instance_state") - @patch("core.task_runner.create_job_templates") - @patch("core.task_runner.create_labels") - @patch("core.task_runner.create_execution_environment") - @patch("core.task_runner.create_project") + @patch("core.tasks.pattern.get_http_session") + @patch("core.tasks.pattern.assign_execute_roles") + @patch("core.tasks.pattern.save_instance_state") + @patch("core.tasks.pattern.create_job_templates") + @patch("core.tasks.pattern.create_labels") + @patch("core.tasks.pattern.create_execution_environment") + @patch("core.tasks.pattern.create_project") @patch("core.models.Task.set_status", autospec=True) def test_run_pattern_instance_success( self, @@ -168,7 +169,7 @@ def test_run_pattern_instance_success( mock_create_labels.side_effect = [[]] mock_create_jts.side_effect = [[]] - run_pattern_instance_task( + pattern_instance_create( instance_id=self.pattern_instance.id, task_id=self.task.id, ) @@ -198,12 +199,12 @@ def test_run_pattern_instance_success( mock_save_instance.assert_called_once() mock_assign_roles.assert_called_once() - @patch("core.task_runner.assign_execute_roles") - @patch("core.task_runner.save_instance_state") - @patch("core.task_runner.create_job_templates") - @patch("core.task_runner.create_labels") - @patch("core.task_runner.create_execution_environment") - @patch("core.task_runner.create_project") + @patch("core.tasks.pattern.assign_execute_roles") + @patch("core.tasks.pattern.save_instance_state") + @patch("core.tasks.pattern.create_job_templates") + @patch("core.tasks.pattern.create_labels") + @patch("core.tasks.pattern.create_execution_environment") + @patch("core.tasks.pattern.create_project") @patch("core.models.Task.set_status", autospec=True) def test_failure_path( self, @@ -215,7 +216,7 @@ def test_failure_path( mock_create_project.side_effect = RuntimeError("error") # No exception should propagate because the task function swallows it - run_pattern_instance_task( + pattern_instance_create( instance_id=self.pattern_instance.id, task_id=self.task.id, ) diff --git a/core/tests/test_views.py b/core/tests/test_views.py index f7e109d0..acf1e376 100644 --- a/core/tests/test_views.py +++ b/core/tests/test_views.py @@ -1,3 +1,5 @@ +from unittest.mock import patch + from django.urls import reverse from rest_framework import status from rest_framework.test import APITestCase @@ -60,7 +62,8 @@ def test_pattern_detail_view(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["collection_name"], "mynamespace.mycollection") - def test_pattern_create_view(self): + @patch("core.views.submit_task", return_value=False) + def test_pattern_create_view(self, mock_submit_task): url = reverse("pattern-list") data = { "collection_name": "new.namespace.collection", @@ -72,6 +75,8 @@ def test_pattern_create_view(self): response = self.client.post(url, data, format="json") self.assertEqual(response.status_code, status.HTTP_202_ACCEPTED) + mock_submit_task.assert_called_once() + # Pattern created pattern = Pattern.objects.get(pattern_name="new_pattern") self.assertIsNotNone(pattern) @@ -149,7 +154,8 @@ def test_pattern_instance_detail_view(self): self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["organization_id"], 1) - def test_pattern_instance_create_view(self): + @patch("core.views.submit_task", return_value=False) + def test_pattern_instance_create_view(self, mock_submit_task): url = reverse("pattern_instance-list") data = { "organization_id": 2, @@ -170,6 +176,9 @@ def test_pattern_instance_create_view(self): self.assertEqual(instance.organization_id, 2) self.assertEqual(instance.credentials["user"], "tester") + # assert dispatcher task created + mock_submit_task.assert_called_once() + # Task id returned directly task_id = response.data.get("task_id") self.assertIsInstance(task_id, int) diff --git a/core/views.py b/core/views.py index ddfca669..68258c13 100644 --- a/core/views.py +++ b/core/views.py @@ -1,6 +1,7 @@ import uuid from ansible_base.lib.utils.views.ansible_base import AnsibleBaseView +from dispatcherd.publish import submit_task from drf_spectacular.utils import extend_schema from drf_spectacular.utils import extend_schema_view from rest_framework import status @@ -21,7 +22,10 @@ from core.serializers import PatternInstanceSerializer from core.serializers import PatternSerializer from core.serializers import TaskSerializer +from core.tasks.common import DISPATCHERD_DEFAULT_CHANNEL from core.tasks.demo import sumbit_hello_world +from core.tasks.pattern import pattern_create +from core.tasks.pattern import pattern_instance_create class CoreViewSet(AnsibleBaseView): @@ -61,6 +65,15 @@ def create(self, request: Request, *args: tuple, **kwargs: dict) -> Response: status="Initiated", details={"model": "Pattern", "id": pattern.id} ) + submit_task( + pattern_create, + queue=DISPATCHERD_DEFAULT_CHANNEL, + args=( + pattern.id, + task.id, + ), + ) + headers = self.get_success_headers(serializer.data) return Response( { @@ -129,6 +142,14 @@ def create(self, request: Request, *args: tuple, **kwargs: dict) -> Response: status="Initiated", details={"model": "PatternInstance", "id": instance.id} ) + submit_task( + pattern_instance_create, + queue=DISPATCHERD_DEFAULT_CHANNEL, + args=( + instance.id, + task.id, + ), + ) headers = self.get_success_headers(serializer.data) return Response( { diff --git a/pattern_service/settings/defaults.py b/pattern_service/settings/defaults.py index 1c6cfaeb..0d6d3e39 100644 --- a/pattern_service/settings/defaults.py +++ b/pattern_service/settings/defaults.py @@ -115,9 +115,7 @@ "version": 2, "service": { "main_kwargs": {"node_id": "pattern-service-a"}, - "process_manager_kwargs": { - "preload_modules": ["pattern_service.core.tasks.hazmat"] - }, + "process_manager_kwargs": {"preload_modules": ["core.tasks.hazmat"]}, }, "brokers": { "pg_notify": { diff --git a/pattern_service/settings/dispatcher.py b/pattern_service/settings/dispatcher.py index f5d331fb..c8cc4721 100644 --- a/pattern_service/settings/dispatcher.py +++ b/pattern_service/settings/dispatcher.py @@ -32,6 +32,21 @@ def override_dispatcher_settings(loaded_settings: Dynaconf) -> None: db_sslkey = loaded_settings.get("DB_SSLKEY", default="") db_sslrootcert = loaded_settings.get("DB_SSLROOTCERT", default="") + databases["default"] = { + "ENGINE": "django.db.backends.postgresql", + "HOST": db_host, + "PORT": db_port, + "USER": db_user, + "PASSWORD": db_user_pass, + "NAME": db_name, + "OPTIONS": { + "sslmode": db_sslmode, + "sslcert": db_sslcert, + "sslkey": db_sslkey, + "sslrootcert": db_sslrootcert, + }, + } + databases["dispatcher"] = { "ENGINE": "django.db.backends.postgresql", "HOST": db_host, diff --git a/pattern_service/settings/testing_defaults.py b/pattern_service/settings/testing_defaults.py index 33ce0e58..3d31200f 100644 --- a/pattern_service/settings/testing_defaults.py +++ b/pattern_service/settings/testing_defaults.py @@ -4,7 +4,7 @@ BASE_DIR = Path(__file__).resolve().parent.parent SECRET_KEY = "insecure" -DB_NAME = "test_pattern_db" +DB_NAME = "postgres" DB_USER = "postgres" DB_PASSWORD = "insecure" @@ -22,6 +22,31 @@ }, } +DEBUG = True + +LOGGING = { + "version": 1, + "disable_existing_loggers": False, + "formatters": { + "simple": { + "format": "{levelname} {name} {lineno} {message}", + "style": "{", + }, + }, + "handlers": { + "console": { + "level": DEBUG, + "class": "logging.StreamHandler", + "formatter": "simple", + }, + }, + "filters": {"require_debug_false": {"()": "django.utils.log.RequireDebugFalse"}}, + "loggers": { + "dispatcherd": {"handlers": ["console"], "level": "INFO"}, + }, +} + + # Base URL of your AAP service AAP_URL = "http://localhost:44926" # or your default URL diff --git a/tools/podman/Containerfile.hub b/tools/podman/Containerfile.hub new file mode 100644 index 00000000..ba9cdb25 --- /dev/null +++ b/tools/podman/Containerfile.hub @@ -0,0 +1,15 @@ +FROM busybox:1.35 + +# Create a non-root user to own the files and run our server +RUN adduser -D static +USER static +WORKDIR /home/static + +# Copy the static website +RUN mkdir -p api/galaxy/v3/plugin/ansible/content/published/collections/artifacts + +# Use the .dockerignore file to control what ends up inside the image! +COPY core/tests/integration/collections/*.tar.gz api/galaxy/v3/plugin/ansible/content/published/collections/artifacts + +# Run BusyBox httpd +CMD ["busybox", "httpd", "-f", "-v", "-p", "3000"] diff --git a/tools/podman/compose-test.yaml b/tools/podman/compose-test.yaml index 7761312f..992e3353 100644 --- a/tools/podman/compose-test.yaml +++ b/tools/podman/compose-test.yaml @@ -5,3 +5,39 @@ services: POSTGRESQL_ADMIN_PASSWORD: insecure ports: - '5432:5432' + # volumes: + # - 'postgres_data:/var/lib/pgsql/data' + healthcheck: + test: ["CMD", "pg_isready", "-U", "postgres"] + interval: 5s + timeout: 5s + retries: 3 + start_period: 5s + worker: + image: localhost/pattern-service-worker + environment: + PATTERN_SERVICE_DISPATCHER_NODE_ID: test-worker + PATTERN_SERVICE_MODE: testing + PATTERN_SERVICE_DB_HOST: postgres + PATTERN_SERVICE_DB_PORT: 5432 + PATTERN_SERVICE_DB_NAME: postgres + PATTERN_SERVICE_DB_USER: postgres + PATTERN_SERVICE_DB_PASSWORD: insecure + PATTERN_SERVICE_AAP_URL: "http://localhost:3000" + build: + context: ../../ + dockerfile: tools/podman/Containerfile.dev + command: + - /bin/bash + - -c + - python3.11 /app/manage.py worker + depends_on: + postgres: + condition: service_healthy + restart: always + hub: + image: localhost/pattern-service-integration-hub + build: + context: ../../ + dockerfile: tools/podman/Containerfile.hub + restart: always