assemblyline-core 4.5.0.34__tar.gz → 4.5.0.36__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.0.34 → assemblyline-core-4.5.0.36}/PKG-INFO +1 -1
  2. assemblyline-core-4.5.0.36/assemblyline_core/VERSION +1 -0
  3. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/scaler_server.py +9 -2
  4. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/updater/helper.py +120 -74
  5. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core.egg-info/PKG-INFO +1 -1
  6. assemblyline-core-4.5.0.34/assemblyline_core/VERSION +0 -1
  7. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/LICENCE.md +0 -0
  8. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/README.md +0 -0
  9. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/__init__.py +0 -0
  10. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/alerter/__init__.py +0 -0
  11. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/alerter/processing.py +0 -0
  12. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/alerter/run_alerter.py +0 -0
  13. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/archiver/__init__.py +0 -0
  14. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/archiver/run_archiver.py +0 -0
  15. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/badlist_client.py +0 -0
  16. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/dispatching/__init__.py +0 -0
  17. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/dispatching/__main__.py +0 -0
  18. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/dispatching/client.py +0 -0
  19. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/dispatching/dispatcher.py +0 -0
  20. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/dispatching/schedules.py +0 -0
  21. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/dispatching/timeout.py +0 -0
  22. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/expiry/__init__.py +0 -0
  23. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/expiry/run_expiry.py +0 -0
  24. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/ingester/__init__.py +0 -0
  25. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/ingester/__main__.py +0 -0
  26. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/ingester/constants.py +0 -0
  27. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/ingester/ingester.py +0 -0
  28. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/__init__.py +0 -0
  29. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/es_metrics.py +0 -0
  30. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/heartbeat_formatter.py +0 -0
  31. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/helper.py +0 -0
  32. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/metrics_server.py +0 -0
  33. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/run_heartbeat_manager.py +0 -0
  34. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/run_metrics_aggregator.py +0 -0
  35. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/metrics/run_statistics_aggregator.py +0 -0
  36. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/plumber/__init__.py +0 -0
  37. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/plumber/run_plumber.py +0 -0
  38. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/__init__.py +0 -0
  39. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/client.py +0 -0
  40. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/creator/__init__.py +0 -0
  41. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/creator/run.py +0 -0
  42. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/creator/run_worker.py +0 -0
  43. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/loader/__init__.py +0 -0
  44. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/loader/run.py +0 -0
  45. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/loader/run_worker.py +0 -0
  46. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/replay/replay.py +0 -0
  47. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/safelist_client.py +0 -0
  48. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/__init__.py +0 -0
  49. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/collection.py +0 -0
  50. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/controllers/__init__.py +0 -0
  51. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/controllers/docker_ctl.py +0 -0
  52. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/controllers/interface.py +0 -0
  53. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/controllers/kubernetes_ctl.py +0 -0
  54. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/scaler/run_scaler.py +0 -0
  55. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/server_base.py +0 -0
  56. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/signature_client.py +0 -0
  57. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/submission_client.py +0 -0
  58. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/tasking_client.py +0 -0
  59. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/updater/__init__.py +0 -0
  60. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/updater/run_updater.py +0 -0
  61. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/vacuum/__init__.py +0 -0
  62. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/vacuum/crawler.py +0 -0
  63. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/vacuum/department_map.py +0 -0
  64. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/vacuum/safelist.py +0 -0
  65. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/vacuum/stream_map.py +0 -0
  66. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/vacuum/worker.py +0 -0
  67. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/workflow/__init__.py +0 -0
  68. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core/workflow/run_workflow.py +0 -0
  69. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core.egg-info/SOURCES.txt +0 -0
  70. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core.egg-info/dependency_links.txt +0 -0
  71. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core.egg-info/requires.txt +0 -0
  72. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/assemblyline_core.egg-info/top_level.txt +0 -0
  73. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/setup.cfg +0 -0
  74. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/setup.py +0 -0
  75. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_alerter.py +0 -0
  76. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_badlist_client.py +0 -0
  77. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_dispatcher.py +0 -0
  78. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_expiry.py +0 -0
  79. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_plumber.py +0 -0
  80. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_replay.py +0 -0
  81. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_safelist_client.py +0 -0
  82. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_scaler.py +0 -0
  83. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_scheduler.py +0 -0
  84. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_signature_client.py +0 -0
  85. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_simulation.py +0 -0
  86. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_vacuum.py +0 -0
  87. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/test/test_worker_ingest.py +0 -0
  88. {assemblyline-core-4.5.0.34 → assemblyline-core-4.5.0.36}/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.0.34
