diff --git a/app/objects/c_schedule.py b/app/objects/c_schedule.py index 135d0c2bf..5fb68fe35 100644 --- a/app/objects/c_schedule.py +++ b/app/objects/c_schedule.py @@ -1,6 +1,7 @@ import uuid import marshmallow as ma +from croniter import croniter from app.objects.interfaces.i_object import FirstClassObjectInterface from app.objects.c_operation import OperationSchema @@ -13,9 +14,14 @@ class Meta: unknown = ma.EXCLUDE id = ma.fields.String() - schedule = ma.fields.String(required=True) + schedule = ma.fields.String(required=True, metadata={"example": "5 4 * * *"}) task = ma.fields.Nested(OperationSchema()) + @ma.validates('schedule') + def validate_schedule(self, value): + if not croniter.is_valid(value): + raise ma.ValidationError("Invalid cron syntax for schedule field.") + @ma.post_load def build_schedule(self, data, **kwargs): return None if kwargs.get('partial') is True else Schedule(**data) diff --git a/app/service/app_svc.py b/app/service/app_svc.py index 906a765e7..ec831a3f8 100644 --- a/app/service/app_svc.py +++ b/app/service/app_svc.py @@ -6,6 +6,7 @@ import os import re import time +import uuid from collections import namedtuple from datetime import datetime, timezone from importlib import import_module @@ -100,6 +101,8 @@ async def run_scheduler(self): if interval > diff.total_seconds() > 0: self.log.debug('Pulling %s off the scheduler' % s.id) sop = copy.deepcopy(s.task) + sop.id = str(uuid.uuid4()) + sop.name += f" ({datetime.now(timezone.utc).replace(microsecond=0).isoformat()})" sop.set_start_details() await sop.update_operation_agents(self.get_services()) await self._services.get('data_svc').store(sop) diff --git a/tests/api/v2/handlers/test_schedules_api.py b/tests/api/v2/handlers/test_schedules_api.py index 446a6f2dd..9f9410c4b 100644 --- a/tests/api/v2/handlers/test_schedules_api.py +++ b/tests/api/v2/handlers/test_schedules_api.py @@ -11,7 +11,7 @@ @pytest.fixture def updated_schedule_payload(): payload = { - 'schedule': '01:00:00.000000', + 'schedule': '0 1 * * *', 'task': { 'autonomous': 1, 'obfuscator': 'base64', @@ -40,7 +40,7 @@ def _merge_dictionaries(dict1, dict2): @pytest.fixture def replaced_schedule_payload(): payload = { - 'schedule': '12:12:00.000000', + 'schedule': '0 12 12 * *', 'task': { 'autonomous': 1, 'obfuscator': 'base64', @@ -57,7 +57,7 @@ def replaced_schedule_payload(): @pytest.fixture def new_schedule_payload(test_planner, test_adversary, test_source): - payload = dict(schedule='00:00:00.000000', + payload = dict(schedule='0 0 * * *', id='456', task={ 'name': 'new_scheduled_operation', @@ -80,7 +80,7 @@ def expected_new_schedule_dump(new_schedule_payload): def test_schedule(test_operation, event_loop): operation = OperationSchema().load(test_operation) schedule = ScheduleSchema().load(dict(id='123', - schedule='03:00:00.000000', + schedule='0 3 * * *', task=operation.schema.dump(operation))) event_loop.run_until_complete(BaseService.get_service('data_svc').store(schedule)) return schedule