assemblyline-core 4.5.1.dev38__tar.gz → 4.5.1.dev90__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 (88) hide show
  1. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/PKG-INFO +1 -1
  2. assemblyline-core-4.5.1.dev90/assemblyline_core/VERSION +1 -0
  3. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/archiver/run_archiver.py +20 -3
  4. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/heartbeat_formatter.py +53 -0
  5. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/metrics_server.py +70 -0
  6. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/docker_ctl.py +12 -1
  7. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/kubernetes_ctl.py +2 -13
  8. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/helper.py +93 -96
  9. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/run_updater.py +3 -3
  10. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/worker.py +1 -0
  11. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/PKG-INFO +1 -1
  12. assemblyline-core-4.5.1.dev38/assemblyline_core/VERSION +0 -1
  13. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/LICENCE.md +0 -0
  14. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/README.md +0 -0
  15. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/__init__.py +0 -0
  16. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/alerter/__init__.py +0 -0
  17. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/alerter/processing.py +0 -0
  18. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/alerter/run_alerter.py +0 -0
  19. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/archiver/__init__.py +0 -0
  20. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/badlist_client.py +0 -0
  21. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/__init__.py +0 -0
  22. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/__main__.py +0 -0
  23. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/client.py +0 -0
  24. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/dispatcher.py +0 -0
  25. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/schedules.py +0 -0
  26. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/dispatching/timeout.py +0 -0
  27. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/expiry/__init__.py +0 -0
  28. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/expiry/run_expiry.py +0 -0
  29. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/__init__.py +0 -0
  30. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/__main__.py +0 -0
  31. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/constants.py +0 -0
  32. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/ingester/ingester.py +0 -0
  33. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/__init__.py +0 -0
  34. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/es_metrics.py +0 -0
  35. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/helper.py +0 -0
  36. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/run_heartbeat_manager.py +0 -0
  37. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/run_metrics_aggregator.py +0 -0
  38. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/metrics/run_statistics_aggregator.py +0 -0
  39. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/plumber/__init__.py +0 -0
  40. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/plumber/run_plumber.py +0 -0
  41. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/__init__.py +0 -0
  42. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/client.py +0 -0
  43. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/creator/__init__.py +0 -0
  44. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/creator/run.py +0 -0
  45. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/creator/run_worker.py +0 -0
  46. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/loader/__init__.py +0 -0
  47. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/loader/run.py +0 -0
  48. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/loader/run_worker.py +0 -0
  49. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/replay/replay.py +0 -0
  50. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/safelist_client.py +0 -0
  51. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/__init__.py +0 -0
  52. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/collection.py +0 -0
  53. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/__init__.py +0 -0
  54. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/controllers/interface.py +0 -0
  55. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/run_scaler.py +0 -0
  56. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/scaler/scaler_server.py +0 -0
  57. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/server_base.py +0 -0
  58. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/signature_client.py +0 -0
  59. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/submission_client.py +0 -0
  60. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/tasking_client.py +0 -0
  61. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/updater/__init__.py +0 -0
  62. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/__init__.py +0 -0
  63. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/crawler.py +0 -0
  64. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/department_map.py +0 -0
  65. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/safelist.py +0 -0
  66. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/vacuum/stream_map.py +0 -0
  67. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/workflow/__init__.py +0 -0
  68. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core/workflow/run_workflow.py +0 -0
  69. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/SOURCES.txt +0 -0
  70. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/dependency_links.txt +0 -0
  71. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/requires.txt +0 -0
  72. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/assemblyline_core.egg-info/top_level.txt +0 -0
  73. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/setup.cfg +0 -0
  74. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/setup.py +0 -0
  75. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_alerter.py +0 -0
  76. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_badlist_client.py +0 -0
  77. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_dispatcher.py +0 -0
  78. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_expiry.py +0 -0
  79. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_plumber.py +0 -0
  80. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_replay.py +0 -0
  81. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_safelist_client.py +0 -0
  82. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_scaler.py +0 -0
  83. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_scheduler.py +0 -0
  84. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_signature_client.py +0 -0
  85. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_simulation.py +0 -0
  86. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_vacuum.py +0 -0
  87. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_worker_ingest.py +0 -0
  88. {assemblyline-core-4.5.1.dev38 → assemblyline-core-4.5.1.dev90}/test/test_worker_submit.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: assemblyline-core
