Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add manifest arch, os, and compressed layers size fields #1782

Merged
merged 1 commit into from
Nov 1, 2024
Merged
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/1767.feature
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
Added `architecture`, `os`, and `compressed_image_size` fields to Manifest.
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,22 @@ def handle(self, *args, **options):
manifests_updated_count = 0

manifests_v1 = Manifest.objects.filter(
Q(media_type=MEDIA_TYPE.MANIFEST_V1), Q(data__isnull=True) | Q(type__isnull=True)
Q(media_type=MEDIA_TYPE.MANIFEST_V1),
Q(data__isnull=True)
| Q(type__isnull=True)
| Q(architecture__isnull=True)
| Q(os__isnull=True)
| Q(compressed_image_size__isnull=True),
)
manifests_updated_count += self.update_manifests(manifests_v1)

manifests_v2 = Manifest.objects.filter(
Q(data__isnull=True) | Q(annotations={}, labels={}) | Q(type__isnull=True)
Q(data__isnull=True)
| Q(annotations={}, labels={})
| Q(type__isnull=True)
| Q(architecture__isnull=True)
| Q(os__isnull=True)
| Q(compressed_image_size__isnull=True)
)
manifests_v2 = manifests_v2.exclude(
media_type__in=[MEDIA_TYPE.MANIFEST_LIST, MEDIA_TYPE.INDEX_OCI, MEDIA_TYPE.MANIFEST_V1]
Expand Down Expand Up @@ -79,6 +89,9 @@ def update_manifests(self, manifests_qs):
"is_flatpak",
"data",
"type",
"os",
"architecture",
"compressed_image_size",
]

for manifest in manifests_qs.iterator():
Expand Down Expand Up @@ -106,6 +119,7 @@ def update_manifests(self, manifests_qs):
return manifests_updated_count

def init_manifest(self, manifest):
updated = False
if not manifest.data:
manifest_artifact = manifest._artifacts.get()
manifest_data, raw_bytes_data = get_content_data(manifest_artifact)
Expand All @@ -114,9 +128,25 @@ def init_manifest(self, manifest):
if not (manifest.annotations or manifest.labels or manifest.type):
manifest.init_metadata(manifest_data)

if self.needs_os_arch_size_update(manifest):
self.init_manifest_os_arch_size(manifest)
manifest._artifacts.clear()
return True
updated = True

elif not manifest.type:
return manifest.init_image_nature()
return False
if not manifest.type:
updated = manifest.init_image_nature()

if self.needs_os_arch_size_update(manifest):
self.init_manifest_os_arch_size(manifest)
updated = True

return updated

def needs_os_arch_size_update(self, manifest):
return manifest.media_type not in [MEDIA_TYPE.MANIFEST_LIST, MEDIA_TYPE.INDEX_OCI] and not (
manifest.architecture or manifest.os or manifest.compressed_image_size
)

def init_manifest_os_arch_size(self, manifest):
manifest.init_architecture_and_os()
manifest.init_compressed_image_size()
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
# Generated by Django 4.2.16 on 2024-10-30 11:09
import warnings

from django.db import migrations, models

def print_warning_for_updating_manifest_fields(apps, schema_editor):
warnings.warn(
"Run 'pulpcore-manager container-handle-image-data' to update the manifests' "
"os, architecture, and compressed_image_size fields."
)

class Migration(migrations.Migration):

dependencies = [
('container', '0042_add_manifest_nature_field'),
]

operations = [
migrations.AddField(
model_name='manifest',
name='architecture',
field=models.TextField(null=True),
),
migrations.AddField(
model_name='manifest',
name='compressed_image_size',
field=models.IntegerField(null=True),
),
migrations.AddField(
model_name='manifest',
name='os',
field=models.TextField(null=True),
),
migrations.AlterField(
model_name='manifestlistmanifest',
name='architecture',
field=models.TextField(blank=True, default=''),
),
migrations.AlterField(
model_name='manifestlistmanifest',
name='os',
field=models.TextField(blank=True, default=''),
),
migrations.RunPython(
print_warning_for_updating_manifest_fields,
reverse_code=migrations.RunPython.noop,
elidable=True,
),
]
46 changes: 44 additions & 2 deletions pulp_container/app/models.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,11 @@ class Manifest(Content):
labels (models.JSONField): Metadata stored inside the image configuration.
is_bootable (models.BooleanField): Indicates whether the image is bootable or not.
is_flatpak (models.BooleanField): Indicates whether the image is a flatpak package or not.
architecture (models.TextField): CPU architecture for which the binaries in the image are
designed to run.
os (models.TextField): Operating System which the image is built to run on.
compressed_image_size (models.IntegerField): Sum of the sizes, in bytes, of all compressed
layers.

Relations:
blobs (models.ManyToManyField): Many-to-many relationship with Blob.
Expand Down Expand Up @@ -112,6 +117,9 @@ class Manifest(Content):

annotations = models.JSONField(default=dict)
labels = models.JSONField(default=dict)
architecture = models.TextField(null=True)
os = models.TextField(null=True)
compressed_image_size = models.IntegerField(null=True)