3
+ Version: 4.5.0.36
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.36
@@ -35,6 +35,7 @@ from assemblyline.common.uid import get_id_from_data
35
35
  from assemblyline.common.forge import get_classification, get_service_queue, get_apm_client
36
36
  from assemblyline.common.constants import SCALER_TIMEOUT_QUEUE, SERVICE_STATE_HASH, ServiceStatus
37
37
  from assemblyline.common.version import FRAMEWORK_VERSION, SYSTEM_VERSION
38
+ from assemblyline_core.updater.helper import get_registry_config
38
39
  from assemblyline_core.scaler.controllers import KubernetesController
39
40
  from assemblyline_core.scaler.controllers.interface import ServiceControlError
40
41
  from assemblyline_core.server_base import ServiceStage, ThreadedCoreBase
@@ -514,6 +515,12 @@ class ScalerServer(ThreadedCoreBase):
514
515
  for var in default_settings.environment:
515
516
  if var.name not in set_keys:
516
517
  docker_config.environment.append(var)
518
+
519
+ # Set authentication to registry to pull the image
520
+ auth_config = get_registry_config(docker_config, self.config)
521
+ docker_config.registry_username = auth_config['username']
522
+ docker_config.registry_password = auth_config['password']
523
+
517
524
  return docker_config
518
525
 
519
526
  # noinspection PyBroadException
@@ -533,7 +540,7 @@ class ScalerServer(ThreadedCoreBase):
533
540
  if not service.version.startswith(system_spec):
534
541
  # If FW and SYS version don't prefix in the service version, we can't guarantee the
535
542
  # service is compatible. Disable and treat it as incompatible due to service version.
