Skip to content

Commit 4d48de6

Browse files
authored
Merge branch 'main' into add-pulp-exceptions
2 parents 005810f + 0873f64 commit 4d48de6

16 files changed

Lines changed: 686 additions & 42 deletions

File tree

.github/workflows/scripts/release.sh

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,4 +24,5 @@ towncrier build --yes --version "${NEW_VERSION}"
2424
bump-my-version bump release --commit --message "Release {new_version}" --tag --tag-name "{new_version}" --tag-message "Release {new_version}" --allow-dirty
2525
bump-my-version bump patch --commit
2626

27-
git push origin "${BRANCH}" "${NEW_VERSION}"
27+
# Git push is not atomic by default!
28+
git push --atomic origin "${BRANCH}" "${NEW_VERSION}"

.github/workflows/update_ci.yml

Lines changed: 38 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -165,6 +165,37 @@ jobs:
165165
env:
166166
GH_TOKEN: "${{ secrets.RELEASE_TOKEN }}"
167167
continue-on-error: true
168+
- uses: "actions/checkout@v6"
169+
with:
170+
fetch-depth: 0
171+
path: "pulp_python"
172+
ref: "3.19"
173+
174+
- name: "Run update"
175+
working-directory: "pulp_python"
176+
run: |
177+
../plugin_template/scripts/update_ci.sh --release
178+
179+
- name: "Create Pull Request for CI files"
180+
uses: "peter-evans/create-pull-request@v8"
181+
id: "create_pr_3_19"
182+
with:
183+
token: "${{ secrets.RELEASE_TOKEN }}"
184+
path: "pulp_python"
185+
committer: "pulpbot <pulp-infra@redhat.com>"
186+
author: "pulpbot <pulp-infra@redhat.com>"
187+
title: "Update CI files for branch 3.19"
188+
branch: "update-ci/3.19"
189+
base: "3.19"
190+
delete-branch: true
191+
- name: "Mark PR automerge"
192+
working-directory: "pulp_python"
193+
run: |
194+
gh pr merge --rebase --auto "${{ steps.create_pr_3_19.outputs.pull-request-number }}"
195+
if: "steps.create_pr_3_19.outputs.pull-request-number"
196+
env:
197+
GH_TOKEN: "${{ secrets.RELEASE_TOKEN }}"
198+
continue-on-error: true
168199
- uses: "actions/checkout@v6"
169200
with:
170201
fetch-depth: 0
@@ -200,7 +231,7 @@ jobs:
200231
with:
201232
fetch-depth: 0
202233
path: "pulp_python"
203-
ref: "3.28"
234+
ref: "3.29"
204235

205236
- name: "Run update"
206237
working-directory: "pulp_python"
@@ -209,21 +240,21 @@ jobs:
209240
210241
- name: "Create Pull Request for CI files"
211242
uses: "peter-evans/create-pull-request@v8"
212-
id: "create_pr_3_28"
243+
id: "create_pr_3_29"
213244
with:
214245
token: "${{ secrets.RELEASE_TOKEN }}"
215246
path: "pulp_python"
216247
committer: "pulpbot <pulp-infra@redhat.com>"
217248
author: "pulpbot <pulp-infra@redhat.com>"
218-
title: "Update CI files for branch 3.28"
219-
branch: "update-ci/3.28"
220-
base: "3.28"
249+
title: "Update CI files for branch 3.29"
250+
branch: "update-ci/3.29"
251+
base: "3.29"
221252
delete-branch: true
222253
- name: "Mark PR automerge"
223254
working-directory: "pulp_python"
224255
run: |
225-
gh pr merge --rebase --auto "${{ steps.create_pr_3_28.outputs.pull-request-number }}"
226-
if: "steps.create_pr_3_28.outputs.pull-request-number"
256+
gh pr merge --rebase --auto "${{ steps.create_pr_3_29.outputs.pull-request-number }}"
257+
if: "steps.create_pr_3_29.outputs.pull-request-number"
227258
env:
228259
GH_TOKEN: "${{ secrets.RELEASE_TOKEN }}"
229260
continue-on-error: true

CHANGES.md

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,30 @@
88