# DEPRECATED: this field is deprecated and will be removed in a future release.
is_bootable = models.BooleanField(default=False)
Expand Down Expand Up @@ -205,6 +213,31 @@ def init_manifest_nature(self):

return False

def init_architecture_and_os(self):
# schema1 has the architecture/os definition in the Manifest (not in the ConfigBlob)
# and none of these fields are required
if self.json_manifest.get("architecture", None) or self.json_manifest.get("os", None):
self.architecture = self.json_manifest.get("architecture", None)
self.os = self.json_manifest.get("os", None)
return

config_artifact = self.config_blob._artifacts.get()
config_data, _ = get_content_data(config_artifact)
self.architecture = config_data.get("architecture", None)
self.os = config_data.get("os", None)

def init_compressed_image_size(self):
# manifestv2 schema1 has only blobSum definition for each layer
if self.json_manifest.get("fsLayers", None):
self.compressed_image_size = 0
return

layers = self.json_manifest.get("layers")
compressed_size = 0
for layer in layers:
compressed_size += layer.get("size")
self.compressed_image_size = compressed_size

def is_bootable_image(self):
return (
self.annotations.get("containers.bootc") == "1"
Expand Down Expand Up @@ -278,8 +311,9 @@ class ManifestListManifest(models.Model):
manifest_list (models.ForeignKey): Many-to-one relationship with ManifestList.
"""

architecture = models.TextField()
os = models.TextField()
# in oci-index spec, platform is an optional field
architecture = models.TextField(default="", blank=True)
os = models.TextField(default="", blank=True)
os_version = models.TextField(default="", blank=True)
os_features = models.TextField(default="", blank=True)
features = models.TextField(default="", blank=True)
Expand All @@ -292,6 +326,14 @@ class ManifestListManifest(models.Model):
Manifest, related_name="manifest_lists", on_delete=models.CASCADE
)

def set_platform_configs(self, platform):
self.architecture = platform["architecture"]
self.os = platform["os"]
self.features = platform.get("features", "")
self.variant = platform.get("variant", "")
self.os_version = platform.get("os.version", "")
self.os_features = platform.get("os.features", "")

class Meta:
unique_together = ("image_manifest", "manifest_list")

Expand Down
2 changes: 2 additions & 0 deletions pulp_container/app/registry.py
Original file line number Diff line number Diff line change
Expand Up @@ -454,10 +454,12 @@ async def init_pending_content(self, digest, manifest_data, media_type, raw_text
config_blob=config_blob,
data=raw_text_data,
)
await sync_to_async(manifest.init_architecture_and_os)()

# skip if media_type of schema1
if media_type in (MEDIA_TYPE.MANIFEST_V2, MEDIA_TYPE.MANIFEST_OCI):
await sync_to_async(manifest.init_metadata)(manifest_data=manifest_data)
await sync_to_async(manifest.init_compressed_image_size)()

try:
await manifest.asave()
Expand Down
13 changes: 5 additions & 8 deletions pulp_container/app/registry_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -1204,7 +1204,7 @@ def put(self, request, path, pk=None):
if is_manifest_list:
manifests = {}
for manifest in content_data.get("manifests"):
manifests[manifest["digest"]] = manifest["platform"]
manifests[manifest["digest"]] = manifest.get("platform", None)

digests = set(manifests.keys())

Expand All @@ -1219,17 +1219,12 @@ def put(self, request, path, pk=None):

manifests_to_list = []
for manifest in found_manifests:
platform = manifests[manifest.digest]
manifest_to_list = models.ManifestListManifest(
manifest_list=manifest,
image_manifest=manifest_list,
architecture=platform["architecture"],
os=platform["os"],
features=platform.get("features", ""),
variant=platform.get("variant", ""),
os_version=platform.get("os.version", ""),
os_features=platform.get("os.features", ""),
)
if platform := manifests[manifest.digest]:
manifest_to_list.set_platform_configs(platform)
manifests_to_list.append(manifest_to_list)

models.ManifestListManifest.objects.bulk_create(
Expand Down Expand Up @@ -1300,6 +1295,8 @@ def put(self, request, path, pk=None):
config_blob = found_config_blobs.first()
manifest = self._init_manifest(manifest_digest, media_type, raw_text_data, config_blob)
manifest.init_metadata(manifest_data=content_data)
manifest.init_architecture_and_os()
manifest.init_compressed_image_size()
git-hyagi marked this conversation as resolved.
Show resolved Hide resolved

manifest = self._save_manifest(manifest)

Expand Down
18 changes: 18 additions & 0 deletions pulp_container/app/serializers.py
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,21 @@ class ManifestSerializer(NoArtifactContentSerializer):
"[deprecated] check type field instead"
),
)
architecture = serializers.CharField(
help_text="The CPU architecture which the binaries in this image are built to run on.",
required=False,
default=None,
)
os = serializers.CharField(
help_text="The name of the operating system which the image is built to run on.",
required=False,
default=None,
)
compressed_image_size = serializers.IntegerField(
help_text="Specifies the sum of the sizes, in bytes, of all compressed layers",
required=False,
default=None,
)

class Meta:
fields = NoArtifactContentSerializer.Meta.fields + (
Expand All @@ -130,6 +145,9 @@ class Meta:
"is_bootable",
"is_flatpak",
"type",
"architecture",
"os",
"compressed_image_size",
)
model = models.Manifest

Expand Down
6 changes: 5 additions & 1 deletion pulp_container/app/tasks/builder.py
Original file line number Diff line number Diff line change
Expand Up @@ -87,11 +87,15 @@ def add_image_from_directory_to_repository(path, repository, tag):

config_blob = get_or_create_blob(manifest_json["config"], manifest, path)
manifest.config_blob = config_blob
manifest.save()
manifest.init_architecture_and_os()

pks_to_add = []
compressed_size = 0
for layer in manifest_json["layers"]:
compressed_size += layer.get("size")
pks_to_add.append(get_or_create_blob(layer, manifest, path).pk)
manifest.compressed_image_size = compressed_size
manifest.save()

pks_to_add.extend([manifest.pk, tag.pk, config_blob.pk])
new_repo_version.add_content(Content.objects.filter(pk__in=pks_to_add))
Expand Down
40 changes: 20 additions & 20 deletions pulp_container/app/tasks/sync_stages.py
Original file line number Diff line number Diff line change
Expand Up @@ -281,6 +281,7 @@ async def resolve_flush(self):
manifest_dc.content.config_blob = await config_blob_dc.resolution()
await sync_to_async(manifest_dc.content.init_labels)()
manifest_dc.content.init_image_nature()
await sync_to_async(manifest_dc.content.init_architecture_and_os)()
for blob_dc in manifest_dc.extra_data["blob_dcs"]:
# Just await here. They will be associated in the post_save hook.
await blob_dc.resolution()
Expand Down Expand Up @@ -334,12 +335,15 @@ async def handle_blobs(self, manifest_dc, content_data):
Handle blobs.
"""
manifest_dc.extra_data["blob_dcs"] = []
compressed_size = 0
for layer in content_data.get("layers") or content_data.get("fsLayers"):
if not self._include_layer(layer):
continue
compressed_size += layer.get("size", 0)
blob_dc = self.create_blob(layer)
manifest_dc.extra_data["blob_dcs"].append(blob_dc)
await self.put(blob_dc)
manifest_dc.content.compressed_image_size = compressed_size
layer = content_data.get("config", None)
if layer:
blob_dc = self.create_blob(layer, deferred_download=False)
Expand Down Expand Up @@ -392,6 +396,8 @@ def create_manifest(self, manifest_data, raw_text_data, media_type, digest=None)
media_type=media_type,
data=raw_text_data,
annotations=manifest_data.get("annotations", {}),
architecture=manifest_data.get("architecture", None),
os=manifest_data.get("os", None),
)

