From 399c524bfaa16005d95c4b92a81707e383b4692e Mon Sep 17 00:00:00 2001 From: abikouo Date: Thu, 10 Jul 2025 16:12:31 +0200 Subject: [PATCH 1/9] Make the application acting as a python library --- .github/workflows/docker.yml | 52 ++++ .github/workflows/units.yml | 12 +- .gitignore | 3 +- Dockerfile.dev | 21 -- Makefile | 20 +- core/admin.py | 9 - core/migrations/0001_initial.py | 165 ----------- core/migrations/0003_task.py | 59 ---- core/models.py | 70 ----- core/serializers.py | 66 ----- core/urls.py | 16 -- pyproject.toml | 148 ++++++++-- run_mypy.sh | 12 + {core => src/pattern_service}/__init__.py | 0 .../pattern_service}/asgi.py | 0 .../pattern_service/core}/__init__.py | 0 src/pattern_service/core/admin.py | 9 + {core => src/pattern_service/core}/apps.py | 2 +- .../core/migrations/0001_initial.py | 272 ++++++++++++++++++ ...rn_created_by_pattern_modified_and_more.py | 68 +++-- .../core/migrations/0003_task.py | 87 ++++++ .../core/migrations}/__init__.py | 0 src/pattern_service/core/models.py | 108 +++++++ src/pattern_service/core/serializers.py | 68 +++++ .../pattern_service/core/tests}/__init__.py | 0 .../core}/tests/test_models.py | 8 +- .../core}/tests/test_serializers.py | 127 ++++---- .../pattern_service/core}/tests/test_views.py | 46 ++- src/pattern_service/core/urls.py | 22 ++ {core => src/pattern_service/core}/views.py | 34 ++- manage.py => src/pattern_service/manage.py | 0 .../pattern_service}/settings/__init__.py | 3 +- .../pattern_service}/settings/defaults.py | 6 +- .../pattern_service}/urls.py | 9 +- .../pattern_service}/wsgi.py | 0 tools/docker/Dockerfile.dev | 31 ++ tox.ini | 24 +- 37 files changed, 1004 insertions(+), 573 deletions(-) create mode 100644 .github/workflows/docker.yml delete mode 100644 Dockerfile.dev delete mode 100644 core/admin.py delete mode 100644 core/migrations/0001_initial.py delete mode 100644 core/migrations/0003_task.py delete mode 100644 core/models.py delete mode 100644 core/serializers.py delete mode 100644 core/urls.py create mode 100755 run_mypy.sh rename {core => src/pattern_service}/__init__.py (100%) rename {pattern_service => src/pattern_service}/asgi.py (100%) rename {core/migrations => src/pattern_service/core}/__init__.py (100%) create mode 100644 src/pattern_service/core/admin.py rename {core => src/pattern_service/core}/apps.py (78%) create mode 100644 src/pattern_service/core/migrations/0001_initial.py rename {core => src/pattern_service/core}/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py (53%) create mode 100644 src/pattern_service/core/migrations/0003_task.py rename {core/tests => src/pattern_service/core/migrations}/__init__.py (100%) create mode 100644 src/pattern_service/core/models.py create mode 100644 src/pattern_service/core/serializers.py rename {pattern_service => src/pattern_service/core/tests}/__init__.py (100%) rename {core => src/pattern_service/core}/tests/test_models.py (85%) rename {core => src/pattern_service/core}/tests/test_serializers.py (64%) rename {core => src/pattern_service/core}/tests/test_views.py (84%) create mode 100644 src/pattern_service/core/urls.py rename {core => src/pattern_service/core}/views.py (72%) rename manage.py => src/pattern_service/manage.py (100%) rename {pattern_service => src/pattern_service}/settings/__init__.py (63%) rename {pattern_service => src/pattern_service}/settings/defaults.py (96%) rename {pattern_service => src/pattern_service}/urls.py (80%) rename {pattern_service => src/pattern_service}/wsgi.py (100%) create mode 100644 tools/docker/Dockerfile.dev diff --git a/.github/workflows/docker.yml b/.github/workflows/docker.yml new file mode 100644 index 00000000..9ae5924d --- /dev/null +++ b/.github/workflows/docker.yml @@ -0,0 +1,52 @@ +--- +name: Docker + +concurrency: + group: ${{ github.workflow }}-${{ github.event.pull_request.number || github.sha }} + cancel-in-progress: true + +on: # yamllint disable-line rule:truthy + pull_request: + branches: + - main + - stable-* + tags: + - "*" + +jobs: + build: + name: Build docker image + runs-on: ubuntu-latest + env: + image_name: "ansible-pattern-service-test" + image_tag: "${{ github.sha }}" + steps: + - name: Checkout + uses: actions/checkout@v3 + + - name: Build the container image + run: make build + shell: bash + env: + CONTAINER_RUNTIME: docker + IMAGE_NAME: "${{ env.image_name }}" + IMAGE_TAG: "${{ env.image_tag }}" + + - name: Run the container image + run: docker run -d -p "8000:5000" --name ansible-pattern-service-api "${IMAGE_NAME}:${IMAGE_TAG}" + shell: bash + env: + IMAGE_NAME: "${{ env.image_name }}" + IMAGE_TAG: "${{ env.image_tag }}" + + - name: List running containers + run: docker ps -a + shell: bash + + - name: Wait for the services to be up and running + run: sleep 15s + shell: bash + + - name: Test the pattern service application + run: curl http://localhost:8000/ping/ + shell: bash diff --git a/.github/workflows/units.yml b/.github/workflows/units.yml index d73818d2..de8ed8d6 100644 --- a/.github/workflows/units.yml +++ b/.github/workflows/units.yml @@ -30,11 +30,9 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - pip install -r requirements/requirements-dev.txt - - - name: Run migrations - run: python manage.py migrate - - - name: Run core tests - run: python manage.py test core + python -m pip install -U tox + shell: bash + - name: Run unit tests + run: tox -e unit_tests -vv + shell: bash diff --git a/.gitignore b/.gitignore index 6afa2c4e..6e50fd8a 100644 --- a/.gitignore +++ b/.gitignore @@ -15,7 +15,8 @@ pre-commit-user *.py[c,o] /.eggs .python-version - +/.install +/.tests # Testing .cache diff --git a/Dockerfile.dev b/Dockerfile.dev deleted file mode 100644 index 3453f514..00000000 --- a/Dockerfile.dev +++ /dev/null @@ -1,21 +0,0 @@ -FROM registry.access.redhat.com/ubi9/ubi:latest - -WORKDIR /app - -COPY requirements/requirements.txt . - -RUN dnf install python3-pip -y - -RUN python3 -m pip install --no-cache-dir -r requirements.txt - -ADD core /app/core - -ADD pattern_service /app/pattern_service - -COPY manage.py . - -RUN python3 manage.py migrate - -EXPOSE 5000 - -CMD ["python3", "/app/manage.py", "runserver", "0.0.0.0:5000"] diff --git a/Makefile b/Makefile index 78f1c909..dfef1e95 100644 --- a/Makefile +++ b/Makefile @@ -1,19 +1,19 @@ -.PHONY: build build-multi run test clean install-deps lint push-quay login-quay push-quay-multi +.PHONY: build clean push # Image name and tag CONTAINER_RUNTIME ?= podman IMAGE_NAME ?= ansible-pattern-service IMAGE_TAG ?= latest + # Build the Docker image -build: +build_amd64: @echo "Building container image..." - $(CONTAINER_RUNTIME) build -t $(IMAGE_NAME):$(IMAGE_TAG) -f Dockerfile.dev --arch amd64 . + $(CONTAINER_RUNTIME) build -t $(IMAGE_NAME):$(IMAGE_TAG) -f tools/docker/Dockerfile.dev --arch amd64 . -ensure-namespace: -ifndef QUAY_NAMESPACE -$(error QUAY_NAMESPACE is required to push quay.io) -endif +build: + @echo "Building container image..." + $(CONTAINER_RUNTIME) build -t $(IMAGE_NAME):$(IMAGE_TAG) -f tools/docker/Dockerfile.dev . # Clean up clean: @@ -25,3 +25,9 @@ push: ensure-namespace build @echo "Tagging and pushing to registry..." $(CONTAINER_RUNTIME) tag $(IMAGE_NAME):$(IMAGE_TAG) quay.io/$(QUAY_NAMESPACE)/$(IMAGE_NAME):$(IMAGE_TAG) $(CONTAINER_RUNTIME) push quay.io/$(QUAY_NAMESPACE)/$(IMAGE_NAME):$(IMAGE_TAG) + +ensure-namespace: +ifndef QUAY_NAMESPACE + $(error QUAY_NAMESPACE is required to push quay.io) +endif + diff --git a/core/admin.py b/core/admin.py deleted file mode 100644 index 37252c7a..00000000 --- a/core/admin.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib import admin - -from core import models - -admin.site.register(models.Pattern) -admin.site.register(models.ControllerLabel) -admin.site.register(models.PatternInstance) -admin.site.register(models.Automation) -admin.site.register(models.Task) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py deleted file mode 100644 index cbb5e0a6..00000000 --- a/core/migrations/0001_initial.py +++ /dev/null @@ -1,165 +0,0 @@ -# Generated by Django 4.2.21 on 2025-06-09 18:49 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name='Automation', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), - ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), - ('automation_type', models.CharField(choices=[('job_template', 'Job template')], max_length=200)), - ('automation_id', models.BigIntegerField()), - ('primary', models.BooleanField(default=False)), - ], - options={ - 'ordering': ['id'], - }, - ), - migrations.CreateModel( - name='ControllerLabel', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), - ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), - ('label_id', models.BigIntegerField(unique=True)), - ], - options={ - 'ordering': ['id'], - }, - ), - migrations.CreateModel( - name='Pattern', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('collection_name', models.CharField(max_length=200)), - ('collection_version', models.CharField(max_length=50)), - ('collection_version_uri', models.CharField(blank=True, max_length=200)), - ('pattern_name', models.CharField(max_length=200)), - ('pattern_definition', models.JSONField(blank=True)), - ], - options={ - 'ordering': ['id'], - }, - ), - migrations.CreateModel( - name='PatternInstance', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), - ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), - ('organization_id', models.BigIntegerField()), - ('controller_project_id', models.BigIntegerField(blank=True)), - ('controller_ee_id', models.BigIntegerField(blank=True, null=True)), - ('credentials', models.JSONField()), - ('executors', models.JSONField(blank=True, null=True)), - ('controller_labels', models.ManyToManyField(blank=True, related_name='pattern_instances', to='core.controllerlabel')), - ( - 'created_by', - models.ForeignKey( - default=None, - editable=False, - help_text='The user who created this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_created+', - to=settings.AUTH_USER_MODEL, - ), - ), - ( - 'modified_by', - models.ForeignKey( - default=None, - editable=False, - help_text='The user who last modified this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_modified+', - to=settings.AUTH_USER_MODEL, - ), - ), - ('pattern', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pattern_instances', to='core.pattern')), - ], - options={ - 'ordering': ['id'], - }, - ), - migrations.AddConstraint( - model_name='pattern', - constraint=models.UniqueConstraint(fields=('collection_name', 'collection_version', 'pattern_name'), name='unique_pattern_collection_version'), - ), - migrations.AddField( - model_name='controllerlabel', - name='created_by', - field=models.ForeignKey( - default=None, - editable=False, - help_text='The user who created this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_created+', - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name='controllerlabel', - name='modified_by', - field=models.ForeignKey( - default=None, - editable=False, - help_text='The user who last modified this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_modified+', - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name='automation', - name='created_by', - field=models.ForeignKey( - default=None, - editable=False, - help_text='The user who created this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_created+', - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name='automation', - name='modified_by', - field=models.ForeignKey( - default=None, - editable=False, - help_text='The user who last modified this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_modified+', - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name='automation', - name='pattern_instance', - field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='automations', to='core.patterninstance'), - ), - migrations.AddConstraint( - model_name='patterninstance', - constraint=models.UniqueConstraint(fields=('organization_id', 'pattern'), name='unique_pattern_instance_organization'), - ), - ] diff --git a/core/migrations/0003_task.py b/core/migrations/0003_task.py deleted file mode 100644 index a6bdcf0a..00000000 --- a/core/migrations/0003_task.py +++ /dev/null @@ -1,59 +0,0 @@ -# Generated by Django 4.2.23 on 2025-06-26 19:27 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations -from django.db import models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('core', '0002_pattern_created_pattern_created_by_pattern_modified_and_more'), - ] - - operations = [ - migrations.CreateModel( - name='Task', - fields=[ - ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), - ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), - ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), - ( - 'status', - models.CharField( - choices=[('Initiated', 'Initiated'), ('Running', 'Running'), ('Completed', 'Completed'), ('Failed', 'Failed')], max_length=20 - ), - ), - ('details', models.JSONField(blank=True, null=True)), - ( - 'created_by', - models.ForeignKey( - default=None, - editable=False, - help_text='The user who created this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_created+', - to=settings.AUTH_USER_MODEL, - ), - ), - ( - 'modified_by', - models.ForeignKey( - default=None, - editable=False, - help_text='The user who last modified this resource.', - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_modified+', - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - 'ordering': ['id'], - }, - ), - ] diff --git a/core/models.py b/core/models.py deleted file mode 100644 index 2ea1b58b..00000000 --- a/core/models.py +++ /dev/null @@ -1,70 +0,0 @@ -from __future__ import annotations - -from ansible_base.lib.abstract_models import CommonModel -from django.db import models - - -class Pattern(CommonModel): - class Meta: - app_label = 'core' - ordering = ['id'] - constraints = [models.UniqueConstraint(fields=["collection_name", "collection_version", "pattern_name"], name="unique_pattern_collection_version")] - - collection_name: models.CharField = models.CharField(max_length=200) - collection_version: models.CharField = models.CharField(max_length=50) - collection_version_uri: models.CharField = models.CharField(max_length=200, blank=True, null=True) - pattern_name: models.CharField = models.CharField(max_length=200) - pattern_definition: models.JSONField = models.JSONField(blank=True, null=True) - - -class ControllerLabel(CommonModel): - class Meta: - app_label = 'core' - ordering = ['id'] - - label_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField(unique=True) - - -class PatternInstance(CommonModel): - class Meta: - app_label = 'core' - ordering = ['id'] - constraints = [models.UniqueConstraint(fields=["organization_id", "pattern"], name="unique_pattern_instance_organization")] - - organization_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField() - controller_project_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField(blank=True, null=True) - controller_ee_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField(null=True, blank=True) - credentials: models.JSONField = models.JSONField() - executors: models.JSONField = models.JSONField(null=True, blank=True) - - pattern: models.ForeignKey = models.ForeignKey(Pattern, on_delete=models.CASCADE, related_name="pattern_instances") - controller_labels: models.ManyToManyField = models.ManyToManyField(ControllerLabel, related_name="pattern_instances", blank=True) - - -class Automation(CommonModel): - class Meta: - app_label = 'core' - ordering = ['id'] - - automation_type_choices = (("job_template", "Job template"),) - automation_type: models.CharField = models.CharField(max_length=200, choices=automation_type_choices) - automation_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField() - primary: models.BooleanField = models.BooleanField(default=False) - - pattern_instance: models.ForeignKey = models.ForeignKey(PatternInstance, on_delete=models.CASCADE, related_name="automations") - - -class Task(CommonModel): - class Meta: - app_label = 'core' - ordering = ['id'] - - status_choices = ( - ("Initiated", "Initiated"), - ("Running", "Running"), - ("Completed", "Completed"), - ("Failed", "Failed"), - ) - - status: models.CharField = models.CharField(max_length=20, choices=status_choices) - details: models.JSONField = models.JSONField(null=True, blank=True) diff --git a/core/serializers.py b/core/serializers.py deleted file mode 100644 index 38b4a382..00000000 --- a/core/serializers.py +++ /dev/null @@ -1,66 +0,0 @@ -from __future__ import annotations - -from ansible_base.lib.serializers.common import CommonModelSerializer - -from .models import Automation -from .models import ControllerLabel -from .models import Pattern -from .models import PatternInstance -from .models import Task - - -class PatternSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = Pattern - fields = CommonModelSerializer.Meta.fields + [ - 'id', - 'collection_name', - 'collection_version', - 'collection_version_uri', - 'pattern_name', - 'pattern_definition', - ] - read_only_fields = ['pattern_definition', 'collection_version_uri'] - - -class ControllerLabelSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = ControllerLabel - fields = CommonModelSerializer.Meta.fields + ['id', 'label_id'] - - -class PatternInstanceSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = PatternInstance - fields = CommonModelSerializer.Meta.fields + [ - 'id', - 'organization_id', - 'controller_project_id', - 'controller_ee_id', - 'controller_labels', - 'credentials', - 'executors', - 'pattern', - ] - read_only_fields = ['controller_project_id', 'controller_ee_id', 'controller_labels'] - - -class AutomationSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = Automation - fields = CommonModelSerializer.Meta.fields + [ - 'id', - 'automation_type', - 'automation_id', - 'primary', - 'pattern_instance', - ] - - -class TaskSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = Task - fields = CommonModelSerializer.Meta.fields + [ - 'status', - 'details', - ] diff --git a/core/urls.py b/core/urls.py deleted file mode 100644 index 2d1b5c8c..00000000 --- a/core/urls.py +++ /dev/null @@ -1,16 +0,0 @@ -from ansible_base.lib.routers import AssociationResourceRouter - -from .views import AutomationViewSet -from .views import ControllerLabelViewSet -from .views import PatternInstanceViewSet -from .views import PatternViewSet -from .views import TaskViewSet - -router = AssociationResourceRouter() -router.register(r'patterns', PatternViewSet, basename='pattern') -router.register(r'controllerlabels', ControllerLabelViewSet, basename='controllerlabel') -router.register(r'patterninstances', PatternInstanceViewSet, basename='patterninstance') -router.register(r'automations', AutomationViewSet, basename='automation') -router.register(r'tasks', TaskViewSet, basename='task') - -urlpatterns = router.urls diff --git a/pyproject.toml b/pyproject.toml index ef592006..d63a8cd6 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,30 +1,142 @@ +[build-system] +requires = ["poetry-core>=2.0,<3.0"] +build-backend = "poetry.core.masonry.api" + [project] -name = "pattern_service" +name = "aap-pattern-service" version = "0.1.0" -description = "Pattern Service Django project" +description = "" readme = "README.md" -requires-python = ">=3.10" -dependencies = [ - "django-ansible-base==2025.5.8" +authors = [ + { name = "Ansible, Inc.", email = "Red Hat, Inc. " }, ] +requires-python = ">=3.11,<3.13" + +[project.scripts] +aap-pattern-service-manage = 'pattern_service.manage:main' + + +# ------------------------------------- +# Poetry: Metadata +# ------------------------------------- +[tool.poetry] +requires-poetry = ">=2.0,<3.0" +packages = [{ include = "pattern_service", from = "src" }] + + +# ------------------------------------- +# Poetry: Dependencies +# ------------------------------------- +[tool.poetry.extras] +all = ["psycopg"] +dev = ["psycopg-binary"] + +[tool.poetry.dependencies] +python = ">=3.11,<3.13" +django = ">=4.2,<4.3" +djangorestframework = "3.15.*" +drf-spectacular = ">=0.26.5,<0.27" +channels = { version = "4.0.*", extras = ["daphne"] } +psycopg-binary = { version = "*", optional = true } +django-filter = ">23.2,<24" +pydantic = ">=1.8.1,<1.11" +cryptography = ">=42,<43" +django-ansible-base = { git = "https://github.com/ansible/django-ansible-base.git", tag = "2025.5.8", extras = [ + "channel-auth", + "rbac", + "resource-registry", + "jwt-consumer", + "rest-filters", + "feature-flags", +] } +jinja2 = ">=3.1.3,<3.2" +django-split-settings = "^1.2.0" +pexpect = "^4.9.0" +python-gnupg = "^0.5.2" +autobahn = { git = "https://github.com/crossbario/autobahn-python.git", rev = "v24.4.2" } +psycopg = "^3.1.17" +xxhash = "3.4.*" +pyjwt = { version = "2.7.*", extras = ["crypto"] } +ecdsa = "0.18.*" +validators = "^0.34.0" +django-flags = "^5.0.13" +insights-analytics-collector = "^0.3.2" +distro = "^1.9.0" +dispatcherd = { version = "v2025.05.19", extras = ["pg_notify"] } + + +[tool.poetry.group.test.dependencies] +pytest = "*" +pytest-env = "*" +pytest-django = "*" +pytest-asyncio = "*" +requests = { version = "*", python = "<4.0" } +pytest-cov = "^4.1.0" +pytest-lazy-fixture = "^0.6.3" +requests-mock = "*" +httpie = "^3.2.3" + +[tool.poetry.group.lint.dependencies] +flake8 = "*" +isort = "*" +black = "*" +flake8-broken-line = { version = "*", python = "<4.0" } +flake8-string-format = "*" +# This is an experimental linter. +ruff = "*" +# The rull claims that the flake8 plugins listed below are re-implemented, +# These plugins will remain included until it's verified. +pep8-naming = "*" +flake8-bugbear = "*" +flake8-comprehensions = "*" +flake8-debugger = "*" +flake8-docstrings = "*" +flake8-eradicate = { version = "*", python = "<4.0" } +flake8-print = "*" + +[tool.poetry.group.dev.dependencies] +ipython = "*" + +# ------------------------------------- +# Tools +# ------------------------------------- [tool.black] -line-length = 160 -fast = true -skip-string-normalization = true +line-length = 79 +target-version = ["py39", "py310"] [tool.isort] profile = "black" -force_single_line = true -line_length = 120 +combine_as_imports = true +line_length = 79 -[build-system] -requires = ["setuptools>=61", "wheel"] -build-backend = "setuptools.build_meta" +[tool.ruff] +line-length = 79 + +[tool.ruff.lint] +select = [ + "E", + "F", + "D", # flake8-docstrings + "TID", # flake8-tidy-imports +] +extend-ignore = [ + "D1", # Missing docstrings errors +] + + +[tool.ruff.lint.per-file-ignores] +"__init__.py" = ["F401"] +"src/pattern_service/core/migrations/*" = ["E501"] +"tests/**/*.py" = [ + "S101", # Asserts allowed in tests + "ARG", # Fixtures are not always used explicitly + "SLF001", # Call private methods in tests + "D", # Docstrings are not required in tests +] -[tool.setuptools] -packages = ["pattern_service"] +[tool.ruff.lint.flake8-tidy-imports] +ban-relative-imports = "parents" -[[tool.mypy.overrides]] -module = ["ansible_base.*", "rest_framework.*"] -ignore_missing_imports = true +[tool.ruff.lint.pydocstyle] +convention = "pep257" diff --git a/run_mypy.sh b/run_mypy.sh new file mode 100755 index 00000000..b1218d69 --- /dev/null +++ b/run_mypy.sh @@ -0,0 +1,12 @@ +#!/usr/bin/env bash +set -eux + +SCRIPT_DIR=$( cd -- "$( dirname -- "${BASH_SOURCE[0]}" )" &> /dev/null && pwd ) + +rm -rf "${SCRIPT_DIR}/.install" +mkdir -p "${SCRIPT_DIR}/.install" +ln -s "${SCRIPT_DIR}/src/pattern_service" "${SCRIPT_DIR}/.install/pattern_service" +cd "${SCRIPT_DIR}/.install" +export MYPYPATH="${SCRIPT_DIR}/.install" +mypy -p pattern_service +rm -rf "${SCRIPT_DIR}/.install" \ No newline at end of file diff --git a/core/__init__.py b/src/pattern_service/__init__.py similarity index 100% rename from core/__init__.py rename to src/pattern_service/__init__.py diff --git a/pattern_service/asgi.py b/src/pattern_service/asgi.py similarity index 100% rename from pattern_service/asgi.py rename to src/pattern_service/asgi.py diff --git a/core/migrations/__init__.py b/src/pattern_service/core/__init__.py similarity index 100% rename from core/migrations/__init__.py rename to src/pattern_service/core/__init__.py diff --git a/src/pattern_service/core/admin.py b/src/pattern_service/core/admin.py new file mode 100644 index 00000000..987e24ee --- /dev/null +++ b/src/pattern_service/core/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task + +admin.site.register(Pattern) +admin.site.register(ControllerLabel) +admin.site.register(PatternInstance) +admin.site.register(Automation) +admin.site.register(Task) diff --git a/core/apps.py b/src/pattern_service/core/apps.py similarity index 78% rename from core/apps.py rename to src/pattern_service/core/apps.py index c0ce093b..abe8e43d 100644 --- a/core/apps.py +++ b/src/pattern_service/core/apps.py @@ -3,4 +3,4 @@ class CoreConfig(AppConfig): default_auto_field = "django.db.models.BigAutoField" - name = "core" + name = "pattern_service.core" diff --git a/src/pattern_service/core/migrations/0001_initial.py b/src/pattern_service/core/migrations/0001_initial.py new file mode 100644 index 00000000..232e65a2 --- /dev/null +++ b/src/pattern_service/core/migrations/0001_initial.py @@ -0,0 +1,272 @@ +# Generated by Django 4.2.21 on 2025-06-09 18:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name="Automation", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "modified", + models.DateTimeField( + auto_now=True, + help_text="The date/time this resource was created.", + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + help_text="The date/time this resource was created.", + ), + ), + ( + "automation_type", + models.CharField( + choices=[("job_template", "Job template")], + max_length=200, + ), + ), + ("automation_id", models.BigIntegerField()), + ("primary", models.BooleanField(default=False)), + ], + options={ + "ordering": ["id"], + }, + ), + migrations.CreateModel( + name="ControllerLabel", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "modified", + models.DateTimeField( + auto_now=True, + help_text="The date/time this resource was created.", + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + help_text="The date/time this resource was created.", + ), + ), + ("label_id", models.BigIntegerField(unique=True)), + ], + options={ + "ordering": ["id"], + }, + ), + migrations.CreateModel( + name="Pattern", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ("collection_name", models.CharField(max_length=200)), + ("collection_version", models.CharField(max_length=50)), + ( + "collection_version_uri", + models.CharField(blank=True, max_length=200), + ), + ("pattern_name", models.CharField(max_length=200)), + ("pattern_definition", models.JSONField(blank=True)), + ], + options={ + "ordering": ["id"], + }, + ), + migrations.CreateModel( + name="PatternInstance", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "modified", + models.DateTimeField( + auto_now=True, + help_text="The date/time this resource was created.", + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + help_text="The date/time this resource was created.", + ), + ), + ("organization_id", models.BigIntegerField()), + ("controller_project_id", models.BigIntegerField(blank=True)), + ( + "controller_ee_id", + models.BigIntegerField(blank=True, null=True), + ), + ("credentials", models.JSONField()), + ("executors", models.JSONField(blank=True, null=True)), + ( + "controller_labels", + models.ManyToManyField( + blank=True, + related_name="pattern_instances", + to="core.controllerlabel", + ), + ), + ( + "created_by", + models.ForeignKey( + default=None, + editable=False, + help_text="The user who created this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "modified_by", + models.ForeignKey( + default=None, + editable=False, + help_text="The user who last modified this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_modified+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "pattern", + models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="pattern_instances", + to="core.pattern", + ), + ), + ], + options={ + "ordering": ["id"], + }, + ), + migrations.AddConstraint( + model_name="pattern", + constraint=models.UniqueConstraint( + fields=( + "collection_name", + "collection_version", + "pattern_name", + ), + name="unique_pattern_collection_version", + ), + ), + migrations.AddField( + model_name="controllerlabel", + name="created_by", + field=models.ForeignKey( + default=None, + editable=False, + help_text="The user who created this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created+", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="controllerlabel", + name="modified_by", + field=models.ForeignKey( + default=None, + editable=False, + help_text="The user who last modified this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_modified+", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="automation", + name="created_by", + field=models.ForeignKey( + default=None, + editable=False, + help_text="The user who created this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created+", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="automation", + name="modified_by", + field=models.ForeignKey( + default=None, + editable=False, + help_text="The user who last modified this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_modified+", + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name="automation", + name="pattern_instance", + field=models.ForeignKey( + on_delete=django.db.models.deletion.CASCADE, + related_name="automations", + to="core.patterninstance", + ), + ), + migrations.AddConstraint( + model_name="patterninstance", + constraint=models.UniqueConstraint( + fields=("organization_id", "pattern"), + name="unique_pattern_instance_organization", + ), + ), + ] diff --git a/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py b/src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py similarity index 53% rename from core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py rename to src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py index 099ff977..5a316812 100644 --- a/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py +++ b/src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py @@ -3,88 +3,94 @@ import django.db.models.deletion import django.utils.timezone from django.conf import settings -from django.db import migrations -from django.db import models +from django.db import migrations, models class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ('core', '0001_initial'), + ("core", "0001_initial"), ] operations = [ migrations.AddField( - model_name='pattern', - name='created', - field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='The date/time this resource was created.'), + model_name="pattern", + name="created", + field=models.DateTimeField( + auto_now_add=True, + default=django.utils.timezone.now, + help_text="The date/time this resource was created.", + ), preserve_default=False, ), migrations.AddField( - model_name='pattern', - name='created_by', + model_name="pattern", + name="created_by", field=models.ForeignKey( default=None, editable=False, - help_text='The user who created this resource.', + help_text="The user who created this resource.", null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_created+', + related_name="%(app_label)s_%(class)s_created+", to=settings.AUTH_USER_MODEL, ), ), migrations.AddField( - model_name='pattern', - name='modified', - field=models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.'), + model_name="pattern", + name="modified", + field=models.DateTimeField( + auto_now=True, + help_text="The date/time this resource was created.", + ), ), migrations.AddField( - model_name='pattern', - name='modified_by', + model_name="pattern", + name="modified_by", field=models.ForeignKey( default=None, editable=False, - help_text='The user who last modified this resource.', + help_text="The user who last modified this resource.", null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name='%(app_label)s_%(class)s_modified+', + related_name="%(app_label)s_%(class)s_modified+", to=settings.AUTH_USER_MODEL, ), ), migrations.AlterField( - model_name='automation', - name='automation_id', + model_name="automation", + name="automation_id", field=models.PositiveBigIntegerField(), ), migrations.AlterField( - model_name='controllerlabel', - name='label_id', + model_name="controllerlabel", + name="label_id", field=models.PositiveBigIntegerField(unique=True), ), migrations.AlterField( - model_name='pattern', - name='collection_version_uri', + model_name="pattern", + name="collection_version_uri", field=models.CharField(blank=True, max_length=200, null=True), ), migrations.AlterField( - model_name='pattern', - name='pattern_definition', + model_name="pattern", + name="pattern_definition", field=models.JSONField(blank=True, null=True), ), migrations.AlterField( - model_name='patterninstance', - name='controller_ee_id', + model_name="patterninstance", + name="controller_ee_id", field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AlterField( - model_name='patterninstance', - name='controller_project_id', + model_name="patterninstance", + name="controller_project_id", field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AlterField( - model_name='patterninstance', - name='organization_id', + model_name="patterninstance", + name="organization_id", field=models.PositiveBigIntegerField(), ), ] diff --git a/src/pattern_service/core/migrations/0003_task.py b/src/pattern_service/core/migrations/0003_task.py new file mode 100644 index 00000000..7e58c036 --- /dev/null +++ b/src/pattern_service/core/migrations/0003_task.py @@ -0,0 +1,87 @@ +# Generated by Django 4.2.23 on 2025-06-26 19:27 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations, models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ( + "core", + "0002_pattern_created_pattern_created_by_pattern_modified_and_more", + ), + ] + + operations = [ + migrations.CreateModel( + name="Task", + fields=[ + ( + "id", + models.BigAutoField( + auto_created=True, + primary_key=True, + serialize=False, + verbose_name="ID", + ), + ), + ( + "modified", + models.DateTimeField( + auto_now=True, + help_text="The date/time this resource was created.", + ), + ), + ( + "created", + models.DateTimeField( + auto_now_add=True, + help_text="The date/time this resource was created.", + ), + ), + ( + "status", + models.CharField( + choices=[ + ("Initiated", "Initiated"), + ("Running", "Running"), + ("Completed", "Completed"), + ("Failed", "Failed"), + ], + max_length=20, + ), + ), + ("details", models.JSONField(blank=True, null=True)), + ( + "created_by", + models.ForeignKey( + default=None, + editable=False, + help_text="The user who created this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_created+", + to=settings.AUTH_USER_MODEL, + ), + ), + ( + "modified_by", + models.ForeignKey( + default=None, + editable=False, + help_text="The user who last modified this resource.", + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name="%(app_label)s_%(class)s_modified+", + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + "ordering": ["id"], + }, + ), + ] diff --git a/core/tests/__init__.py b/src/pattern_service/core/migrations/__init__.py similarity index 100% rename from core/tests/__init__.py rename to src/pattern_service/core/migrations/__init__.py diff --git a/src/pattern_service/core/models.py b/src/pattern_service/core/models.py new file mode 100644 index 00000000..59564ffe --- /dev/null +++ b/src/pattern_service/core/models.py @@ -0,0 +1,108 @@ +from __future__ import annotations + +from ansible_base.lib.abstract_models import CommonModel # type: ignore +from django.db import models + + +class Pattern(CommonModel): + class Meta: + app_label = "core" + ordering = ["id"] + constraints = [ + models.UniqueConstraint( + fields=[ + "collection_name", + "collection_version", + "pattern_name", + ], + name="unique_pattern_collection_version", + ) + ] + + collection_name: models.CharField = models.CharField(max_length=200) + collection_version: models.CharField = models.CharField(max_length=50) + collection_version_uri: models.CharField = models.CharField( + max_length=200, blank=True, null=True + ) + pattern_name: models.CharField = models.CharField(max_length=200) + pattern_definition: models.JSONField = models.JSONField( + blank=True, null=True + ) + + +class ControllerLabel(CommonModel): + class Meta: + app_label = "core" + ordering = ["id"] + + label_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField( + unique=True + ) + + +class PatternInstance(CommonModel): + class Meta: + app_label = "core" + ordering = ["id"] + constraints = [ + models.UniqueConstraint( + fields=["organization_id", "pattern"], + name="unique_pattern_instance_organization", + ) + ] + + organization_id: models.PositiveBigIntegerField = ( + models.PositiveBigIntegerField() + ) + controller_project_id: models.PositiveBigIntegerField = ( + models.PositiveBigIntegerField(blank=True, null=True) + ) + controller_ee_id: models.PositiveBigIntegerField = ( + models.PositiveBigIntegerField(null=True, blank=True) + ) + credentials: models.JSONField = models.JSONField() + executors: models.JSONField = models.JSONField(null=True, blank=True) + + pattern: models.ForeignKey = models.ForeignKey( + Pattern, on_delete=models.CASCADE, related_name="pattern_instances" + ) + controller_labels: models.ManyToManyField = models.ManyToManyField( + ControllerLabel, related_name="pattern_instances", blank=True + ) + + +class Automation(CommonModel): + class Meta: + app_label = "core" + ordering = ["id"] + + automation_type_choices = (("job_template", "Job template"),) + automation_type: models.CharField = models.CharField( + max_length=200, choices=automation_type_choices + ) + automation_id: models.PositiveBigIntegerField = ( + models.PositiveBigIntegerField() + ) + primary: models.BooleanField = models.BooleanField(default=False) + + pattern_instance: models.ForeignKey = models.ForeignKey( + PatternInstance, on_delete=models.CASCADE, related_name="automations" + ) + + +class Task(CommonModel): + class Meta: + app_label = "core" + ordering = ["id"] + + status_choices = ( + ("Initiated", "Initiated"), + ("Running", "Running"), + ("Completed", "Completed"), + ("Failed", "Failed"), + ) + + status: models.CharField = models.CharField( + max_length=20, choices=status_choices + ) + details: models.JSONField = models.JSONField(null=True, blank=True) diff --git a/src/pattern_service/core/serializers.py b/src/pattern_service/core/serializers.py new file mode 100644 index 00000000..4554b36e --- /dev/null +++ b/src/pattern_service/core/serializers.py @@ -0,0 +1,68 @@ +from __future__ import annotations + +from ansible_base.lib.serializers.common import ( # type: ignore + CommonModelSerializer, +) + +from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task + + +class PatternSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = Pattern + fields = CommonModelSerializer.Meta.fields + [ + "id", + "collection_name", + "collection_version", + "collection_version_uri", + "pattern_name", + "pattern_definition", + ] + read_only_fields = ["pattern_definition", "collection_version_uri"] + + +class ControllerLabelSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = ControllerLabel + fields = CommonModelSerializer.Meta.fields + ["id", "label_id"] + + +class PatternInstanceSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = PatternInstance + fields = CommonModelSerializer.Meta.fields + [ + "id", + "organization_id", + "controller_project_id", + "controller_ee_id", + "controller_labels", + "credentials", + "executors", + "pattern", + ] + read_only_fields = [ + "controller_project_id", + "controller_ee_id", + "controller_labels", + ] + + +class AutomationSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = Automation + fields = CommonModelSerializer.Meta.fields + [ + "id", + "automation_type", + "automation_id", + "primary", + "pattern_instance", + ] + + +class TaskSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = Task + fields = CommonModelSerializer.Meta.fields + [ + "status", + "details", + ] diff --git a/pattern_service/__init__.py b/src/pattern_service/core/tests/__init__.py similarity index 100% rename from pattern_service/__init__.py rename to src/pattern_service/core/tests/__init__.py diff --git a/core/tests/test_models.py b/src/pattern_service/core/tests/test_models.py similarity index 85% rename from core/tests/test_models.py rename to src/pattern_service/core/tests/test_models.py index 496ac3af..79d5124e 100644 --- a/core/tests/test_models.py +++ b/src/pattern_service/core/tests/test_models.py @@ -1,9 +1,7 @@ from django.core.exceptions import ValidationError from django.test import TestCase -from core.models import ControllerLabel -from core.models import Pattern -from core.models import Task +from pattern_service.core.models import ControllerLabel, Pattern, Task class ModelTestCase(TestCase): @@ -23,7 +21,9 @@ def test_task_invalid_status_choice(self): task.full_clean() # triggers choice validation def test_task_status_choices_valid_running_with_info(self): - task = Task.objects.create(status="Running", details={"info": "in progress"}) + task = Task.objects.create( + status="Running", details={"info": "in progress"} + ) self.assertEqual(task.status, "Running") self.assertEqual(task.details["info"], "in progress") diff --git a/core/tests/test_serializers.py b/src/pattern_service/core/tests/test_serializers.py similarity index 64% rename from core/tests/test_serializers.py rename to src/pattern_service/core/tests/test_serializers.py index 605d96b5..5b8c7952 100644 --- a/core/tests/test_serializers.py +++ b/src/pattern_service/core/tests/test_serializers.py @@ -1,15 +1,19 @@ from django.test import TestCase -from core.models import Automation -from core.models import ControllerLabel -from core.models import Pattern -from core.models import PatternInstance -from core.models import Task -from core.serializers import AutomationSerializer -from core.serializers import ControllerLabelSerializer -from core.serializers import PatternInstanceSerializer -from core.serializers import PatternSerializer -from core.serializers import TaskSerializer +from pattern_service.core.models import ( + Automation, + ControllerLabel, + Pattern, + PatternInstance, + Task, +) +from pattern_service.core.serializers import ( + AutomationSerializer, + ControllerLabelSerializer, + PatternInstanceSerializer, + PatternSerializer, + TaskSerializer, +) class SharedTestFixture(TestCase): @@ -38,18 +42,21 @@ def test_serializer_fields_present(self): serializer = PatternSerializer(instance=self.pattern) data = serializer.data - self.assertIn('id', data) - self.assertIn('collection_name', data) - self.assertIn('collection_version', data) - self.assertIn('collection_version_uri', data) - self.assertIn('pattern_name', data) - self.assertIn('pattern_definition', data) - - self.assertEqual(data['collection_name'], "mynamespace.mycollection") - self.assertEqual(data['collection_version'], "1.0.0") - self.assertEqual(data['collection_version_uri'], "https://example.com/mynamespace/mycollection/") - self.assertEqual(data['pattern_name'], "example_pattern") - self.assertEqual(data['pattern_definition'], {"Test": "Value"}) + self.assertIn("id", data) + self.assertIn("collection_name", data) + self.assertIn("collection_version", data) + self.assertIn("collection_version_uri", data) + self.assertIn("pattern_name", data) + self.assertIn("pattern_definition", data) + + self.assertEqual(data["collection_name"], "mynamespace.mycollection") + self.assertEqual(data["collection_version"], "1.0.0") + self.assertEqual( + data["collection_version_uri"], + "https://example.com/mynamespace/mycollection/", + ) + self.assertEqual(data["pattern_name"], "example_pattern") + self.assertEqual(data["pattern_definition"], {"Test": "Value"}) def test_pattern_definition_read_only(self): input_data = { @@ -62,7 +69,7 @@ def test_pattern_definition_read_only(self): serializer = PatternSerializer(data=input_data) self.assertTrue(serializer.is_valid(), serializer.errors) - self.assertNotIn('pattern_definition', serializer.validated_data) + self.assertNotIn("pattern_definition", serializer.validated_data) def test_serializer_validation_success(self): input_data = { @@ -85,16 +92,16 @@ def test_serializer_fields(self): serializer = ControllerLabelSerializer(instance=self.label) data = serializer.data - self.assertIn('id', data) - self.assertIn('label_id', data) - self.assertEqual(data['label_id'], 123) + self.assertIn("id", data) + self.assertIn("label_id", data) + self.assertEqual(data["label_id"], 123) def test_serializer_validation(self): - serializer = ControllerLabelSerializer(data={'label_id': 321}) + serializer = ControllerLabelSerializer(data={"label_id": 321}) self.assertTrue(serializer.is_valid(), serializer.errors) def test_valid_label_id(self): - serializer = ControllerLabelSerializer(data={'label_id': 5}) + serializer = ControllerLabelSerializer(data={"label_id": 5}) self.assertTrue(serializer.is_valid()) @@ -103,13 +110,13 @@ def test_serializer_fields(self): serializer = PatternInstanceSerializer(instance=self.pattern_instance) data = serializer.data - self.assertIn('id', data) - self.assertIn('organization_id', data) - self.assertIn('controller_project_id', data) - self.assertIn('controller_ee_id', data) - self.assertIn('pattern', data) - self.assertEqual(data['controller_project_id'], 123) - self.assertEqual(data['controller_ee_id'], 987) + self.assertIn("id", data) + self.assertIn("organization_id", data) + self.assertIn("controller_project_id", data) + self.assertIn("controller_ee_id", data) + self.assertIn("pattern", data) + self.assertEqual(data["controller_project_id"], 123) + self.assertEqual(data["controller_ee_id"], 987) def test_serializer_validation(self): input_data = { @@ -129,7 +136,7 @@ class AutomationSerializerTest(SharedTestFixture): def setUpTestData(cls): super().setUpTestData() cls.automation = Automation.objects.create( - automation_type='job_template', + automation_type="job_template", automation_id=321, primary=True, pattern_instance=cls.pattern_instance, @@ -139,37 +146,46 @@ def test_serializer_fields_present(self): serializer = AutomationSerializer(instance=self.automation) data = serializer.data - self.assertIn('id', data) - self.assertIn('automation_type', data) - self.assertIn('automation_id', data) - self.assertIn('primary', data) - self.assertIn('pattern_instance', data) - - self.assertEqual(data['id'], self.automation.id) - self.assertEqual(data['automation_type'], self.automation.automation_type) - self.assertEqual(data['automation_id'], self.automation.automation_id) - self.assertEqual(data['primary'], self.automation.primary) + self.assertIn("id", data) + self.assertIn("automation_type", data) + self.assertIn("automation_id", data) + self.assertIn("primary", data) + self.assertIn("pattern_instance", data) + + self.assertEqual(data["id"], self.automation.id) + self.assertEqual( + data["automation_type"], self.automation.automation_type + ) + self.assertEqual(data["automation_id"], self.automation.automation_id) + self.assertEqual(data["primary"], self.automation.primary) def test_serializer_validation_success(self): - input_data = {'automation_type': 'job_template', 'automation_id': 123, 'primary': False, 'pattern_instance': self.pattern_instance.id} + input_data = { + "automation_type": "job_template", + "automation_id": 123, + "primary": False, + "pattern_instance": self.pattern_instance.id, + } serializer = AutomationSerializer(data=input_data) self.assertTrue(serializer.is_valid(), serializer.errors) def test_serializer_validation_failure(self): input_data = { - 'automation_type': '', - 'automation_id': '', - 'primary': False, + "automation_type": "", + "automation_id": "", + "primary": False, } serializer = AutomationSerializer(data=input_data) self.assertFalse(serializer.is_valid()) - self.assertIn('automation_type', serializer.errors) - self.assertIn('automation_id', serializer.errors) + self.assertIn("automation_type", serializer.errors) + self.assertIn("automation_id", serializer.errors) class TaskSerializerTest(SharedTestFixture): def test_serializer_fields_present(self): - task = Task.objects.create(status="Initiated", details={"info": "test"}) + task = Task.objects.create( + status="Initiated", details={"info": "test"} + ) serializer = TaskSerializer(instance=task) data = serializer.data @@ -187,7 +203,10 @@ def test_serializer_validation_success(self): serializer = TaskSerializer(data=input_data) self.assertTrue(serializer.is_valid(), serializer.errors) self.assertEqual(serializer.validated_data["status"], "Running") - self.assertEqual(serializer.validated_data["details"], {"step": 1, "info": "in progress"}) + self.assertEqual( + serializer.validated_data["details"], + {"step": 1, "info": "in progress"}, + ) def test_serializer_invalid_status(self): input_data = { diff --git a/core/tests/test_views.py b/src/pattern_service/core/tests/test_views.py similarity index 84% rename from core/tests/test_views.py rename to src/pattern_service/core/tests/test_views.py index cff2a114..0a254436 100644 --- a/core/tests/test_views.py +++ b/src/pattern_service/core/tests/test_views.py @@ -2,11 +2,13 @@ from rest_framework import status from rest_framework.test import APITestCase -from core.models import Automation -from core.models import ControllerLabel -from core.models import Pattern -from core.models import PatternInstance -from core.models import Task +from pattern_service.core.models import ( + Automation, + ControllerLabel, + Pattern, + PatternInstance, + Task, +) class SharedDataMixin: @@ -39,9 +41,15 @@ def setUpTestData(cls): pattern_instance=cls.pattern_instance, ) - cls.task1 = Task.objects.create(status="Running", details={"progress": "50%"}) - cls.task2 = Task.objects.create(status="Completed", details={"result": "success"}) - cls.task3 = Task.objects.create(status="Failed", details={"error": "timeout"}) + cls.task1 = Task.objects.create( + status="Running", details={"progress": "50%"} + ) + cls.task2 = Task.objects.create( + status="Completed", details={"result": "success"} + ) + cls.task3 = Task.objects.create( + status="Failed", details={"error": "timeout"} + ) class TaskViewSetTest(SharedDataMixin, APITestCase): @@ -55,21 +63,25 @@ def test_task_detail_view(self): url = reverse("task-detail", args=[self.task1.pk]) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertIn('id', response.data) - self.assertIn('status', response.data) - self.assertIn('details', response.data) + self.assertIn("id", response.data) + self.assertIn("status", response.data) + self.assertIn("details", response.data) def test_task_list_view_returns_all_tasks(self): url = reverse("task-list") response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) # Verify we get all created tasks - task_ids = [task['id'] for task in response.data] + task_ids = [task["id"] for task in response.data] expected_ids = [self.task1.id, self.task2.id, self.task3.id] self.assertEqual(sorted(task_ids), sorted(expected_ids)) def test_task_detail_view_for_different_statuses(self): - tasks_to_test = [(self.task1, "Running"), (self.task2, "Completed"), (self.task3, "Failed")] + tasks_to_test = [ + (self.task1, "Running"), + (self.task2, "Completed"), + (self.task3, "Failed"), + ] for task, expected_status in tasks_to_test: with self.subTest(status=expected_status): @@ -95,7 +107,9 @@ def test_pattern_detail_view(self): url = reverse("pattern-detail", args=[self.pattern.pk]) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) - self.assertEqual(response.data["collection_name"], "mynamespace.mycollection") + self.assertEqual( + response.data["collection_name"], "mynamespace.mycollection" + ) def test_pattern_create_view(self): url = reverse("pattern-list") @@ -132,7 +146,9 @@ def test_pattern_instance_list_view(self): self.assertEqual(len(response.data), 1) def test_pattern_instance_detail_view(self): - url = reverse("patterninstance-detail", args=[self.pattern_instance.pk]) + url = reverse( + "patterninstance-detail", args=[self.pattern_instance.pk] + ) response = self.client.get(url) self.assertEqual(response.status_code, status.HTTP_200_OK) self.assertEqual(response.data["organization_id"], 1) diff --git a/src/pattern_service/core/urls.py b/src/pattern_service/core/urls.py new file mode 100644 index 00000000..412fc8b0 --- /dev/null +++ b/src/pattern_service/core/urls.py @@ -0,0 +1,22 @@ +from ansible_base.lib.routers import AssociationResourceRouter # type: ignore + +from .views import ( + AutomationViewSet, + ControllerLabelViewSet, + PatternInstanceViewSet, + PatternViewSet, + TaskViewSet, +) + +router = AssociationResourceRouter() +router.register(r"patterns", PatternViewSet, basename="pattern") +router.register( + r"controllerlabels", ControllerLabelViewSet, basename="controllerlabel" +) +router.register( + r"patterninstances", PatternInstanceViewSet, basename="patterninstance" +) +router.register(r"automations", AutomationViewSet, basename="automation") +router.register(r"tasks", TaskViewSet, basename="task") + +urlpatterns = router.urls diff --git a/core/views.py b/src/pattern_service/core/views.py similarity index 72% rename from core/views.py rename to src/pattern_service/core/views.py index 138b18d5..9aa81294 100644 --- a/core/views.py +++ b/src/pattern_service/core/views.py @@ -1,20 +1,19 @@ -from ansible_base.lib.utils.views.ansible_base import AnsibleBaseView +from ansible_base.lib.utils.views.ansible_base import ( # type: ignore + AnsibleBaseView, +) from rest_framework import status from rest_framework.decorators import api_view from rest_framework.response import Response -from rest_framework.viewsets import ModelViewSet -from rest_framework.viewsets import ReadOnlyModelViewSet +from rest_framework.viewsets import ModelViewSet, ReadOnlyModelViewSet -from .models import Automation -from .models import ControllerLabel -from .models import Pattern -from .models import PatternInstance -from .models import Task -from .serializers import AutomationSerializer -from .serializers import ControllerLabelSerializer -from .serializers import PatternInstanceSerializer -from .serializers import PatternSerializer -from .serializers import TaskSerializer +from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task +from .serializers import ( + AutomationSerializer, + ControllerLabelSerializer, + PatternInstanceSerializer, + PatternSerializer, + TaskSerializer, +) class CoreViewSet(AnsibleBaseView): @@ -30,7 +29,9 @@ def create(self, request, *args, **kwargs): serializer.is_valid(raise_exception=True) pattern = serializer.save() - task = Task.objects.create(status="Initiated", details={"model": "Pattern", "id": pattern.id}) + task = Task.objects.create( + status="Initiated", details={"model": "Pattern", "id": pattern.id} + ) return Response( { @@ -57,7 +58,10 @@ def create(self, request, *args, **kwargs): instance = serializer.save() # Create a Task entry to track this processing - task = Task.objects.create(status="Initiated", details={"model": "PatternInstance", "id": instance.id}) + task = Task.objects.create( + status="Initiated", + details={"model": "PatternInstance", "id": instance.id}, + ) return Response( { diff --git a/manage.py b/src/pattern_service/manage.py similarity index 100% rename from manage.py rename to src/pattern_service/manage.py diff --git a/pattern_service/settings/__init__.py b/src/pattern_service/settings/__init__.py similarity index 63% rename from pattern_service/settings/__init__.py rename to src/pattern_service/settings/__init__.py index 48e182c1..8ef61296 100644 --- a/pattern_service/settings/__init__.py +++ b/src/pattern_service/settings/__init__.py @@ -1,5 +1,4 @@ -from ansible_base.lib.dynamic_config import export -from ansible_base.lib.dynamic_config import factory +from ansible_base.lib.dynamic_config import export, factory # type: ignore # Django Ansible Base Dynaconf settings DYNACONF = factory(__name__, "PATTERN_SERVICE", settings_files=["defaults.py"]) diff --git a/pattern_service/settings/defaults.py b/src/pattern_service/settings/defaults.py similarity index 96% rename from pattern_service/settings/defaults.py rename to src/pattern_service/settings/defaults.py index 60dbe28a..841d133f 100644 --- a/pattern_service/settings/defaults.py +++ b/src/pattern_service/settings/defaults.py @@ -21,7 +21,9 @@ # See https://docs.djangoproject.com/en/5.2/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! -SECRET_KEY = "django-insecure-_f^+pc=x%dd&p8ht4qv7rqr8&a%@j#lda6v!x9353m+)fm8&gk" +SECRET_KEY = ( + "django-insecure-_f^+pc=x%dd&p8ht4qv7rqr8&a%@j#lda6v!x9353m+)fm8&gk" +) # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True @@ -39,7 +41,7 @@ "django.contrib.messages", "django.contrib.staticfiles", "rest_framework", - "core", + "pattern_service.core", ] MIDDLEWARE = [ diff --git a/pattern_service/urls.py b/src/pattern_service/urls.py similarity index 80% rename from pattern_service/urls.py rename to src/pattern_service/urls.py index 9ac40eb0..149be144 100644 --- a/pattern_service/urls.py +++ b/src/pattern_service/urls.py @@ -16,15 +16,14 @@ """ from django.contrib import admin -from django.urls import include -from django.urls import path +from django.urls import include, path -from core.views import ping -from core.views import test +from pattern_service.core import urls as core_urls +from pattern_service.core.views import ping, test urlpatterns = [ path("admin/", admin.site.urls), - path("api/pattern-service/v1/", include('core.urls')), + path("api/pattern-service/v1/", include(core_urls)), path("ping/", ping), path("api/pattern-service/v1/test/", test), ] diff --git a/pattern_service/wsgi.py b/src/pattern_service/wsgi.py similarity index 100% rename from pattern_service/wsgi.py rename to src/pattern_service/wsgi.py diff --git a/tools/docker/Dockerfile.dev b/tools/docker/Dockerfile.dev new file mode 100644 index 00000000..bb97b781 --- /dev/null +++ b/tools/docker/Dockerfile.dev @@ -0,0 +1,31 @@ +FROM registry.access.redhat.com/ubi9/ubi:latest + +WORKDIR /app + +COPY requirements/requirements.txt . + +RUN dnf install git python3.11 python3.11-devel python3.11-pip -y + +ENV VIRTUAL_ENV=/app/venv + +# create virtual environment +RUN python3.11 -m venv "$VIRTUAL_ENV" + +ENV PATH="${VIRTUAL_ENV}/bin:$PATH" + +RUN python -m pip install --no-cache-dir -r requirements.txt + +# install the pattern service application +ADD src /app/src + +COPY pyproject.toml /app/ + +COPY README.md /app/ + +RUN python -m pip install /app/ + +RUN aap-pattern-service-manage migrate + +EXPOSE 5000 + +CMD ["aap-pattern-service-manage", "runserver", "0.0.0.0:5000"] diff --git a/tox.ini b/tox.ini index b391f79e..a2ffab99 100644 --- a/tox.ini +++ b/tox.ini @@ -8,17 +8,17 @@ setenv = PIP_CONSTRAINT = {toxinidir}/requirements/requirements-all.txt [common] -code_dirs = {toxinidir}/pattern_service {toxinidir}/core +code_dirs = {toxinidir}/src/pattern_service [testenv:mypy] +allowlist_externals = bash deps = -c {env:PIP_CONSTRAINT} mypy django-stubs[compatible-mypy] djangorestframework-stubs[compatible-mypy] -skip_install = - true -commands = mypy -p core -p pattern_service +commands = bash {toxinidir}/run_mypy.sh + [testenv:black] depends = @@ -63,6 +63,7 @@ commands = flynt --dry-run --fail-on-change {[common]code_dirs} [testenv:linters] +allowlist_externals = {[testenv:mypy]allowlist_externals} deps = -c {env:PIP_CONSTRAINT} {[testenv:black]deps} @@ -87,10 +88,23 @@ commands = flake8 {[common]code_dirs} [testenv:pip-compile] -deps = +deps = pip-tools commands = pip-compile --output-file=requirements/requirements.txt requirements/requirements.in + pip-compile --output-file=requirements/requirements-dev.txt requirements/requirements-dev.in + +[testenv:unit_tests] +allowlist_externals = bash +setenv = + PS_FEATURE_DISPATCHERD = "false" +deps = + {toxinidir} + ; -r{toxinidir}/requirements/requirements-dev.txt +commands = + aap-pattern-service-manage makemigrations core --no-input + aap-pattern-service-manage migrate core --no-input + aap-pattern-service-manage test pattern_service.core --verbosity=2 [flake8] # E123, E125 skipped as they are invalid PEP-8. From d5c8a53fb304c84b8f0a1b94c6af8f8da5832e16 Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:06:57 +0200 Subject: [PATCH 2/9] trying to move file --- Makefile | 2 +- core/admin.py | 9 +++++++++ src/pattern_service/core/admin.py | 9 --------- 3 files changed, 10 insertions(+), 10 deletions(-) create mode 100644 core/admin.py delete mode 100644 src/pattern_service/core/admin.py diff --git a/Makefile b/Makefile index dfef1e95..f3cd3352 100644 --- a/Makefile +++ b/Makefile @@ -1,4 +1,4 @@ -.PHONY: build clean push +.PHONY: build clean push build_amd64 # Image name and tag CONTAINER_RUNTIME ?= podman diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 00000000..37252c7a --- /dev/null +++ b/core/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from core import models + +admin.site.register(models.Pattern) +admin.site.register(models.ControllerLabel) +admin.site.register(models.PatternInstance) +admin.site.register(models.Automation) +admin.site.register(models.Task) diff --git a/src/pattern_service/core/admin.py b/src/pattern_service/core/admin.py deleted file mode 100644 index 987e24ee..00000000 --- a/src/pattern_service/core/admin.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib import admin - -from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task - -admin.site.register(Pattern) -admin.site.register(ControllerLabel) -admin.site.register(PatternInstance) -admin.site.register(Automation) -admin.site.register(Task) From 1f5536f7b9e9bec803ab5a303152f37ffabc2c1b Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:07:19 +0200 Subject: [PATCH 3/9] Move file --- {core => src/pattern_service/core}/admin.py | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename {core => src/pattern_service/core}/admin.py (100%) diff --git a/core/admin.py b/src/pattern_service/core/admin.py similarity index 100% rename from core/admin.py rename to src/pattern_service/core/admin.py From b92e3603664a7996c665be42812bb312d2af530a Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:10:27 +0200 Subject: [PATCH 4/9] attempt to move --- core/models.py | 70 +++++++++++++++ core/serializers.py | 66 +++++++++++++++ core/urls.py | 16 ++++ src/pattern_service/core/admin.py | 12 +-- src/pattern_service/core/models.py | 108 ------------------------ src/pattern_service/core/serializers.py | 68 --------------- src/pattern_service/core/urls.py | 22 ----- 7 files changed, 158 insertions(+), 204 deletions(-) create mode 100644 core/models.py create mode 100644 core/serializers.py create mode 100644 core/urls.py delete mode 100644 src/pattern_service/core/models.py delete mode 100644 src/pattern_service/core/serializers.py delete mode 100644 src/pattern_service/core/urls.py diff --git a/core/models.py b/core/models.py new file mode 100644 index 00000000..2ea1b58b --- /dev/null +++ b/core/models.py @@ -0,0 +1,70 @@ +from __future__ import annotations + +from ansible_base.lib.abstract_models import CommonModel +from django.db import models + + +class Pattern(CommonModel): + class Meta: + app_label = 'core' + ordering = ['id'] + constraints = [models.UniqueConstraint(fields=["collection_name", "collection_version", "pattern_name"], name="unique_pattern_collection_version")] + + collection_name: models.CharField = models.CharField(max_length=200) + collection_version: models.CharField = models.CharField(max_length=50) + collection_version_uri: models.CharField = models.CharField(max_length=200, blank=True, null=True) + pattern_name: models.CharField = models.CharField(max_length=200) + pattern_definition: models.JSONField = models.JSONField(blank=True, null=True) + + +class ControllerLabel(CommonModel): + class Meta: + app_label = 'core' + ordering = ['id'] + + label_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField(unique=True) + + +class PatternInstance(CommonModel): + class Meta: + app_label = 'core' + ordering = ['id'] + constraints = [models.UniqueConstraint(fields=["organization_id", "pattern"], name="unique_pattern_instance_organization")] + + organization_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField() + controller_project_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField(blank=True, null=True) + controller_ee_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField(null=True, blank=True) + credentials: models.JSONField = models.JSONField() + executors: models.JSONField = models.JSONField(null=True, blank=True) + + pattern: models.ForeignKey = models.ForeignKey(Pattern, on_delete=models.CASCADE, related_name="pattern_instances") + controller_labels: models.ManyToManyField = models.ManyToManyField(ControllerLabel, related_name="pattern_instances", blank=True) + + +class Automation(CommonModel): + class Meta: + app_label = 'core' + ordering = ['id'] + + automation_type_choices = (("job_template", "Job template"),) + automation_type: models.CharField = models.CharField(max_length=200, choices=automation_type_choices) + automation_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField() + primary: models.BooleanField = models.BooleanField(default=False) + + pattern_instance: models.ForeignKey = models.ForeignKey(PatternInstance, on_delete=models.CASCADE, related_name="automations") + + +class Task(CommonModel): + class Meta: + app_label = 'core' + ordering = ['id'] + + status_choices = ( + ("Initiated", "Initiated"), + ("Running", "Running"), + ("Completed", "Completed"), + ("Failed", "Failed"), + ) + + status: models.CharField = models.CharField(max_length=20, choices=status_choices) + details: models.JSONField = models.JSONField(null=True, blank=True) diff --git a/core/serializers.py b/core/serializers.py new file mode 100644 index 00000000..38b4a382 --- /dev/null +++ b/core/serializers.py @@ -0,0 +1,66 @@ +from __future__ import annotations + +from ansible_base.lib.serializers.common import CommonModelSerializer + +from .models import Automation +from .models import ControllerLabel +from .models import Pattern +from .models import PatternInstance +from .models import Task + + +class PatternSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = Pattern + fields = CommonModelSerializer.Meta.fields + [ + 'id', + 'collection_name', + 'collection_version', + 'collection_version_uri', + 'pattern_name', + 'pattern_definition', + ] + read_only_fields = ['pattern_definition', 'collection_version_uri'] + + +class ControllerLabelSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = ControllerLabel + fields = CommonModelSerializer.Meta.fields + ['id', 'label_id'] + + +class PatternInstanceSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = PatternInstance + fields = CommonModelSerializer.Meta.fields + [ + 'id', + 'organization_id', + 'controller_project_id', + 'controller_ee_id', + 'controller_labels', + 'credentials', + 'executors', + 'pattern', + ] + read_only_fields = ['controller_project_id', 'controller_ee_id', 'controller_labels'] + + +class AutomationSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = Automation + fields = CommonModelSerializer.Meta.fields + [ + 'id', + 'automation_type', + 'automation_id', + 'primary', + 'pattern_instance', + ] + + +class TaskSerializer(CommonModelSerializer): + class Meta(CommonModelSerializer.Meta): + model = Task + fields = CommonModelSerializer.Meta.fields + [ + 'status', + 'details', + ] diff --git a/core/urls.py b/core/urls.py new file mode 100644 index 00000000..2d1b5c8c --- /dev/null +++ b/core/urls.py @@ -0,0 +1,16 @@ +from ansible_base.lib.routers import AssociationResourceRouter + +from .views import AutomationViewSet +from .views import ControllerLabelViewSet +from .views import PatternInstanceViewSet +from .views import PatternViewSet +from .views import TaskViewSet + +router = AssociationResourceRouter() +router.register(r'patterns', PatternViewSet, basename='pattern') +router.register(r'controllerlabels', ControllerLabelViewSet, basename='controllerlabel') +router.register(r'patterninstances', PatternInstanceViewSet, basename='patterninstance') +router.register(r'automations', AutomationViewSet, basename='automation') +router.register(r'tasks', TaskViewSet, basename='task') + +urlpatterns = router.urls diff --git a/src/pattern_service/core/admin.py b/src/pattern_service/core/admin.py index 37252c7a..987e24ee 100644 --- a/src/pattern_service/core/admin.py +++ b/src/pattern_service/core/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin -from core import models +from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task -admin.site.register(models.Pattern) -admin.site.register(models.ControllerLabel) -admin.site.register(models.PatternInstance) -admin.site.register(models.Automation) -admin.site.register(models.Task) +admin.site.register(Pattern) +admin.site.register(ControllerLabel) +admin.site.register(PatternInstance) +admin.site.register(Automation) +admin.site.register(Task) diff --git a/src/pattern_service/core/models.py b/src/pattern_service/core/models.py deleted file mode 100644 index 59564ffe..00000000 --- a/src/pattern_service/core/models.py +++ /dev/null @@ -1,108 +0,0 @@ -from __future__ import annotations - -from ansible_base.lib.abstract_models import CommonModel # type: ignore -from django.db import models - - -class Pattern(CommonModel): - class Meta: - app_label = "core" - ordering = ["id"] - constraints = [ - models.UniqueConstraint( - fields=[ - "collection_name", - "collection_version", - "pattern_name", - ], - name="unique_pattern_collection_version", - ) - ] - - collection_name: models.CharField = models.CharField(max_length=200) - collection_version: models.CharField = models.CharField(max_length=50) - collection_version_uri: models.CharField = models.CharField( - max_length=200, blank=True, null=True - ) - pattern_name: models.CharField = models.CharField(max_length=200) - pattern_definition: models.JSONField = models.JSONField( - blank=True, null=True - ) - - -class ControllerLabel(CommonModel): - class Meta: - app_label = "core" - ordering = ["id"] - - label_id: models.PositiveBigIntegerField = models.PositiveBigIntegerField( - unique=True - ) - - -class PatternInstance(CommonModel): - class Meta: - app_label = "core" - ordering = ["id"] - constraints = [ - models.UniqueConstraint( - fields=["organization_id", "pattern"], - name="unique_pattern_instance_organization", - ) - ] - - organization_id: models.PositiveBigIntegerField = ( - models.PositiveBigIntegerField() - ) - controller_project_id: models.PositiveBigIntegerField = ( - models.PositiveBigIntegerField(blank=True, null=True) - ) - controller_ee_id: models.PositiveBigIntegerField = ( - models.PositiveBigIntegerField(null=True, blank=True) - ) - credentials: models.JSONField = models.JSONField() - executors: models.JSONField = models.JSONField(null=True, blank=True) - - pattern: models.ForeignKey = models.ForeignKey( - Pattern, on_delete=models.CASCADE, related_name="pattern_instances" - ) - controller_labels: models.ManyToManyField = models.ManyToManyField( - ControllerLabel, related_name="pattern_instances", blank=True - ) - - -class Automation(CommonModel): - class Meta: - app_label = "core" - ordering = ["id"] - - automation_type_choices = (("job_template", "Job template"),) - automation_type: models.CharField = models.CharField( - max_length=200, choices=automation_type_choices - ) - automation_id: models.PositiveBigIntegerField = ( - models.PositiveBigIntegerField() - ) - primary: models.BooleanField = models.BooleanField(default=False) - - pattern_instance: models.ForeignKey = models.ForeignKey( - PatternInstance, on_delete=models.CASCADE, related_name="automations" - ) - - -class Task(CommonModel): - class Meta: - app_label = "core" - ordering = ["id"] - - status_choices = ( - ("Initiated", "Initiated"), - ("Running", "Running"), - ("Completed", "Completed"), - ("Failed", "Failed"), - ) - - status: models.CharField = models.CharField( - max_length=20, choices=status_choices - ) - details: models.JSONField = models.JSONField(null=True, blank=True) diff --git a/src/pattern_service/core/serializers.py b/src/pattern_service/core/serializers.py deleted file mode 100644 index 4554b36e..00000000 --- a/src/pattern_service/core/serializers.py +++ /dev/null @@ -1,68 +0,0 @@ -from __future__ import annotations - -from ansible_base.lib.serializers.common import ( # type: ignore - CommonModelSerializer, -) - -from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task - - -class PatternSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = Pattern - fields = CommonModelSerializer.Meta.fields + [ - "id", - "collection_name", - "collection_version", - "collection_version_uri", - "pattern_name", - "pattern_definition", - ] - read_only_fields = ["pattern_definition", "collection_version_uri"] - - -class ControllerLabelSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = ControllerLabel - fields = CommonModelSerializer.Meta.fields + ["id", "label_id"] - - -class PatternInstanceSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = PatternInstance - fields = CommonModelSerializer.Meta.fields + [ - "id", - "organization_id", - "controller_project_id", - "controller_ee_id", - "controller_labels", - "credentials", - "executors", - "pattern", - ] - read_only_fields = [ - "controller_project_id", - "controller_ee_id", - "controller_labels", - ] - - -class AutomationSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = Automation - fields = CommonModelSerializer.Meta.fields + [ - "id", - "automation_type", - "automation_id", - "primary", - "pattern_instance", - ] - - -class TaskSerializer(CommonModelSerializer): - class Meta(CommonModelSerializer.Meta): - model = Task - fields = CommonModelSerializer.Meta.fields + [ - "status", - "details", - ] diff --git a/src/pattern_service/core/urls.py b/src/pattern_service/core/urls.py deleted file mode 100644 index 412fc8b0..00000000 --- a/src/pattern_service/core/urls.py +++ /dev/null @@ -1,22 +0,0 @@ -from ansible_base.lib.routers import AssociationResourceRouter # type: ignore - -from .views import ( - AutomationViewSet, - ControllerLabelViewSet, - PatternInstanceViewSet, - PatternViewSet, - TaskViewSet, -) - -router = AssociationResourceRouter() -router.register(r"patterns", PatternViewSet, basename="pattern") -router.register( - r"controllerlabels", ControllerLabelViewSet, basename="controllerlabel" -) -router.register( - r"patterninstances", PatternInstanceViewSet, basename="patterninstance" -) -router.register(r"automations", AutomationViewSet, basename="automation") -router.register(r"tasks", TaskViewSet, basename="task") - -urlpatterns = router.urls From cf912ce0841e7f0a76d4c86396631c3f253e2300 Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:11:31 +0200 Subject: [PATCH 5/9] move files --- {core => src/pattern_service/core}/models.py | 0 {core => src/pattern_service/core}/serializers.py | 0 {core => src/pattern_service/core}/urls.py | 0 3 files changed, 0 insertions(+), 0 deletions(-) rename {core => src/pattern_service/core}/models.py (100%) rename {core => src/pattern_service/core}/serializers.py (100%) rename {core => src/pattern_service/core}/urls.py (100%) diff --git a/core/models.py b/src/pattern_service/core/models.py similarity index 100% rename from core/models.py rename to src/pattern_service/core/models.py diff --git a/core/serializers.py b/src/pattern_service/core/serializers.py similarity index 100% rename from core/serializers.py rename to src/pattern_service/core/serializers.py diff --git a/core/urls.py b/src/pattern_service/core/urls.py similarity index 100% rename from core/urls.py rename to src/pattern_service/core/urls.py From 28ef8a5e8cf1bff10f1b9c13216ec54cc78220cf Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:13:01 +0200 Subject: [PATCH 6/9] move --- core/admin.py | 9 + core/migrations/0001_initial.py | 165 +++++++++++ ...rn_created_by_pattern_modified_and_more.py | 68 ++--- core/migrations/0003_task.py | 59 ++++ .../core => core}/migrations/__init__.py | 0 src/pattern_service/core/admin.py | 9 - .../core/migrations/0001_initial.py | 272 ------------------ .../core/migrations/0003_task.py | 87 ------ 8 files changed, 264 insertions(+), 405 deletions(-) create mode 100644 core/admin.py create mode 100644 core/migrations/0001_initial.py rename {src/pattern_service/core => core}/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py (53%) create mode 100644 core/migrations/0003_task.py rename {src/pattern_service/core => core}/migrations/__init__.py (100%) delete mode 100644 src/pattern_service/core/admin.py delete mode 100644 src/pattern_service/core/migrations/0001_initial.py delete mode 100644 src/pattern_service/core/migrations/0003_task.py diff --git a/core/admin.py b/core/admin.py new file mode 100644 index 00000000..37252c7a --- /dev/null +++ b/core/admin.py @@ -0,0 +1,9 @@ +from django.contrib import admin + +from core import models + +admin.site.register(models.Pattern) +admin.site.register(models.ControllerLabel) +admin.site.register(models.PatternInstance) +admin.site.register(models.Automation) +admin.site.register(models.Task) diff --git a/core/migrations/0001_initial.py b/core/migrations/0001_initial.py new file mode 100644 index 00000000..cbb5e0a6 --- /dev/null +++ b/core/migrations/0001_initial.py @@ -0,0 +1,165 @@ +# Generated by Django 4.2.21 on 2025-06-09 18:49 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + initial = True + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ] + + operations = [ + migrations.CreateModel( + name='Automation', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), + ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), + ('automation_type', models.CharField(choices=[('job_template', 'Job template')], max_length=200)), + ('automation_id', models.BigIntegerField()), + ('primary', models.BooleanField(default=False)), + ], + options={ + 'ordering': ['id'], + }, + ), + migrations.CreateModel( + name='ControllerLabel', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), + ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), + ('label_id', models.BigIntegerField(unique=True)), + ], + options={ + 'ordering': ['id'], + }, + ), + migrations.CreateModel( + name='Pattern', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('collection_name', models.CharField(max_length=200)), + ('collection_version', models.CharField(max_length=50)), + ('collection_version_uri', models.CharField(blank=True, max_length=200)), + ('pattern_name', models.CharField(max_length=200)), + ('pattern_definition', models.JSONField(blank=True)), + ], + options={ + 'ordering': ['id'], + }, + ), + migrations.CreateModel( + name='PatternInstance', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), + ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), + ('organization_id', models.BigIntegerField()), + ('controller_project_id', models.BigIntegerField(blank=True)), + ('controller_ee_id', models.BigIntegerField(blank=True, null=True)), + ('credentials', models.JSONField()), + ('executors', models.JSONField(blank=True, null=True)), + ('controller_labels', models.ManyToManyField(blank=True, related_name='pattern_instances', to='core.controllerlabel')), + ( + 'created_by', + models.ForeignKey( + default=None, + editable=False, + help_text='The user who created this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_created+', + to=settings.AUTH_USER_MODEL, + ), + ), + ( + 'modified_by', + models.ForeignKey( + default=None, + editable=False, + help_text='The user who last modified this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_modified+', + to=settings.AUTH_USER_MODEL, + ), + ), + ('pattern', models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='pattern_instances', to='core.pattern')), + ], + options={ + 'ordering': ['id'], + }, + ), + migrations.AddConstraint( + model_name='pattern', + constraint=models.UniqueConstraint(fields=('collection_name', 'collection_version', 'pattern_name'), name='unique_pattern_collection_version'), + ), + migrations.AddField( + model_name='controllerlabel', + name='created_by', + field=models.ForeignKey( + default=None, + editable=False, + help_text='The user who created this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_created+', + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name='controllerlabel', + name='modified_by', + field=models.ForeignKey( + default=None, + editable=False, + help_text='The user who last modified this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_modified+', + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name='automation', + name='created_by', + field=models.ForeignKey( + default=None, + editable=False, + help_text='The user who created this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_created+', + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name='automation', + name='modified_by', + field=models.ForeignKey( + default=None, + editable=False, + help_text='The user who last modified this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_modified+', + to=settings.AUTH_USER_MODEL, + ), + ), + migrations.AddField( + model_name='automation', + name='pattern_instance', + field=models.ForeignKey(on_delete=django.db.models.deletion.CASCADE, related_name='automations', to='core.patterninstance'), + ), + migrations.AddConstraint( + model_name='patterninstance', + constraint=models.UniqueConstraint(fields=('organization_id', 'pattern'), name='unique_pattern_instance_organization'), + ), + ] diff --git a/src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py b/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py similarity index 53% rename from src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py rename to core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py index 5a316812..099ff977 100644 --- a/src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py +++ b/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py @@ -3,94 +3,88 @@ import django.db.models.deletion import django.utils.timezone from django.conf import settings -from django.db import migrations, models +from django.db import migrations +from django.db import models class Migration(migrations.Migration): dependencies = [ migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ("core", "0001_initial"), + ('core', '0001_initial'), ] operations = [ migrations.AddField( - model_name="pattern", - name="created", - field=models.DateTimeField( - auto_now_add=True, - default=django.utils.timezone.now, - help_text="The date/time this resource was created.", - ), + model_name='pattern', + name='created', + field=models.DateTimeField(auto_now_add=True, default=django.utils.timezone.now, help_text='The date/time this resource was created.'), preserve_default=False, ), migrations.AddField( - model_name="pattern", - name="created_by", + model_name='pattern', + name='created_by', field=models.ForeignKey( default=None, editable=False, - help_text="The user who created this resource.", + help_text='The user who created this resource.', null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_created+", + related_name='%(app_label)s_%(class)s_created+', to=settings.AUTH_USER_MODEL, ), ), migrations.AddField( - model_name="pattern", - name="modified", - field=models.DateTimeField( - auto_now=True, - help_text="The date/time this resource was created.", - ), + model_name='pattern', + name='modified', + field=models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.'), ), migrations.AddField( - model_name="pattern", - name="modified_by", + model_name='pattern', + name='modified_by', field=models.ForeignKey( default=None, editable=False, - help_text="The user who last modified this resource.", + help_text='The user who last modified this resource.', null=True, on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_modified+", + related_name='%(app_label)s_%(class)s_modified+', to=settings.AUTH_USER_MODEL, ), ), migrations.AlterField( - model_name="automation", - name="automation_id", + model_name='automation', + name='automation_id', field=models.PositiveBigIntegerField(), ), migrations.AlterField( - model_name="controllerlabel", - name="label_id", + model_name='controllerlabel', + name='label_id', field=models.PositiveBigIntegerField(unique=True), ), migrations.AlterField( - model_name="pattern", - name="collection_version_uri", + model_name='pattern', + name='collection_version_uri', field=models.CharField(blank=True, max_length=200, null=True), ), migrations.AlterField( - model_name="pattern", - name="pattern_definition", + model_name='pattern', + name='pattern_definition', field=models.JSONField(blank=True, null=True), ), migrations.AlterField( - model_name="patterninstance", - name="controller_ee_id", + model_name='patterninstance', + name='controller_ee_id', field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AlterField( - model_name="patterninstance", - name="controller_project_id", + model_name='patterninstance', + name='controller_project_id', field=models.PositiveBigIntegerField(blank=True, null=True), ), migrations.AlterField( - model_name="patterninstance", - name="organization_id", + model_name='patterninstance', + name='organization_id', field=models.PositiveBigIntegerField(), ), ] diff --git a/core/migrations/0003_task.py b/core/migrations/0003_task.py new file mode 100644 index 00000000..a6bdcf0a --- /dev/null +++ b/core/migrations/0003_task.py @@ -0,0 +1,59 @@ +# Generated by Django 4.2.23 on 2025-06-26 19:27 + +import django.db.models.deletion +from django.conf import settings +from django.db import migrations +from django.db import models + + +class Migration(migrations.Migration): + + dependencies = [ + migrations.swappable_dependency(settings.AUTH_USER_MODEL), + ('core', '0002_pattern_created_pattern_created_by_pattern_modified_and_more'), + ] + + operations = [ + migrations.CreateModel( + name='Task', + fields=[ + ('id', models.BigAutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')), + ('modified', models.DateTimeField(auto_now=True, help_text='The date/time this resource was created.')), + ('created', models.DateTimeField(auto_now_add=True, help_text='The date/time this resource was created.')), + ( + 'status', + models.CharField( + choices=[('Initiated', 'Initiated'), ('Running', 'Running'), ('Completed', 'Completed'), ('Failed', 'Failed')], max_length=20 + ), + ), + ('details', models.JSONField(blank=True, null=True)), + ( + 'created_by', + models.ForeignKey( + default=None, + editable=False, + help_text='The user who created this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_created+', + to=settings.AUTH_USER_MODEL, + ), + ), + ( + 'modified_by', + models.ForeignKey( + default=None, + editable=False, + help_text='The user who last modified this resource.', + null=True, + on_delete=django.db.models.deletion.SET_NULL, + related_name='%(app_label)s_%(class)s_modified+', + to=settings.AUTH_USER_MODEL, + ), + ), + ], + options={ + 'ordering': ['id'], + }, + ), + ] diff --git a/src/pattern_service/core/migrations/__init__.py b/core/migrations/__init__.py similarity index 100% rename from src/pattern_service/core/migrations/__init__.py rename to core/migrations/__init__.py diff --git a/src/pattern_service/core/admin.py b/src/pattern_service/core/admin.py deleted file mode 100644 index 987e24ee..00000000 --- a/src/pattern_service/core/admin.py +++ /dev/null @@ -1,9 +0,0 @@ -from django.contrib import admin - -from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task - -admin.site.register(Pattern) -admin.site.register(ControllerLabel) -admin.site.register(PatternInstance) -admin.site.register(Automation) -admin.site.register(Task) diff --git a/src/pattern_service/core/migrations/0001_initial.py b/src/pattern_service/core/migrations/0001_initial.py deleted file mode 100644 index 232e65a2..00000000 --- a/src/pattern_service/core/migrations/0001_initial.py +++ /dev/null @@ -1,272 +0,0 @@ -# Generated by Django 4.2.21 on 2025-06-09 18:49 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - initial = True - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ] - - operations = [ - migrations.CreateModel( - name="Automation", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "modified", - models.DateTimeField( - auto_now=True, - help_text="The date/time this resource was created.", - ), - ), - ( - "created", - models.DateTimeField( - auto_now_add=True, - help_text="The date/time this resource was created.", - ), - ), - ( - "automation_type", - models.CharField( - choices=[("job_template", "Job template")], - max_length=200, - ), - ), - ("automation_id", models.BigIntegerField()), - ("primary", models.BooleanField(default=False)), - ], - options={ - "ordering": ["id"], - }, - ), - migrations.CreateModel( - name="ControllerLabel", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "modified", - models.DateTimeField( - auto_now=True, - help_text="The date/time this resource was created.", - ), - ), - ( - "created", - models.DateTimeField( - auto_now_add=True, - help_text="The date/time this resource was created.", - ), - ), - ("label_id", models.BigIntegerField(unique=True)), - ], - options={ - "ordering": ["id"], - }, - ), - migrations.CreateModel( - name="Pattern", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ("collection_name", models.CharField(max_length=200)), - ("collection_version", models.CharField(max_length=50)), - ( - "collection_version_uri", - models.CharField(blank=True, max_length=200), - ), - ("pattern_name", models.CharField(max_length=200)), - ("pattern_definition", models.JSONField(blank=True)), - ], - options={ - "ordering": ["id"], - }, - ), - migrations.CreateModel( - name="PatternInstance", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "modified", - models.DateTimeField( - auto_now=True, - help_text="The date/time this resource was created.", - ), - ), - ( - "created", - models.DateTimeField( - auto_now_add=True, - help_text="The date/time this resource was created.", - ), - ), - ("organization_id", models.BigIntegerField()), - ("controller_project_id", models.BigIntegerField(blank=True)), - ( - "controller_ee_id", - models.BigIntegerField(blank=True, null=True), - ), - ("credentials", models.JSONField()), - ("executors", models.JSONField(blank=True, null=True)), - ( - "controller_labels", - models.ManyToManyField( - blank=True, - related_name="pattern_instances", - to="core.controllerlabel", - ), - ), - ( - "created_by", - models.ForeignKey( - default=None, - editable=False, - help_text="The user who created this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_created+", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "modified_by", - models.ForeignKey( - default=None, - editable=False, - help_text="The user who last modified this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_modified+", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "pattern", - models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="pattern_instances", - to="core.pattern", - ), - ), - ], - options={ - "ordering": ["id"], - }, - ), - migrations.AddConstraint( - model_name="pattern", - constraint=models.UniqueConstraint( - fields=( - "collection_name", - "collection_version", - "pattern_name", - ), - name="unique_pattern_collection_version", - ), - ), - migrations.AddField( - model_name="controllerlabel", - name="created_by", - field=models.ForeignKey( - default=None, - editable=False, - help_text="The user who created this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_created+", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="controllerlabel", - name="modified_by", - field=models.ForeignKey( - default=None, - editable=False, - help_text="The user who last modified this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_modified+", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="automation", - name="created_by", - field=models.ForeignKey( - default=None, - editable=False, - help_text="The user who created this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_created+", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="automation", - name="modified_by", - field=models.ForeignKey( - default=None, - editable=False, - help_text="The user who last modified this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_modified+", - to=settings.AUTH_USER_MODEL, - ), - ), - migrations.AddField( - model_name="automation", - name="pattern_instance", - field=models.ForeignKey( - on_delete=django.db.models.deletion.CASCADE, - related_name="automations", - to="core.patterninstance", - ), - ), - migrations.AddConstraint( - model_name="patterninstance", - constraint=models.UniqueConstraint( - fields=("organization_id", "pattern"), - name="unique_pattern_instance_organization", - ), - ), - ] diff --git a/src/pattern_service/core/migrations/0003_task.py b/src/pattern_service/core/migrations/0003_task.py deleted file mode 100644 index 7e58c036..00000000 --- a/src/pattern_service/core/migrations/0003_task.py +++ /dev/null @@ -1,87 +0,0 @@ -# Generated by Django 4.2.23 on 2025-06-26 19:27 - -import django.db.models.deletion -from django.conf import settings -from django.db import migrations, models - - -class Migration(migrations.Migration): - - dependencies = [ - migrations.swappable_dependency(settings.AUTH_USER_MODEL), - ( - "core", - "0002_pattern_created_pattern_created_by_pattern_modified_and_more", - ), - ] - - operations = [ - migrations.CreateModel( - name="Task", - fields=[ - ( - "id", - models.BigAutoField( - auto_created=True, - primary_key=True, - serialize=False, - verbose_name="ID", - ), - ), - ( - "modified", - models.DateTimeField( - auto_now=True, - help_text="The date/time this resource was created.", - ), - ), - ( - "created", - models.DateTimeField( - auto_now_add=True, - help_text="The date/time this resource was created.", - ), - ), - ( - "status", - models.CharField( - choices=[ - ("Initiated", "Initiated"), - ("Running", "Running"), - ("Completed", "Completed"), - ("Failed", "Failed"), - ], - max_length=20, - ), - ), - ("details", models.JSONField(blank=True, null=True)), - ( - "created_by", - models.ForeignKey( - default=None, - editable=False, - help_text="The user who created this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_created+", - to=settings.AUTH_USER_MODEL, - ), - ), - ( - "modified_by", - models.ForeignKey( - default=None, - editable=False, - help_text="The user who last modified this resource.", - null=True, - on_delete=django.db.models.deletion.SET_NULL, - related_name="%(app_label)s_%(class)s_modified+", - to=settings.AUTH_USER_MODEL, - ), - ), - ], - options={ - "ordering": ["id"], - }, - ), - ] From 141209e71d269f30a91f2ca5008dc407107f2759 Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:14:05 +0200 Subject: [PATCH 7/9] move files --- {core => src/pattern_service/core}/admin.py | 0 {core => src/pattern_service/core}/migrations/0001_initial.py | 0 ...attern_created_pattern_created_by_pattern_modified_and_more.py | 0 {core => src/pattern_service/core}/migrations/0003_task.py | 0 {core => src/pattern_service/core}/migrations/__init__.py | 0 5 files changed, 0 insertions(+), 0 deletions(-) rename {core => src/pattern_service/core}/admin.py (100%) rename {core => src/pattern_service/core}/migrations/0001_initial.py (100%) rename {core => src/pattern_service/core}/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py (100%) rename {core => src/pattern_service/core}/migrations/0003_task.py (100%) rename {core => src/pattern_service/core}/migrations/__init__.py (100%) diff --git a/core/admin.py b/src/pattern_service/core/admin.py similarity index 100% rename from core/admin.py rename to src/pattern_service/core/admin.py diff --git a/core/migrations/0001_initial.py b/src/pattern_service/core/migrations/0001_initial.py similarity index 100% rename from core/migrations/0001_initial.py rename to src/pattern_service/core/migrations/0001_initial.py diff --git a/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py b/src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py similarity index 100% rename from core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py rename to src/pattern_service/core/migrations/0002_pattern_created_pattern_created_by_pattern_modified_and_more.py diff --git a/core/migrations/0003_task.py b/src/pattern_service/core/migrations/0003_task.py similarity index 100% rename from core/migrations/0003_task.py rename to src/pattern_service/core/migrations/0003_task.py diff --git a/core/migrations/__init__.py b/src/pattern_service/core/migrations/__init__.py similarity index 100% rename from core/migrations/__init__.py rename to src/pattern_service/core/migrations/__init__.py From fe6f3393841cfe8ed6e75cfd4e7981819830c3ca Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:17:53 +0200 Subject: [PATCH 8/9] Fix unit tests --- src/pattern_service/core/admin.py | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/pattern_service/core/admin.py b/src/pattern_service/core/admin.py index 37252c7a..987e24ee 100644 --- a/src/pattern_service/core/admin.py +++ b/src/pattern_service/core/admin.py @@ -1,9 +1,9 @@ from django.contrib import admin -from core import models +from .models import Automation, ControllerLabel, Pattern, PatternInstance, Task -admin.site.register(models.Pattern) -admin.site.register(models.ControllerLabel) -admin.site.register(models.PatternInstance) -admin.site.register(models.Automation) -admin.site.register(models.Task) +admin.site.register(Pattern) +admin.site.register(ControllerLabel) +admin.site.register(PatternInstance) +admin.site.register(Automation) +admin.site.register(Task) From 9c581bd1763202beb9a9962ad82e6b714fd1cc1b Mon Sep 17 00:00:00 2001 From: aubin bikouo Date: Fri, 11 Jul 2025 16:21:47 +0200 Subject: [PATCH 9/9] Fix linters --- src/pattern_service/core/models.py | 2 +- src/pattern_service/core/serializers.py | 2 +- src/pattern_service/core/urls.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/src/pattern_service/core/models.py b/src/pattern_service/core/models.py index 2ea1b58b..76b65f2a 100644 --- a/src/pattern_service/core/models.py +++ b/src/pattern_service/core/models.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ansible_base.lib.abstract_models import CommonModel +from ansible_base.lib.abstract_models import CommonModel # type: ignore from django.db import models diff --git a/src/pattern_service/core/serializers.py b/src/pattern_service/core/serializers.py index 38b4a382..2a6991cd 100644 --- a/src/pattern_service/core/serializers.py +++ b/src/pattern_service/core/serializers.py @@ -1,6 +1,6 @@ from __future__ import annotations -from ansible_base.lib.serializers.common import CommonModelSerializer +from ansible_base.lib.serializers.common import CommonModelSerializer # type: ignore from .models import Automation from .models import ControllerLabel diff --git a/src/pattern_service/core/urls.py b/src/pattern_service/core/urls.py index 2d1b5c8c..b68063aa 100644 --- a/src/pattern_service/core/urls.py +++ b/src/pattern_service/core/urls.py @@ -1,4 +1,4 @@ -from ansible_base.lib.routers import AssociationResourceRouter +from ansible_base.lib.routers import AssociationResourceRouter # type: ignore from .views import AutomationViewSet from .views import ControllerLabelViewSet