pulpcore 3.83.2__py3-none-any.whl → 3.85.0__py3-none-any.whl

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 pulpcore might be problematic. Click here for more details.

Files changed (169) hide show
  1. pulp_certguard/app/__init__.py +1 -1
  2. pulp_certguard/app/models.py +7 -26
  3. pulp_certguard/app/serializers.py +0 -2
  4. pulp_certguard/rhsm/__init__.py +4 -0
  5. pulp_certguard/rhsm/rhsm_check_path.py +198 -0
  6. pulp_certguard/tests/unit/certdata.py +249 -0
  7. pulp_certguard/tests/unit/test_rhsm_check_path.py +213 -0
  8. pulp_file/app/__init__.py +1 -1
  9. pulp_file/app/migrations/0001_initial_squashed_0016_add_domain.py +0 -20
  10. pulp_file/app/migrations/0017_alter_filealternatecontentsource_alternatecontentsource_ptr_and_more.py +1 -1
  11. pulpcore/app/apps.py +2 -12
  12. pulpcore/app/entrypoint.py +22 -22
  13. pulpcore/app/migrations/0001_squashed_0090_char_to_text_field.py +0 -95
  14. pulpcore/app/migrations/0091_systemid.py +1 -1
  15. pulpcore/app/migrations/0134_task_insert_trigger.py +81 -0
  16. pulpcore/app/migrations/0135_task_pulp_task_resources_index.py +25 -0
  17. pulp_file/app/migrations/0006_delete_filefilesystemexporter.py → pulpcore/app/migrations/0136_delete_basedistribution.py +3 -3
  18. pulpcore/app/migrations/0137_appstatus.py +33 -0
  19. pulpcore/app/migrations/0138_vulnerabilityreport.py +33 -0
  20. pulpcore/app/models/__init__.py +4 -1
  21. pulpcore/app/models/publication.py +0 -41
  22. pulpcore/app/models/status.py +145 -0
  23. pulpcore/app/models/task.py +8 -0
  24. pulpcore/app/models/vulnerability_report.py +34 -0
  25. pulpcore/app/serializers/__init__.py +1 -0
  26. pulpcore/app/serializers/content.py +13 -1
  27. pulpcore/app/serializers/repository.py +8 -1
  28. pulpcore/app/serializers/vulnerability_report.py +27 -0
  29. pulpcore/app/settings.py +13 -38
  30. pulpcore/app/tasks/__init__.py +2 -0
  31. pulpcore/app/tasks/purge.py +8 -5
  32. pulpcore/app/tasks/vulnerability_report.py +159 -0
  33. pulpcore/app/viewsets/__init__.py +1 -0
  34. pulpcore/app/viewsets/vulnerability_report.py +20 -0
  35. pulpcore/constants.py +8 -0
  36. pulpcore/content/__init__.py +23 -22
  37. pulpcore/content/handler.py +5 -2
  38. pulpcore/migrations.py +38 -11
  39. pulpcore/openapi/__init__.py +8 -0
  40. pulpcore/plugin/models/__init__.py +2 -0
  41. pulpcore/plugin/serializers/__init__.py +2 -0
  42. pulpcore/plugin/tasking.py +2 -0
  43. pulpcore/plugin/viewsets/__init__.py +2 -0
  44. pulpcore/pytest_plugin.py +21 -21
  45. pulpcore/tasking/entrypoint.py +12 -2
  46. pulpcore/tasking/tasks.py +5 -30
  47. pulpcore/tasking/worker.py +115 -74
  48. pulpcore/tests/functional/api/test_auth.py +18 -3
  49. pulpcore/tests/functional/api/test_login.py +62 -32
  50. pulpcore/tests/functional/api/test_openapi_schema.py +32 -15
  51. pulpcore/tests/functional/api/using_plugin/test_checkpoint.py +23 -1
  52. pulpcore/tests/functional/api/using_plugin/test_proxy.py +1 -1
  53. pulpcore/tests/unit/content/test_heartbeat.py +11 -8
  54. pulpcore/tests/unit/test_vulnerability_report.py +74 -0
  55. {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/METADATA +13 -18
  56. {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/RECORD +60 -156
  57. pulp_certguard/app/utils.py +0 -28
  58. pulp_certguard/tests/unit/test_models.py +0 -9
  59. pulp_file/app/migrations/0001_initial.py +0 -59
  60. pulp_file/app/migrations/0002_file_related_names.py +0 -55
  61. pulp_file/app/migrations/0003_auto_20191014_1721.py +0 -18
  62. pulp_file/app/migrations/0004_filefilesystemexporter.py +0 -21
  63. pulp_file/app/migrations/0005_filerepository.py +0 -24
  64. pulp_file/app/migrations/0007_filefilesystemexporter.py +0 -25
  65. pulp_file/app/migrations/0008_add_manifest_field.py +0 -19
  66. pulp_file/app/migrations/0009_move_data_to_new_master_distribution_model.py +0 -77
  67. pulp_file/app/migrations/0010_auto_publish.py +0 -23
  68. pulp_file/app/migrations/0011_fix_auto_publish.py +0 -36
  69. pulp_file/app/migrations/0012_delete_filefilesystemexporter.py +0 -28
  70. pulp_file/app/migrations/0013_file_acs.py +0 -24
  71. pulp_file/app/migrations/0014_new_rbac_permissions.py +0 -33
  72. pulp_file/app/migrations/0015_allow_null_manifest.py +0 -23
  73. pulp_file/app/migrations/0016_add_domain.py +0 -25
  74. pulpcore/app/migrations/0001_initial.py +0 -451
  75. pulpcore/app/migrations/0002_increase_artifact_size_field.py +0 -18
  76. pulpcore/app/migrations/0003_remove_upload_completed.py +0 -17
  77. pulpcore/app/migrations/0004_add_duplicated_reserved_resources.py +0 -45
  78. pulpcore/app/migrations/0005_progressreport_code.py +0 -19
  79. pulpcore/app/migrations/0006_repository_plugin_managed.py +0 -18
  80. pulpcore/app/migrations/0007_delete_progress_proxies.py +0 -19
  81. pulpcore/app/migrations/0008_published_metadata_as_content.py +0 -44
  82. pulpcore/app/migrations/0009_remove_task_non_fatal_errors.py +0 -17
  83. pulpcore/app/migrations/0010_pulp_fields.py +0 -570
  84. pulpcore/app/migrations/0011_relative_path.py +0 -28
  85. pulpcore/app/migrations/0012_auto_20191104_2000.py +0 -31
  86. pulpcore/app/migrations/0013_repository_pulp_type.py +0 -18
  87. pulpcore/app/migrations/0014_remove_repository_plugin_managed.py +0 -17
  88. pulpcore/app/migrations/0015_auto_20191112_1426.py +0 -33
  89. pulpcore/app/migrations/0016_charfield_to_textfield.py +0 -68
  90. pulpcore/app/migrations/0017_remove_task_parent.py +0 -17
  91. pulpcore/app/migrations/0018_auto_20191127_2350.py +0 -20
  92. pulpcore/app/migrations/0019_add_signing_service_model.py +0 -27
  93. pulpcore/app/migrations/0020_change_publishedartifact_constraints.py +0 -17
  94. pulpcore/app/migrations/0021_add_signing_service_foreign_key.py +0 -24
  95. pulpcore/app/migrations/0022_rename_last_version.py +0 -27
  96. pulpcore/app/migrations/0023_change_exporter_models.py +0 -82
  97. pulpcore/app/migrations/0024_use_local_storage_for_uploads.py +0 -19
  98. pulpcore/app/migrations/0025_task_parent_task.py +0 -19
  99. pulpcore/app/migrations/0026_task_group.py +0 -32
  100. pulpcore/app/migrations/0027_export_backend.py +0 -31
  101. pulpcore/app/migrations/0028_import_importer_pulpimporter_pulpimporterrepository.py +0 -85
  102. pulpcore/app/migrations/0029_export_delete.py +0 -19
  103. pulpcore/app/migrations/0030_taskgroup_all_tasks_dispatched.py +0 -24
  104. pulpcore/app/migrations/0031_import_export_validate_params.py +0 -19
  105. pulpcore/app/migrations/0032_export_to_chunks.py +0 -27
  106. pulpcore/app/migrations/0033_increase_remote_artifact_size_field.py +0 -18
  107. pulpcore/app/migrations/0034_groupprogressreport.py +0 -32
  108. pulpcore/app/migrations/0035_content_upstream_id.py +0 -18
  109. pulpcore/app/migrations/0036_unprotect_last_export.py +0 -19
  110. pulpcore/app/migrations/0037_pulptemporaryfile.py +0 -28
  111. pulpcore/app/migrations/0038_repository_remote.py +0 -19
  112. pulpcore/app/migrations/0039_change_download_concurrency.py +0 -25
  113. pulpcore/app/migrations/0040_set_admin_is_staff.py +0 -28
  114. pulpcore/app/migrations/0041_accesspolicy.py +0 -29
  115. pulpcore/app/migrations/0042_rbac_for_tasks.py +0 -56
  116. pulpcore/app/migrations/0043_toc_attribute.py +0 -19
  117. pulpcore/app/migrations/0044_temp_file_artifact_field.py +0 -20
  118. pulpcore/app/migrations/0045_accesspolicy_permissions_allow_null.py +0 -19
  119. pulpcore/app/migrations/0046_task__resource_job_id.py +0 -35
  120. pulpcore/app/migrations/0047_improve_orphan_cleanup.py +0 -59
  121. pulpcore/app/migrations/0048_fips_checksums.py +0 -38
  122. pulpcore/app/migrations/0049_add_file_field_to_uploadchunk.py +0 -24
  123. pulpcore/app/migrations/0050_namespace_access_policies.py +0 -28
  124. pulpcore/app/migrations/0051_timeoutfields.py +0 -34
  125. pulpcore/app/migrations/0052_tasking_logging_cid.py +0 -18
  126. pulpcore/app/migrations/0053_remote_headers.py +0 -19
  127. pulpcore/app/migrations/0054_add_public_key.py +0 -104
  128. pulpcore/app/migrations/0055_label.py +0 -31
  129. pulpcore/app/migrations/0056_remote_rate_limit.py +0 -18
  130. pulpcore/app/migrations/0057_add_label_indexes.py +0 -23
  131. pulpcore/app/migrations/0058_accesspolicy_customized.py +0 -18
  132. pulpcore/app/migrations/0059_proxy_creds.py +0 -23
  133. pulpcore/app/migrations/0060_data_migration_proxy_creds.py +0 -45
  134. pulpcore/app/migrations/0061_call_handle_artifact_checksums_command.py +0 -87
  135. pulpcore/app/migrations/0062_add_new_distribution_mastermodel.py +0 -36
  136. pulpcore/app/migrations/0063_repository_retained_versions.py +0 -18
  137. pulpcore/app/migrations/0064_add_new_style_task_columns.py +0 -109
  138. pulpcore/app/migrations/0064_repository_user_hidden.py +0 -18
  139. pulpcore/app/migrations/0065_merge_20210615_1211.py +0 -14
  140. pulpcore/app/migrations/0066_download_concurrency_and_retry_changes.py +0 -24
  141. pulpcore/app/migrations/0067_add_protect_to_task_reservation.py +0 -19
  142. pulpcore/app/migrations/0068_add_timestamp_of_interest.py +0 -23
  143. pulpcore/app/migrations/0069_update_json_fields.py +0 -63
  144. pulpcore/app/migrations/0070_rename_retained_versions.py +0 -18
  145. pulpcore/app/migrations/0071_filesystemexport_filesystemexporter.py +0 -35
  146. pulpcore/app/migrations/0072_add_method_to_filesystem_exporter.py +0 -18
  147. pulpcore/app/migrations/0073_encrypt_remote_fields.py +0 -139
  148. pulpcore/app/migrations/0074_acs.py +0 -47
  149. pulpcore/app/migrations/0075_rbaccontentguard.py +0 -25
  150. pulpcore/app/migrations/0076_remove_reserved_resource.py +0 -39
  151. pulpcore/app/migrations/0077_move_remote_url_credentials.py +0 -41
  152. pulpcore/app/migrations/0078_grouprole_role_userrole.py +0 -70
  153. pulpcore/app/migrations/0079_rename_permissions_assignment_accesspolicy_creation_hooks.py +0 -18
  154. pulpcore/app/migrations/0080_proxy_group_model.py +0 -37
  155. pulpcore/app/migrations/0081_reapplabel_group_permissions.py +0 -59
  156. pulpcore/app/migrations/0082_add_manage_roles_permissions.py +0 -17
  157. pulpcore/app/migrations/0083_alter_group_options.py +0 -17
  158. pulpcore/app/migrations/0084_alter_rbaccontentguard_options.py +0 -17
  159. pulpcore/app/migrations/0085_contentredirectcontentguard.py +0 -26
  160. pulpcore/app/migrations/0086_task_json_fields.py +0 -77
  161. pulpcore/app/migrations/0087_taskschedule.py +0 -34
  162. pulpcore/app/migrations/0088_accesspolicy_queryset_scoping.py +0 -18
  163. pulpcore/app/migrations/0089_alter_contentredirectcontentguard_options.py +0 -17
  164. pulpcore/app/migrations/0090_char_to_text_field.py +0 -79
  165. pulpcore/tests/unit/migration/test_0077_move_remote_url_credentials.py +0 -35
  166. {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/WHEEL +0 -0
  167. {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/entry_points.txt +0 -0
  168. {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/licenses/LICENSE +0 -0
  169. {pulpcore-3.83.2.dist-info → pulpcore-3.85.0.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,33 @@
1
+ # Generated by Django 4.2.19 on 2025-08-07 00:07
2
+
3
+ from django.db import migrations, models
4
+ import django.db.models.deletion
5
+ import django_lifecycle.mixins
6
+ import pulpcore.app.models.base
7
+ import pulpcore.app.util
8
+
9
+
10
+ class Migration(migrations.Migration):
11
+
12
+ dependencies = [
13
+ ('core', '0137_appstatus'),
14
+ ]
15
+
16
+ operations = [
17
+ migrations.CreateModel(
18
+ name='VulnerabilityReport',
19
+ fields=[
20
+ ('pulp_id', models.UUIDField(default=pulpcore.app.models.base.pulp_uuid, editable=False, primary_key=True, serialize=False)),
21
+ ('pulp_created', models.DateTimeField(auto_now_add=True)),
22
+ ('pulp_last_updated', models.DateTimeField(auto_now=True, null=True)),
23
+ ('vulns', models.JSONField()),
24
+ ('content', models.OneToOneField(on_delete=django.db.models.deletion.CASCADE, to='core.content')),
25
+ ('pulp_domain', models.ForeignKey(default=pulpcore.app.util.get_domain_pk, on_delete=django.db.models.deletion.CASCADE, to='core.domain')),
26
+ ('repo_versions', models.ManyToManyField(blank=True, to='core.repositoryversion')),
27
+ ],
28
+ options={
29
+ 'default_related_name': '%(app_label)s_%(model_name)s',
30
+ },
31
+ bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
32
+ ),
33
+ ]
@@ -69,7 +69,7 @@ from .repository import (
69
69
  RepositoryVersionContentDetails,
70
70
  )
71
71
 
72
- from .status import ApiAppStatus, ContentAppStatus
72
+ from .status import AppStatus, ApiAppStatus, ContentAppStatus
73
73
 
74
74
  from .task import (
75
75
  CreatedResource,
@@ -87,6 +87,8 @@ from .upload import (
87
87
  UploadChunk,
88
88
  )
89
89
 
90
+ from .vulnerability_report import VulnerabilityReport
91
+
90
92
  # Moved here to avoid a circular import with Task
91
93
  from .progress import GroupProgressReport, ProgressReport
92
94
 
@@ -170,4 +172,5 @@ __all__ = [
170
172
  "OpenPGPSignature",
171
173
  "OpenPGPUserAttribute",
172
174
  "OpenPGPUserID",
175
+ "VulnerabilityReport",
173
176
  ]
@@ -576,47 +576,6 @@ class CompositeContentGuard(ContentGuard, AutoAddObjPermsMixin):
576
576
  )
577
577
 
578
578
 
579
- class BaseDistribution(MasterModel):
580
- """
581
- A distribution defines how a publication is distributed by the Content App.
582
-
583
- This abstract model can be used by plugin writers to create concrete distributions that are
584
- stored in separate tables from the Distributions provided by pulpcore.
585
-
586
- The `name` must be unique.
587
-
588
- The ``base_path`` must have no overlapping components. So if a Distribution with ``base_path``
589
- of ``a/path/foo`` existed, you could not make a second Distribution with a ``base_path`` of
590
- ``a/path`` or ``a`` because both are subpaths of ``a/path/foo``.
591
-
592
- Note:
593
- This class is no longer supported and cannot be removed from Pulp 3 due to possible
594
- problems with old migrations for plugins. Until the migrations are squashed, this class
595
- should be preserved.
596
-
597
- Fields:
598
- name (models.TextField): The name of the distribution. Examples: "rawhide" and "stable".
599
- base_path (models.TextField): The base (relative) path component of the published url.
600
-
601
- Relations:
602
- content_guard (models.ForeignKey): An optional content-guard.
603
- remote (models.ForeignKey): A remote that the content app can use to find content not
604
- yet stored in Pulp.
605
- """
606
-
607
- name = models.TextField(db_index=True, unique=True)
608
- base_path = models.TextField(unique=True)
609
-
610
- content_guard = models.ForeignKey(ContentGuard, null=True, on_delete=models.SET_NULL)
611
- remote = models.ForeignKey(Remote, null=True, on_delete=models.SET_NULL)
612
-
613
- def __init__(self, *args, **kwargs):
614
- raise Exception(
615
- "BaseDistribution is no longer supported. "
616
- "Please use pulpcore.plugin.models.Distribution instead."
617
- )
618
-
619
-
620
579
  class Distribution(MasterModel):
621
580
  """
622
581
  A Distribution defines how the Content App distributes a publication or repository_version.
@@ -6,7 +6,9 @@ from datetime import timedelta
6
6
 
7
7
  from django.conf import settings
8
8
  from django.contrib.postgres.fields import HStoreField
9
+ from django.contrib.postgres.functions import TransactionNow
9
10
  from django.db import models
11
+ from django.db.models import F, Value
10
12
  from django.utils import timezone
11
13
 
12
14
  from pulpcore.app.models import BaseModel
@@ -47,9 +49,138 @@ class AppStatusManager(models.Manager):
47
49
  return self.filter(last_heartbeat__lt=age_threshold)
48
50
 
49
51
 
52
+ class _AppStatusManager(AppStatusManager):
53
+ # This is an intermediate class in order to allow a ZDU.
54
+ # It should be removed from the chain with 3.87.
55
+ def create(self, app_type, **kwargs):
56
+ if app_type == "api":
57
+ old_obj = ApiAppStatus.objects.create(**kwargs)
58
+ elif app_type == "worker":
59
+ from pulpcore.app.models import Worker
60
+
61
+ old_obj = Worker.objects.create(**kwargs)
62
+ else:
63
+ raise NotImplementedError(f"Invalid app_type: {app_type}")
64
+ obj = super().create(app_type=app_type, **kwargs)
65
+ obj._old_status = old_obj
66
+ return obj
67
+
68
+ async def acreate(self, app_type, **kwargs):
69
+ if app_type == "content":
70
+ old_obj = await ContentAppStatus.objects.acreate(**kwargs)
71
+ else:
72
+ raise NotImplementedError(f"Invalid app_type: {app_type}")
73
+ obj = await super().acreate(app_type=app_type, **kwargs)
74
+ obj._old_status = old_obj
75
+ return obj
76
+
77
+ def online(self):
78
+ """
79
+ Returns a queryset of objects that are online.
80
+ """
81
+ return self.filter(last_heartbeat__gte=TransactionNow() - F("ttl"))
82
+
83
+ def missing(self):
84
+ """
85
+ Returns a queryset of workers that are missing.
86
+ """
87
+ return self.filter(last_heartbeat__lt=TransactionNow() - F("ttl"))
88
+
89
+ def older_than(self, age):
90
+ """
91
+ Returns a queryset of workers that are older than age.
92
+ """
93
+ return self.filter(last_heartbeat__lt=TransactionNow() - Value(age))
94
+
95
+
96
+ class AppStatus(BaseModel):
97
+ APP_TYPES = [
98
+ ("api", "api"),
99
+ ("content", "content"),
100
+ ("worker", "worker"),
101
+ ]
102
+ _APP_TTL = {
103
+ "api": settings.API_APP_TTL,
104
+ "content": settings.CONTENT_APP_TTL,
105
+ "worker": settings.WORKER_TTL,
106
+ }
107
+ objects = _AppStatusManager()
108
+
109
+ app_type = models.CharField(max_length=10, choices=APP_TYPES)
110
+ name = models.TextField(db_index=True, unique=True)
111
+ versions = HStoreField(default=dict)
112
+ ttl = models.DurationField(null=False)
113
+ last_heartbeat = models.DateTimeField(auto_now=True)
114
+
115
+ def __init__(self, *args, **kwargs):
116
+ super().__init__(*args, **kwargs)
117
+ self.ttl = timedelta(seconds=self._APP_TTL[self.app_type])
118
+ self._old_status = None
119
+
120
+ def delete(self, *args, **kwargs):
121
+ # adelete will call into this, so we should not replicate that one here.
122
+ if self._old_status is not None:
123
+ self._old_status.delete(*args, **kwargs)
124
+ super().delete(*args, **kwargs)
125
+
126
+ @property
127
+ def online(self) -> bool:
128
+ """
129
+ To be considered 'online', an app must have a timestamp more recent than ``self.ttl``.
130
+ """
131
+ age_threshold = timezone.now() - self.ttl
132
+ return self.last_heartbeat >= age_threshold
133
+
134
+ @property
135
+ def missing(self):
136
+ """
137
+ Whether an app can be considered 'missing'
138
+
139
+ To be considered 'missing', an App must have a timestamp older than ``self.ttl``.
140
+
141
+ Returns:
142
+ bool: True if the app is considered missing, otherwise False
143
+ """
144
+ return not self.online
145
+
146
+ def save_heartbeat(self):
147
+ """
148
+ Update the last_heartbeat field to now and save it.
149
+
150
+ Only the last_heartbeat field will be saved. No other changes will be saved.
151
+
152
+ Raises:
153
+ ValueError: When the model instance has never been saved before. This method can
154
+ only update an existing database record.
155
+ """
156
+ self._old_status.save_heartbeat()
157
+ self.save(update_fields=["last_heartbeat"])
158
+
159
+ async def asave_heartbeat(self):
160
+ """
161
+ Update the last_heartbeat field to now and save it.
162
+
163
+ Only the last_heartbeat field will be saved. No other changes will be saved.
164
+
165
+ Raises:
166
+ ValueError: When the model instance has never been saved before. This method can
167
+ only update an existing database record.
168
+ """
169
+ await self._old_status.asave_heartbeat()
170
+ await self.asave(update_fields=["last_heartbeat"])
171
+
172
+ @property
173
+ def current_task(self):
174
+ """
175
+ The task this worker is currently executing, if any.
176
+ """
177
+ return self.tasks.filter(state="running").first()
178
+
179
+
50
180
  class BaseAppStatus(BaseModel):
51
181
  """
52
182
  Represents an AppStatus.
183
+ Deprecated, to be removed with 3.87.
53
184
 
54
185
  This class is abstract. Subclasses must define `APP_TTL` as a `timedelta`.
55
186
 
@@ -103,6 +234,18 @@ class BaseAppStatus(BaseModel):
103
234
  """
104
235
  self.save(update_fields=["last_heartbeat"])
105
236
 
237
+ async def asave_heartbeat(self):
238
+ """
239
+ Update the last_heartbeat field to now and save it.
240
+
241
+ Only the last_heartbeat field will be saved. No other changes will be saved.
242
+
243
+ Raises:
244
+ ValueError: When the model instance has never been saved before. This method can
245
+ only update an existing database record.
246
+ """
247
+ await self.asave(update_fields=["last_heartbeat"])
248
+
106
249
  class Meta:
107
250
  abstract = True
108
251
 
@@ -110,6 +253,7 @@ class BaseAppStatus(BaseModel):
110
253
  class ApiAppStatus(BaseAppStatus):
111
254
  """
112
255
  Represents a Api App Status
256
+ Deprecated, to be removed with 3.87.
113
257
  """
114
258
 
115
259
  APP_TTL = timedelta(seconds=settings.API_APP_TTL)
@@ -118,6 +262,7 @@ class ApiAppStatus(BaseAppStatus):
118
262
  class ContentAppStatus(BaseAppStatus):
119
263
  """
120
264
  Represents a Content App Status
265
+ Deprecated, to be removed with 3.87.
121
266
  """
122
267
 
123
268
  APP_TTL = timedelta(seconds=settings.CONTENT_APP_TTL)
@@ -9,6 +9,7 @@ from gettext import gettext as _
9
9
 
10
10
  from django.conf import settings
11
11
  from django.contrib.postgres.fields import ArrayField, HStoreField
12
+ from django.contrib.postgres.indexes import GinIndex
12
13
  from django.core.serializers.json import DjangoJSONEncoder
13
14
  from django.db import connection, models
14
15
  from django.utils import timezone
@@ -32,6 +33,7 @@ _logger = logging.getLogger(__name__)
32
33
  class Worker(BaseAppStatus):
33
34
  """
34
35
  Represents a worker
36
+ Deprecated, to be removed with 3.87.
35
37
  """
36
38
 
37
39
  APP_TTL = timedelta(seconds=settings.WORKER_TTL)
@@ -348,6 +350,12 @@ class Task(BaseModel, AutoAddObjPermsMixin):
348
350
  models.Index(fields=["unblocked_at"]),
349
351
  models.Index(fields=["state"]),
350
352
  models.Index(fields=["state", "pulp_created"]),
353
+ GinIndex(
354
+ name="pulp_task_resources_index",
355
+ fields=["reserved_resources_record"],
356
+ condition=~models.Q(state__in=["completed", "failed", "canceled", "skipped"]),
357
+ opclasses=["array_ops"],
358
+ ),
351
359
  ]
352
360
  permissions = [
353
361
  ("manage_roles_task", "Can manage role assignments on task"),
@@ -0,0 +1,34 @@
1
+ from django.db import models
2
+
3
+ from pulpcore.app.models.base import BaseModel
4
+ from pulpcore.app.util import get_domain_pk
5
+
6
+
7
+ class VulnerabilityReport(BaseModel):
8
+ """
9
+ A model for storing vulnerability reports for Content/RepositoryVersion.
10
+
11
+ Fields:
12
+ vulns (models.JSONField): A JSON field containing the list of vulnerabilities found
13
+ in osv.dev.
14
+
15
+ Relations:
16
+ content (models.OneToOneField): The Content object that was scanned for vulnerabilities.
17
+ pulp_domain (models.ForeignKey): The Domain this vulnerability report belongs to,
18
+ providing multi-tenancy isolation.
19
+ repo_versions (models.ManyToManyField): The RepositoryVersion(s) where the scanned
20
+ Content appears. This allows tracking which repository versions contain
21
+ vulnerable content.
22
+ """
23
+
24
+ content = models.OneToOneField(
25
+ "Content",
26
+ on_delete=models.CASCADE,
27
+ unique=True,
28
+ )
29
+ vulns = models.JSONField()
30
+ pulp_domain = models.ForeignKey("Domain", default=get_domain_pk, on_delete=models.CASCADE)
31
+ repo_versions = models.ManyToManyField("RepositoryVersion", blank=True)
32
+
33
+ class Meta:
34
+ default_related_name = "%(app_label)s_%(model_name)s"
@@ -121,6 +121,7 @@ from .user import (
121
121
  UserSerializer,
122
122
  )
123
123
  from .replica import UpstreamPulpSerializer
124
+ from .vulnerability_report import VulnerabilityReportSerializer
124
125
  from .openpgp import (
125
126
  OpenPGPDistributionSerializer,
126
127
  OpenPGPKeyringSerializer,
@@ -5,7 +5,13 @@ from rest_framework import serializers
5
5
  from rest_framework.validators import UniqueValidator
6
6
 
7
7
  from pulpcore.app import models
8
- from pulpcore.app.serializers import base, fields, pulp_labels_validator, DetailRelatedField
8
+ from pulpcore.app.serializers import (
9
+ base,
10
+ fields,
11
+ pulp_labels_validator,
12
+ DetailRelatedField,
13
+ RelatedField,
14
+ )
9
15
  from pulpcore.app.util import get_domain
10
16
 
11
17
 
@@ -27,6 +33,11 @@ class NoArtifactContentSerializer(base.ModelSerializer):
27
33
  view_name_pattern=r"repositories(-.*/.*)-detail",
28
34
  queryset=models.Repository.objects.all(),
29
35
  )
36
+ vuln_report = RelatedField(
37
+ read_only=True,
38
+ view_name="vuln_report-detail",
39
+ source="core_vulnerabilityreport",
40
+ )
30
41
 
31
42
  def get_artifacts(self, validated_data):
32
43
  """
@@ -116,6 +127,7 @@ class NoArtifactContentSerializer(base.ModelSerializer):
116
127
  fields = base.ModelSerializer.Meta.fields + (
117
128
  "repository",
118
129
  "pulp_labels",
130
+ "vuln_report",
119
131
  )
120
132
 
121
133
 
@@ -7,7 +7,7 @@ from rest_framework import fields, serializers
7
7
  from rest_framework_nested.serializers import NestedHyperlinkedModelSerializer
8
8
 
9
9
  from pulpcore.app import models, settings
10
- from pulpcore.app.util import get_prn
10
+ from pulpcore.app.util import get_prn, reverse
11
11
  from pulpcore.app.serializers import (
12
12
  DetailIdentityField,
13
13
  DetailRelatedField,
@@ -490,6 +490,12 @@ class RepositoryVersionSerializer(ModelSerializer, NestedHyperlinkedModelSeriali
490
490
  source="*",
491
491
  read_only=True,
492
492
  )
493
+ vuln_report = serializers.SerializerMethodField(
494
+ read_only=True,
495
+ )
496
+
497
+ def get_vuln_report(self, object):
498
+ return f"{reverse('vuln_report-list')}?repository_version={get_prn(object)}"
493
499
 
494
500
  class Meta:
495
501
  model = models.RepositoryVersion
@@ -499,6 +505,7 @@ class RepositoryVersionSerializer(ModelSerializer, NestedHyperlinkedModelSeriali
499
505
  "repository",
500
506
  "base_version",
501
507
  "content_summary",
508
+ "vuln_report",
502
509
  )
503
510
 
504
511
 
@@ -0,0 +1,27 @@
1
+ from rest_framework import serializers
2
+
3
+ from pulpcore.app.models import VulnerabilityReport
4
+ from pulpcore.app.serializers import (
5
+ DetailRelatedField,
6
+ IdentityField,
7
+ ModelSerializer,
8
+ RepositoryVersionRelatedField,
9
+ )
10
+
11
+
12
+ class VulnerabilityReportSerializer(ModelSerializer):
13
+ """
14
+ A serializer for the VulnerabilityReport Model.
15
+ """
16
+
17
+ vulns = serializers.JSONField()
18
+ pulp_href = IdentityField(view_name="vuln_report-detail")
19
+ content = DetailRelatedField(
20
+ read_only=True,
21
+ view_name_pattern=r"content(-.*/.*)-detail",
22
+ )
23
+ repo_versions = RepositoryVersionRelatedField(many=True, required=False)
24
+
25
+ class Meta:
26
+ model = VulnerabilityReport
27
+ fields = ModelSerializer.Meta.fields + ("vulns", "repo_versions", "content")
pulpcore/app/settings.py CHANGED
@@ -12,12 +12,11 @@ import sys
12
12
 
13
13
  from contextlib import suppress
14
14
  from importlib import import_module
15
+ from importlib.metadata import entry_points
15
16
  from logging import getLogger
16
17
  from pathlib import Path
17
18
 
18
19
  from cryptography.fernet import Fernet
19
- from django.core.files.storage import storages
20
- from django.conf import global_settings
21
20
  from django.core.exceptions import ImproperlyConfigured
22
21
  from django.db import connection
23
22
  from dynaconf import DjangoDynaconf, Dynaconf, Validator
@@ -32,12 +31,6 @@ try:
32
31
  except ImportError:
33
32
  pass
34
33
 
35
- if sys.version_info < (3, 10):
36
- # Python 3.9 has a rather different interface for `entry_points`.
37
- # Let's use a compatibility version.
38
- from importlib_metadata import entry_points
39
- else:
40
- from importlib.metadata import entry_points
41
34
 
42
35
  # Load settings first pass before applying all the defaults to get a grip on ENABLED_PLUGINS.
43
36
  enabled_plugins_validator = Validator(
@@ -77,16 +70,7 @@ MEDIA_ROOT = str(DEPLOY_ROOT / "media") # Django 3.1 adds support for pathlib.P
77
70
  STATIC_URL = "/assets/"
78
71
  STATIC_ROOT = DEPLOY_ROOT / STATIC_URL.strip("/")
79
72
 
80
- # begin compatibility layer for DEFAULT_FILE_STORAGE
81
- # Remove on pulpcore=3.85 or pulpcore=4.0
82
-
83
- # - What is this?
84
- # We shouldn't use STORAGES or DEFAULT_FILE_STORAGE directly because those are
85
- # mutually exclusive by django, which constraints users to use whatever we use.
86
- # This is a hack/workaround to set Pulp's default while still enabling users to choose
87
- # the legacy or the new storage setting.
88
- _DEFAULT_FILE_STORAGE = "pulpcore.app.models.storage.FileSystem"
89
- _STORAGES = {
73
+ STORAGES = {
90
74
  "default": {
91
75
  "BACKEND": "pulpcore.app.models.storage.FileSystem",
92
76
  },
@@ -95,10 +79,6 @@ _STORAGES = {
95
79
  },
96
80
  }
97
81
 
98
- setattr(global_settings, "DEFAULT_FILE_STORAGE", _DEFAULT_FILE_STORAGE)
99
- setattr(global_settings, "STORAGES", _STORAGES)
100
- # end DEFAULT_FILE_STORAGE deprecation layer
101
-
102
82
  REDIRECT_TO_OBJECT_STORAGE = True
103
83
 
104
84
  WORKING_DIRECTORY = DEPLOY_ROOT / "tmp"
@@ -328,6 +308,7 @@ TASK_PROTECTION_TIME = 0
328
308
  TMPFILE_PROTECTION_TIME = 0
329
309
 
330
310
  REMOTE_USER_ENVIRON_NAME = "REMOTE_USER"
311
+ REMOTE_USER_OPENAPI_SECURITY_SCHEME = {"type": "mutualTLS"}
331
312
 
332
313
  AUTHENTICATION_JSON_HEADER = ""
333
314
  AUTHENTICATION_JSON_HEADER_JQ_FILTER = ""
@@ -347,6 +328,7 @@ CACHE_SETTINGS = {
347
328
  REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN = 5 * 60 # 5 minutes
348
329
 
349
330
  SPECTACULAR_SETTINGS = {
331
+ "OAS_VERSION": "3.1.1",
350
332
  "SERVE_URLCONF": ROOT_URLCONF,
351
333
  "DEFAULT_GENERATOR_CLASS": "pulpcore.openapi.PulpSchemaGenerator",
352
334
  "DEFAULT_SCHEMA_CLASS": "pulpcore.openapi.PulpAutoSchema",
@@ -424,6 +406,9 @@ KAFKA_SASL_PASSWORD = None
424
406
  # opentelemetry settings
425
407
  OTEL_ENABLED = False
426
408
 
409
+ # VulnerabilityReport settings
410
+ VULN_REPORT_TASK_LIMITER = 10
411
+
427
412
  # Replaces asyncio event loop with uvloop
428
413
  UVLOOP_ENABLED = False
429
414
 
@@ -432,19 +417,17 @@ UVLOOP_ENABLED = False
432
417
 
433
418
  # Validators
434
419
 
435
- storage_keys = ("STORAGES.default.BACKEND", "DEFAULT_FILE_STORAGE")
436
420
  storage_validator = (
437
421
  Validator("REDIRECT_TO_OBJECT_STORAGE", eq=False)
438
- | Validator(*storage_keys, eq="pulpcore.app.models.storage.FileSystem")
439
- | Validator(*storage_keys, eq="storages.backends.azure_storage.AzureStorage")
440
- | Validator(*storage_keys, eq="storages.backends.s3.S3Storage")
441
- | Validator(*storage_keys, eq="storages.backends.s3boto3.S3Boto3Storage")
442
- | Validator(*storage_keys, eq="storages.backends.gcloud.GoogleCloudStorage")
422
+ | Validator("STORAGES.default.BACKEND", eq="pulpcore.app.models.storage.FileSystem")
423
+ | Validator("STORAGES.default.BACKEND", eq="storages.backends.azure_storage.AzureStorage")
424
+ | Validator("STORAGES.default.BACKEND", eq="storages.backends.s3.S3Storage")
425
+ | Validator("STORAGES.default.BACKEND", eq="storages.backends.s3boto3.S3Boto3Storage")
426
+ | Validator("STORAGES.default.BACKEND", eq="storages.backends.gcloud.GoogleCloudStorage")
443
427
  )
444
428
  storage_validator.messages["combined"] = (
445
429
  "'REDIRECT_TO_OBJECT_STORAGE=True' is only supported with the local file, S3, GCP or Azure "
446
- "storage backend configured in STORAGES['default']['BACKEND'] "
447
- "(deprecated DEFAULT_FILE_STORAGE)."
430
+ "storage backend configured in STORAGES['default']['BACKEND']."
448
431
  )
449
432
 
450
433
  cache_enabled_validator = Validator("CACHE_ENABLED", eq=True)
@@ -551,14 +534,6 @@ settings = DjangoDynaconf(
551
534
  post_hooks=(otel_middleware_hook,),
552
535
  )
553
536
 
554
- # begin compatibility layer for DEFAULT_FILE_STORAGE
555
- # Remove on pulpcore=3.85 or pulpcore=4.0
556
-
557
- # Ensures the cached property storage.backends uses the the right value
558
- storages._backends = settings.STORAGES.copy()
559
- storages.backends
560
- # end compatibility layer
561
-
562
537
  _logger = getLogger(__name__)
563
538
 
564
539
 
@@ -25,3 +25,5 @@ from .replica import replicate_distributions
25
25
  from .repository import repair_all_artifacts
26
26
 
27
27
  from .analytics import post_analytics
28
+
29
+ from .vulnerability_report import check_content
@@ -82,17 +82,20 @@ def purge(finished_before=None, states=None, **kwargs):
82
82
  states (Optional[List[str]]): List of task-states we want to purge.
83
83
 
84
84
  """
85
+ scheduled = current_task.get().taskschedule_set.exists()
86
+
85
87
  if finished_before is None:
86
88
  assert settings.TASK_PROTECTION_TIME > 0
87
89
  finished_before = timezone.now() - timezone.timedelta(minutes=settings.TASK_PROTECTION_TIME)
88
90
  if states is None:
89
91
  states = TASK_FINAL_STATES
90
- domain = get_domain()
92
+
91
93
  # Tasks, prior to the specified date, in the specified state, owned by the current-user, in the
92
94
  # current domain
93
- candidate_qs = Task.objects.filter(
94
- finished_at__lt=finished_before, state__in=states, pulp_domain=domain
95
- )
95
+ candidate_qs = Task.objects.filter(finished_at__lt=finished_before, state__in=states)
96
+
97
+ if not scheduled:
98
+ candidate_qs = candidate_qs.filter(pulp_domain=get_domain())
96
99
  if "user_pk" in kwargs:
97
100
  if (user_pk := kwargs["user_pk"]) is not None:
98
101
  current_user = User.objects.get(pk=user_pk)
@@ -101,7 +104,7 @@ def purge(finished_before=None, states=None, **kwargs):
101
104
  # This is the old task signature (<= 3.74) without "user_pk".
102
105
  # Has this task not been dispatched from a task schedule? Then we assume there was a user
103
106
  # doing that.
104
- if not current_task.get().taskschedule_set.exists():
107
+ if not scheduled:
105
108
  current_user = get_current_authenticated_user()
106
109
  if current_user is None:
107
110
  raise RuntimeError(