manifest.init_manifest_nature()
Expand Down Expand Up @@ -434,6 +440,8 @@ async def _download_and_instantiate_manifest(self, manifest_url, digest):
media_type=media_type,
data=raw_text_data,
annotations=content_data.get("annotations", {}),
architecture=content_data.get("architecture", None),
os=content_data.get("os", None),
)
return content_data, manifest

Expand Down Expand Up @@ -472,14 +480,11 @@ async def create_listed_manifest(self, manifest_data):
manifest_url, digest
)

platform = {}
p = manifest_data["platform"]
platform["architecture"] = p["architecture"]
platform["os"] = p["os"]
platform["features"] = p.get("features", "")
platform["variant"] = p.get("variant", "")
platform["os.version"] = p.get("os.version", "")
platform["os.features"] = p.get("os.features", "")
# in oci-index spec, platform is an optional field
platform = manifest_data.get("platform", None)
if platform:
manifest.os = platform["os"]
manifest.architecture = platform["architecture"]
man_dc = DeclarativeContent(content=manifest)
return {"manifest_dc": man_dc, "platform": platform, "content_data": content_data}

Expand Down Expand Up @@ -629,19 +634,14 @@ def _post_save(self, batch):
manifest_lists.append(dc.content)
for listed_manifest in dc.extra_data["listed_manifests"]:
manifest_dc = listed_manifest["manifest_dc"]
platform = listed_manifest["platform"]
manifest_list_manifests.append(
ManifestListManifest(
manifest_list=manifest_dc.content,
image_manifest=dc.content,
architecture=platform["architecture"],
os=platform["os"],
features=platform.get("features"),
variant=platform.get("variant"),
os_version=platform.get("os.version"),
os_features=platform.get("os.features"),
)
manifest_list_manifest = ManifestListManifest(
manifest_list=manifest_dc.content,
image_manifest=dc.content,
)
if platform := listed_manifest.get("platform", None):
manifest_list_manifest.set_platform_configs(platform)
manifest_list_manifests.append(manifest_list_manifest)

if blob_manifests:
BlobManifest.objects.bulk_create(blob_manifests, ignore_conflicts=True)
if manifest_list_manifests:
Expand Down
Loading