Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add run-on to Boefje Setup page #4061

Merged
merged 31 commits into from
Feb 7, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
31 commits
Select commit Hold shift + click to select a range
fe7415c
Add run_on to Boefje in katalogus
madelondohmen Jan 30, 2025
828caa9
Add run_on to boefje setup page (with js)
madelondohmen Jan 30, 2025
3a0e410
Show run_on on boefje detail page
madelondohmen Jan 30, 2025
18fd80d
Fixes for all edge cases
madelondohmen Feb 3, 2025
dd6ba80
Merge branch 'main' into feature/boefje-variants-run-on-
madelondohmen Feb 4, 2025
b48de4d
Update translations
madelondohmen Feb 4, 2025
b3c009d
Merge branch 'feature/boefje-variants-run-on-' of https://github.com/…
madelondohmen Feb 4, 2025
ccd2762
Code fixes
madelondohmen Feb 4, 2025
0413dae
- Create enum out of run_on, suggested "all" instead of "create_updat…
Donnype Feb 5, 2025
0cd530f
- Separate RunOn into RunOnDB and RunOn
Donnype Feb 5, 2025
cafa536
Fixes in frontend for RunOn
madelondohmen Feb 5, 2025
b12b15a
Make run_on in mula a list[RunOn]
madelondohmen Feb 5, 2025
279f313
Update boefjes/boefjes/sql/db_models.py
madelondohmen Feb 6, 2025
fbc973d
Update boefjes/boefjes/models.py
madelondohmen Feb 6, 2025
99d7182
Merge branch 'main' into feature/boefje-variants-run-on-
madelondohmen Feb 6, 2025
46174e6
Pre-commit fix
madelondohmen Feb 6, 2025
4f8b54d
Update form.scss
underdarknl Feb 7, 2025
3576352
Update and rename boefjeScanTypeToggler.js to choiceToggle.js
underdarknl Feb 7, 2025
074a903
Update boefje_setup.html
underdarknl Feb 7, 2025
df52061
Update boefje.py
underdarknl Feb 7, 2025
ff8c94e
Update boefje_setup.py
underdarknl Feb 7, 2025
f94216f
Update boefje.py
underdarknl Feb 7, 2025
265eee5
Merge branch 'main' into feature/boefje-variants-run-on-
underdarknl Feb 7, 2025
9b3a94e
Update choiceToggle.js
underdarknl Feb 7, 2025
5f624e3
Update boefje.py
underdarknl Feb 7, 2025
b264a3b
Update choiceToggle.js
underdarknl Feb 7, 2025
f1e937e
Merge branch 'main' into feature/boefje-variants-run-on-
underdarknl Feb 7, 2025
8957770
Update choiceToggle.js
underdarknl Feb 7, 2025
4c32471
refactor some duplicate code in boefje_setup.py
underdarknl Feb 7, 2025
be6af11
I was a little too eager.
underdarknl Feb 7, 2025
c3ab124
Update boefje_setup.py
underdarknl Feb 7, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions boefjes/boefjes/katalogus/plugins.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
get_plugins_filter_parameters,
)
from boefjes.models import FilterParameters, PaginationParameters, PluginType
from boefjes.sql.db_models import RunOn
from boefjes.sql.plugin_storage import get_plugin_storage
from boefjes.storage.interfaces import DuplicatePlugin, IntegrityError, NotAllowed, PluginStorage

Expand Down Expand Up @@ -130,6 +131,7 @@ class BoefjeIn(BaseModel):
boefje_schema: dict | None = None
cron: str | None = None
interval: int | None = None
run_on: list[RunOn] | None = None
oci_image: str | None = None
oci_arguments: list[str] = Field(default_factory=list)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
"""Add run on field to boefje
Revision ID: fc0295b38184
Revises: 9f48560b0000
Create Date: 2025-02-04 16:43:59.171960
"""

import sqlalchemy as sa
from alembic import op

# revision identifiers, used by Alembic.
revision = "fc0295b38184"
down_revision = "9f48560b0000"
branch_labels = None
depends_on = None


run_on = sa.Enum("create", "update", "create_update", name="run_on")


def upgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
run_on.create(op.get_bind())
op.add_column("boefje", sa.Column("run_on", run_on, nullable=True))
# ### end Alembic commands ###


