Skip to content

Commit e970c0a

Browse files
committed
Support Architect-Variant fields
1 parent 3b9509b commit e970c0a

File tree

7 files changed

+249
-87
lines changed

7 files changed

+249
-87
lines changed
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
# Generated by Django 5.2.10 on 2026-02-19 10:21
2+
3+
from django.db import migrations, models
4+
5+
6+
class Migration(migrations.Migration):
7+
8+
dependencies = [
9+
('deb', '0034_aptpublication_layout'),
10+
]
11+
12+
operations = [
13+
migrations.AlterUniqueTogether(
14+
name='packagereleasecomponent',
15+
unique_together=set(),
16+
),
17+
migrations.AddField(
18+
model_name='packagereleasecomponent',
19+
name='index_architecture',
20+
field=models.TextField(null=True),
21+
),
22+
migrations.AddField(
23+
model_name='releasearchitecture',
24+
name='base_architecture',
25+
field=models.TextField(null=True),
26+
),
27+
migrations.AddField(
28+
model_name='releasearchitecture',
29+
name='variant_architecture',
30+
field=models.TextField(null=True),
31+
),
32+
migrations.AlterUniqueTogether(
33+
name='packagereleasecomponent',
34+
unique_together={('package', 'release_component', 'index_architecture', '_pulp_domain')},
35+
),
36+
]

pulp_deb/app/models/content/content.py

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,9 @@ def name(self):
7878
"""Print a nice name for Packages."""
7979
return "{}_{}_{}".format(self.package, self.version, self.architecture)
8080

81-
def filename(self, component="", layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY):
81+
def filename(
82+
self, component="", layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY, basename_override=None
83+
):
8284
"""Assemble filename in pool directory."""
8385
sourcename = self.source or self.package
8486
sourcename = sourcename.split("(", 1)[0].rstrip()
@@ -87,13 +89,15 @@ def filename(self, component="", layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY):
8789
else:
8890
prefix = sourcename[0]
8991
path = os.path.join("pool", component, prefix, sourcename)
92+
93+
basename = basename_override or "{}.{}".format(self.name, self.SUFFIX)
9094
if layout == LAYOUT_TYPES.NESTED_ALPHABETICALLY:
91-
return os.path.join(path, "{}.{}".format(self.name, self.SUFFIX))
95+
return os.path.join(path, basename)
9296
else: # NESTED_BY_DIGEST or NESTED_BY_BOTH. The primary URL in BOTH is by digest.
9397
return os.path.join(
9498
path,
9599
"by-digest",
96-
"{}-{}.{}".format(self.sha256[0:6], self.name, self.SUFFIX),
100+
"{}-{}".format(self.sha256[0:6], basename),
97101
)
98102

99103
class Meta:

pulp_deb/app/models/content/structure_content.py

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,8 @@ class ReleaseArchitecture(Content):
4040

4141
distribution = models.TextField()
4242
architecture = models.TextField()
43+
base_architecture = models.TextField(null=True)
44+
variant_architecture = models.TextField(null=True)
4345
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)
4446

4547
class Meta:
@@ -94,11 +96,12 @@ class PackageReleaseComponent(Content):
9496

9597
package = models.ForeignKey(Package, on_delete=models.CASCADE)
9698
release_component = models.ForeignKey(ReleaseComponent, on_delete=models.CASCADE)
99+
index_architecture = models.TextField(null=True)
97100
_pulp_domain = models.ForeignKey("core.Domain", default=get_domain_pk, on_delete=models.PROTECT)
98101

99102
class Meta:
100103
default_related_name = "%(app_label)s_%(model_name)s"
101-
unique_together = (("package", "release_component", "_pulp_domain"),)
104+
unique_together = (("package", "release_component", "index_architecture", "_pulp_domain"),)
102105

103106

104107
class SourcePackageReleaseComponent(Content):

