assemblyline-core 4.5.0.76__tar.gz → 4.5.0.79__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.

Potentially problematic release.


This version of assemblyline-core might be problematic. Click here for more details.

Files changed (90) hide show
  1. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/PKG-INFO +1 -1
  2. assemblyline_core-4.5.0.79/assemblyline_core/VERSION +1 -0
  3. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/tasking_client.py +53 -34
  4. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/workflow/run_workflow.py +6 -1
  5. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core.egg-info/PKG-INFO +1 -1
  6. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core.egg-info/SOURCES.txt +3 -1
  7. assemblyline_core-4.5.0.79/test/test_tasking_client.py +37 -0
  8. assemblyline_core-4.5.0.79/test/test_workflow.py +45 -0
  9. assemblyline_core-4.5.0.76/assemblyline_core/VERSION +0 -1
  10. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/LICENCE.md +0 -0
  11. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/README.md +0 -0
  12. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/__init__.py +0 -0
  13. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/alerter/__init__.py +0 -0
  14. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/alerter/processing.py +0 -0
  15. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/alerter/run_alerter.py +0 -0
  16. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/archiver/__init__.py +0 -0
  17. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/archiver/run_archiver.py +0 -0
  18. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/badlist_client.py +0 -0
  19. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/dispatching/__init__.py +0 -0
  20. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/dispatching/__main__.py +0 -0
  21. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/dispatching/client.py +0 -0
  22. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/dispatching/dispatcher.py +0 -0
  23. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/dispatching/schedules.py +0 -0
  24. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/dispatching/timeout.py +0 -0
  25. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/expiry/__init__.py +0 -0
  26. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/expiry/run_expiry.py +0 -0
  27. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/ingester/__init__.py +0 -0
  28. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/ingester/__main__.py +0 -0
  29. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/ingester/constants.py +0 -0
  30. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/ingester/ingester.py +0 -0
  31. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/__init__.py +0 -0
  32. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/es_metrics.py +0 -0
  33. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/heartbeat_formatter.py +0 -0
  34. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/helper.py +0 -0
  35. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/metrics_server.py +0 -0
  36. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/run_heartbeat_manager.py +0 -0
  37. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/run_metrics_aggregator.py +0 -0
  38. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/metrics/run_statistics_aggregator.py +0 -0
  39. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/plumber/__init__.py +0 -0
  40. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/plumber/run_plumber.py +0 -0
  41. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/__init__.py +0 -0
  42. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/client.py +0 -0
  43. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/creator/__init__.py +0 -0
  44. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/creator/run.py +0 -0
  45. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/creator/run_worker.py +0 -0
  46. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/loader/__init__.py +0 -0
  47. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/loader/run.py +0 -0
  48. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/loader/run_worker.py +0 -0
  49. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/replay/replay.py +0 -0
  50. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/safelist_client.py +0 -0
  51. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/__init__.py +0 -0
  52. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/collection.py +0 -0
  53. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/controllers/__init__.py +0 -0
  54. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/controllers/docker_ctl.py +0 -0
  55. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/controllers/interface.py +0 -0
  56. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/controllers/kubernetes_ctl.py +0 -0
  57. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/run_scaler.py +0 -0
  58. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/scaler/scaler_server.py +0 -0
  59. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/server_base.py +0 -0
  60. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/signature_client.py +0 -0
  61. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/submission_client.py +0 -0
  62. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/updater/__init__.py +0 -0
  63. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/updater/helper.py +0 -0
  64. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/updater/run_updater.py +0 -0
  65. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/vacuum/__init__.py +0 -0
  66. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/vacuum/crawler.py +0 -0
  67. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/vacuum/department_map.py +0 -0
  68. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/vacuum/safelist.py +0 -0
  69. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/vacuum/stream_map.py +0 -0
  70. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/vacuum/worker.py +0 -0
  71. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core/workflow/__init__.py +0 -0
  72. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core.egg-info/dependency_links.txt +0 -0
  73. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core.egg-info/requires.txt +0 -0
  74. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/assemblyline_core.egg-info/top_level.txt +0 -0
  75. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/setup.cfg +0 -0
  76. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/setup.py +0 -0
  77. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_alerter.py +0 -0
  78. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_badlist_client.py +0 -0
  79. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_dispatcher.py +0 -0
  80. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_expiry.py +0 -0
  81. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_plumber.py +0 -0
  82. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_replay.py +0 -0
  83. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_safelist_client.py +0 -0
  84. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_scaler.py +0 -0
  85. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_scheduler.py +0 -0
  86. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_signature_client.py +0 -0
  87. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_simulation.py +0 -0
  88. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_vacuum.py +0 -0
  89. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_worker_ingest.py +0 -0
  90. {assemblyline_core-4.5.0.76 → assemblyline_core-4.5.0.79}/test/test_worker_submit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: assemblyline-core