3
- Version: 4.5.1.dev38
3
+ Version: 4.5.1.dev90
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.1.dev90
@@ -7,8 +7,8 @@ from assemblyline.common import forge
7
7
  from assemblyline.common.archiving import ARCHIVE_QUEUE_NAME
8
8
  from assemblyline.common.metrics import MetricsFactory
9
9
  from assemblyline.datastore.collection import ESCollection, Index
10
+ from assemblyline.datastore.exceptions import VersionConflictException
10
11
  from assemblyline.odm.messages.archive_heartbeat import Metrics
11
- from assemblyline.odm.models.submission import Submission
12
12
  from assemblyline.remote.datatypes import get_client
13
13
  from assemblyline.remote.datatypes.queues.named import NamedQueue
14
14
 
@@ -62,7 +62,12 @@ class Archiver(ServerBase):
62
62
  return
63
63
  else:
64
64
  try:
65
- archive_type, type_id, delete_after = message
65
+ if len(message) == 3:
66
+ archive_type, type_id, delete_after = message
67
+ metadata = None
68
+ else:
69
+ archive_type, type_id, delete_after, metadata = message
70
+
66
71
  self.counter.increment('received')
67
72
  except Exception:
68
73
  self.log.error(f"Invalid message received: {message}")
@@ -76,7 +81,19 @@ class Archiver(ServerBase):
76
81
  if archive_type == "submission":
77
82
  self.counter.increment('submission')
78
83
  # Load submission
79
- submission: Submission = self.datastore.submission.get_if_exists(type_id)
84
+ while True:
85
+ try:
86
+ submission, version = self.datastore.submission.get_if_exists(type_id, version=True)
87
+
88
+ # If we have metadata passed in the message, we need to apply it before archiving the submission
89
+ if metadata and self.config.core.archiver.use_metadata:
90
+ submission.metadata.update({f"archive.{k}": v for k, v in metadata.items()})
91
+ self.datastore.submission.save(type_id, submission, version=version)
92
+
93
+ break
94
+ except VersionConflictException as vce:
95
+ self.log.info(f"Retrying saving metadata due to version conflict: {str(vce)}")
96
+
80
97
  if not submission:
81
98
  raise SubmissionNotFound(type_id)
82
99
 
@@ -7,6 +7,7 @@ from assemblyline.common.archiving import ARCHIVE_QUEUE_NAME
7
7
  from assemblyline.common.constants import DISPATCH_TASK_HASH, SUBMISSION_QUEUE, \
8
8
  SERVICE_STATE_HASH, ServiceStatus
9
9
  from assemblyline.datastore.exceptions import SearchException
10
+ from assemblyline.odm.messages.retrohunt_heartbeat import RetrohuntMessage
10
11
  from assemblyline.odm.messages.scaler_heartbeat import ScalerMessage
11
12
  from assemblyline.odm.messages.scaler_status_heartbeat import ScalerStatusMessage
12
13
  from assemblyline.odm.messages.alerter_heartbeat import AlerterMessage
@@ -15,6 +16,7 @@ from assemblyline.odm.messages.dispatcher_heartbeat import DispatcherMessage
15
16
  from assemblyline.odm.messages.expiry_heartbeat import ExpiryMessage
16
17
  from assemblyline.odm.messages.ingest_heartbeat import IngestMessage
17
18
  from assemblyline.odm.messages.service_heartbeat import ServiceMessage
19
+ from assemblyline.odm.messages.elastic_heartbeat import ElasticMessage
18
20
  from assemblyline.odm.messages.vacuum_heartbeat import VacuumMessage
19
21
  from assemblyline.remote.datatypes import get_client
20
22
  from assemblyline.remote.datatypes.hash import Hash, ExpiringHash