def downgrade() -> None:
# ### commands auto generated by Alembic - please adjust! ###
op.drop_column("boefje", "run_on")
run_on.drop(op.get_bind(), checkfirst=False)
# ### end Alembic commands ###
14 changes: 14 additions & 0 deletions boefjes/boefjes/models.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import datetime
from enum import Enum
from functools import total_ordering
from typing import Literal

from croniter import croniter
Expand All @@ -8,6 +9,18 @@
from pydantic import BaseModel, Field, field_validator


# This makes the RunOn sortable when in a list. This is convenient for e.g. the RunOnDB.from_run_ons method, that now
# does not have to take the ordering of a boefje.run_on into account in its match statement. This is especially handy
# once we introduce more RunOn values such as DELETE.
@total_ordering
class RunOn(Enum):
CREATE = "create"
UPDATE = "update"

def __lt__(self, other):
return self.value < other.value


class Organisation(BaseModel):
id: str
name: str
Expand All @@ -34,6 +47,7 @@ class Boefje(Plugin):
boefje_schema: dict | None = None
cron: str | None = None
interval: int | None = None
run_on: list[RunOn] | None = None
runnable_hash: str | None = None
oci_image: str | None = None
oci_arguments: list[str] = Field(default_factory=list)
Expand Down
5 changes: 1 addition & 4 deletions boefjes/boefjes/plugins/kat_export_http/boefje.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,5 @@
"consumes": [],
"scan_level": 4,
"oci_image": "ghcr.io/minvws/openkat/export-http:latest",
"run_on": [
"create",
"update"
]
"run_on": ["create", "update"]
}
32 changes: 32 additions & 0 deletions boefjes/boefjes/sql/db_models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, UniqueConstraint, types
from sqlalchemy.orm import relationship

from boefjes.models import RunOn
from boefjes.sql.db import SQL_BASE


Expand All @@ -14,6 +15,36 @@ class ScanLevel(Enum):
L4 = 4


class RunOnDB(Enum):
CREATE = "create"
UPDATE = "update"
CREATE_UPDATE = "create_update"