3
- Version: 4.5.0.76
3
+ Version: 4.5.0.79
4
4
  Summary: Assemblyline 4 - Core components
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-core/
6
6
  Author: CCCS Assemblyline development team
@@ -0,0 +1 @@
1
+ 4.5.0.79
@@ -49,6 +49,8 @@ class TaskingClient:
49
49
  self.event_sender = EventSender('changes', redis)
50
50
  self.cleanup = False
51
51
  self.event_listener = None
52
+ self.heuristics: dict[str, Heuristic] = {}
53
+ self.register_only = register_only
52
54
 
53
55
  # If we're performing service registration, we only need a connection to the datastore
54
56
  if not register_only:
@@ -56,7 +58,6 @@ class TaskingClient:
56
58
  self.event_listener = EventWatcher(redis)
57
59
  self.filestore = filestore or forge.get_filestore(self.config)
58
60
  self.heuristic_handler = HeuristicHandler(self.datastore)
59
- self.heuristics: dict[str, Heuristic] = {}
60
61
  self.reload_heuristics({})
61
62
  self.status_table = ExpiringHash(SERVICE_STATE_HASH, ttl=60*30, host=redis)
62
63
  self.tag_safelister = forge.CachedObject(forge.get_tag_safelister, kwargs=dict(
@@ -126,7 +127,7 @@ class TaskingClient:
126
127
 
127
128
  try:
128
129
  # Get heuristics list
129
- heuristics = service_data.pop('heuristics', None)
130
+ heuristics = service_data.pop('heuristics', [])
130
131
 
131
132
  # Patch update_channel, registry_type before Service registration object creation
132
133
  service_data['update_channel'] = service_data.get(
@@ -162,44 +163,62 @@ class TaskingClient:
162
163
  self.log.info(f"{log_prefix}{service.name} version ({service.version}) registered")
163
164
 
164
165
  new_heuristics = []
165
- if heuristics:
166
- plan = self.datastore.heuristic.get_bulk_plan()
167
- for index, heuristic in enumerate(heuristics):
168
- heuristic_id = f'#{index}' # Set heuristic id to it's position in the list for logging purposes
169
- try:
170
- # Append service name to heuristic ID
171
- heuristic['heur_id'] = f"{service.name.upper()}.{str(heuristic['heur_id'])}"
172
-
173
- # Attack_id field is now a list, make it a list if we receive otherwise
174
- attack_id = heuristic.get('attack_id', None)
175
- if isinstance(attack_id, str):
176
- heuristic['attack_id'] = [attack_id]
177
-
178
- heuristic = Heuristic(heuristic)
179
- heuristic_id = heuristic.heur_id
180
- existing_heuristic_obj = self.datastore.heuristic.get_if_exists(heuristic_id)
181
- if existing_heuristic_obj:
182
- # Ensure statistics of heuristic are preserved
183
- heuristic.stats = existing_heuristic_obj.stats
184
- plan.add_upsert_operation(heuristic_id, heuristic)
185
- except Exception as e:
186
- msg = f"{service.name} has an invalid heuristic ({heuristic_id}): {str(e)}"
187
- self.log.exception(f"{log_prefix}{msg}")
188
- raise ValueError(msg)
189
-
166
+
167
+ plan = self.datastore.heuristic.get_bulk_plan()
168
+ for index, heuristic in enumerate(heuristics):
169
+ heuristic_id = f'#{index}' # Set heuristic id to it's position in the list for logging purposes
170
+ try:
171
+ # Append service name to heuristic ID
172
+ heuristic['heur_id'] = f"{service.name.upper()}.{str(heuristic['heur_id'])}"
173
+
174
+ # Attack_id field is now a list, make it a list if we receive otherwise
175
+ attack_id = heuristic.get('attack_id', None)
176
+ if isinstance(attack_id, str):
177
+ heuristic['attack_id'] = [attack_id]
178
+
179
+ heuristic = Heuristic(heuristic)
180
+ heuristic_id = heuristic.heur_id
181
+ existing_heuristic_obj = self.datastore.heuristic.get_if_exists(heuristic_id)
182
+ if existing_heuristic_obj:
183
+ # Ensure statistics of heuristic are preserved
184
+ heuristic.stats = existing_heuristic_obj.stats
185
+ plan.add_upsert_operation(heuristic_id, heuristic)
186
+ except Exception as e:
187
+ msg = f"{service.name} has an invalid heuristic ({heuristic_id}): {str(e)}"
188
+ self.log.exception(f"{log_prefix}{msg}")
189
+ raise ValueError(msg)
190
+
191
+ if plan.operations:
190
192
  for item in self.datastore.heuristic.bulk(plan)['items']:
191
193
  if item['update']['result'] != "noop":
192
194
  new_heuristics.append(item['update']['_id'])
193
195
  self.log.info(f"{log_prefix}{service.name} "
194
- f"heuristic {item['update']['_id']}: {item['update']['result'].upper()}")
196
+ f"heuristic {item['update']['_id']}: {item['update']['result'].upper()}")
197
+
198
+ # Look for heuristics that are no longer managed by the service and clean them up
199
+ if self.register_only:
200
+ # Fetch current heuristics for the service during registration
201
+ self.reload_heuristics({'service_name': service.name})
202
+
203
+ all_heuristics = set(h_id for h_id in self.heuristics.keys()
204
+ if h_id.startswith(f"{service.name.upper()}."))
205
+ removed_heuristics = all_heuristics - set(h['heur_id'] for h in heuristics)
195
206
 
196
- self.datastore.heuristic.commit()
207
+ for heuristic in removed_heuristics:
208
+ # Only remove heuristics that aren't actively referenced in a result
209
+ if not self.datastore.result.search(f"result.sections.heuristic.heur_id:{heuristic}",
210
+ rows=0, track_total_hits=True)['total']:
211
+ self.datastore.heuristic.delete(heuristic)
197
212
 
198
- # Notify components watching for heuristic config changes
199
- self.event_sender.send('heuristics', {
200
- 'operation': Operation.Modified,
201
- 'service_name': service.name
202
- })
213
+
214
+
215
+ self.datastore.heuristic.commit()
216
+
217
+ # Notify components watching for heuristic config changes
218
+ self.event_sender.send('heuristics', {
219
+ 'operation': Operation.Modified,
220
+ 'service_name': service.name
221
+ })
203
222
 
204
223
  service_config = self.datastore.get_service_with_delta(service.name, as_obj=False)
205
224
 
@@ -59,7 +59,7 @@ class WorkflowManager(ServerBase):
59
59
 
60
60
  return ret_val
61
61
 
62
- def try_run(self):
62
+ def try_run(self, run_once=False):
63
63
  self.datastore.alert.commit()
64
64
  while self.running:
65
65
  self.heartbeat()
@@ -109,6 +109,9 @@ class WorkflowManager(ServerBase):
109
109
  if not workflow.enabled:
110
110
  continue
111
111
 
112
+ # Trigger a heartbeat to let the system know the workflow manager is still alive between tasks
113
+ self.heartbeat()
114
+
112
115
  # Start of transaction
113
116
  if self.apm_client:
114
117
  self.apm_client.begin_transaction("Execute workflows")
@@ -217,6 +220,8 @@ class WorkflowManager(ServerBase):
217
220
  else:
218
221
  self.log.info("Skipping all workflows since there where no new alerts in the specified time period.")
219
222
 
223
+ if run_once:
224
+ break
220
225
  time.sleep(30)
221
226
  self.start_ts = end_ts
222
227
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.2
2
2
  Name: assemblyline-core
3
- Version: 4.5.0.76
3
+ Version: 4.5.0.79
4
4
  Summary: Assemblyline 4 - Core components
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-core/
6
6
  Author: CCCS Assemblyline development team
@@ -81,6 +81,8 @@ test/test_scaler.py
81
81
  test/test_scheduler.py
82
82
  test/test_signature_client.py
83
83
  test/test_simulation.py
84
+ test/test_tasking_client.py
84
85
  test/test_vacuum.py
85
86
  test/test_worker_ingest.py
86
- test/test_worker_submit.py
87
+ test/test_worker_submit.py
88
+ test/test_workflow.py
@@ -0,0 +1,37 @@
1
+ from assemblyline_core.tasking_client import TaskingClient
2
+
3
+ from assemblyline.odm.models.service import Service
4
+ from assemblyline.odm.models.heuristic import Heuristic
5
+ from assemblyline.odm.models.result import Result, Section, Heuristic as SectionHeuristic
6
+
7
+ from assemblyline.odm.randomizer import random_minimal_obj
8
+
9
+ def test_register_service(datastore_connection):
10
+ client = TaskingClient(datastore_connection, register_only=True)
11
+
12
+ # Test service registration
13
+ service = random_minimal_obj(Service).as_primitives()
14
+ heuristics = [random_minimal_obj(Heuristic).as_primitives() for _ in range(2)]
15
+ service['heuristics'] = heuristics
16
+ assert client.register_service(service)
17
+ assert all([datastore_connection.heuristic.exists(h['heur_id']) for h in heuristics])
18
+
19
+ # Test registration with heuristics that were removed but still have related results
20
+ heuristic = heuristics.pop(0)
21
+ result = random_minimal_obj(Result)
22
+ section = random_minimal_obj(Section)
23
+ section.heuristic = SectionHeuristic(heuristic)
24
+ result.result.sections = [section]
25
+ datastore_connection.result.save('test_result', result)
26
+ datastore_connection.result.commit()
27
+
28
+ # Heuristics that were removed should still reside in the system if there are still associated data to it
29
+ service['heuristics'] = heuristics
30
+ assert client.register_service(service)
31
+ assert datastore_connection.heuristic.exists(heuristic['heur_id'])
32
+
33
+ # Test registration with removed heuristics that have no related results
34
+ datastore_connection.result.delete('test_result')
35
+ datastore_connection.result.commit()
36
+ assert client.register_service(service)
37
+ assert not datastore_connection.heuristic.exists(heuristic['heur_id'])
@@ -0,0 +1,45 @@
1
+ import pytest
2
+ import random
3
+ from assemblyline_core.workflow.run_workflow import WorkflowManager
4
+
5
+ from assemblyline.common.isotime import now_as_iso
6
+ from assemblyline.odm.models.workflow import Workflow
7
+ from assemblyline.odm.random_data import create_alerts, wipe_alerts, wipe_workflows
8
+ from assemblyline.odm.randomizer import random_minimal_obj
9
+
10
+
11
+ @pytest.fixture(scope="module")
12
+ def manager(datastore_connection):
13
+ try:
14
+ create_alerts(datastore_connection)
15
+ wipe_workflows(datastore_connection)
16
+ datastore_connection.alert.update_by_query("*", [(datastore_connection.alert.UPDATE_SET, 'reporting_ts', now_as_iso())])
17
+ datastore_connection.alert.commit()
18
+ yield WorkflowManager()
19
+ finally:
20
+ wipe_alerts(datastore_connection)
21
+
22
+ def test_workflow(manager, datastore_connection):
23
+ # Create workflow that targets alerts based on YARA rule association
24
+ workflow = random_minimal_obj(Workflow)
25
+
26
+ yara_rule = random.choice(list(datastore_connection.alert.facet("al.yara").keys()))
27
+ workflow.query = f'al.yara:"{yara_rule}"'
28
+ workflow.workflow_id = "AL_TEST"
29
+ workflow.labels = ["AL_TEST"]
30
+ workflow.priority = "LOW"
31
+ workflow.status = "MALICIOUS"
32
+ datastore_connection.workflow.save(workflow.workflow_id, workflow)
33
+ datastore_connection.workflow.commit()
34
+
35
+ # Run Workflow manager to process new workflow against existing alerts
36
+ manager.running = True
37
+ manager.get_last_reporting_ts = lambda x: "now/d+1d"
38
+ manager.try_run(run_once=True)
39
+ datastore_connection.alert.commit()
40
+
41
+ # Assert that custom labels were applied to alerts
42
+ assert datastore_connection.alert.search("label:AL_TEST", track_total_hits=True)['total']
43
+
44
+ # Assert that the change has been record in the alerts' event history
45
+ assert datastore_connection.alert.search(f"events.entity_id:{workflow.workflow_id}", track_total_hits=True)['total']
@@ -1 +0,0 @@
1
- 4.5.0.76