@@ -289,5 +291,56 @@ class HeartbeatFormatter(object):
289
291
  except Exception:
290
292
  self.log.exception("An exception occurred while generating VacuumMessage")
291
293
 
294
+ elif m_type == "elastic":
295
+ try:
296
+ msg = {
297
+ "sender": self.sender,
298
+ "msg": {
299
+ "instances": instances,
300
+ 'unassigned_shards': m_data['unassigned'],
301
+ "request_time": m_data['request_time'],
302
+ "shard_sizes": [{'name': index, 'shard_size': size}
303
+ for index, size in m_data['shard_sizes'].items()],
304
+ }
305
+ }
306
+ self.status_queue.publish(ElasticMessage(msg).as_primitives())
307
+ self.log.info(f"Sent elastic heartbeat: {msg['msg']}")
308
+ except Exception:
309
+ self.log.exception("An exception occurred while generating ElasticMessage")
310
+
311
+ elif m_type == "retrohunt":
312
+ try:
313
+
314
+ status = m_data.get('status', {})
315
+ fetcher = status.get('fetcher', {})
316
+ resources = status.get('resources', {})
317
+ storage = status.get('storage', {})
318
+ last_minute_cpu = sum(report.get('cpu_load_1m', 0) for report in resources.values())
319
+ memory = sum(report.get('memory', 0) for report in resources.values())
320
+ free_storage = [report.get('high_water', 0) for report in storage.values()]
321
+ min_storage = sum_storage = 0
322
+ if free_storage:
323
+ min_storage = min(free_storage)
324
+ sum_storage = sum(free_storage)
325
+
326
+ msg = {
327
+ "sender": self.sender,
328
+ "msg": {
329
+ 'instances': instances,
330
+ 'request_time': m_data['request_time'],
331
+ 'pending_files': fetcher.get('pending_files', 0),
332
+ 'ingested_last_minute': fetcher.get('last_minute_throughput', 0),
333
+ 'worker_storage_available': min_storage,
334
+ 'total_storage_available': sum_storage,
335
+ 'active_searches': status.get('active_searches', 0),
336
+ 'last_minute_cpu': last_minute_cpu,
337
+ 'total_memory_used': memory,
338
+ }
339
+ }
340
+ self.status_queue.publish(RetrohuntMessage(msg).as_primitives())
341
+ self.log.info(f"Sent retrohunt heartbeat: {msg['msg']}")
342
+ except Exception:
343
+ self.log.exception("An exception occurred while generating RetrohuntMessage")
344
+
292
345
  else:
293
346
  self.log.warning(f"Skipping unknown counter: {m_name} [{m_type}] ==> {m_data}")
@@ -3,6 +3,7 @@
3
3
  import tempfile
4
4
  import sys
5
5
  import time
6
+ import threading
6
7
  from collections import Counter
7
8
  from threading import Lock, Thread
8
9
  from os import environ, path
@@ -259,6 +260,7 @@ class HeartbeatManager(ServerBase):
259
260
  self.metrics_queue = CommsQueue(METRICS_QUEUE)
260
261
  self.scheduler = BackgroundScheduler(daemon=True)
261
262
  self.hm = HeartbeatFormatter("heartbeat_manager", self.log, config=self.config)
263
+ self.hauntedhouse_client = forge.get_hauntedhouse_client(self.config)
262
264
 
263
265
  self.counters_lock = Lock()
264
266
  self.counters = {}
@@ -281,6 +283,10 @@ class HeartbeatManager(ServerBase):
281
283
  self.scheduler.add_job(self._export_hearbeats, 'interval', seconds=self.config.core.metrics.export_interval)
282
284
  self.scheduler.start()
283
285
 
286
+ threading.Thread(target=self._call_interval, args=('es_shards', self._fetch_shards), daemon=True).start()
287
+ if self.config.retrohunt.enabled:
288
+ threading.Thread(target=self._call_interval, args=('retrohunt', self._fetch_retrohunt), daemon=True).start()
289
+
284
290
  while self.running:
285
291
  for msg in self.metrics_queue.listen():
286
292
  # APM Transaction start
@@ -316,6 +322,70 @@ class HeartbeatManager(ServerBase):
316
322
  if self.apm_client:
317
323
  self.apm_client.end_transaction('process_message', 'success')
318
324
 
325
+ def _call_interval(self, name, method):
326
+ while self.running:
327
+ # Run the heartbeat
328
+ send_time = time.time()
329
+ try:
330
+ method()
331
+ except Exception:
332
+ self.log.exception('Error running metric fetcher %s', name)
333
+
334
+ # Wait until we are inline with the heartbeat interval
335
+ elapsed = time.time() - send_time
336
+ remaining = self.config.core.metrics.export_interval - elapsed
337
+ if remaining > 0:
338
+ self.sleep(remaining)
339
+
340
+ def _fetch_shards(self):
341
+ request_time = None
342
+ sizes = {}
343
+ unassigned = 0
344
+ nodes = set()
345
+ try:
346
+ # Pull shard data from elastisearch
347
+ start_time = time.time()
348
+ response = self.datastore.ds.client.cat.shards(bytes='b', format='json', master_timeout='5s')
349
+ for shard in response.body:
350
+ index = shard['index']
351
+ node = shard['node']
352
+ if node:
353
+ sizes.setdefault(index, 0)
354
+ sizes[index] = max(sizes[index], int(shard.get('store') or 0))
355
+ nodes.add(node)
356
+ else:
357
+ unassigned += 1
358
+ request_time = time.time() - start_time
359
+
360
+ finally:
361
+ # Export metrics heartbeat, send None and empty if an error occurs while getting the data
362
+ # the error will be logged in the outer function
363
+ metrics = {
364
+ 'shard_sizes': sizes,
365
+ 'request_time': request_time,
366
+ 'unassigned': unassigned
367
+ }
368
+ self.hm.send_heartbeat('elastic', 'datastore', metrics, len(nodes))
369
+
370
+ def _fetch_retrohunt(self):
371
+ status = {}
372
+ request_time = None
373
+ try:
374
+ # Pull status message from retrohunt
375
+ start_time = time.time()
376
+ status = self.hauntedhouse_client.status()
377
+ request_time = time.time() - start_time
378
+
379
+ finally:
380
+ metrics = {
381
+ 'request_time': request_time,
382
+ 'status': status,
383
+ }
384
+ instances = len(status.get('storage', {}))
385
+
386
+ # Export heartbeat
387
+ self.hm.send_heartbeat('retrohunt', 'hauntedhouse', metrics, instances)
388
+
319
389
  def _export_hearbeats(self):
320
390
  try:
321
391
  self.heartbeat()
@@ -121,7 +121,10 @@ class DockerController(ControllerInterface):
121
121
  aliases=['service-server'])
122
122
 
123
123
  # As long as the current service server is still running, just block its exit code in this thread
124
- self.service_server.wait()
124
+ try:
125
+ self.service_server.wait()
126
+ except docker.errors.NotFound:
127
+ pass
125
128
 
126
129
  # If it does return, find the new service server
127
130
  self.service_server = self.find_service_server()
@@ -158,6 +161,14 @@ class DockerController(ControllerInterface):
158
161
  if 'already exists' in str(e):
159
162
  return
160
163
  raise e
164
+ except docker.errors.NotFound as e:
165
+ if aliases == ['service-server']:
166
+ # We've lost our service-server container, time to find another
167
+ self.service_server = self.find_service_server()
168
+ network.connect(self.service_server, aliases=aliases)
169
+ return
170
+ raise e
171
+
161
172
 
162
173
  def _start(self, service_name):
163
174
  """Launch a docker container in a manner suitable for Assemblyline."""
@@ -21,7 +21,7 @@ from assemblyline.odm.models.config import Selector
21
21
 
22
22
  from kubernetes import client, config, watch
23
23
  from kubernetes.client import V1Deployment, V1DeploymentSpec, V1PodTemplateSpec, V1DeploymentStrategy, \