@classmethod
def from_run_ons(cls, run_ons: list[RunOn] | None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def from_run_ons(cls, run_ons: list[RunOn] | None):
def from_run_ons(cls, run_ons: list[RunOn] | None) -> RunOn | None:

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@madelondohmen This should be Self from typing_extensions (probably) it should actually return RunOnDB | None which won't work because it references itself.

Suggested change
def from_run_ons(cls, run_ons: list[RunOn] | None):
def from_run_ons(cls, run_ons: list[RunOn] | None) -> Self | None:

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Donnype is right. "RunOn | None" won't work since it returns a RunOnDB. But Self doesn't work as well. So I suggest to leave it as it was.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

typing.Self is introduced in Python 3.11, but we still use 3.10 for development. Therefore:
def from_run_ons(cls, run_ons: list[RunOn] | None) -> "RunOnDB" | None:

if run_ons is None:
return None

match sorted(run_ons):
case [RunOn.CREATE]:
return cls.CREATE
case [RunOn.UPDATE]:
return cls.UPDATE
case [RunOn.CREATE, RunOn.UPDATE]:
return cls.CREATE_UPDATE
case _:
return None

def to_run_ons(self) -> list[RunOn]:
match self:
case RunOnDB.CREATE:
return [RunOn.CREATE]
case RunOnDB.UPDATE:
return [RunOn.UPDATE]
case RunOnDB.CREATE_UPDATE:
return [RunOn.CREATE, RunOn.UPDATE]


class OrganisationInDB(SQL_BASE):
__tablename__ = "organisation"

Expand Down Expand Up @@ -72,6 +103,7 @@ class BoefjeInDB(SQL_BASE):
schema = Column(types.JSON(), nullable=True)
cron = Column(types.String(length=128), nullable=True)
interval = Column(types.Integer, nullable=True)
run_on = Column(types.Enum(*[x.value for x in RunOnDB], name="run_on"), nullable=True)

# Image specifications
oci_image = Column(types.String(length=256), nullable=True)
Expand Down
5 changes: 4 additions & 1 deletion boefjes/boefjes/sql/plugin_storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from boefjes.config import Settings, settings
from boefjes.models import Boefje, Normalizer, PluginType
from boefjes.sql.db import ObjectNotFoundException, session_managed_iterator
from boefjes.sql.db_models import BoefjeInDB, NormalizerInDB
from boefjes.sql.db_models import BoefjeInDB, NormalizerInDB, RunOnDB
from boefjes.sql.session import SessionMixin
from boefjes.storage.interfaces import NotAllowed, PluginNotFound, PluginStorage

Expand Down Expand Up @@ -98,6 +98,7 @@ def _db_normalizer_instance_by_id(self, normalizer_id: str) -> NormalizerInDB:

@staticmethod
def to_boefje_in_db(boefje: Boefje, pk: int | None = None) -> BoefjeInDB:
run_on_db = RunOnDB.from_run_ons(boefje.run_on)
boefje = BoefjeInDB(
plugin_id=boefje.id,
created=boefje.created,
Expand All @@ -109,6 +110,7 @@ def to_boefje_in_db(boefje: Boefje, pk: int | None = None) -> BoefjeInDB:
schema=boefje.boefje_schema,
cron=boefje.cron,
interval=boefje.interval,
run_on=run_on_db.value if run_on_db is not None else None,
oci_image=boefje.oci_image,
oci_arguments=boefje.oci_arguments,
version=boefje.version,
Expand Down Expand Up @@ -152,6 +154,7 @@ def to_boefje(boefje_in_db: BoefjeInDB) -> Boefje:
boefje_schema=boefje_in_db.schema,
cron=boefje_in_db.cron,
interval=boefje_in_db.interval,
run_on=RunOnDB(boefje_in_db.run_on).to_run_ons() if boefje_in_db.run_on else None,
oci_image=boefje_in_db.oci_image,
oci_arguments=boefje_in_db.oci_arguments,
version=boefje_in_db.version,
Expand Down
16 changes: 16 additions & 0 deletions boefjes/tests/integration/test_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,22 @@ def test_enable_boefje(test_client, organisation, second_organisation):
assert response.json()["enabled"] is False


def test_run_on(test_client, organisation, second_organisation):
test_client.patch(f"/v1/organisations/{organisation.id}/plugins/export-to-http-api", json={"enabled": True})

response = test_client.get(f"/v1/organisations/{organisation.id}/plugins/export-to-http-api")
assert response.json()["enabled"] is True
assert response.json()["run_on"] == ["create", "update"]

boefje = Boefje(id="test_run_on", name="Run On", static=False, run_on=["create"])
response = test_client.post(f"/v1/organisations/{organisation.id}/plugins", content=boefje.model_dump_json())
assert response.status_code == 201

response = test_client.get(f"/v1/organisations/{organisation.id}/plugins/test_run_on")
assert response.json()["enabled"] is False
assert response.json()["run_on"] == [x.value for x in boefje.run_on]


def test_cannot_add_static_plugin_with_duplicate_name(test_client, organisation):
boefje = Boefje(id="test_plugin", name="DNS records", static=False)
response = test_client.post(f"/v1/organisations/{organisation.id}/plugins", content=boefje.model_dump_json())
Expand Down
15 changes: 15 additions & 0 deletions boefjes/tests/test_models.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
from boefjes.models import RunOn
from boefjes.sql.db_models import RunOnDB


def test_run_on():
assert RunOnDB.from_run_ons([RunOn.CREATE]) == RunOnDB.CREATE
assert RunOnDB.from_run_ons([RunOn.UPDATE]) == RunOnDB.UPDATE
assert RunOnDB.from_run_ons([RunOn.CREATE, RunOn.UPDATE]) == RunOnDB.CREATE_UPDATE
assert RunOnDB.from_run_ons([RunOn.UPDATE, RunOn.CREATE]) == RunOnDB.CREATE_UPDATE
assert RunOnDB.from_run_ons([1]) is None
assert RunOnDB.from_run_ons([]) is None

assert RunOnDB.CREATE.to_run_ons() == [RunOn.CREATE]
assert RunOnDB.UPDATE.to_run_ons() == [RunOn.UPDATE]
assert RunOnDB.CREATE_UPDATE.to_run_ons() == [RunOn.CREATE, RunOn.UPDATE]
5 changes: 5 additions & 0 deletions mula/scheduler/models/ooi.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,11 @@ class MutationOperationType(Enum):
DELETE = "delete"


class RunOn(Enum):
CREATE = MutationOperationType.CREATE.value
UPDATE = MutationOperationType.UPDATE.value


class ScanProfile(BaseModel):
level: int
reference: str
Expand Down
4 changes: 3 additions & 1 deletion mula/scheduler/models/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

from pydantic import BaseModel

from scheduler.models.ooi import RunOn


class Plugin(BaseModel):
id: str
Expand All @@ -19,4 +21,4 @@ class Plugin(BaseModel):
produces: list[str]
cron: str | None = None
interval: int | None = None
run_on: list[str] | None = None
run_on: list[RunOn] | None = None
5 changes: 3 additions & 2 deletions mula/scheduler/schedulers/schedulers/boefje.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
Task,
TaskStatus,
)
from scheduler.models.ooi import RunOn
from scheduler.schedulers import Scheduler
from scheduler.schedulers.queue import PriorityQueue, QueueFullError
from scheduler.schedulers.rankers import BoefjeRanker
Expand Down Expand Up @@ -214,9 +215,9 @@ def push_tasks_for_scan_profile_mutations(self, body: bytes) -> None:
create_schedule = False
run_task = False
if mutation.operation == MutationOperationType.CREATE:
run_task = "create" in boefje.run_on
run_task = RunOn.CREATE in boefje.run_on
elif mutation.operation == MutationOperationType.UPDATE:
run_task = "update" in boefje.run_on
run_task = RunOn.UPDATE in boefje.run_on

if not run_task:
self.logger.debug(
Expand Down
3 changes: 2 additions & 1 deletion mula/tests/factories/plugin.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
from factory import Factory, LazyFunction, Sequence, fuzzy
from scheduler.models import Plugin
from scheduler.models.ooi import RunOn


class PluginFactory(Factory):
Expand All @@ -13,4 +14,4 @@ class Meta:
enabled: bool = True
cron: str | None = None
interval: int | None = None
run_on: list[str] | None = None
run_on: RunOn | None = None
17 changes: 9 additions & 8 deletions mula/tests/integration/test_boefje_scheduler.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
from unittest import mock

from scheduler import clients, config, models, schedulers, storage
from scheduler.models.ooi import RunOn
from scheduler.storage import stores
from structlog.testing import capture_logs

Expand Down Expand Up @@ -1283,7 +1284,7 @@ def test_push_tasks_for_scan_profile_mutations_op_create_run_on_create(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=["create"])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[RunOn.CREATE])
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.CREATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand Down Expand Up @@ -1320,7 +1321,7 @@ def test_push_tasks_for_scan_profile_mutations_op_create_run_on_create_update(se
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=["create", "update"])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[RunOn.CREATE, RunOn.UPDATE])
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.CREATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand Down Expand Up @@ -1357,7 +1358,7 @@ def test_push_tasks_for_scan_profile_mutations_op_create_run_on_update(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=["update"])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[RunOn.UPDATE])
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.CREATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand All @@ -1381,7 +1382,7 @@ def test_push_tasks_for_scan_profile_mutations_op_create_run_on_none(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=None)
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.CREATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand Down Expand Up @@ -1419,7 +1420,7 @@ def test_push_tasks_for_scan_profile_mutations_op_update_run_on_create(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=["create"])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[RunOn.CREATE])
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.UPDATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand All @@ -1443,7 +1444,7 @@ def test_push_tasks_scan_profile_mutations_op_update_run_on_create_update(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=["create", "update"])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[RunOn.CREATE, RunOn.UPDATE])
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.UPDATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand Down Expand Up @@ -1480,7 +1481,7 @@ def test_push_tasks_scan_profile_mutations_op_update_run_on_update(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=["update"])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[RunOn.UPDATE])
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.UPDATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand Down Expand Up @@ -1517,7 +1518,7 @@ def test_push_tasks_scan_profile_mutations_op_update_run_on_none(self):
# Arrange
scan_profile = ScanProfileFactory(level=0)
ooi = OOIFactory(scan_profile=scan_profile)
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=[])
boefje = PluginFactory(scan_level=0, consumes=[ooi.object_type], run_on=None)
mutation = models.ScanProfileMutation(
operation=models.MutationOperationType.UPDATE, primary_key=ooi.primary_key, value=ooi
).model_dump_json()
Expand Down
Loading
Loading