99
[//]: # (towncrier release notes start)
1010

11+
## 3.29.0 (2026-04-17) {: #3.29.0 }
12+
13+
#### Features {: #3.29.0-feature }
14+
15+
- Added repository-specific package blocklist.
16+
[#1166](https://github.com/pulp/pulp_python/issues/1166)
17+
18+
#### Bugfixes {: #3.29.0-bugfix }
19+
20+
- Fixed "Worker has gone missing" errors during repair_metadata on large repositories (1000+ packages) by reducing peak memory consumption.
21+
[#1188](https://github.com/pulp/pulp_python/issues/1188)
22+
- Support "atomic" replications in pulpcore 3.107
23+
24+
---
25+
26+
## 3.28.2 (2026-04-14) {: #3.28.2 }
27+
28+
#### Bugfixes {: #3.28.2-bugfix }
29+
30+
- Fixed "Worker has gone missing" errors during repair_metadata on large repositories (1000+ packages) by reducing peak memory consumption.
31+
[#1188](https://github.com/pulp/pulp_python/issues/1188)
32+
33+
---
34+
1135
## 3.28.1 (2026-04-01) {: #3.28.1 }
1236

1337
#### Bugfixes {: #3.28.1-bugfix }
@@ -33,6 +57,15 @@
3357

3458
---
3559

60+
## 3.27.2 (2026-04-14) {: #3.27.2 }
61+
62+
#### Bugfixes {: #3.27.2-bugfix }
63+
64+
- Fixed "Worker has gone missing" errors during repair_metadata on large repositories (1000+ packages) by reducing peak memory consumption.
65+
[#1188](https://github.com/pulp/pulp_python/issues/1188)
66+
67+
---
68+
3669
## 3.27.1 (2026-04-01) {: #3.27.1 }
3770

3871
#### Bugfixes {: #3.27.1-bugfix }
@@ -522,6 +555,12 @@ No significant changes.
522555

523556
---
524557

558+
## 3.11.8 (2026-04-21) {: #3.11.8 }
559+
560+
No significant changes.
561+
562+
---
563+
525564
## 3.11.7 (2025-11-18) {: #3.11.7 }
526565

527566
No significant changes.

CHANGES/+atomic-replication-support.bugfix

Lines changed: 0 additions & 1 deletion
This file was deleted.

docs/user/guides/_SUMMARY.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,3 +4,4 @@
44
* [Host Python Content](host.md)
55
* [Vulnerability Report](vulnerability_report.md)
66
* [Attestation Hosting](attestation.md)
7+
* [Package Blocklist](blocklist.md)

docs/user/guides/blocklist.md

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
# Package Blocklist
2+
3+
A repository can have a blocklist that prevents specific packages from being added.
4+
Blocklist entries can match by package `name` (all versions), package `name` with an exact `version`, or exact `filename`.
5+
Exactly one of `name` or `filename` must be provided.
6+
7+
Each entry records the PRN of the user who created it in the `added_by` field.
8+
9+
## Setup
10+
11+
If you do not already have a repository, create one:
12+
13+
```bash
14+
pulp python repository create --name foo
15+
```
16+
17+
Set the API base URL and repository HREF for use in the subsequent commands:
18+
19+
```bash
20+
PULP_API="http://localhost:5001"
21+
REPO_HREF=$(pulp python repository show --name foo | jq -r ".pulp_href")
22+
```
23+
24+
## Add a blocklist entry
25+
26+
=== "By name (all versions)"
27+
28+
```bash
29+
# Block all versions of shelf-reader
30+
http POST "${PULP_API}${REPO_HREF}blocklist_entries/" name="shelf-reader"
31+
```
32+
33+
=== "By name and version"
34+
35+
```bash
36+
# Block only shelf-reader 0.1
37+
http POST "${PULP_API}${REPO_HREF}blocklist_entries/" name="shelf-reader" version="0.1"
38+
```
39+
40+
=== "By filename"
41+
42+
```bash
43+
# Block only shelf-reader-0.1.tar.gz
44+
http POST "${PULP_API}${REPO_HREF}blocklist_entries/" filename="shelf-reader-0.1.tar.gz"
45+
```
46+
47+
Set the UUID of a created entry for use in the subsequent commands:
48+
49+
```bash
50+
ENTRY_UUID=$(http GET "${PULP_API}${REPO_HREF}blocklist_entries/" | jq -r '.results[0].prn | split(":") | .[-1]')
51+
```
52+
53+
## List blocklist entries
54+
55+
List all entries for a repository:
56+
57+
```bash
58+
http GET "${PULP_API}${REPO_HREF}blocklist_entries/"
59+
```
60+
61+
Show a single entry:
62+
63+
```bash
64+
http GET "${PULP_API}${REPO_HREF}blocklist_entries/${ENTRY_UUID}/"
65+
```
66+
67+
## Remove a blocklist entry
68+
69+
```bash
70+
http DELETE "${PULP_API}${REPO_HREF}blocklist_entries/${ENTRY_UUID}/"
71+
```
72+
73+
Once an entry is removed, packages matching it can be added to the repository again.

pulp_python/app/__init__.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ class PulpPythonPluginAppConfig(PulpPluginAppConfig):
1010

1111
name = "pulp_python.app"
1212
label = "python"
13-
version = "3.29.0.dev"
13+
version = "3.30.0.dev"
1414
python_package_name = "pulp-python"
1515
domain_compatible = True
1616

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
# Generated by Django 5.2.10 on 2026-04-16 14:00
2+
3+
import django.db.models.deletion
4+
import django_lifecycle.mixins
5+
import pulpcore.app.models.base
6+
from django.db import migrations, models
7+
8+
9+
class Migration(migrations.Migration):
10+
11+
dependencies = [
12+
("python", "0021_pythonrepository_upload_duplicate_filenames"),
13+
]
14+
15+
operations = [
16+
migrations.CreateModel(
17+
name="PythonBlocklistEntry",
18+
fields=[
19+
(
20+
"pulp_id",
21+
models.UUIDField(
22+
default=pulpcore.app.models.base.pulp_uuid,
23+
editable=False,
24+
primary_key=True,
25+
serialize=False,
26+
),
27+
),
28+
("pulp_created", models.DateTimeField(auto_now_add=True)),
29+
("pulp_last_updated", models.DateTimeField(auto_now=True, null=True)),
30+
("name", models.TextField(default=None, null=True)),
31+
("version", models.TextField(default=None, null=True)),
32+
("filename", models.TextField(default=None, null=True)),
33+
("added_by", models.TextField(default="")),
34+
(
35+
"repository",
36+
models.ForeignKey(
37+
on_delete=django.db.models.deletion.CASCADE,
38+
related_name="blocklist_entries",
39+
to="python.pythonrepository",
40+
),
41+
),
42+
],
43+
options={
44+
"default_related_name": "%(app_label)s_%(model_name)s",
45+
},
46+
bases=(django_lifecycle.mixins.LifecycleModelMixin, models.Model),
47+
),
48+
]

pulp_python/app/models.py

Lines changed: 69 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@
1313
)
1414
from pulpcore.plugin.models import (
1515
AutoAddObjPermsMixin,
16+
BaseModel,
1617
Content,
1718
Publication,
1819
Distribution,
@@ -399,9 +400,12 @@ def finalize_new_version(self, new_version):
399400
400401
When allow_package_substitution is False, reject any new version that would implicitly
401402
replace existing content with different checksums (content substitution).
403+
404+
Also checks newly added content against the repository's blocklist entries.
402405
"""
403406
if not self.allow_package_substitution:
404407
self._check_for_package_substitution(new_version)
408+
self._check_blocklist(new_version)
405409
remove_duplicates(new_version)
406410
validate_repo_version(new_version)
407411

@@ -413,4 +417,68 @@ def _check_for_package_substitution(self, new_version):
413417
qs = PythonPackageContent.objects.filter(pk__in=new_version.content)
414418
duplicates = collect_duplicates(qs, ("filename",))
415419
if duplicates:
416-
raise PackageSubstitutionError(duplicates)
420+
raise ValidationError(
421+
"Found duplicate packages being added with the same filename but different checksums. " # noqa: E501
422+
"To allow this, set 'allow_package_substitution' to True on the repository. "
423+
f"Conflicting packages: {duplicates}"
424+
)
425+
426+
def _check_blocklist(self, new_version):
427+
"""
428+
Check newly added content in a repository version against the blocklist.
429+
"""
430+
added_content = PythonPackageContent.objects.filter(
431+
pk__in=new_version.added().values_list("pk", flat=True)
432+
).only("filename", "name_normalized", "version")
433+
if added_content.exists():
434+
self.check_blocklist_for_packages(added_content)
435+
436+
def check_blocklist_for_packages(self, packages):
437+
"""
438+
Raise a ValidationError if any of the given packages match a blocklist entry.
439+
"""
440+
entries = PythonBlocklistEntry.objects.filter(repository=self)
441+
if not entries.exists():
442+
return
443+
444+
blocked = []
445+
for pkg in packages:
446+
for entry in entries:
447+
if entry.filename and entry.filename == pkg.filename:
448+
blocked.append(pkg.filename)
449+
break
450+
if entry.name == pkg.name_normalized:
451+
if not entry.version or entry.version == pkg.version:
452+
blocked.append(pkg.filename)
453+
break
454+
if blocked:
455+
raise ValidationError(
456+
"Blocklisted packages cannot be added to this repository: "
457+
"{}".format(", ".join(blocked))
458+
)
459+
460+
461+
class PythonBlocklistEntry(BaseModel):
462+
"""
463+
An entry in a PythonRepository's package blocklist.
464+
465+
Blocklist entries prevent packages from being added to the repository.
466+
Entries can match by package `name` (all versions), package `name` + `version`,
467+
or exact `filename`. Exactly one of `name` or `filename` must be provided.
468+
"""
469+
470+
name = models.TextField(null=True, default=None)
471+
version = models.TextField(null=True, default=None)
472+
filename = models.TextField(null=True, default=None)
473+
added_by = models.TextField(default="")
474+
repository = models.ForeignKey(
475+
PythonRepository, on_delete=models.CASCADE, related_name="blocklist_entries"
476+
)
477+
478+
def __str__(self):
479+
if self.filename:
480+
return f"<{self._meta.object_name}: {self.filename}>"
481+
return f"<{self._meta.object_name}: {self.name} [{self.version or 'all'}]>"
482+
483+
class Meta:
484+
default_related_name = "%(app_label)s_%(model_name)s"

0 commit comments

Comments
 (0)