24
- V1PodSpec, V1PodOS, V1ObjectMeta, V1Volume, V1Container, V1VolumeMount, V1EnvVar, V1ConfigMapVolumeSource, \
24
+ V1PodSpec, V1ObjectMeta, V1Volume, V1Container, V1VolumeMount, V1EnvVar, V1ConfigMapVolumeSource, \
25
25
  V1PersistentVolumeClaimVolumeSource, V1LabelSelector, V1ResourceRequirements, V1PersistentVolumeClaim, \
26
26
  V1PersistentVolumeClaimSpec, V1NetworkPolicy, V1NetworkPolicySpec, V1NetworkPolicyEgressRule, V1NetworkPolicyPeer, \
27
27
  V1NetworkPolicyIngressRule, V1Secret, V1SecretVolumeSource, V1LocalObjectReference, V1Service, \
@@ -839,25 +839,14 @@ class KubernetesController(ControllerInterface):
839
839
  volume_mounts=chown_mounts
840
840
  ))
841
841
 
842
- pod_os = None
843
- security_context = V1PodSecurityContext(fs_group=1000)
844
- if docker_config.operating_system:
845
- # Allow Kubernetes to schedule the pod to a compatible node
846
- pod_os = V1PodOS(name=docker_config.operating_system)
847
-
848
- if docker_config.operating_system == 'windows':
849
- security_context = None
850
-
851
-
852
842
  pod = V1PodSpec(
853
843
  init_containers=init_containers,
854
844
  volumes=all_volumes,
855
845
  containers=self._create_containers(service_name, deployment_name, docker_config,
856
846
  all_mounts, core_container=core_mounts),
857
- os=pod_os,
858
847
  priority_class_name=self.dependency_priority if high_priority else self.priority,
859
848
  termination_grace_period_seconds=shutdown_seconds,
860
- security_context=security_context,
849
+ security_context=V1PodSecurityContext(fs_group=1000),
861
850
  service_account_name=service_account,
862
851
  affinity=selector_to_node_affinity(self.linux_node_selector),
863
852
  )
@@ -12,114 +12,95 @@ from base64 import b64encode
12
12
  from collections import defaultdict
13
13
  from logging import Logger
14
14
  from packaging.version import parse, Version
15
- from typing import Any, Dict, List, Tuple
16
15
 
17
16
  DEFAULT_DOCKER_REGISTRY = "registry.hub.docker.com"
18
17
 
19
18
 
20
19
  class ContainerRegistry():
21
- def __init__(self, server, headers: Dict[str, str] = None, verify: bool = True,
22
- proxies: Dict[str, str] = None, *args, **kwargs):
23
- self.server = server
24
- self.session = requests.Session()
25
- self.session.headers = headers
26
- self.session.verify = verify
27
- self.session.proxies = proxies
28
-
29
- def _make_request(self, path: str) -> Dict[str, Any]:
30
- request_path = f"{self.server}{path}"
31
- resp = None
32
- try:
33
- resp = self.session.get(f"https://{request_path}")
34
- except requests.exceptions.SSLError:
35
- # Connect to insecure registry over HTTP (development only)
36
- if not self.session.verify:
37
- resp = self.session.get(f"http://{request_path}")
38
- # Test for valid response
39
- if resp and resp.ok:
40
- return resp.json()
41
- return None
42
-
43
20
  # Provide a means of obtaining a list of tags from a container registry
44
- def get_image_tags(self, image_name) -> List[str]:
45
- raise NotImplementedError()
46
-
47
- # Provide a means of obtaining the compatible operating system for the container image
48
- def get_image_os(self, image_name, image_tag) -> str:
21
+ def _get_proprietary_registry_tags(self, server, image_name, auth, verify, proxies=None, token_server=None):
49
22
  raise NotImplementedError()
50
23
 
51
24
 