536
- self.log.warning("Disabling service with incompatible version. "
543
+ self.log.warning(f"Disabling {service.name} with incompatible version. "
537
544
  f"[{service.version} != '{system_spec}.X.{service.update_channel}Y'].")
538
545
  disable_incompatible_service()
539
546
  elif service.update_config and service.update_config.wait_for_update and not service.update_config.sources:
@@ -648,7 +655,7 @@ class ScalerServer(ThreadedCoreBase):
648
655
  profile.privileged = service.privileged
649
656
 
650
657
  for dependency_name, dependency_blob in dependency_blobs.items():
651
- if profile.dependency_blobs[dependency_name] != dependency_blob:
658
+ if profile.dependency_blobs.get(dependency_name, '') != dependency_blob:
652
659
  self.log.info(f"Updating deployment information for {name}/{dependency_name}")
653
660
  profile.dependency_blobs[dependency_name] = dependency_blob
654
661
  self.controller.start_stateful_container(
@@ -5,13 +5,15 @@ import string
5
5
  import time
6
6
 
7
7
  from assemblyline.common.version import FRAMEWORK_VERSION, SYSTEM_VERSION
8
- from assemblyline.odm.models.config import Config as SystemConfig
9
- from assemblyline.odm.models.service import Service as ServiceConfig
8
+ from assemblyline.odm.models.config import Config as SystemConfig, ServiceRegistry
9
+ from assemblyline.odm.models.service import Service as ServiceConfig, DockerConfig
10
10
 
11
11
  from base64 import b64encode
12
12
  from collections import defaultdict
13
13
  from logging import Logger
14
+ from typing import Dict, List
14
15
  from packaging.version import parse, Version
16
+ from urllib.parse import urlencode
15
17
 
16
18
  DEFAULT_DOCKER_REGISTRY = "hub.docker.com"
17
19
 
@@ -130,9 +132,42 @@ REGISTRY_TYPE_MAPPING = {
130
132
  'harbor': HarborRegistry()
131
133
  }
132
134
 
133
-
134
- def get_latest_tag_for_service(
135
- service_config: ServiceConfig, system_config: SystemConfig, logger: Logger, prefix: str = ""):
135
+ def get_registry_config(docker_config: DockerConfig, system_config: SystemConfig) -> Dict[str, str]:
136
+ # Fix service image for calling Docker API
137
+ image_variables = defaultdict(str)
138
+ image_variables.update(system_config.services.image_variables)
139
+ image_variables.update(system_config.services.update_image_variables)
140
+ searchable_image = string.Template(docker_config.image).safe_substitute(image_variables)
141
+
142
+ server = searchable_image.split("/", 1)[0]
143
+
144
+ # Prioritize authentication given as a system configuration
145
+ registries: List[ServiceRegistry] = system_config.services.registries or []
146
+ for registry in registries:
147
+ if server.startswith(registry.name):
148
+ # Return authentication credentials and the type of registry
149
+ return dict(username=registry.username, password=registry.password, type=registry.type)
150
+
151
+ # Otherwise return what's configured for the service
152
+ return dict(username=docker_config.registry_username, password=docker_config.registry_password,
153
+ type=docker_config.registry_type)
154
+
155
+ def get_latest_tag_for_service(service_config: ServiceConfig, system_config: SystemConfig, logger: Logger,
156
+ prefix: str = ""):
157
+ """
158
+ Retrieves the latest compatible tag for a given service from its respective Docker registry.
159
+ This function processes the image name to determine the correct server, authenticates
160
+ with the registry, and then fetches the latest tag that matches the service's framework
161
+ and system version constraints. It handles different registry configurations including
162
+ private and public ones.
163
+ Args:
164
+ service_config (ServiceConfig): The configuration object for the service which includes Docker configuration.
165
+ system_config (SystemConfig): System-wide configuration which includes service updates and registry configurations.
166
+ logger (Logger): Logger object for logging messages.
167
+ prefix (str, optional): A prefix string to prepend to log messages for clarity in logs.
168
+ Returns:
169
+ tuple: Returns a tuple of the final image name, the latest tag, and authentication config used for the Docker registry.
170
+ """
136
171
  def process_image(image):
137
172
  # Find which server to search in
138
173
  server = image.split("/")[0]
@@ -152,10 +187,9 @@ def get_latest_tag_for_service(
152
187
 
153
188
  # Split repo name without the tag
154
189
  image_name = image_name.rsplit(":", 1)[0]
155
-
156
190
  return server, image_name
157
191
 
158
- # Extract info
192
+ # Extract and process service information
159
193
  service_name = service_config.name
160
194
  image = service_config.docker_config.image
161
195
  update_channel = service_config.update_channel
@@ -168,36 +202,23 @@ def get_latest_tag_for_service(
168
202
 
169
203
  # Get authentication
170
204
  auth = None
171
- auth_config = None
172
205
  server, image_name = process_image(searchable_image)
173
206
 
174
- if not (service_config.docker_config.registry_username and service_config.docker_config.registry_password):
175
- # If the passed in service configuration is missing registry credentials, check against system configuration
176
- for registry in system_config.services.registries:
177
- if server.startswith(registry['name']):
178
- # Apply the credentials that the system is configured to use with the registry
179
- service_config.docker_config.registry_username = registry['username']
180
- service_config.docker_config.registry_password = registry['password']
181
- service_config.docker_config.registry_type = registry['type']
182
- break
183
-
184
- if service_config.docker_config.registry_username and service_config.docker_config.registry_password:
185
- # We're authenticating using Basic Auth
186
- auth_config = {
187
- 'username': service_config.docker_config.registry_username,
188
- 'password': service_config.docker_config.registry_password
189
- }
190
- upass = f"{service_config.docker_config.registry_username}:{service_config.docker_config.registry_password}"
207
+ # Generate 'Authenication' header value for pulling tag list from registry
208
+ auth_config = get_registry_config(service_config.docker_config, system_config)
209
+ registry_type = auth_config.pop('type')
210
+ if auth_config['username'] and auth_config['password']:
211
+ upass = f"{auth_config['username']}:{auth_config['password']}"
191
212
  auth = f"Basic {b64encode(upass.encode()).decode()}"
192
- elif service_config.docker_config.registry_password:
213
+ elif auth_config['password']:
193
214
  # We're assuming that if only a password is given, then this is a token
194
- auth = f"Bearer {service_config.docker_config.registry_password}"
215
+ auth = f"Bearer {auth_config['password']}"
195
216
 
196
217
  if server.endswith(".azurecr.io"):
197
218
  # This is an Azure Container Registry based on the server name
198
219
  registry = AzureContainerRegistry()
199
220
  else:
200
- registry = REGISTRY_TYPE_MAPPING[service_config.docker_config.registry_type]
221
+ registry = REGISTRY_TYPE_MAPPING[registry_type]
201
222
  token_server = None
202
223
  proxies = None
203
224
  for reg_conf in system_config.core.updater.registry_configs:
@@ -207,32 +228,27 @@ def get_latest_tag_for_service(
207
228
  break
208
229
 
209
230
  if server == DEFAULT_DOCKER_REGISTRY:
210
- tags = _get_dockerhub_tags(image_name, update_channel, proxies)
231
+ tags = _get_dockerhub_tags(image_name, update_channel, prefix, proxies, logger=logger)
211
232
  else:
212
233
  tags = registry._get_proprietary_registry_tags(server, image_name, auth,
213
234
  not system_config.services.allow_insecure_registry,
214
235
  proxies, token_server)
215
-
216
- tag_name = None
217
-
218
236
  # Pre-filter tags to only consider 'compatible' tags relative to the running system
219
- tags = [t for t in tags
220
- if re.match(f"({FRAMEWORK_VERSION})\.({SYSTEM_VERSION})\.\\d+\.({update_channel})\\d+", t)]
221
-
237
+ tags = [tag for tag in tags
238
+ if re.match(f"({FRAMEWORK_VERSION})\.({SYSTEM_VERSION})\.\\d+\.({update_channel})\\d+", tag)]
222
239
  if not tags:
223
- logger.warning(f"{prefix}Cannot fetch latest tag for service {service_name} - {image_name}"
240
+ logger.warning(f"{prefix} Cannot fetch latest tag for service {service_name} - {image_name}" \
224
241
  f" => [server: {server}, repo_name: {image_name}, channel: {update_channel}]")
225
- else:
226
- for t in tags:
227
- t_version = Version(t.replace(update_channel, ""))
228
- # Tag name gets assigned to the first viable option then relies on comparison to get the latest
229
- if not tag_name:
230
- tag_name = t
231
- elif t_version.major == FRAMEWORK_VERSION and t_version.minor == SYSTEM_VERSION and \
232
- t_version > parse(tag_name.replace(update_channel, "")):
233
- tag_name = t
234
-
235
- logger.info(f"{prefix}Latest {service_name} tag on {update_channel.upper()} channel is: {tag_name}")
242
+ tag_name = None
243
+ for tag in tags:
244
+ t_version = Version(tag.replace(update_channel, ""))
245
+ # Tag name gets assigned to the first viable option then relies on comparison to get the latest
246
+ if not tag_name:
247
+ tag_name = tag
248
+ elif t_version.major == FRAMEWORK_VERSION and t_version.minor == SYSTEM_VERSION and \
249
+ t_version > parse(tag_name.replace(update_channel, "")):
250
+ tag_name = tag
251
+ logger.info(f"{prefix}Latest {service_name} tag on {update_channel.upper()} channel is: {tag_name}")
236
252
 
237
253
  # Fix service image for use in Kubernetes
238
254
  image_variables = defaultdict(str)
@@ -246,43 +262,73 @@ def get_latest_tag_for_service(
246
262
 
247
263
  return image_name, tag_name, auth_config
248
264
 
249
-
250
- # Default for obtaining tags from DockerHub
251
- def _get_dockerhub_tags(image_name, update_channel, proxies=None):
252
- # Find latest tag for each types
253
- rv = []
265
+ def _get_dockerhub_tags(image_name, update_channel, prefix, proxies=None, docker_registry=DEFAULT_DOCKER_REGISTRY,
266
+ logger: Logger=None):
267
+ """
268
+ Retrieve DockerHub tags for a specific image and update channel.
269
+ Args:
270
+ image_name (str): Full name of the image (including namespace).
271
+ update_channel (str): Specific channel to filter tags (e.g., stable, beta).
272
+ proxies (dict, optional): Proxy configuration.
273
+ docker_registry (str, optional): Docker registry URL. Defaults to DEFAULT_DOCKER_REGISTRY.
274
+ Returns:
275
+ list: A list of tag names that match the given criteria, or None if an error occurs.
276
+ """
254
277
  namespace, repository = image_name.split('/', 1)
255
- url = f"https://{DEFAULT_DOCKER_REGISTRY}/v2/namespaces/{namespace}/repositories/{repository}/tags" \
256
- f"?page_size=50&page=1&name={update_channel}&ordering=last_updated"
257
-
278
+ base_url = f"https://{docker_registry}/v2/namespaces/{namespace}/repositories/{repository}/tags"
279
+ query_params = {
280
+ 'page_size': 50,
281
+ 'page': 1,
282
+ 'name': update_channel,
283
+ 'ordering': 'last_updated'
284
+ }
285
+ url = f"{base_url}?{urlencode(query_params)}"
258
286
  while True:
259
- resp = requests.get(url, proxies=proxies)
260
- if resp.ok:
261
- resp_data = resp.json()
287
+ try:
288
+ response = requests.get(url, proxies=proxies)
289
+ response.raise_for_status() # Raises an HTTPError for bad responses
290
+ response_data = response.json()
291
+ tags = []
262
292
  if namespace == "cccs":
263
293
  # The tags are coming from a repository managed by the Assemblyline team
264
- for x in resp_data['results']:
265
- tag_name = x['name']
266
- if tag_name == f"{FRAMEWORK_VERSION}.{SYSTEM_VERSION}.{update_channel}":
294
+ for tag_info in response_data['results']:
295
+ tag_name = tag_info.get('name')
296
+ if tag_name and tag_name == f"{FRAMEWORK_VERSION}.{SYSTEM_VERSION}.{update_channel}":
267
297
  # Ignore tag aliases containing the update_channel
268
298
  continue
269
-
270
- if tag_name.startswith(f"{FRAMEWORK_VERSION}.{SYSTEM_VERSION}"):
299
+ if tag_name and tag_name.startswith(f"{FRAMEWORK_VERSION}.{SYSTEM_VERSION}"):
271
300
  # Because the order of paging is based on `last_updated`,
272
301
  # we can return the first valid candidate
302
+ logger.info(f"{prefix}Valid tag found for {repository}: {tag_name}")
273
303
  return [tag_name]
274
304
  else:
275
305
  # The tags are coming from another repository, so we're don't know the ordering of tags
276
- rv.extend([x['name'] for x in resp_data['results']])
277
- # Page until there are no results left
278
- url = resp_data.get('next', None)
279
- if url is None:
306
+ tags.extend(tag_info.get('name') for tag_info in response_data['results'] if tag_info.get('name'))
307
+ url = response_data.get('next')
308
+ if not url:
309
+ logger.info(f"{prefix}No more pages left to fetch.")
280
310
  break
281
- elif resp.status_code == 429:
282
- # Based on https://docs.docker.com/docker-hub/api/latest/#tag/rate-limiting
283
- # We've hit the rate limit so we have to wait and try again later
284
- time.sleep(int(resp.headers['retry-after']) - int(time.time()))
285
- else:
311
+ except requests.exceptions.HTTPError as e:
312
+ if response.status_code == 429:
313
+ # Based on https://docs.docker.com/docker-hub/api/latest/#tag/rate-limiting
314
+ # We've hit the rate limit so we have to wait and try again later
315
+ logger.warning(f"{prefix}Rate limit reached for DockerHub. Retrying after sleep..")
316
+ time.sleep(int(response.headers['retry-after']) - int(time.time()))
317
+ else:
318
+ logger.error(f"{prefix}HTTP error occurred: {e}")
286
319
  break
287
-
288
- return rv
320
+ except requests.exceptions.ConnectionError as e:
321
+ logger.error(f"{prefix}Connection error occurred: {e}")
322
+ break
323
+ except requests.exceptions.Timeout as e:
324
+ logger.error(f"{prefix}Request timed out.")
325
+ break
326
+ except requests.exceptions.RequestException as e:
327
+ logger.error(f"{prefix}An error occurred during requests: {e}")
328
+ break
329
+ except ValueError as e:
330
+ logger.error(f"{prefix}JSON decode error: {e}")
331
+ break
332
+ if tags:
333
+ logger.info(f"{prefix}Tags retrieved successfully: {tags}")
334
+ return tags
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: assemblyline-core
3
- Version: 4.5.0.34
3
+ Version: 4.5.0.36
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.0.34