pulpcore 3.77.1__py3-none-any.whl → 3.79.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.
- pulp_certguard/app/__init__.py +1 -1
- pulp_file/app/__init__.py +1 -1
- pulpcore/app/apps.py +1 -1
- pulpcore/app/entrypoint.py +1 -1
- pulpcore/app/management/commands/clean-up-progress-reports.py +52 -0
- pulpcore/app/management/commands/dump-publications-to-fs.py +1 -1
- pulpcore/app/models/task.py +70 -70
- pulpcore/app/openpgp.py +6 -6
- pulpcore/app/replica.py +1 -1
- pulpcore/app/serializers/base.py +1 -1
- pulpcore/app/serializers/user.py +65 -19
- pulpcore/app/settings.py +4 -3
- pulpcore/app/tasks/repository.py +3 -3
- pulpcore/app/util.py +4 -3
- pulpcore/content/__init__.py +8 -0
- pulpcore/content/entrypoint.py +7 -1
- pulpcore/content/handler.py +4 -3
- pulpcore/tasking/tasks.py +12 -1
- pulpcore/tasking/worker.py +1 -1
- pulpcore/tests/functional/api/using_plugin/test_content_delivery.py +7 -7
- pulpcore/tests/performance/test_performance.py +8 -10
- pulpcore/tests/unit/models/test_task.py +44 -0
- pulpcore/tests/unit/serializers/test_user.py +86 -0
- pulpcore/tests/unit/test_viewsets.py +1 -1
- {pulpcore-3.77.1.dist-info → pulpcore-3.79.0.dist-info}/METADATA +10 -8
- {pulpcore-3.77.1.dist-info → pulpcore-3.79.0.dist-info}/RECORD +30 -27
- {pulpcore-3.77.1.dist-info → pulpcore-3.79.0.dist-info}/WHEEL +1 -1
- {pulpcore-3.77.1.dist-info → pulpcore-3.79.0.dist-info}/entry_points.txt +0 -0
- {pulpcore-3.77.1.dist-info → pulpcore-3.79.0.dist-info}/licenses/LICENSE +0 -0
- {pulpcore-3.77.1.dist-info → pulpcore-3.79.0.dist-info}/top_level.txt +0 -0
pulp_certguard/app/__init__.py
CHANGED
pulp_file/app/__init__.py
CHANGED
pulpcore/app/apps.py
CHANGED
pulpcore/app/entrypoint.py
CHANGED
|
@@ -48,7 +48,7 @@ class PulpApiWorker(SyncWorker):
|
|
|
48
48
|
from pulpcore.app.models import ApiAppStatus
|
|
49
49
|
|
|
50
50
|
if settings.API_APP_TTL < 2 * self.timeout:
|
|
51
|
-
logger.
|
|
51
|
+
logger.warning(
|
|
52
52
|
"API_APP_TTL (%s) is smaller than double the gunicorn timeout (%s). "
|
|
53
53
|
"You may experience workers wrongly reporting as missing",
|
|
54
54
|
settings.API_APP_TTL,
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
from gettext import gettext as _
|
|
2
|
+
|
|
3
|
+
from django.core.management import BaseCommand
|
|
4
|
+
from django.db.models import F
|
|
5
|
+
|
|
6
|
+
from pulpcore.app.models import ProgressReport
|
|
7
|
+
from pulpcore.constants import TASK_STATES
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class Command(BaseCommand):
|
|
11
|
+
"""
|
|
12
|
+
Django management command for repairing progress-reports in inconsistent states.
|
|
13
|
+
"""
|
|
14
|
+
|
|
15
|
+
help = (
|
|
16
|
+
"Repairs issue #3609. Long-running tasks that utilize ProgressReports, which "
|
|
17
|
+
"fail or are cancelled, can leave their associated reports in state 'running'. "
|
|
18
|
+
"This script finds the ProgressReports marked as 'running', whose owning task "
|
|
19
|
+
"is in either 'cancelled or 'failed', and moves the state of the ProgressReport "
|
|
20
|
+
"to match that of the task."
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
def add_arguments(self, parser):
|
|
24
|
+
"""Set up arguments."""
|
|
25
|
+
parser.add_argument(
|
|
26
|
+
"--dry-run",
|
|
27
|
+
action="store_true",
|
|
28
|
+
help=_(
|
|
29
|
+
"Don't modify anything, just collect results on how many ProgressReports "
|
|
30
|
+
"are impacted."
|
|
31
|
+
),
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
def handle(self, *args, **options):
|
|
35
|
+
dry_run = options["dry_run"]
|
|
36
|
+
for state in [TASK_STATES.CANCELED, TASK_STATES.FAILED]:
|
|
37
|
+
if dry_run:
|
|
38
|
+
to_be_updated = ProgressReport.objects.filter(
|
|
39
|
+
task__state__ne=F("state"), state=TASK_STATES.RUNNING, task__state=state
|
|
40
|
+
).count()
|
|
41
|
+
print(
|
|
42
|
+
_("Number of ProgressReports in inconsistent state for {} tasks: {}").format(
|
|
43
|
+
state, to_be_updated
|
|
44
|
+
)
|
|
45
|
+
)
|
|
46
|
+
else:
|
|
47
|
+
updated = ProgressReport.objects.filter(
|
|
48
|
+
task__state__ne=F("state"), state=TASK_STATES.RUNNING, task__state=state
|
|
49
|
+
).update(state=state)
|
|
50
|
+
print(
|
|
51
|
+
_("Number of ProgressReports updated for {} tasks: {}").format(state, updated)
|
|
52
|
+
)
|
|
@@ -109,7 +109,7 @@ class Command(BaseCommand):
|
|
|
109
109
|
repo_path = os.path.join(options["dest"], distribution.base_path)
|
|
110
110
|
to_export.append((repo_path, publication))
|
|
111
111
|
except ObjectDoesNotExist:
|
|
112
|
-
logging.
|
|
112
|
+
logging.warning(
|
|
113
113
|
"No publication found for the repo published at '{}': skipping".format(
|
|
114
114
|
distribution.base_path
|
|
115
115
|
)
|
pulpcore/app/models/task.py
CHANGED
|
@@ -4,7 +4,6 @@ Django models related to the Tasking system
|
|
|
4
4
|
|
|
5
5
|
import logging
|
|
6
6
|
import traceback
|
|
7
|
-
from contextlib import suppress
|
|
8
7
|
from datetime import timedelta
|
|
9
8
|
from gettext import gettext as _
|
|
10
9
|
|
|
@@ -76,6 +75,21 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
76
75
|
"""
|
|
77
76
|
Represents a task
|
|
78
77
|
|
|
78
|
+
The Tasks state machine works like a finite automaton without loops:
|
|
79
|
+
The initial state is WAITING.
|
|
80
|
+
Final states are COMPLETED, FAILED and CANCELED.
|
|
81
|
+
The possible transitions are:
|
|
82
|
+
WAITING -> RUNNING
|
|
83
|
+
WAITING ->* CANCELING
|
|
84
|
+
RUNNING -> COMPLETED
|
|
85
|
+
RUNNING -> FAILED
|
|
86
|
+
RUNNING ->* CANCELING
|
|
87
|
+
CANCELING -> CANCELED
|
|
88
|
+
|
|
89
|
+
The transitions to CANCELING (marked with *) are the only ones allowed to happen without
|
|
90
|
+
holding the tasks advisory lock. Canceling is meant to be initiated asyncronously by a sparate
|
|
91
|
+
process before signalling the worker via Postgres LISTEN.
|
|
92
|
+
|
|
79
93
|
Fields:
|
|
80
94
|
|
|
81
95
|
state (models.TextField): The state of the task
|
|
@@ -180,24 +194,26 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
180
194
|
"""Set the "core.task_user_dispatcher" role for the current user after creation."""
|
|
181
195
|
self.add_roles_for_object_creator("core.task_user_dispatcher")
|
|
182
196
|
|
|
197
|
+
def _cleanup_progress_reports(self, state):
|
|
198
|
+
"""Find any running progress-reports and set their states to the specified end-state."""
|
|
199
|
+
self.progress_reports.filter(state=TASK_STATES.RUNNING).update(state=state)
|
|
200
|
+
|
|
183
201
|
def set_running(self):
|
|
184
202
|
"""
|
|
185
203
|
Set this Task to the running state, save it, and log output in warning cases.
|
|
186
204
|
|
|
187
205
|
This updates the :attr:`started_at` and sets the :attr:`state` to :attr:`RUNNING`.
|
|
188
206
|
"""
|
|
207
|
+
started_at = timezone.now()
|
|
189
208
|
rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.WAITING).update(
|
|
190
|
-
state=TASK_STATES.RUNNING,
|
|
209
|
+
state=TASK_STATES.RUNNING,
|
|
210
|
+
started_at=started_at,
|
|
191
211
|
)
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
del self.finished_at
|
|
198
|
-
with suppress(AttributeError):
|
|
199
|
-
del self.error
|
|
200
|
-
if rows != 1:
|
|
212
|
+
if rows == 1:
|
|
213
|
+
self.state = TASK_STATES.RUNNING
|
|
214
|
+
self.started_at = started_at
|
|
215
|
+
else:
|
|
216
|
+
self.refresh_from_db()
|
|
201
217
|
raise RuntimeError(
|
|
202
218
|
_("Attempt to set not waiting task {} to running from '{}'.").format(
|
|
203
219
|
self.pk, self.state
|
|
@@ -212,18 +228,16 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
212
228
|
"""
|
|
213
229
|
# Only set the state to finished if it's running. This is important for when the task has
|
|
214
230
|
# been canceled, so we don't move the task from canceled to finished.
|
|
231
|
+
finished_at = timezone.now()
|
|
215
232
|
rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.RUNNING).update(
|
|
216
|
-
state=TASK_STATES.COMPLETED,
|
|
233
|
+
state=TASK_STATES.COMPLETED,
|
|
234
|
+
finished_at=finished_at,
|
|
217
235
|
)
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
del self.finished_at
|
|
224
|
-
with suppress(AttributeError):
|
|
225
|
-
del self.error
|
|
226
|
-
if rows != 1:
|
|
236
|
+
if rows == 1:
|
|
237
|
+
self.state = TASK_STATES.COMPLETED
|
|
238
|
+
self.finished_at = finished_at
|
|
239
|
+
else:
|
|
240
|
+
self.refresh_from_db()
|
|
227
241
|
# If the user requested to cancel this task while the worker finished it, we leave it
|
|
228
242
|
# as it is, but accept this is not an error condition.
|
|
229
243
|
if self.state != TASK_STATES.CANCELING:
|
|
@@ -232,6 +246,7 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
232
246
|
self.pk, self.state
|
|
233
247
|
)
|
|
234
248
|
)
|
|
249
|
+
self._cleanup_progress_reports(TASK_STATES.COMPLETED)
|
|
235
250
|
|
|
236
251
|
def set_failed(self, exc, tb):
|
|
237
252
|
"""
|
|
@@ -244,26 +259,26 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
244
259
|
exc (Exception): The exception raised by the task.
|
|
245
260
|
tb (traceback): Traceback instance for the current exception.
|
|
246
261
|
"""
|
|
262
|
+
finished_at = timezone.now()
|
|
247
263
|
tb_str = "".join(traceback.format_tb(tb))
|
|
264
|
+
error = exception_to_dict(exc, tb_str)
|
|
248
265
|
rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.RUNNING).update(
|
|
249
266
|
state=TASK_STATES.FAILED,
|
|
250
|
-
finished_at=
|
|
251
|
-
error=
|
|
267
|
+
finished_at=finished_at,
|
|
268
|
+
error=error,
|
|
252
269
|
)
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
with suppress(AttributeError):
|
|
260
|
-
del self.error
|
|
261
|
-
if rows != 1:
|
|
270
|
+
if rows == 1:
|
|
271
|
+
self.state = TASK_STATES.FAILED
|
|
272
|
+
self.finished_at = finished_at
|
|
273
|
+
self.error = error
|
|
274
|
+
else:
|
|
275
|
+
self.refresh_from_db()
|
|
262
276
|
raise RuntimeError(
|
|
263
277
|
_("Attempt to set not running task {} to failed from '{}'.").format(
|
|
264
278
|
self.pk, self.state
|
|
265
279
|
)
|
|
266
280
|
)
|
|
281
|
+
self._cleanup_progress_reports(TASK_STATES.FAILED)
|
|
267
282
|
|
|
268
283
|
def set_canceling(self):
|
|
269
284
|
"""
|
|
@@ -274,15 +289,10 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
274
289
|
rows = Task.objects.filter(pk=self.pk, state__in=TASK_INCOMPLETE_STATES).update(
|
|
275
290
|
state=TASK_STATES.CANCELING,
|
|
276
291
|
)
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
with suppress(AttributeError):
|
|
282
|
-
del self.finished_at
|
|
283
|
-
with suppress(AttributeError):
|
|
284
|
-
del self.error
|
|
285
|
-
if rows != 1:
|
|
292
|
+
if rows == 1:
|
|
293
|
+
self.state = TASK_STATES.CANCELING
|
|
294
|
+
else:
|
|
295
|
+
self.refresh_from_db()
|
|
286
296
|
raise RuntimeError(
|
|
287
297
|
_("Attempt to set not incomplete task {} to canceling from '{}'.").format(
|
|
288
298
|
self.pk, self.state
|
|
@@ -295,50 +305,40 @@ class Task(BaseModel, AutoAddObjPermsMixin):
|
|
|
295
305
|
"""
|
|
296
306
|
# Make sure this function was called with a proper final state
|
|
297
307
|
assert final_state in [TASK_STATES.CANCELED, TASK_STATES.FAILED]
|
|
308
|
+
finished_at = timezone.now()
|
|
298
309
|
task_data = {}
|
|
299
310
|
if reason:
|
|
300
311
|
task_data["error"] = {"reason": reason}
|
|
301
312
|
rows = Task.objects.filter(pk=self.pk, state=TASK_STATES.CANCELING).update(
|
|
302
313
|
state=final_state,
|
|
303
|
-
finished_at=
|
|
314
|
+
finished_at=finished_at,
|
|
304
315
|
**task_data,
|
|
305
316
|
)
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
del self.error
|
|
314
|
-
if rows != 1:
|
|
317
|
+
if rows == 1:
|
|
318
|
+
self.state = final_state
|
|
319
|
+
self.finished_at = finished_at
|
|
320
|
+
if reason:
|
|
321
|
+
self.error = task_data["error"]
|
|
322
|
+
else:
|
|
323
|
+
self.refresh_from_db()
|
|
315
324
|
raise RuntimeError(
|
|
316
325
|
_("Attempt to set not canceling task {} to canceled from '{}'.").format(
|
|
317
326
|
self.pk, self.state
|
|
318
327
|
)
|
|
319
328
|
)
|
|
329
|
+
self._cleanup_progress_reports(final_state)
|
|
320
330
|
|
|
321
331
|
def unblock(self):
|
|
322
332
|
# This should be safe to be called without holding the lock.
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
if fields is not None:
|
|
333
|
-
fields = set(fields)
|
|
334
|
-
deferred_fields = {
|
|
335
|
-
field for field in self.get_deferred_fields() if not field.startswith("enc_")
|
|
336
|
-
}
|
|
337
|
-
# If any state related deferred field is going to be loaded
|
|
338
|
-
if fields.intersection(deferred_fields):
|
|
339
|
-
# then load all of them
|
|
340
|
-
fields = fields.union(deferred_fields)
|
|
341
|
-
super().refresh_from_db(using, fields, **kwargs)
|
|
333
|
+
unblocked_at = timezone.now()
|
|
334
|
+
rows = Task.objects.filter(pk=self.pk).update(unblocked_at=unblocked_at)
|
|
335
|
+
if rows == 1:
|
|
336
|
+
self.unblocked_at = unblocked_at
|
|
337
|
+
else:
|
|
338
|
+
self.refresh_from_db()
|
|
339
|
+
raise RuntimeError(
|
|
340
|
+
_("Falied to set task {} unblocked in state '{}'.").format(self.pk, self.state)
|
|
341
|
+
)
|
|
342
342
|
|
|
343
343
|
class Meta:
|
|
344
344
|
indexes = [
|
pulpcore/app/openpgp.py
CHANGED
|
@@ -289,15 +289,15 @@ def analyze_signature(data, pubkey, signed_packet_type, signed_packet):
|
|
|
289
289
|
if version == 4:
|
|
290
290
|
hash_payload = b"\x99" + len(signed_packet).to_bytes(2, "big") + signed_packet
|
|
291
291
|
else: # version == 5
|
|
292
|
-
hash_payload = b"\
|
|
292
|
+
hash_payload = b"\x9a" + len(signed_packet).to_bytes(4, "big") + signed_packet
|
|
293
293
|
elif signature_type in [0x10, 0x11, 0x12, 0x13, 0x16, 0x30]:
|
|
294
294
|
# 0x10 - 0x13 Certification of a user id or attribute
|
|
295
295
|
# 0x16 Attested Key Signature
|
|
296
296
|
# 0x30 Certification Revocation Signature
|
|
297
297
|
if signed_packet_type == 13:
|
|
298
|
-
hash_payload = b"\
|
|
298
|
+
hash_payload = b"\xb4" + len(signed_packet).to_bytes(4, "big") + signed_packet
|
|
299
299
|
elif signed_packet_type == 17:
|
|
300
|
-
hash_payload = b"\
|
|
300
|
+
hash_payload = b"\xd1" + len(signed_packet).to_bytes(4, "big") + signed_packet
|
|
301
301
|
else:
|
|
302
302
|
raise ValueError("Out of band user ID or attribute signature.")
|
|
303
303
|
elif signature_type in [0x1F, 0x20, 0x30]:
|
|
@@ -318,18 +318,18 @@ def analyze_signature(data, pubkey, signed_packet_type, signed_packet):
|
|
|
318
318
|
if version == 4:
|
|
319
319
|
h.update(b"\x99" + len(pubkey).to_bytes(2, "big") + pubkey)
|
|
320
320
|
else: # version == 5
|
|
321
|
-
h.update(b"\
|
|
321
|
+
h.update(b"\x9a" + len(pubkey).to_bytes(4, "big") + pubkey)
|
|
322
322
|
h.update(hash_payload)
|
|
323
323
|
if version == 4:
|
|
324
324
|
h.update(
|
|
325
325
|
data[: 6 + hashed_size]
|
|
326
|
-
+ b"\x04\
|
|
326
|
+
+ b"\x04\xff"
|
|
327
327
|
+ ((6 + hashed_size) % (1 << 32)).to_bytes(4, "big")
|
|
328
328
|
)
|
|
329
329
|
else: # version == 5
|
|
330
330
|
h.update(
|
|
331
331
|
data[: 6 + hashed_size]
|
|
332
|
-
+ b"\x05\
|
|
332
|
+
+ b"\x05\xff"
|
|
333
333
|
+ ((6 + hashed_size) % (1 << 64)).to_bytes(8, "big")
|
|
334
334
|
)
|
|
335
335
|
if not h.digest().startswith(canary):
|
pulpcore/app/replica.py
CHANGED
pulpcore/app/serializers/base.py
CHANGED
|
@@ -112,7 +112,7 @@ class _DetailFieldMixin(HrefPrnFieldMixin):
|
|
|
112
112
|
if view_name_pattern:
|
|
113
113
|
view_name = _MatchingRegexViewName(view_name_pattern)
|
|
114
114
|
else:
|
|
115
|
-
log.
|
|
115
|
+
log.warning(
|
|
116
116
|
_(
|
|
117
117
|
"Please provide either 'view_name' or 'view_name_pattern' for {} on {}."
|
|
118
118
|
).format(self.__class__.__name__, traceback.extract_stack()[-4][2])
|
pulpcore/app/serializers/user.py
CHANGED
|
@@ -24,8 +24,10 @@ from pulpcore.app.serializers import (
|
|
|
24
24
|
PRNField,
|
|
25
25
|
)
|
|
26
26
|
from pulpcore.app.util import (
|
|
27
|
-
get_viewset_for_model,
|
|
28
27
|
get_request_without_query_params,
|
|
28
|
+
get_url,
|
|
29
|
+
get_prn,
|
|
30
|
+
resolve_prn,
|
|
29
31
|
)
|
|
30
32
|
|
|
31
33
|
User = get_user_model()
|
|
@@ -60,27 +62,42 @@ class PermissionField(serializers.RelatedField):
|
|
|
60
62
|
class ContentObjectField(serializers.CharField):
|
|
61
63
|
"""Content object field"""
|
|
62
64
|
|
|
63
|
-
def to_representation(self,
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
65
|
+
def to_representation(self, value):
|
|
66
|
+
if value is None:
|
|
67
|
+
return None
|
|
68
|
+
else:
|
|
68
69
|
request = get_request_without_query_params(self.context)
|
|
70
|
+
return get_url(value, request=request)
|
|
69
71
|
|
|
70
|
-
|
|
71
|
-
return serializer.data.get("pulp_href")
|
|
72
|
-
|
|
73
|
-
def to_internal_value(self, data):
|
|
72
|
+
def to_internal_value(self, value):
|
|
74
73
|
# ... circular import ...
|
|
75
74
|
from pulpcore.app.viewsets.base import NamedModelViewSet
|
|
76
75
|
|
|
77
|
-
if
|
|
78
|
-
return
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
76
|
+
if value is None:
|
|
77
|
+
return None
|
|
78
|
+
else:
|
|
79
|
+
try:
|
|
80
|
+
obj = NamedModelViewSet.get_resource(value)
|
|
81
|
+
except serializers.ValidationError:
|
|
82
|
+
raise serializers.ValidationError(_("Invalid value: {}.").format(value))
|
|
83
|
+
return obj
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
class ContentObjectPRNField(serializers.CharField):
|
|
87
|
+
"""Content object PRN field"""
|
|
88
|
+
|
|
89
|
+
def to_representation(self, value):
|
|
90
|
+
if value is None:
|
|
91
|
+
return None
|
|
92
|
+
else:
|
|
93
|
+
return get_prn(value)
|
|
94
|
+
|
|
95
|
+
def to_internal_value(self, value):
|
|
96
|
+
if value is None:
|
|
97
|
+
return None
|
|
98
|
+
else:
|
|
99
|
+
model, pk = resolve_prn(value)
|
|
100
|
+
return model.objects.get(pk=pk)
|
|
84
101
|
|
|
85
102
|
|
|
86
103
|
class UserGroupSerializer(serializers.ModelSerializer):
|
|
@@ -247,6 +264,13 @@ class ValidateRoleMixin:
|
|
|
247
264
|
and checks if the user/group already has the role. Does not set any value
|
|
248
265
|
in data or return anything.
|
|
249
266
|
"""
|
|
267
|
+
if "content_object" not in data:
|
|
268
|
+
raise serializers.ValidationError(
|
|
269
|
+
_(
|
|
270
|
+
"Either 'content_object' or 'content_object_prn' needs to be specified."
|
|
271
|
+
" Use 'null' for global or domain level access."
|
|
272
|
+
)
|
|
273
|
+
)
|
|
250
274
|
natural_key_args = {
|
|
251
275
|
f"{role_type}_id": data[role_type].pk,
|
|
252
276
|
"role_id": data["role"].pk,
|
|
@@ -308,8 +332,18 @@ class UserRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedMo
|
|
|
308
332
|
"pulp_href of the object for which role permissions should be asserted. "
|
|
309
333
|
"If set to 'null', permissions will act on either domain or model-level."
|
|
310
334
|
),
|
|
311
|
-
source="*",
|
|
312
335
|
allow_null=True,
|
|
336
|
+
required=False,
|
|
337
|
+
)
|
|
338
|
+
|
|
339
|
+
content_object_prn = ContentObjectPRNField(
|
|
340
|
+
help_text=_(
|
|
341
|
+
"prn of the object for which role permissions should be asserted. "
|
|
342
|
+
"If set to 'null', permissions will act on either domain or model-level."
|
|
343
|
+
),
|
|
344
|
+
source="content_object",
|
|
345
|
+
allow_null=True,
|
|
346
|
+
required=False,
|
|
313
347
|
)
|
|
314
348
|
|
|
315
349
|
domain = RelatedField(
|
|
@@ -343,6 +377,7 @@ class UserRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedMo
|
|
|
343
377
|
fields = ModelSerializer.Meta.fields + (
|
|
344
378
|
"role",
|
|
345
379
|
"content_object",
|
|
380
|
+
"content_object_prn",
|
|
346
381
|
"description",
|
|
347
382
|
"permissions",
|
|
348
383
|
"domain",
|
|
@@ -366,8 +401,18 @@ class GroupRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedM
|
|
|
366
401
|
"pulp_href of the object for which role permissions should be asserted. "
|
|
367
402
|
"If set to 'null', permissions will act on the model-level."
|
|
368
403
|
),
|
|
369
|
-
source="*",
|
|
370
404
|
allow_null=True,
|
|
405
|
+
required=False,
|
|
406
|
+
)
|
|
407
|
+
|
|
408
|
+
content_object_prn = ContentObjectPRNField(
|
|
409
|
+
help_text=_(
|
|
410
|
+
"prn of the object for which role permissions should be asserted. "
|
|
411
|
+
"If set to 'null', permissions will act on either domain or model-level."
|
|
412
|
+
),
|
|
413
|
+
source="content_object",
|
|
414
|
+
allow_null=True,
|
|
415
|
+
required=False,
|
|
371
416
|
)
|
|
372
417
|
|
|
373
418
|
domain = RelatedField(
|
|
@@ -403,6 +448,7 @@ class GroupRoleSerializer(ValidateRoleMixin, ModelSerializer, NestedHyperlinkedM
|
|
|
403
448
|
fields = ModelSerializer.Meta.fields + (
|
|
404
449
|
"role",
|
|
405
450
|
"content_object",
|
|
451
|
+
"content_object_prn",
|
|
406
452
|
"description",
|
|
407
453
|
"permissions",
|
|
408
454
|
"domain",
|
pulpcore/app/settings.py
CHANGED
|
@@ -342,9 +342,7 @@ CACHE_SETTINGS = {
|
|
|
342
342
|
"EXPIRES_TTL": 600, # 10 minutes
|
|
343
343
|
}
|
|
344
344
|
|
|
345
|
-
# The time a RemoteArtifact will be ignored after failure.
|
|
346
|
-
# In on-demand, if a fetching content from a remote failed due to corrupt data,
|
|
347
|
-
# the corresponding RemoteArtifact will be ignored for that time (seconds).
|
|
345
|
+
# The time in seconds a RemoteArtifact will be ignored after failure.
|
|
348
346
|
REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN = 5 * 60 # 5 minutes
|
|
349
347
|
|
|
350
348
|
SPECTACULAR_SETTINGS = {
|
|
@@ -425,6 +423,9 @@ KAFKA_SASL_PASSWORD = None
|
|
|
425
423
|
# opentelemetry settings
|
|
426
424
|
OTEL_ENABLED = False
|
|
427
425
|
|
|
426
|
+
# Replaces asyncio event loop with uvloop
|
|
427
|
+
UVLOOP_ENABLED = False
|
|
428
|
+
|
|
428
429
|
# HERE STARTS DYNACONF EXTENSION LOAD (Keep at the very bottom of settings.py)
|
|
429
430
|
# Read more at https://www.dynaconf.com/django/
|
|
430
431
|
|
pulpcore/app/tasks/repository.py
CHANGED
|
@@ -79,7 +79,7 @@ async def _repair_ca(content_artifact, repaired=None):
|
|
|
79
79
|
try:
|
|
80
80
|
dl_result = await downloader.run()
|
|
81
81
|
except Exception as e:
|
|
82
|
-
log.
|
|
82
|
+
log.warning(_("Redownload failed from '{}': {}.").format(remote_artifact.url, str(e)))
|
|
83
83
|
else:
|
|
84
84
|
if dl_result.artifact_attributes["sha256"] == content_artifact.artifact.sha256:
|
|
85
85
|
with open(dl_result.path, "rb") as src:
|
|
@@ -138,7 +138,7 @@ async def _repair_artifacts_for_content(subset=None, verify_checksums=True):
|
|
|
138
138
|
valid = await loop.run_in_executor(None, storage.exists, artifact.file.name)
|
|
139
139
|
if not valid:
|
|
140
140
|
await missing.aincrement()
|
|
141
|
-
log.
|
|
141
|
+
log.warning(_("Missing file for {}").format(artifact))
|
|
142
142
|
elif verify_checksums:
|
|
143
143
|
# default ThreadPoolExecutor uses num cores x 5 threads. Since we're doing
|
|
144
144
|
# such long and sequential reads, using too many threads might hurt more
|
|
@@ -151,7 +151,7 @@ async def _repair_artifacts_for_content(subset=None, verify_checksums=True):
|
|
|
151
151
|
)
|
|
152
152
|
if not valid:
|
|
153
153
|
await corrupted.aincrement()
|
|
154
|
-
log.
|
|
154
|
+
log.warning(_("Digest mismatch for {}").format(artifact))
|
|
155
155
|
|
|
156
156
|
if not valid:
|
|
157
157
|
if len(pending) >= 5: # Limit the number of concurrent repair tasks
|
pulpcore/app/util.py
CHANGED
|
@@ -66,18 +66,19 @@ def get_url(model, domain=None, request=None):
|
|
|
66
66
|
str: The path component of the resource url
|
|
67
67
|
"""
|
|
68
68
|
kwargs = {}
|
|
69
|
-
view_action = "list"
|
|
70
69
|
if settings.DOMAIN_ENABLED:
|
|
71
70
|
kwargs["pulp_domain"] = "default"
|
|
72
|
-
if not domain and hasattr(model, "pulp_domain") and isinstance(model,
|
|
71
|
+
if not domain and hasattr(model, "pulp_domain") and isinstance(model, Model):
|
|
73
72
|
kwargs["pulp_domain"] = model.pulp_domain.name
|
|
74
73
|
elif isinstance(domain, models.Domain):
|
|
75
74
|
kwargs["pulp_domain"] = domain.name
|
|
76
75
|
elif isinstance(domain, str):
|
|
77
76
|
kwargs["pulp_domain"] = domain
|
|
78
|
-
if isinstance(model,
|
|
77
|
+
if isinstance(model, Model):
|
|
79
78
|
view_action = "detail"
|
|
80
79
|
kwargs["pk"] = model.pk
|
|
80
|
+
else:
|
|
81
|
+
view_action = "list"
|
|
81
82
|
|
|
82
83
|
return reverse(get_view_name_for_model(model, view_action), kwargs=kwargs, request=request)
|
|
83
84
|
|
pulpcore/content/__init__.py
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import asyncio
|
|
2
2
|
from contextlib import suppress
|
|
3
3
|
from importlib import import_module
|
|
4
|
+
from importlib.util import find_spec
|
|
4
5
|
import logging
|
|
5
6
|
import os
|
|
6
7
|
|
|
@@ -35,6 +36,13 @@ if settings.OTEL_ENABLED:
|
|
|
35
36
|
else:
|
|
36
37
|
app = web.Application(middlewares=[guid, authenticate])
|
|
37
38
|
|
|
39
|
+
|
|
40
|
+
if settings.UVLOOP_ENABLED:
|
|
41
|
+
if not find_spec("uvloop"):
|
|
42
|
+
raise RuntimeError("The library 'uvloop' must be installed if UVLOOP_ENABLED is true.")
|
|
43
|
+
log.info("Using uvloop as the asyncio event loop.")
|
|
44
|
+
|
|
45
|
+
|
|
38
46
|
CONTENT_MODULE_NAME = "content"
|
|
39
47
|
|
|
40
48
|
|
pulpcore/content/entrypoint.py
CHANGED
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import click
|
|
2
2
|
from pulpcore.app.pulpcore_gunicorn_application import PulpcoreGunicornApplication
|
|
3
|
+
from django.conf import settings
|
|
3
4
|
|
|
4
5
|
|
|
5
6
|
class PulpcoreContentApplication(PulpcoreGunicornApplication):
|
|
6
7
|
def load_app_specific_config(self):
|
|
8
|
+
worker_class = (
|
|
9
|
+
"aiohttp.GunicornUVLoopWebWorker"
|
|
10
|
+
if settings.UVLOOP_ENABLED
|
|
11
|
+
else "aiohttp.GunicornWebWorker"
|
|
12
|
+
)
|
|
7
13
|
self.set_option("default_proc_name", "pulpcore-content", enforced=True)
|
|
8
|
-
self.set_option("worker_class",
|
|
14
|
+
self.set_option("worker_class", worker_class, enforced=True)
|
|
9
15
|
|
|
10
16
|
def load(self):
|
|
11
17
|
import pulpcore.content
|
pulpcore/content/handler.py
CHANGED
|
@@ -1314,10 +1314,9 @@ class Handler:
|
|
|
1314
1314
|
except DigestValidationError:
|
|
1315
1315
|
remote_artifact.failed_at = timezone.now()
|
|
1316
1316
|
await remote_artifact.asave()
|
|
1317
|
-
await downloader.session.close()
|
|
1318
1317
|
close_tcp_connection(request.transport._sock)
|
|
1319
1318
|
REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN = settings.REMOTE_CONTENT_FETCH_FAILURE_COOLDOWN
|
|
1320
|
-
|
|
1319
|
+
log.error(
|
|
1321
1320
|
f"Pulp tried streaming {remote_artifact.url!r} to "
|
|
1322
1321
|
"the client, but it failed checksum validation.\n\n"
|
|
1323
1322
|
"We can't recover from wrong data already sent so we are:\n"
|
|
@@ -1327,8 +1326,10 @@ class Handler:
|
|
|
1327
1326
|
"If the Remote is known to be fixed, try resyncing the associated repository.\n"
|
|
1328
1327
|
"If the Remote is known to be permanently corrupted, try removing "
|
|
1329
1328
|
"affected Pulp Remote, adding a good one and resyncing.\n"
|
|
1330
|
-
"
|
|
1329
|
+
"Learn more on <https://pulpproject.org/pulpcore/docs/user/learn/"
|
|
1330
|
+
"on-demand-downloading/#on-demand-and-streamed-limitations>"
|
|
1331
1331
|
)
|
|
1332
|
+
return response
|
|
1332
1333
|
|
|
1333
1334
|
if save_artifact and remote.policy != Remote.STREAMED:
|
|
1334
1335
|
content_artifacts = await asyncio.shield(
|
pulpcore/tasking/tasks.py
CHANGED
|
@@ -127,7 +127,18 @@ def _execute_task(task):
|
|
|
127
127
|
send_task_notification(task)
|
|
128
128
|
else:
|
|
129
129
|
task.set_completed()
|
|
130
|
-
|
|
130
|
+
execution_time = task.finished_at - task.started_at
|
|
131
|
+
execution_time_us = int(execution_time.total_seconds() * 1_000_000) # μs
|
|
132
|
+
_logger.info(
|
|
133
|
+
"Task completed %s in domain:"
|
|
134
|
+
" %s, task_type: %s, immediate: %s, deferred: %s, execution_time: %s μs",
|
|
135
|
+
task.pk,
|
|
136
|
+
domain.name,
|
|
137
|
+
task.name,
|
|
138
|
+
str(task.immediate),
|
|
139
|
+
str(task.deferred),
|
|
140
|
+
execution_time_us,
|
|
141
|
+
)
|
|
131
142
|
send_task_notification(task)
|
|
132
143
|
|
|
133
144
|
|
pulpcore/tasking/worker.py
CHANGED
|
@@ -309,7 +309,7 @@ class PulpcoreWorker:
|
|
|
309
309
|
# But during at least one specific upgrade this situation can emerge.
|
|
310
310
|
# In this case, we can assume that the old algorithm was employed to identify the
|
|
311
311
|
# task as unblocked, and we just rectify the situation here.
|
|
312
|
-
_logger.
|
|
312
|
+
_logger.warning(
|
|
313
313
|
"Running task %s was not previously marked unblocked. Fixing.", task.pk
|
|
314
314
|
)
|
|
315
315
|
task.unblock()
|
|
@@ -117,6 +117,7 @@ def test_remote_content_changed_with_on_demand(
|
|
|
117
117
|
file_bindings,
|
|
118
118
|
monitor_task,
|
|
119
119
|
file_distribution_factory,
|
|
120
|
+
tmp_path,
|
|
120
121
|
):
|
|
121
122
|
"""
|
|
122
123
|
GIVEN a remote synced on demand with fileA (e.g, digest=123),
|
|
@@ -124,6 +125,7 @@ def test_remote_content_changed_with_on_demand(
|
|
|
124
125
|
|
|
125
126
|
WHEN the client first requests that content
|
|
126
127
|
THEN the content app will start a response but close the connection before finishing
|
|
128
|
+
AND no file will be present in the filesystem
|
|
127
129
|
|
|
128
130
|
WHEN the client requests that content again (within the RA cooldown interval)
|
|
129
131
|
THEN the content app will return a 404
|
|
@@ -143,9 +145,12 @@ def test_remote_content_changed_with_on_demand(
|
|
|
143
145
|
get_url = urljoin(distribution_base_url(distribution.base_url), expected_file_list[0][0])
|
|
144
146
|
|
|
145
147
|
# WHEN (first request)
|
|
146
|
-
|
|
148
|
+
output_file = tmp_path / "out.rpm"
|
|
149
|
+
cmd = ["curl", "-v", get_url, "-o", str(output_file)]
|
|
150
|
+
result = subprocess.run(cmd, stdout=subprocess.PIPE, stderr=subprocess.PIPE)
|
|
147
151
|
|
|
148
152
|
# THEN
|
|
153
|
+
assert not output_file.exists()
|
|
149
154
|
assert result.returncode == 18
|
|
150
155
|
assert b"* Closing connection 0" in result.stderr
|
|
151
156
|
assert b"curl: (18) transfer closed with outstanding read data remaining" in result.stderr
|
|
@@ -211,10 +216,6 @@ def test_handling_remote_artifact_on_demand_streaming_failure(
|
|
|
211
216
|
distribution = file_distribution_factory(repository=repo.pulp_href)
|
|
212
217
|
return distribution
|
|
213
218
|
|
|
214
|
-
def refresh_acs(acs):
|
|
215
|
-
monitor_task_group(file_bindings.AcsFileApi.refresh(acs.pulp_href).task_group)
|
|
216
|
-
return acs
|
|
217
|
-
|
|
218
219
|
def get_original_content_info(remote):
|
|
219
220
|
expected_files = get_files_in_manifest(remote.url)
|
|
220
221
|
content_unit = list(expected_files)[0]
|
|
@@ -231,8 +232,7 @@ def test_handling_remote_artifact_on_demand_streaming_failure(
|
|
|
231
232
|
acs_manifest_path = write_3_iso_file_fixture_data_factory("acs", seed=123)
|
|
232
233
|
remote = create_simple_remote(basic_manifest_path)
|
|
233
234
|
distribution = sync_publish_and_distribute(remote)
|
|
234
|
-
|
|
235
|
-
refresh_acs(acs)
|
|
235
|
+
create_acs_remote(acs_manifest_path)
|
|
236
236
|
write_3_iso_file_fixture_data_factory("acs", overwrite=True) # corrupt
|
|
237
237
|
|
|
238
238
|
# WHEN/THEN (first request)
|
|
@@ -9,8 +9,6 @@ from collections import namedtuple
|
|
|
9
9
|
from urllib.parse import urljoin
|
|
10
10
|
from uuid import uuid4
|
|
11
11
|
|
|
12
|
-
from .pulpperf import reporting
|
|
13
|
-
|
|
14
12
|
Args = namedtuple("Arguments", "limit processes repositories")
|
|
15
13
|
|
|
16
14
|
|
|
@@ -92,7 +90,7 @@ def test_performance(
|
|
|
92
90
|
responses.append(response)
|
|
93
91
|
|
|
94
92
|
results = [monitor_task(response.task) for response in responses]
|
|
95
|
-
|
|
93
|
+
report_tasks_stats("Sync tasks", results)
|
|
96
94
|
|
|
97
95
|
"""Measure time of resynchronization."""
|
|
98
96
|
responses = []
|
|
@@ -102,7 +100,7 @@ def test_performance(
|
|
|
102
100
|
responses.append(response)
|
|
103
101
|
|
|
104
102
|
results = [monitor_task(response.task) for response in responses]
|
|
105
|
-
|
|
103
|
+
report_tasks_stats("Resync tasks", results)
|
|
106
104
|
|
|
107
105
|
"""Measure time of repository publishing."""
|
|
108
106
|
responses = []
|
|
@@ -111,7 +109,7 @@ def test_performance(
|
|
|
111
109
|
responses.append(response)
|
|
112
110
|
|
|
113
111
|
results = [monitor_task(response.task) for response in responses]
|
|
114
|
-
|
|
112
|
+
report_tasks_stats("Publication tasks", results)
|
|
115
113
|
|
|
116
114
|
for i in range(len(results)):
|
|
117
115
|
data[i]["publication_href"] = results[i].created_resources[0]
|
|
@@ -132,7 +130,7 @@ def test_performance(
|
|
|
132
130
|
responses.append(response)
|
|
133
131
|
|
|
134
132
|
results = [monitor_task(response.task) for response in responses]
|
|
135
|
-
|
|
133
|
+
report_tasks_stats("Distribution tasks", results)
|
|
136
134
|
|
|
137
135
|
for i in range(len(results)):
|
|
138
136
|
data[i]["distribution_href"] = results[i].created_resources[0]
|
|
@@ -152,7 +150,7 @@ def test_performance(
|
|
|
152
150
|
pool.starmap(download, params)
|
|
153
151
|
|
|
154
152
|
after = datetime.datetime.utcnow()
|
|
155
|
-
|
|
153
|
+
print_fmt_experiment_time("Repository download", before, after)
|
|
156
154
|
|
|
157
155
|
"""Measure time of inspecting the repository content."""
|
|
158
156
|
before = datetime.datetime.utcnow()
|
|
@@ -172,7 +170,7 @@ def test_performance(
|
|
|
172
170
|
with multiprocessing.Pool(processes=args.processes) as pool:
|
|
173
171
|
pool.starmap(measureit, params)
|
|
174
172
|
after = datetime.datetime.utcnow()
|
|
175
|
-
|
|
173
|
+
print_fmt_experiment_time("Content inspection", before, after)
|
|
176
174
|
|
|
177
175
|
"""Measure time of repository cloning."""
|
|
178
176
|
for r in data:
|
|
@@ -192,7 +190,7 @@ def test_performance(
|
|
|
192
190
|
responses.append(response)
|
|
193
191
|
|
|
194
192
|
results = [monitor_task(response.task) for response in responses]
|
|
195
|
-
|
|
193
|
+
report_tasks_stats("Version clone with base_version tasks", results)
|
|
196
194
|
|
|
197
195
|
hrefs = [
|
|
198
196
|
i["pulp_href"]
|
|
@@ -206,7 +204,7 @@ def test_performance(
|
|
|
206
204
|
responses.append(response)
|
|
207
205
|
|
|
208
206
|
results = [monitor_task(response.task) for response in responses]
|
|
209
|
-
|
|
207
|
+
report_tasks_stats("Version clone with add_content_units tasks", results)
|
|
210
208
|
|
|
211
209
|
|
|
212
210
|
def download(base_url, file_name, file_size):
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
import sys
|
|
3
|
+
from pulpcore.app.models import Task, ProgressReport
|
|
4
|
+
from pulpcore.constants import TASK_STATES
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
@pytest.mark.parametrize(
|
|
8
|
+
"to_state,use_canceled",
|
|
9
|
+
[
|
|
10
|
+
(TASK_STATES.FAILED, False),
|
|
11
|
+
(TASK_STATES.CANCELED, False),
|
|
12
|
+
(TASK_STATES.CANCELED, True),
|
|
13
|
+
],
|
|
14
|
+
)
|
|
15
|
+
@pytest.mark.django_db
|
|
16
|
+
def test_report_state_changes(to_state, use_canceled):
|
|
17
|
+
task = Task.objects.create(name="test", state=TASK_STATES.RUNNING)
|
|
18
|
+
reports = {}
|
|
19
|
+
for state in vars(TASK_STATES):
|
|
20
|
+
report = ProgressReport(message="test", code="test", state=state, task=task)
|
|
21
|
+
report.save()
|
|
22
|
+
reports[state] = report
|
|
23
|
+
|
|
24
|
+
if TASK_STATES.FAILED == to_state:
|
|
25
|
+
# Two ways to fail a task - set_failed and set_canceled("failed")
|
|
26
|
+
if use_canceled:
|
|
27
|
+
task.set_cancelling()
|
|
28
|
+
task.set_canceled(TASK_STATES.FAILED)
|
|
29
|
+
else:
|
|
30
|
+
try:
|
|
31
|
+
raise ValueError("test")
|
|
32
|
+
except ValueError:
|
|
33
|
+
exc_type, exc, tb = sys.exc_info()
|
|
34
|
+
task.set_failed(exc, tb)
|
|
35
|
+
elif TASK_STATES.CANCELED == to_state:
|
|
36
|
+
task.set_canceling()
|
|
37
|
+
task.set_canceled()
|
|
38
|
+
|
|
39
|
+
for state in vars(TASK_STATES):
|
|
40
|
+
report = ProgressReport.objects.get(pk=reports[state].pulp_id)
|
|
41
|
+
if TASK_STATES.RUNNING == state: # report *was* running, should be changed
|
|
42
|
+
assert to_state == report.state
|
|
43
|
+
else:
|
|
44
|
+
assert state == report.state
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import pytest
|
|
2
|
+
from unittest.mock import Mock
|
|
3
|
+
|
|
4
|
+
from django.contrib.auth import get_user_model
|
|
5
|
+
from django.contrib.auth.models import Group
|
|
6
|
+
from rest_framework.exceptions import ValidationError
|
|
7
|
+
|
|
8
|
+
from pulpcore.app.util import get_url, get_prn
|
|
9
|
+
from pulpcore.app.serializers import UserRoleSerializer, GroupRoleSerializer
|
|
10
|
+
from pulp_file.app.models import FileRepository
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
pytestmark = [pytest.mark.django_db]
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@pytest.fixture(params=[UserRoleSerializer, GroupRoleSerializer])
|
|
17
|
+
def serializer_class(request):
|
|
18
|
+
return request.param
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@pytest.fixture
|
|
22
|
+
def context(db, serializer_class):
|
|
23
|
+
request = Mock()
|
|
24
|
+
if serializer_class == UserRoleSerializer:
|
|
25
|
+
User = get_user_model()
|
|
26
|
+
user = User.objects.create()
|
|
27
|
+
request.resolver_match.kwargs = {"user_pk": user.pk}
|
|
28
|
+
elif serializer_class == GroupRoleSerializer:
|
|
29
|
+
group = Group.objects.create()
|
|
30
|
+
request.resolver_match.kwargs = {"group_pk": group.pk}
|
|
31
|
+
else:
|
|
32
|
+
pytest.fail("This fixture received an unknown serializer class.")
|
|
33
|
+
return {"request": request}
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
@pytest.fixture
|
|
37
|
+
def repository(db):
|
|
38
|
+
return FileRepository.objects.create(name="test1")
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
@pytest.mark.parametrize(
|
|
42
|
+
"field",
|
|
43
|
+
[
|
|
44
|
+
"role",
|
|
45
|
+
"content_object",
|
|
46
|
+
"content_object_prn",
|
|
47
|
+
],
|
|
48
|
+
)
|
|
49
|
+
def test_nested_role_serializer_has_certain_fields(serializer_class, field):
|
|
50
|
+
serializer = serializer_class()
|
|
51
|
+
assert field in serializer.fields
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
def test_nested_role_serializer_fails_without_content(serializer_class, context):
|
|
55
|
+
data = {"role": "file.filerepository_owner"}
|
|
56
|
+
serializer = serializer_class(data=data, context=context)
|
|
57
|
+
with pytest.raises(ValidationError):
|
|
58
|
+
serializer.is_valid(raise_exception=True)
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
def test_nested_role_serializer_with_null_content(serializer_class, context):
|
|
62
|
+
data = {"role": "file.filerepository_owner", "content_object": None}
|
|
63
|
+
serializer = serializer_class(data=data, context=context)
|
|
64
|
+
assert serializer.is_valid(raise_exception=True)
|
|
65
|
+
assert serializer.validated_data["content_object"] is None
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def test_nested_role_serializer_with_null_content_prn(serializer_class, context):
|
|
69
|
+
data = {"role": "file.filerepository_owner", "content_object_prn": None}
|
|
70
|
+
serializer = serializer_class(data=data, context=context)
|
|
71
|
+
assert serializer.is_valid(raise_exception=True)
|
|
72
|
+
assert serializer.validated_data["content_object"] is None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
def test_nested_role_serializer_allows_href(serializer_class, context, repository):
|
|
76
|
+
data = {"role": "file.filerepository_owner", "content_object": get_url(repository)}
|
|
77
|
+
serializer = serializer_class(data=data, context=context)
|
|
78
|
+
assert serializer.is_valid(raise_exception=True)
|
|
79
|
+
assert serializer.validated_data["content_object"] == repository
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def test_nested_role_serializer_allows_prn(serializer_class, context, repository):
|
|
83
|
+
data = {"role": "file.filerepository_owner", "content_object_prn": get_prn(repository)}
|
|
84
|
+
serializer = serializer_class(data=data, context=context)
|
|
85
|
+
assert serializer.is_valid(raise_exception=True)
|
|
86
|
+
assert serializer.validated_data["content_object"] == repository
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pulpcore
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.79.0
|
|
4
4
|
Summary: Pulp Django Application and Related Modules
|
|
5
5
|
Author-email: Pulp Team <pulp-list@redhat.com>
|
|
6
6
|
Project-URL: Homepage, https://pulpproject.org
|
|
@@ -21,14 +21,14 @@ Classifier: Programming Language :: Python :: 3.12
|
|
|
21
21
|
Requires-Python: >=3.9
|
|
22
22
|
Description-Content-Type: text/markdown
|
|
23
23
|
License-File: LICENSE
|
|
24
|
-
Requires-Dist: aiodns<=3.
|
|
24
|
+
Requires-Dist: aiodns<=3.4.0,>=3.0
|
|
25
25
|
Requires-Dist: aiofiles<24.2.0,>=22.1
|
|
26
26
|
Requires-Dist: aiohttp<3.12,>=3.8.1
|
|
27
27
|
Requires-Dist: asyncio-throttle<=1.0.2,>=1.0
|
|
28
28
|
Requires-Dist: async-timeout<4.0.4,>=4.0.3; python_version < "3.11"
|
|
29
29
|
Requires-Dist: backoff<2.2.2,>=2.1.2
|
|
30
30
|
Requires-Dist: click<=8.1.8,>=8.1.0
|
|
31
|
-
Requires-Dist: cryptography<
|
|
31
|
+
Requires-Dist: cryptography<45.0.3,>=38.0.1
|
|
32
32
|
Requires-Dist: Django~=4.2.0
|
|
33
33
|
Requires-Dist: django-filter<=25.1,>=23.1
|
|
34
34
|
Requires-Dist: django-guid<=3.5.1,>=3.3
|
|
@@ -37,7 +37,7 @@ Requires-Dist: django-lifecycle<=1.2.4,>=1.0
|
|
|
37
37
|
Requires-Dist: djangorestframework<=3.16.0,>=3.14.0
|
|
38
38
|
Requires-Dist: djangorestframework-queryfields<=1.1.0,>=1.0
|
|
39
39
|
Requires-Dist: drf-access-policy<1.5.1,>=1.1.2
|
|
40
|
-
Requires-Dist: drf-nested-routers<=0.94.
|
|
40
|
+
Requires-Dist: drf-nested-routers<=0.94.2,>=0.93.4
|
|
41
41
|
Requires-Dist: drf-spectacular==0.27.2
|
|
42
42
|
Requires-Dist: dynaconf<3.3.0,>=3.2.5
|
|
43
43
|
Requires-Dist: gunicorn<23.1.0,>=20.1
|
|
@@ -46,13 +46,13 @@ Requires-Dist: jinja2<=3.1.6,>=3.1
|
|
|
46
46
|
Requires-Dist: json_stream<2.4,>=2.3.2
|
|
47
47
|
Requires-Dist: jq<1.9.0,>=1.6.0
|
|
48
48
|
Requires-Dist: PyOpenSSL<26.0
|
|
49
|
-
Requires-Dist: opentelemetry-api<1.
|
|
50
|
-
Requires-Dist: opentelemetry-sdk<1.
|
|
51
|
-
Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.
|
|
49
|
+
Requires-Dist: opentelemetry-api<1.34,>=1.27.0
|
|
50
|
+
Requires-Dist: opentelemetry-sdk<1.34,>=1.27.0
|
|
51
|
+
Requires-Dist: opentelemetry-exporter-otlp-proto-http<1.34,>=1.27.0
|
|
52
52
|
Requires-Dist: protobuf<6.0,>=4.21.1
|
|
53
53
|
Requires-Dist: pulp-glue<0.33,>=0.18.0
|
|
54
54
|
Requires-Dist: pygtrie<=2.5.0,>=2.5
|
|
55
|
-
Requires-Dist: psycopg[binary]<=3.2.
|
|
55
|
+
Requires-Dist: psycopg[binary]<=3.2.9,>=3.1.8
|
|
56
56
|
Requires-Dist: pyparsing<=3.2.3,>=3.1.0
|
|
57
57
|
Requires-Dist: python-gnupg<=0.5.4,>=0.5
|
|
58
58
|
Requires-Dist: PyYAML<=6.0.2,>=5.1.1
|
|
@@ -78,6 +78,8 @@ Requires-Dist: confluent-kafka<2.10.0,>=2.4.0; extra == "kafka"
|
|
|
78
78
|
Provides-Extra: diagnostics
|
|
79
79
|
Requires-Dist: pyinstrument~=5.0; extra == "diagnostics"
|
|
80
80
|
Requires-Dist: memray~=1.17; extra == "diagnostics"
|
|
81
|
+
Provides-Extra: uvloop
|
|
82
|
+
Requires-Dist: uvloop<0.22,>=0.20; extra == "uvloop"
|
|
81
83
|
Dynamic: license-file
|
|
82
84
|
|
|
83
85
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
pulp_certguard/__init__.py,sha256=llnEd00PrsAretsgAOHiNKFbmvIdXe3iDVPmSaKz7gU,71
|
|
2
2
|
pulp_certguard/pytest_plugin.py,sha256=qhRbChzqN2PROtD-65KuoTfKr5k9T3GPsz9daFgpqpM,852
|
|
3
|
-
pulp_certguard/app/__init__.py,sha256=
|
|
3
|
+
pulp_certguard/app/__init__.py,sha256=37fy6yEmamVjBNpYjBDR0PLwmJVcJLBDKJJecOne2Js,297
|
|
4
4
|
pulp_certguard/app/models.py,sha256=xy5IWxf0LQxayIDmQw25Y2YhB_NrlTGvuvdY-YW7QBU,8119
|
|
5
5
|
pulp_certguard/app/serializers.py,sha256=3jxWu82vU3xA578Qbyz-G4Q9Zlh3MFLGRHzX62M0RF8,1826
|
|
6
6
|
pulp_certguard/app/utils.py,sha256=O6T1Npdb8fu3XqIkDJd8PQdEFJWPUeQ-i_aHXBl7MEc,816
|
|
@@ -49,7 +49,7 @@ pulp_certguard/tests/unit/test_models.py,sha256=TBI0yKsrdbnJSPeBFfxSqhXK7zaNvR6q
|
|
|
49
49
|
pulp_file/__init__.py,sha256=0vOCXofR6Eyxkg4y66esnOGPeESCe23C1cNBHj56w44,61
|
|
50
50
|
pulp_file/manifest.py,sha256=1WwIOJrPSkFcmkRm7CkWifVOCoZvo_nnANgce6uuG7U,3796
|
|
51
51
|
pulp_file/pytest_plugin.py,sha256=Fi_p-Vle_I-VYUSe4Zlg7esb_Ul5fpB8Rx9UGLK5UNQ,13281
|
|
52
|
-
pulp_file/app/__init__.py,sha256=
|
|
52
|
+
pulp_file/app/__init__.py,sha256=H_oQBsMT3QLmqf01BA-pgV9rZGJ3o9eZA4qZX2qbFkY,292
|
|
53
53
|
pulp_file/app/modelresource.py,sha256=v-m-_bBEsfr8wG0TI5ffx1TuKUy2-PsirhuQz4XXF-0,1063
|
|
54
54
|
pulp_file/app/models.py,sha256=QsrVg_2uKqnR89sLN2Y7Zy260_nLIcUfa94uZowlmFw,4571
|
|
55
55
|
pulp_file/app/replica.py,sha256=OtNWVmdFUgNTYhPttftVNQnSrnvx2_hnrJgtW_G0Vrg,1894
|
|
@@ -110,10 +110,10 @@ pulpcore/pytest_plugin.py,sha256=Tq_xlO8Z2iyjFtbnaKHbWQogq6jxcRpjji9XbKrs5_U,377
|
|
|
110
110
|
pulpcore/responses.py,sha256=mIGKmdCfTSoZxbFu4yIH1xbdLx1u5gqt3D99LTamcJg,6125
|
|
111
111
|
pulpcore/app/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
112
112
|
pulpcore/app/access_policy.py,sha256=5vCKy6WoHtIt1_-eS5vMaZ7CmR4G-CIpsrB8yT-d88Q,6079
|
|
113
|
-
pulpcore/app/apps.py,sha256=
|
|
113
|
+
pulpcore/app/apps.py,sha256=JlQDeDMMwjANi3Td8hHSxAJc8YXQRGyE2q-AVEWW54E,17860
|
|
114
114
|
pulpcore/app/authentication.py,sha256=1LIJW6HIQQlZrliHy__jdzkDEh6Oj7xKgd0V-vRcDus,2855
|
|
115
115
|
pulpcore/app/checks.py,sha256=jbfTF7nmftBbky4AQXHigpyCaGydKasvRUXsd72JZVg,1946
|
|
116
|
-
pulpcore/app/entrypoint.py,sha256=
|
|
116
|
+
pulpcore/app/entrypoint.py,sha256=m9kwANkh9OkhyAcWqPbrZg21IMQibLlB8_k1tkVgedg,4888
|
|
117
117
|
pulpcore/app/files.py,sha256=uPodXYTVh7Ud-lQn8F58viSdom7TMh2X1SpoDt6XRKw,5797
|
|
118
118
|
pulpcore/app/global_access_conditions.py,sha256=Jezc1Hf0bQFaZvZFEDPpBrJmK0EvIa6zHRHHZYkez2Y,30398
|
|
119
119
|
pulpcore/app/importexport.py,sha256=x1gGrHgirfMLsv92GEwBIQe12aItJJW9JH8TPij-rms,8795
|
|
@@ -121,24 +121,25 @@ pulpcore/app/loggers.py,sha256=7tteVBBIf4W7jk4tB7QNpFGjCZueDDrPAavHj46LdJI,79
|
|
|
121
121
|
pulpcore/app/manage.py,sha256=5YGD5ly3dJcLjX4jKIo3BBPFi_aqEPqi-CCoogV8lm8,286
|
|
122
122
|
pulpcore/app/mime_types.py,sha256=xQh9gd5jHKxS0RrYqUjg98pST-Cyfcrk_Et1l2eby_w,6706
|
|
123
123
|
pulpcore/app/modelresource.py,sha256=SlnKD_K-ZFtR5Gt8hpoF5odEX2y-1v2bb6UkJcOqnd8,4847
|
|
124
|
-
pulpcore/app/openpgp.py,sha256=
|
|
124
|
+
pulpcore/app/openpgp.py,sha256=MYwCTGz7J9-Zr5aSBrz7KGWWWNC1EI-aItGb5dr7XPE,17246
|
|
125
125
|
pulpcore/app/pulp_hashlib.py,sha256=NoVCO8duLz9rggPcilg0smi6fTDnsn-zS9dXgO831Pg,1327
|
|
126
126
|
pulpcore/app/pulpcore_gunicorn_application.py,sha256=caqbDg9dhzECbx9Ss76biuEARhquj9gQaSL6v3XLy2w,2612
|
|
127
127
|
pulpcore/app/redis_connection.py,sha256=VTdG0ulXuyESjYV6SJdG_jLzkLZH-MlLcD6pielwRSk,952
|
|
128
|
-
pulpcore/app/replica.py,sha256=
|
|
128
|
+
pulpcore/app/replica.py,sha256=6WU-K8olrOoO4q8gwJ2bKc_qmvw8wCOzRZdNZrr895g,11678
|
|
129
129
|
pulpcore/app/response.py,sha256=hYH_jSBrxmRsBr2bknmXE1qfs2g8JjDTXYcQ5ZWlF_c,1950
|
|
130
130
|
pulpcore/app/role_util.py,sha256=84HSt8_9fxB--dtfSyg_TumVgOdyBbyP6rBaiAfTpOU,22393
|
|
131
|
-
pulpcore/app/settings.py,sha256=
|
|
131
|
+
pulpcore/app/settings.py,sha256=f2LXOVLd58iG2z1Whm_XlLop129uKT5-GYxWiBq-ja8,22430
|
|
132
132
|
pulpcore/app/urls.py,sha256=0gdI74CAdycJStXSw1gknviDGe3J3k0UhS4J8RYa5dg,8120
|
|
133
|
-
pulpcore/app/util.py,sha256=
|
|
133
|
+
pulpcore/app/util.py,sha256=nYF6nZXgqVk4U1QeZEpWYX-wqitGSGAJip6W78IfXUk,24432
|
|
134
134
|
pulpcore/app/wsgi.py,sha256=7rpZ_1NHEN_UfeNZCj8206bas1WeqRkHnGdxpd7rdDI,492
|
|
135
135
|
pulpcore/app/management/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
136
136
|
pulpcore/app/management/commands/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
137
137
|
pulpcore/app/management/commands/add-signing-service.py,sha256=jzUHopJoQ0fKkC23ybmd9WjX4R-UtvEHvvBxI_QkJF0,3232
|
|
138
138
|
pulpcore/app/management/commands/analyze-publication.py,sha256=imcHSBYHI20vaT6ZgGJkJ4lVzMgQDVlhAe9mGu07MIk,3124
|
|
139
|
+
pulpcore/app/management/commands/clean-up-progress-reports.py,sha256=3LLB1MJyyq_eHfuVn-XwzojufqphEaNTRIbXNbjgiCM,1971
|
|
139
140
|
pulpcore/app/management/commands/datarepair-2327.py,sha256=HCw3XQcEEFbgYPd7H1bBjboApDapCNplsAxd9ua8f6M,4677
|
|
140
141
|
pulpcore/app/management/commands/dump-permissions.py,sha256=hrwDbEMBpEGM6UmgZtCHR9vpEiVQP8KLPybVmqSMxmA,8662
|
|
141
|
-
pulpcore/app/management/commands/dump-publications-to-fs.py,sha256=
|
|
142
|
+
pulpcore/app/management/commands/dump-publications-to-fs.py,sha256=lkLwxPi4GXzcLZpLvQbPnqYCALxag-G5xjr309gWGwo,7040
|
|
142
143
|
pulpcore/app/management/commands/handle-artifact-checksums.py,sha256=gblm6CkuyXrf9TsiTtts6iIgk4nyZnsJShozGxyALV8,8728
|
|
143
144
|
pulpcore/app/management/commands/migrationstat.py,sha256=Gy19UMSyUeXYT13ERQ-P1PdgnmNX9veJteEOgMMG6QY,1517
|
|
144
145
|
pulpcore/app/management/commands/openapi.py,sha256=p-aPuVfbnFQYIU7BMnipxe9nId-f2agNiviSIy43y9Q,3634
|
|
@@ -302,14 +303,14 @@ pulpcore/app/models/repository.py,sha256=xBMKsryirkpZyrQHnFbwolNbvyX1jHljcqC1ofv
|
|
|
302
303
|
pulpcore/app/models/role.py,sha256=dZklNd2VeAw4cT6dyJ7SyTBt9sZvdqakY86wXGAY3vU,3287
|
|
303
304
|
pulpcore/app/models/status.py,sha256=72oUOJ7BnCAw3uDbc-XuI72oAyP2llCoBic4zb2JP78,3683
|
|
304
305
|
pulpcore/app/models/storage.py,sha256=2b-DQWaO31NqjV6FiISALegND-sQZAU7BVAsduUvm3o,6780
|
|
305
|
-
pulpcore/app/models/task.py,sha256=
|
|
306
|
+
pulpcore/app/models/task.py,sha256=gcK-ou-ppS5Xdqx-6HHE01_lGeZn16H9IzqEZ_P07MY,14652
|
|
306
307
|
pulpcore/app/models/upload.py,sha256=3njXT2rrVJwBjEDegvqcLD9_7cPnnl974lhbAhikEp8,3004
|
|
307
308
|
pulpcore/app/protobuf/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
308
309
|
pulpcore/app/protobuf/analytics_pb2.py,sha256=-4CkbSW8JUAEIjZJBTPAJ5QezFJOdCPiDhx8_KA1bMU,2168
|
|
309
310
|
pulpcore/app/serializers/__init__.py,sha256=M1g5Si6hRi1flhQ-F1n6G-uFEzIZ7BUqHZ8iUE-bRuM,3466
|
|
310
311
|
pulpcore/app/serializers/access_policy.py,sha256=NNtuzDW5H4RGfy5LbRFEHWDTDzXdL-Kihe9uquXnxBU,2818
|
|
311
312
|
pulpcore/app/serializers/acs.py,sha256=wBbGE5YHR7dJWuicbDtiPQUJ7xEgz1zKHGkJEaMfpDU,5834
|
|
312
|
-
pulpcore/app/serializers/base.py,sha256=
|
|
313
|
+
pulpcore/app/serializers/base.py,sha256=ojWmsr2U2Mx8qpSFxqHLNQyfU2Z9q7hY1NUwVs9s3HE,21418
|
|
313
314
|
pulpcore/app/serializers/content.py,sha256=lqfSah9Kg2i6dV-x2MGwQ-1q87GB9VOtjkQdjQKC5tQ,11967
|
|
314
315
|
pulpcore/app/serializers/domain.py,sha256=-xRJS_Olb1s2bqFzKamV0d_QnYO-e2iIyBJw-39uqMI,22688
|
|
315
316
|
pulpcore/app/serializers/exporter.py,sha256=TxAgHDt34YUGPusawn7B8HL3bBymp46__6CnfhXSgGs,11179
|
|
@@ -327,7 +328,7 @@ pulpcore/app/serializers/repository.py,sha256=fSM92qJTjQIGXgnmA4xr62jRG9atYj6tkI
|
|
|
327
328
|
pulpcore/app/serializers/status.py,sha256=nIrQl-MlOzUIvV2DrkgC19gqGmRVNKvWVN4pIBELgcQ,3815
|
|
328
329
|
pulpcore/app/serializers/task.py,sha256=IGJGoSEC_wKS8t77JGnZWRqK-Mk5-4rXSj8j0Ha6nRA,10026
|
|
329
330
|
pulpcore/app/serializers/upload.py,sha256=4r6iBegbYHmgFYjBYPcqB8J7eSxXgY4ukayMxJZNh_M,2402
|
|
330
|
-
pulpcore/app/serializers/user.py,sha256=
|
|
331
|
+
pulpcore/app/serializers/user.py,sha256=QBEnUCfq2my3Lq_pohj7hphDE8wqU6g6fnYuEXl8VtI,18413
|
|
331
332
|
pulpcore/app/tasks/__init__.py,sha256=6fhLD0Z9LMluzqyBwQkatId71qI_2U7-o2-ZI1JH1Ls,576
|
|
332
333
|
pulpcore/app/tasks/analytics.py,sha256=eB3p-sdocH5yyNoe0OG5rUzwiVOfayOfHNzkohAfx-U,4722
|
|
333
334
|
pulpcore/app/tasks/base.py,sha256=4I88Bn5SttqEvvVlNJmIwkPv2IWe7OhpM-kbQiQ9T_U,5929
|
|
@@ -338,7 +339,7 @@ pulpcore/app/tasks/orphan.py,sha256=4rTZLZ549niJ7mGMh_7suy-czIcj06oCTxPYnsPN8mU,
|
|
|
338
339
|
pulpcore/app/tasks/purge.py,sha256=yrnlvQKtg2usjK-75JoDvg4RvvEKipMpI8p4fh69A3o,7472
|
|
339
340
|
pulpcore/app/tasks/reclaim_space.py,sha256=FZ7KFasbScPAU7A6lzK98pdylmqgThssgnNMecG5bEw,3803
|
|
340
341
|
pulpcore/app/tasks/replica.py,sha256=x-Yjd8Z4EUhrhuF1DCX5jCa6F7FTAE3th-161lnLN2g,4509
|
|
341
|
-
pulpcore/app/tasks/repository.py,sha256=
|
|
342
|
+
pulpcore/app/tasks/repository.py,sha256=v-CDXp03YV6S6Lf-rKklPw7PwpfeoQe_Gw3ZyMH6SFQ,9640
|
|
342
343
|
pulpcore/app/tasks/telemetry.py,sha256=QXOcYi7VIx_TBPCfs2BfcaiiVzRCiTFPZCN8MlC-hw8,1338
|
|
343
344
|
pulpcore/app/tasks/test.py,sha256=_3BdJzdtEGdyL7qnzl0apwAoGeleYPeZTr7IJ5COFyo,912
|
|
344
345
|
pulpcore/app/tasks/upload.py,sha256=3YJa32XYUFgqkEEWoERRPB9Q6ph9a6ashMtMi24R15k,1413
|
|
@@ -370,10 +371,10 @@ pulpcore/app/viewsets/upload.py,sha256=Mfy9Vcm5KcqARooH4iExzoXVkL6boDddEqAnGWDWz
|
|
|
370
371
|
pulpcore/app/viewsets/user.py,sha256=86eMawpaVrvp6ilQmb1C4j7SKpesPB5HgMovYL9rY3Q,13813
|
|
371
372
|
pulpcore/cache/__init__.py,sha256=GkYD4PgIMaVL83ywfAsLBC9JNNDUpmTtbitW9zZSslk,131
|
|
372
373
|
pulpcore/cache/cache.py,sha256=d8GMlvjeGG9MOMdi5_9029WpGCKH8Y5q9b2lt3wSREo,17371
|
|
373
|
-
pulpcore/content/__init__.py,sha256=
|
|
374
|
+
pulpcore/content/__init__.py,sha256=CVrhM5Ep2NFZBWOPxuyXXz7xL0bdZsmpBaOLPeA14SI,4010
|
|
374
375
|
pulpcore/content/authentication.py,sha256=lEZBkXBBBkIdtFMCSpHDD7583M0bO-zsZNYXTmpr4k8,3235
|
|
375
|
-
pulpcore/content/entrypoint.py,sha256=
|
|
376
|
-
pulpcore/content/handler.py,sha256=
|
|
376
|
+
pulpcore/content/entrypoint.py,sha256=svs6pEYa5bEGhWAAHpZt-uqlTuVXQ2UdW4U_LRlRHhY,2048
|
|
377
|
+
pulpcore/content/handler.py,sha256=J5LDpT0uJn86HESsX37eIEIiakl84OKXdV934gpP-JI,56712
|
|
377
378
|
pulpcore/content/instrumentation.py,sha256=H0N0GWzvOPGGjFi6eIbGW3mcvagfnAfazccTh-BZVmE,1426
|
|
378
379
|
pulpcore/download/__init__.py,sha256=s3Wh2GKdsmbUooVIR6wSvhYVIhpaTbtfR3Ar1OJhC7s,154
|
|
379
380
|
pulpcore/download/base.py,sha256=G8jgyowvVEFCGA_KTr0CtHn0qHWXKsnv4Xpi0KSMglM,12821
|
|
@@ -426,8 +427,8 @@ pulpcore/tasking/_util.py,sha256=giR8f8fNvsjsTiuJOU9X21Dyb14fFntSYU7xXGwQZzo,970
|
|
|
426
427
|
pulpcore/tasking/entrypoint.py,sha256=Npnn41e39soGvJ7CTaZXT5MjIhOO7UtQmpmNaZtfKYg,1120
|
|
427
428
|
pulpcore/tasking/kafka.py,sha256=76z4DzeXM1WL5uu1HlKnduWeLO3-b-czvGBXdWR6054,3845
|
|
428
429
|
pulpcore/tasking/storage.py,sha256=zQkwlpC_FDQtmZGZ8vKwHqxvD6CLO_gAS4Q7wijZE-k,3106
|
|
429
|
-
pulpcore/tasking/tasks.py,sha256=
|
|
430
|
-
pulpcore/tasking/worker.py,sha256=
|
|
430
|
+
pulpcore/tasking/tasks.py,sha256=Y1tvG2hHREtpzVnQtu-_QYXD6mKtuvyCethOopWUJAI,14327
|
|
431
|
+
pulpcore/tasking/worker.py,sha256=c9RgSYg4J_Jn_q70MVF_2egDeASFgXlLrP00lqWKtnQ,23822
|
|
431
432
|
pulpcore/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
432
433
|
pulpcore/tests/functional/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
433
434
|
pulpcore/tests/functional/content_with_coverage.py,sha256=gQK8himy32s9O9vpXdgoM6-_z2KySaXm5rTga9z0jGI,260
|
|
@@ -461,7 +462,7 @@ pulpcore/tests/functional/api/using_plugin/__init__.py,sha256=QyyfzgjLOi4n32G3o9
|
|
|
461
462
|
pulpcore/tests/functional/api/using_plugin/test_checkpoint.py,sha256=gx1oiHOVUH5QZfF33k_DXSw-AVbYQp39uKii1D96BoI,7965
|
|
462
463
|
pulpcore/tests/functional/api/using_plugin/test_content_access.py,sha256=Ym800bU-M48RCDfQMkVa1UQt_sfgy5ciU0FxorCk9Ds,2551
|
|
463
464
|
pulpcore/tests/functional/api/using_plugin/test_content_cache.py,sha256=OB3gDbPDptQBjyYnr_jHyU9bcI_-ANAoUp9EDiskwug,7312
|
|
464
|
-
pulpcore/tests/functional/api/using_plugin/test_content_delivery.py,sha256=
|
|
465
|
+
pulpcore/tests/functional/api/using_plugin/test_content_delivery.py,sha256=BNinSe7eUGWzJ_PVlzlDykAV02xV_EPHxunQnVjndgo,10566
|
|
465
466
|
pulpcore/tests/functional/api/using_plugin/test_content_directory.py,sha256=w4uY258etnP8-LbrbZ_EZTolciYTt7cY1HJK9Ll7mS0,1931
|
|
466
467
|
pulpcore/tests/functional/api/using_plugin/test_content_path.py,sha256=fvqeptqo-mrUAiKIjlypuvHG1XsFeKKP81ocTmo4hv0,3334
|
|
467
468
|
pulpcore/tests/functional/api/using_plugin/test_content_promotion.py,sha256=Co4ytrfpzklwgDdEthv45dsmrceRpqIQfLJlZWM6EBY,2388
|
|
@@ -483,7 +484,7 @@ pulpcore/tests/functional/api/using_plugin/test_tasks.py,sha256=wIQr2J8DD2isx_0t
|
|
|
483
484
|
pulpcore/tests/functional/api/using_plugin/test_unlinking_repo.py,sha256=rGJP2qcDarJALSpjzEsoO-ewQ0J2kWhFvN3Y1Z9fvzA,1085
|
|
484
485
|
pulpcore/tests/functional/assets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
485
486
|
pulpcore/tests/performance/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
486
|
-
pulpcore/tests/performance/test_performance.py,sha256=
|
|
487
|
+
pulpcore/tests/performance/test_performance.py,sha256=LIUSbDG6voX8IX4y7UXKDNcg4Ih2M0tVcvgB7vfClBc,8502
|
|
487
488
|
pulpcore/tests/unit/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
488
489
|
pulpcore/tests/unit/conftest.py,sha256=GfVjybp2b8c3E75rgq-kXVQ1_Tc3owuG3NK7Z0eGrsY,277
|
|
489
490
|
pulpcore/tests/unit/test_cache.py,sha256=sDNc7W31JLHS9r7ZiIUHz6iB7UkqPCKjLlfVmyTd8t4,3259
|
|
@@ -494,7 +495,7 @@ pulpcore/tests/unit/test_import_checks.py,sha256=Pu78yWFm68e1IfBbJIbzgwgKfDsjkpH
|
|
|
494
495
|
pulpcore/tests/unit/test_pulp_urls.py,sha256=sMRZowo7ej4HIrGEOY_OsDfXI1J4I8k_VXBvRDSCvYA,3416
|
|
495
496
|
pulpcore/tests/unit/test_settings.py,sha256=-yRXxmK-OdgC6mHqS83XgkD-PkZYvY1wZ37gRrGEoYc,2067
|
|
496
497
|
pulpcore/tests/unit/test_util.py,sha256=hgioXXC5-tufFpk6zrmMEHtWPG7UbmMHqeF5CiXOLqQ,884
|
|
497
|
-
pulpcore/tests/unit/test_viewsets.py,sha256=
|
|
498
|
+
pulpcore/tests/unit/test_viewsets.py,sha256=6rek28Rr0kEuYjQZ0_kTSnKsTvmMmD3l-WV_GVb48YQ,3208
|
|
498
499
|
pulpcore/tests/unit/content/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
499
500
|
pulpcore/tests/unit/content/test_handler.py,sha256=f4F9RAqCP60PUanYdeLW_A955UjRt8eCTrRuh0mChDU,19774
|
|
500
501
|
pulpcore/tests/unit/content/test_heartbeat.py,sha256=pHmSNy7KjlU9LpRcejBr8lttaJXrE4k9XQ5e2rDZmT0,1036
|
|
@@ -512,6 +513,7 @@ pulpcore/tests/unit/models/test_base.py,sha256=77hnxOFBJYMNbI1YGEaR5yj8VCapNGmEg
|
|
|
512
513
|
pulpcore/tests/unit/models/test_content.py,sha256=heU0vJKucPIp6py2Ww-eXLvhFopvmK8QjFgzt1jGnYQ,5599
|
|
513
514
|
pulpcore/tests/unit/models/test_remote.py,sha256=KxXwHdA-wj7D-ZpuVi33cLX43wkEeIzeqF9uMsJGt-k,2354
|
|
514
515
|
pulpcore/tests/unit/models/test_repository.py,sha256=rnBw1VOsi2Lv3zez2pV2RDXGk_z70KiaACOtyyXugJM,10379
|
|
516
|
+
pulpcore/tests/unit/models/test_task.py,sha256=rjxeYe383Zsjk8Ck4inMBBTzR4osCrgTeZNWwmHfbjk,1457
|
|
515
517
|
pulpcore/tests/unit/roles/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
516
518
|
pulpcore/tests/unit/roles/test_roles.py,sha256=TkPPCLEHMaxfafsRf_3pc4Z3w8BPTyteY7rFkVo65GM,4973
|
|
517
519
|
pulpcore/tests/unit/serializers/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
@@ -521,14 +523,15 @@ pulpcore/tests/unit/serializers/test_fields.py,sha256=ba25xHl6aezQ3K5o2mPdw-pK9k
|
|
|
521
523
|
pulpcore/tests/unit/serializers/test_orphans_cleanup.py,sha256=z3yWWioSl3A1ZlQR0RGzkzPExp7AggyxHZBsE5cMjl4,617
|
|
522
524
|
pulpcore/tests/unit/serializers/test_pulpexport.py,sha256=gXn7E13X-SP0rFM0bUv8PwpdLI614txrPW-JKHZ4pQE,3010
|
|
523
525
|
pulpcore/tests/unit/serializers/test_repository.py,sha256=eknsHlbHz1K0nqntDntltFLU2EunrSlXCgg3HrV9PTI,9288
|
|
526
|
+
pulpcore/tests/unit/serializers/test_user.py,sha256=lemDxBIDWKrfFmazl9DMW7-k3lQyWtD8uQCxNktHI3Q,3094
|
|
524
527
|
pulpcore/tests/unit/stages/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
525
528
|
pulpcore/tests/unit/stages/test_artifactdownloader.py,sha256=qB1ANdFmNtUnljg8fCdLHTiAakrO3KtX-w9RA5fPSOQ,12480
|
|
526
529
|
pulpcore/tests/unit/stages/test_stages.py,sha256=H1a2BQLjdZlZvcb_qULp62huZ1xy6ItTcthktVyGU0w,4735
|
|
527
530
|
pulpcore/tests/unit/viewsets/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
528
531
|
pulpcore/tests/unit/viewsets/test_viewset_base.py,sha256=W9o3V6758bZctR6krMPPQytb0xJuF-jb4uBWTNDoD_U,4837
|
|
529
|
-
pulpcore-3.
|
|
530
|
-
pulpcore-3.
|
|
531
|
-
pulpcore-3.
|
|
532
|
-
pulpcore-3.
|
|
533
|
-
pulpcore-3.
|
|
534
|
-
pulpcore-3.
|
|
532
|
+
pulpcore-3.79.0.dist-info/licenses/LICENSE,sha256=dhnHU8rJXUdAIgIjveSKAyYG_KzN5eVG-bxETIGrNW0,17988
|
|
533
|
+
pulpcore-3.79.0.dist-info/METADATA,sha256=oza7Ge3RemPRjXrvpfPh3Rc-vgp7wq6ZOutyPjWKjH4,4336
|
|
534
|
+
pulpcore-3.79.0.dist-info/WHEEL,sha256=zaaOINJESkSfm_4HQVc5ssNzHCPXhJm0kEUakpsEHaU,91
|
|
535
|
+
pulpcore-3.79.0.dist-info/entry_points.txt,sha256=OZven4wzXzQA5b5q9MpP4HUpIPPQCSvIOvkKtNInrK0,452
|
|
536
|
+
pulpcore-3.79.0.dist-info/top_level.txt,sha256=6h-Lm3FKQSaT_nL1KSxu_hBnzKE15bcvf_BoU-ea4CI,34
|
|
537
|
+
pulpcore-3.79.0.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|