52
- class DockerHub(ContainerRegistry):
53
- def __init__(self, update_channel, proxies: Dict[str, str] = None, *args, **kwargs):
54
- super().__init__(DEFAULT_DOCKER_REGISTRY, None, True, proxies)
55
- self.update_channel = update_channel
56
-
57
- def get_image_tags(self, image_name) -> List[str]:
58
- resp = self._make_request(f"/v2/repositories/{image_name}/tags?page_size=5&page=1&name={self.update_channel}")
59
- if resp:
60
- return [x['name'] for x in resp['results']]
61
- return []
62
-
63
- def get_image_os(self, image_name, image_tag) -> str:
64
- resp = self._make_request(f"/v2/repositories/{image_name}/tags/{image_tag}")
65
- if resp:
66
- return resp['images'][0]['os']
67
- return None
68
-
69
-
70
- # Ref: https://docs.docker.com/registry/spec/api/#detail
71
25
  class DockerRegistry(ContainerRegistry):
72
- def get_image_tags(self, image_name) -> List[str]:
26
+ def _get_proprietary_registry_tags(self, server, image_name, auth, verify, proxies=None, token_server=None):
73
27
  # Find latest tag for each types
74
- resp = self._make_request(f"/v2/{image_name}/tags/list")
75
- if resp:
76
- return resp['tags'] or []
77
- return []
28
+ url = f"https://{server}/v2/{image_name}/tags/list"
78
29
 
79
- def get_image_os(self, image_name, image_tag) -> str:
80
- resp = self._make_request(f"/v2/{image_name}/manifests/{image_tag}")
81
- if resp:
82
- # Retrieve OS compatibilty from historical record
83
- return json.loads(resp['history'][0]['v1Compatibility'])['os']
30
+ # Get tag list
31
+ headers = {}
32
+ if auth:
33
+ headers["Authorization"] = auth
34
+ else:
35
+ # Retrieve token for authentication: https://distribution.github.io/distribution/spec/auth/token/
84
36
 
85
- # Unable to determine the OS compatibility
86
- return None
37
+ # Assume the token server is the same as the container image registry host if not explicitly set
38
+ token_server = token_server if token_server else server
39
+ token_url = f"https://{token_server}/token?scope=repository:{image_name}:pull"
40
+ resp = requests.get(token_url)
41
+ if resp.ok:
42
+ # Request to obtain token was successful, set Authorization header for registry API
43
+ token = resp.json().get('token')
44
+ headers["Authorization"] = f"Bearer {token}"
87
45
 
46
+ resp = None
47
+ try:
48
+ resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
49
+ except requests.exceptions.SSLError:
50
+ # Connect to insecure registry over HTTP (development only)
51
+ if not verify:
52
+ url = f"http://{server}/v2/{image_name}/tags/list"
53
+ resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
54
+ # Test for valid response
55
+ if resp and resp.ok:
56
+ # Test for positive list of tags
57
+ resp_data = resp.json()
58
+ return resp_data['tags'] or []
59
+ return []
88
60
 
89
- # Ref: https://github.com/goharbor/harbor/blob/main/api/v2.0/swagger.yaml
90
61
  class HarborRegistry(ContainerRegistry):
91
- def _get_project_repo_ids(self, image_name) -> Tuple[str, str]:
62
+ def _get_proprietary_registry_tags(self, server, image_name, auth, verify, proxies=None, token_server=None):
92
63
  # Determine project/repo IDs from image name
93
64
  project_id, repo_id = image_name.split('/', 1)
94
65
  repo_id = repo_id.replace('/', "%2F")
95
- return project_id, repo_id
66
+ url = f"https://{server}/api/v2.0/projects/{project_id}/repositories/{repo_id}/artifacts?page_size=0"
96
67
 
