Skip to content

Commit

Permalink
Add os/arch/image_size fields to manifest model
Browse files Browse the repository at this point in the history
closes: #1767
  • Loading branch information
git-hyagi committed Nov 1, 2024
1 parent 23bf3a3 commit c182212
Show file tree
Hide file tree
Showing 14 changed files with 220 additions and 38 deletions.
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,
),
]
47 changes: 45 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,15 @@ class ManifestListManifest(models.Model):
Manifest, related_name="manifest_lists", on_delete=models.CASCADE
)

def set_platform_configs(self, platform):
for key, value in platform.items():
if key == "os.version":
self.os_version = value
elif key == "os.features":
self.os_features = value
else:
setattr(self, key, value)

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()

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

0 comments on commit c182212

Please sign in to comment.