Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGES/447.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added an opt-in APT publication mode that includes `Architecture: all` packages in every architecture-specific package index for compatibility with APT clients before version 1.1.
18 changes: 18 additions & 0 deletions docs/user/guides/publish.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,24 @@ An example `base_url` could be:
http://my-pulp-instance.com:5001/pulp/content/my-repository/
```

## Compatibility with APT Clients Before 1.1

APT clients before version 1.1 do not download the `binary-all/Packages` index.
To make `Architecture: all` packages available to these clients, create the publication with
`no_support_for_architecture_all` enabled:

```bash
pulp deb publication create \
--repository=${NAME} \
--no-support-for-architecture-all
```

This publishes `Architecture: all` packages in every architecture-specific `Packages` index in
addition to the `binary-all` index. The generated `Release` file advertises the repository format
using `No-Support-for-Architecture-all: Packages`.

The option defaults to disabled, so existing publication behavior remains unchanged.



## Metadata Signing
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
# Generated by Django 5.2.12 on 2026-09-09 00:00

from django.db import migrations, models


class Migration(migrations.Migration):

dependencies = [
("deb", "0042_add_excluded_package_metadata_fields"),
]

operations = [
migrations.AddField(
model_name="aptpublication",
name="no_support_for_architecture_all",
field=models.BooleanField(default=False),
),
]
1 change: 1 addition & 0 deletions pulp_deb/app/models/publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,6 +72,7 @@ class AptPublication(Publication, AutoAddObjPermsMixin):

simple = models.BooleanField(default=False)
structured = models.BooleanField(default=True)
no_support_for_architecture_all = models.BooleanField(default=False)
layout = models.TextField(choices=LAYOUT_CHOICES, default=LAYOUT_TYPES.NESTED_ALPHABETICALLY)
excluded_package_metadata_fields = ArrayField(models.TextField(), default=list)
signing_service = models.ForeignKey(
Expand Down
9 changes: 9 additions & 0 deletions pulp_deb/app/serializers/publication_serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,14 @@ class AptPublicationSerializer(PublicationSerializer):
default=False,
)
structured = BooleanField(help_text="Activate structured publishing mode.", default=True)
no_support_for_architecture_all = BooleanField(
help_text=(
"Publish Architecture: all packages in every architecture-specific Packages index "
"in addition to the binary-all index, and advertise this repository format in "
"Release files."
),
default=False,
)
publish_upstream_release_fields = BooleanField(help_text="", required=False)
checkpoint = serializers.BooleanField(required=False)
layout = serializers.ChoiceField(
Expand Down Expand Up @@ -82,6 +90,7 @@ class Meta:
fields = PublicationSerializer.Meta.fields + (
"simple",
"structured",
"no_support_for_architecture_all",
"checkpoint",
"signing_service",
"publish_upstream_release_fields",
Expand Down
64 changes: 39 additions & 25 deletions pulp_deb/app/tasks/publishing.py
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ def publish(
layout=LAYOUT_TYPES.NESTED_ALPHABETICALLY,
publish_legacy_release_files=False,
excluded_package_metadata_fields=None,
no_support_for_architecture_all=False,
):
"""
Use provided publisher to create a Publication based on a RepositoryVersion.
Expand All @@ -103,6 +104,8 @@ def publish(
publish_legacy_release_files (bool): publish legacy per architecture release files
excluded_package_metadata_fields (list): Custom package metadata fields to omit from
generated package indices.
no_support_for_architecture_all (bool): Publish Architecture: all packages in every
architecture-specific package index as well as the binary-all index.

"""

Expand Down Expand Up @@ -131,6 +134,7 @@ def publish(
) as publication:
publication.simple = simple
publication.structured = structured
publication.no_support_for_architecture_all = no_support_for_architecture_all
publication.signing_service = signing_service
publication.publish_legacy_release_files = publish_legacy_release_files
publication.layout = layout
Expand Down Expand Up @@ -468,33 +472,41 @@ def add_packages(self, package_pairs, artifact_dict, remote_artifact_dict):
)
)

metadata_arch = "all" if package.architecture == "all" else index_arch
package_index_entry = (package.pk, metadata_arch)
if package_index_entry in seen_package_index_entries:
continue
seen_package_index_entries.add(package_index_entry)

package_serializer = Package822Serializer(package, context={"request": None})
try:
package_serializer.to822(
self.component,
artifact_dict,
remote_artifact_dict,
layout=layout,
basename_override=upstream_basename,
excluded_package_metadata_fields=excluded_fields,
).dump(self.package_index_files[metadata_arch][0])
except KeyError:
log.warning(
"Published package '%s' with index architecture '%s' was not added to "
"component '%s' in distribution '%s' because it lacks this architecture!",
getattr(package, "relative_path", None) or getattr(package, "pk", None),
metadata_arch,
self.component,
self.parent.distribution,
)
if (
self.parent.publication.no_support_for_architecture_all
and package.architecture == "all"
):
metadata_architectures = self.parent.architectures
else:
self.package_index_files[metadata_arch][0].write(b"\n")
metadata_architectures = ["all" if package.architecture == "all" else index_arch]

for metadata_arch in metadata_architectures:
package_index_entry = (package.pk, metadata_arch)
if package_index_entry in seen_package_index_entries:
continue
seen_package_index_entries.add(package_index_entry)

try:
package_serializer.to822(
self.component,
artifact_dict,
remote_artifact_dict,
layout=layout,
basename_override=upstream_basename,
excluded_package_metadata_fields=excluded_fields,
).dump(self.package_index_files[metadata_arch][0])
except KeyError:
log.warning(
"Published package '%s' with index architecture '%s' was not added to "
"component '%s' in distribution '%s' because it lacks this architecture!",
getattr(package, "relative_path", None) or getattr(package, "pk", None),
metadata_arch,
self.component,
self.parent.distribution,
)
else:
self.package_index_files[metadata_arch][0].write(b"\n")

with transaction.atomic():
if published_artifacts:
Expand Down Expand Up @@ -634,6 +646,8 @@ def __init__(
release.codename = distribution.split("/")[0] if distribution != "/" else "flat-repo"
self.release["Codename"] = release.codename
self.release["Date"] = datetime.now(tz=timezone.utc).strftime("%a, %d %b %Y %H:%M:%S %z")
if publication.no_support_for_architecture_all:
self.release["No-Support-for-Architecture-all"] = "Packages"
self.release["Architectures"] = " ".join(architectures)
self.release["Components"] = "" # Will be set later
if release.description != NULL_VALUE:
Expand Down
4 changes: 4 additions & 0 deletions pulp_deb/app/viewsets/publication.py
Original file line number Diff line number Diff line change
Expand Up @@ -211,6 +211,9 @@ def create(self, request, **kwargs):
repository_version = serializer.validated_data.get("repository_version")
simple = serializer.validated_data.get("simple")
structured = serializer.validated_data.get("structured")
no_support_for_architecture_all = serializer.validated_data.get(
"no_support_for_architecture_all"
)
checkpoint = serializer.validated_data.get("checkpoint")
signing_service = serializer.validated_data.get("signing_service")
publish_upstream_release_fields = serializer.validated_data.get(
Expand All @@ -226,6 +229,7 @@ def create(self, request, **kwargs):
"repository_version_pk": repository_version.pk,
"simple": simple,
"structured": structured,
"no_support_for_architecture_all": no_support_for_architecture_all,
"signing_service_pk": getattr(signing_service, "pk", None),
"publish_upstream_release_fields": publish_upstream_release_fields,
"publish_legacy_release_files": publish_legacy_release_files,
Expand Down
86 changes: 86 additions & 0 deletions pulp_deb/tests/functional/api/test_publish.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@
DEB_FIXTURE_FLAT_REPOSITORY_NAME,
DEB_FIXTURE_METADATA_UPDATE_REPOSITORY_NAME,
DEB_FIXTURE_MISSING_ARCHITECTURE_REPOSITORY_NAME,
DEB_FIXTURE_MIXED_REPOSITORY_NAME,
DEB_FIXTURE_SINGLE_DIST,
DEB_FIXTURE_VARIANT_REPOSITORY_NAME,
DEB_PACKAGE_INDEX_NAME,
Expand Down Expand Up @@ -823,6 +824,91 @@ def parse_package_index(pkg_idx):
return packages


@pytest.mark.parallel
def test_publish_no_support_for_architecture_all(
create_publication_and_verify_repo_version,
deb_distribution_factory,
download_content_unit,
):
"""Test publishing Architecture: all packages in every binary package index."""
remote_args = {
"distributions": "muspelheim",
"policy": "on_demand",
}
publication_args = {
**DEB_PUBLICATION_ARGS_ONLY_STRUCTURED,
"no_support_for_architecture_all": True,
}

publication, _, _, _ = create_publication_and_verify_repo_version(
remote_args=remote_args,
publication_args=publication_args,
remote_name=DEB_FIXTURE_MIXED_REPOSITORY_NAME,
)
assert publication.no_support_for_architecture_all is True

distribution = deb_distribution_factory(publication)
base_path = distribution.to_dict()["base_path"]

release_file = download_content_unit(base_path, "dists/muspelheim/Release")
release = deb822.Deb822(release_file.decode("utf-8"))
assert release["No-Support-for-Architecture-all"] == "Packages"

expected_packages = {
"asgard": {
"all": {"eir-1.0-all"},
"amd64": {"baldr-1.0-amd64", "eir-1.0-all"},
"ppc64": {"eir-1.0-all", "frigg-1.0-ppc64"},
},
"nidavellir": {
"all": {"regin-1.0-all"},
"amd64": {"hreidmar-1.0-amd64", "regin-1.0-all"},
"ppc64": {"fafner-1.0-ppc64", "regin-1.0-all"},
},
}
for component, architectures in expected_packages.items():
for architecture, expected in architectures.items():
package_index = download_content_unit(
base_path,
f"dists/muspelheim/{component}/binary-{architecture}/Packages",
)
assert set(parse_package_index(package_index)) == expected

package_path = "pool/asgard/e/eir/eir_1.0_all.deb"
cold_download = download_content_unit(base_path, package_path)
warm_download = download_content_unit(base_path, package_path)
assert cold_download.startswith(b"!<arch>\n")
assert warm_download == cold_download


@pytest.mark.parallel
def test_publish_architecture_all_default_format(
create_publication_and_verify_repo_version,
deb_distribution_factory,
download_content_unit,
):
"""Test that the default publisher keeps Architecture: all in binary-all only."""
publication, _, _, _ = create_publication_and_verify_repo_version(
remote_args={"distributions": "muspelheim"},
publication_args=DEB_PUBLICATION_ARGS_ONLY_STRUCTURED,
remote_name=DEB_FIXTURE_MIXED_REPOSITORY_NAME,
)
assert publication.no_support_for_architecture_all is False

distribution = deb_distribution_factory(publication)
base_path = distribution.to_dict()["base_path"]

release_file = download_content_unit(base_path, "dists/muspelheim/Release")
release = deb822.Deb822(release_file.decode("utf-8"))
assert "No-Support-for-Architecture-all" not in release

package_index = download_content_unit(
base_path,
"dists/muspelheim/asgard/binary-amd64/Packages",
)
assert set(parse_package_index(package_index)) == {"baldr-1.0-amd64"}


@pytest.mark.parallel
def test_publish_architecture_variant_package_indices(
create_publication_and_verify_repo_version,
Expand Down
1 change: 1 addition & 0 deletions pulp_deb/tests/functional/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@ def _clean_dict(d):
DEB_FIXTURE_INVALID_REPOSITORY_NAME = "/debian-invalid/"
DEB_FIXTURE_FLAT_REPOSITORY_NAME = "/debian-flat/"
DEB_FIXTURE_VARIANT_REPOSITORY_NAME = "/debian-variant/"
DEB_FIXTURE_MIXED_REPOSITORY_NAME = "/debian-mixed/"
DEB_FIXTURE_BASE = "/"
DEB_FIXTURE_COMPLEX_REPOSITORY_NAME = "/debian-complex-dists"
DEB_FIXTURE_MISSING_ARCHITECTURE_REPOSITORY_NAME = "/debian-missing-architecture/"
Expand Down
Loading