97
- def get_image_tags(self, image_name) -> List[str]:
98
- project_id, repo_id = self._get_project_repo_ids(image_name)
99
- resp = self._make_request(f"/api/v2.0/projects/{project_id}/repositories/{repo_id}/artifacts?page_size=0")
100
- if resp:
101
- return [tag['name'] for image in resp if image['tags'] for tag in image['tags']]
102
- return []
103
-
104
- def get_image_os(self, image_name, image_tag) -> str:
105
- project_id, repo_id = self._get_project_repo_ids(image_name)
106
- resp = self._make_request(f"/api/v2.0/projects/{project_id}/repositories/{repo_id}/artifacts/{image_tag}")
107
- if resp:
108
- # Retrieve OS compatibilty from reference
109
- return resp['references'][0]['platform']['os']
68
+ headers = {}
69
+ if auth:
70
+ headers["Authorization"] = auth
71
+ else:
72
+ # Retrieve token for authentication: https://github.com/goharbor/harbor/wiki/Harbor-FAQs#api
73
+
74
+ # Assume the token server is the same as the container image registry host if not explicitly set
75
+ token_server = token_server if token_server else server
76
+ token_url = f"https://{server}/service/token?scope=repository:{image_name}:pull"
77
+ resp = requests.get(token_url)
78
+ if resp.ok:
79
+ # Request to obtain token was successful, set Authorization header for registry API
80
+ token = resp.json().get('token')
81
+ headers["Authorization"] = f"Bearer {token}"
82
+ resp = None
83
+ try:
84
+ resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
85
+ except requests.exceptions.SSLError:
86
+ # Connect to insecure registry over HTTP (development only)
87
+ if not verify:
88
+ url = f"http://{server}/api/v2.0/projects/{project_id}/repositories/{repo_id}/artifacts"
89
+ resp = requests.get(url, headers=headers, verify=verify, proxies=proxies)
110
90
 
111
- # Unable to determine the OS compatibility
112
- return None
91
+ if resp and resp.ok:
92
+ return [tag['name'] for image in resp.json() if image['tags'] for tag in image['tags']]
93
+ return []
113
94
 
114
95
 
115
96
  REGISTRY_TYPE_MAPPING = {
116
- 'dockerhub': DockerHub,
117
- 'docker': DockerRegistry,
118
- 'harbor': HarborRegistry
97
+ 'docker': DockerRegistry(),
98
+ 'harbor': HarborRegistry()
119
99
  }
120
100
 
121
101
 
122
- def get_latest_tag_for_service(service_config: ServiceConfig, system_config: SystemConfig, logger: Logger, prefix: str = ""):
102
+ def get_latest_tag_for_service(
103
+ service_config: ServiceConfig, system_config: SystemConfig, logger: Logger, prefix: str = ""):
123
104
  def process_image(image):
124
105
  # Find which server to search in
125
106
  server = image.split("/")[0]