pulp_deb/app/serializers/content_serializers.py

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -485,6 +485,7 @@ def to822(
485485
artifact_dict=None,
486486
remote_artifact_dict=None,
487487
layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY,
488+
basename_override=None,
488489
):
489490
"""Create deb822.Package object from model."""
490491
ret = deb822.Packages()
@@ -509,7 +510,15 @@ def to822(
509510
ret.update({"SHA1": artifact.sha1} if artifact.sha1 else {})
510511
ret.update({"SHA256": artifact.sha256})
511512
ret.update({"Size": str(artifact.size)})
512-
ret.update({"Filename": self.instance.filename(component, layout=layout)})
513+
ret.update(
514+
{
515+
"Filename": self.instance.filename(
516+
component,
517+
layout=layout,
518+
basename_override=basename_override,
519+
)
520+
}
521+
)
513522

514523
return ret
515524

pulp_deb/app/serializers/remote_serializers.py

Lines changed: 32 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,14 @@
1-
from rest_framework.serializers import BooleanField, CharField, ChoiceField
1+
import re
2+
from rest_framework.serializers import BooleanField, CharField, ChoiceField, ValidationError
23

34
from pulpcore.plugin.models import Remote
45
from pulpcore.plugin.serializers import RemoteSerializer
56

67
from pulp_deb.app.models import AptRemote
78

9+
ARCH_RE = re.compile(r"^[A-Za-z0-9_+-]+$")
10+
ARCH_VARIANT_RE = re.compile(r"^[A-Za-z0-9_+-]+:[A-Za-z0-9_+-]+$")
11+
812

913
class AptRemoteSerializer(RemoteSerializer):
1014
"""
@@ -83,3 +87,30 @@ class Meta:
8387
"ignore_missing_package_indices",
8488
)
8589
model = AptRemote
90+
91+
def validate_architectures(self, value):
92+
if value in (None, ""):
93+
return value
94+
95+
tokens = value.split()
96+
seen_tokens = set()
97+
for token in tokens:
98+
if token in seen_tokens:
99+
raise ValidationError(f"Duplicate architecture token '{token}'.")
100+
seen_tokens.add(token)
101+
102+
if not (ARCH_RE.match(token) or ARCH_VARIANT_RE.match(token)):
103+
raise ValidationError(
104+
f"Invalid architecture token '{token}'. Use 'amd64' or 'amd64:v3' style."
105+
)
106+
base, var = _parse_arch_token(token)
107+
if not base:
108+
raise ValidationError(f"Invalid architecture token '{token}'.")
109+
return value
110+
111+
112+
def _parse_arch_token(token):
113+
if ":" not in token:
114+
return token, None
115+
base, var = token.split(":", 1)
116+
return base, var

pulp_deb/app/tasks/publishing.py

Lines changed: 69 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -195,14 +195,16 @@ def publish(
195195

196196
release_helpers = []
197197
for distribution in distributions:
198+
release_arch_qs = ReleaseArchitecture.objects.filter(
199+
pk__in=repo_version.content.order_by("-pulp_created"),
200+
distribution=distribution,
201+
)
198202
architectures = list(
199-
ReleaseArchitecture.objects.filter(
200-
pk__in=repo_version.content.order_by("-pulp_created"),
201-
distribution=distribution,
203+
release_arch_qs.distinct("architecture").values_list(
204+
"architecture", flat=True
202205
)
203-
.distinct("architecture")
204-
.values_list("architecture", flat=True)
205206
)
207+
206208
if "all" not in architectures:
207209
architectures.append("all")
208210

@@ -268,16 +270,27 @@ def publish(
268270
)
269271

270272
for component in components:
273+
prcs_for_component = [
274+
prc
275+
for prc in package_release_components
276+
if prc.release_component.component == component
277+
]
271278
packages = Package.objects.filter(
272-
pk__in=[
273-
prc.package.pk
274-
for prc in package_release_components
275-
if prc.release_component.component == component
276-
]
279+
pk__in=[prc.package.pk for prc in prcs_for_component]
277280
).prefetch_related("contentartifact_set", "_artifacts")
278281
artifact_dict, remote_artifact_dict = _batch_fetch_artifacts(packages)
282+
283+
pkg_by_pk = {p.pk: p for p in packages}
284+
package_pairs = [
285+
(
286+
pkg_by_pk[prc.package.pk],
287+
prc.index_architecture or pkg_by_pk[prc.package.pk].architecture,
288+
)
289+
for prc in prcs_for_component
290+
if prc.package.pk in pkg_by_pk
291+
]
279292
release_helper.components[component].add_packages(
280-
packages,
293+
package_pairs,
281294
artifact_dict,
282295
remote_artifact_dict,
283296
)
@@ -338,56 +351,74 @@ def __init__(self, parent, component):
338351
source_index_path,
339352
)
340353

341-
def add_packages(self, packages, artifact_dict, remote_artifact_dict):
354+
def add_packages(self, package_pairs, artifact_dict, remote_artifact_dict):
342355
published_artifacts = []
343-
package_data = []
356+
packages = [p for (p, _idx) in package_pairs]
344357

345358
content_artifacts = {
346359
package.pk: list(package.contentartifact_set.all()) for package in packages
347360
}
348361
layout = self.parent.publication.layout
349362

350-
for package in packages:
363+
for package, index_arch in package_pairs:
351364
with suppress(IntegrityError):
352365
content_artifact = content_artifacts.get(package.pk, [None])[0]
366+
if content_artifact is None:
367+
continue
368+
369+
upstream_basename = os.path.basename(content_artifact.relative_path)
370+
371+
primary_relpath = package.filename(
372+
self.component, layout=layout, basename_override=upstream_basename
373+
)
353374
published_artifact = PublishedArtifact(
354-
relative_path=package.filename(self.component, layout=layout),
375+
relative_path=primary_relpath,
355376
publication=self.parent.publication,
356377
content_artifact=content_artifact,
357378
)
358379
published_artifacts.append(published_artifact)
359-
package_data.append((package, package.architecture))
380+
360381
# In the NESTED_BY_BOTH layout, we want to _also_ publish the package under the
361382
# alphabetical path but _not_ reference it in the repo metadata.
362383
if layout == LAYOUT_TYPES.NESTED_BY_BOTH:
363-
alt_published_artifact = PublishedArtifact(
364-
relative_path=package.filename(
365-
self.component, layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY
366-
),
367-
publication=self.parent.publication,
368-
content_artifact=content_artifact,
384+
alt_relpath = package.filename(
385+
self.component,
386+
layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY,
387+
basename_override=upstream_basename,
369388
)
370-
published_artifacts.append(alt_published_artifact)
389+
published_artifacts.append(
390+
PublishedArtifact(
391+
relative_path=alt_relpath,
392+
publication=self.parent.publication,
393+
content_artifact=content_artifact,
394+
)
395+
)
396+
397+
package_serializer = Package822Serializer(package, context={"request": None})
398+
try:
399+
package_serializer.to822(
400+
self.component,
401+
artifact_dict,
402+
remote_artifact_dict,
403+
layout=layout,
404+
basename_override=upstream_basename,
405+
).dump(self.package_index_files[index_arch][0])
406+
except KeyError:
407+
log.warning(
408+
"Published package '%s' with index architecture '%s' was not added to "
409+
"component '%s' in distribution '%s' because it lacks this architecture!",
410+
getattr(package, "relative_path", None) or getattr(package, "pk", None),
411+
index_arch,
412+
self.component,
413+
self.parent.distribution,
414+
)
415+
else:
416+
self.package_index_files[index_arch][0].write(b"\n")
371417

372418
with transaction.atomic():
373419
if published_artifacts:
374420
PublishedArtifact.objects.bulk_create(published_artifacts, ignore_conflicts=True)
375421

376-
for package, architecture in package_data:
377-
package_serializer = Package822Serializer(package, context={"request": None})
378-
try:
379-
package_serializer.to822(
380-
self.component, artifact_dict, remote_artifact_dict, layout=layout
381-
).dump(self.package_index_files[architecture][0])
382-
except KeyError:
383-
log.warn(
384-
f"Published package '{package.relative_path}' with architecture "
385-
f"'{architecture}' was not added to component '{self.component}' in "
386-
f"distribution '{self.parent.distribution}' because it lacks this architecture!"
387-
)
388-
else:
389-
self.package_index_files[architecture][0].write(b"\n")
390-
391422
# Publish DSC file and setup to create Sources Indices file
392423
def add_source_packages(self, source_packages):
393424
published_artifacts = []

0 commit comments

Comments
 (0)