@@ -180,25 +161,23 @@ def get_latest_tag_for_service(service_config: ServiceConfig, system_config: Sys
180
161
  # We're assuming that if only a password is given, then this is a token
181
162
  auth = f"Bearer {service_config.docker_config.registry_password}"
182
163
 
164
+ registry = REGISTRY_TYPE_MAPPING[service_config.docker_config.registry_type]
165
+ token_server = None
183
166
  proxies = None
184
167
  for reg_conf in system_config.core.updater.registry_configs:
185
168
  if reg_conf.name == server:
186
169
  proxies = reg_conf.proxies or None
170
+ token_server = reg_conf.token_server or None
187
171
  break
188
172
 
189
- registry_type = 'dockerhub' if server == DEFAULT_DOCKER_REGISTRY else service_config.docker_config.registry_type
190
- registry_args = {
191
- 'server': server,
192
- 'headers': {'Authorization': auth},
193
- 'verify': not system_config.services.allow_insecure_registry,
194
- 'proxies': proxies,
195
- 'update_channel': update_channel
196
- }
197
-
198
- registry: ContainerRegistry = REGISTRY_TYPE_MAPPING[registry_type](**registry_args)
199
- tags = registry.get_image_tags(image_name)
200
-
173
+ if server == DEFAULT_DOCKER_REGISTRY:
174
+ tags = _get_dockerhub_tags(image_name, update_channel, proxies)
175
+ else:
176
+ tags = registry._get_proprietary_registry_tags(server, image_name, auth,
177
+ not system_config.services.allow_insecure_registry,
178
+ proxies, token_server)
201
179
  tag_name = None
180
+
202
181
  # Pre-filter tags to only consider 'compatible' tags relative to the running system
203
182
  tags = [t for t in tags
204
183
  if re.match(f"({FRAMEWORK_VERSION})[.]({SYSTEM_VERSION})[.]\\d+[.]({update_channel})\\d+", t)]
@@ -222,10 +201,28 @@ def get_latest_tag_for_service(service_config: ServiceConfig, system_config: Sys
222
201
  image_variables = defaultdict(str)
223
202
  image_variables.update(system_config.services.image_variables)
224
203
  image = string.Template(image).safe_substitute(image_variables)
225
- os = registry.get_image_os(image_name, tag_name)
204
+ server, image_name = process_image(image)
226
205
 
227
206
  # Append server to image if not the default server
228
- if server != DEFAULT_DOCKER_REGISTRY:
207
+ if server != "registry.hub.docker.com":
229
208
  image_name = "/".join([server, image_name])
230
209
 
231
- return image_name, tag_name, auth_config, os
210
+ return image_name, tag_name, auth_config
211
+
212
+
213
+ # Default for obtaining tags from DockerHub
214
+ def _get_dockerhub_tags(image_name, update_channel, proxies=None):
215
+ # Find latest tag for each types
216
+ url = f"https://{DEFAULT_DOCKER_REGISTRY}/v2/repositories/{image_name}/tags" \
217
+ f"?page_size=5&page=1&name={update_channel}"
218
+
219
+ # Get tag list
220
+ resp = requests.get(url, proxies=proxies)
221
+
222
+ # Test for valid response
223
+ if resp.ok:
224
+ # Test for positive list of tags
225
+ resp_data = resp.json()
226
+ return [x['name'] for x in resp_data['results']]
227
+
228
+ return []
@@ -476,10 +476,10 @@ class ServiceUpdater(ThreadedCoreBase):
476
476
  'version': tag,
477
477
  'docker_config': {'image': install_data.get('image')}})
478
478
 
479
- image_name, tag_name, auth, os = get_latest_tag_for_service(
479
+ image_name, tag_name, auth = get_latest_tag_for_service(
480
480
  service, self.config, self.log, prefix="[CI] ")
481
481
 
482
- docker_config = dict(image=f"{image_name}:{tag_name}", operating_system=os)
482
+ docker_config = dict(image=f"{image_name}:{tag_name}")
483
483
  if auth:
484
484
  docker_config.update(dict(registry_username=auth['username'],
485
485
  registry_password=auth['password']))
@@ -675,7 +675,7 @@ class ServiceUpdater(ThreadedCoreBase):
675
675
 
676
676
  for service in self.datastore.list_all_services(full=True):
677
677
  discovered_services.append(service.name)
678
- image_name, tag_name, auth, _ = get_latest_tag_for_service(service, self.config, self.log, prefix="[CV] ")
678
+ image_name, tag_name, auth = get_latest_tag_for_service(service, self.config, self.log, prefix="[CV] ")
679
679
  self.latest_service_tags.set(service.name,
680
680
  {'auth': auth, 'image': image_name, service.update_channel: tag_name})
681
681
 
@@ -270,6 +270,7 @@ class FileProcessor(threading.Thread):
270
270
  # Remove UI specific params
271
271
  settings.pop('default_zip_password', None)
272
272
  settings.pop('download_encoding', None)
273
+ settings.pop('executive_summary', None)
273
274
  settings.pop('expand_min_score', None)
274
275
  settings.pop('submission_view', None)
275
276
  settings.pop('ui4', None)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: assemblyline-core
3
- Version: 4.5.1.dev38
3
+ Version: 4.5.1.dev90
4
4
  Summary: Assemblyline 4 - Core components
5
5
  Home-page: https://github.com/CybercentreCanada/assemblyline-core/
6
6
  Author: CCCS Assemblyline development team
@@ -1 +0,0 @@
1
- 4.5.1.dev38