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 Oct 30, 2024
1 parent 23bf3a3 commit ec2d14e
Show file tree
Hide file tree
Showing 14 changed files with 228 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,
),
]
38 changes: 36 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 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
21 changes: 12 additions & 9 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,17 @@ 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.architecture = (platform["architecture"],)
manifest_to_list.os = (platform["os"],)
manifest_to_list.features = (platform.get("features", ""),)
manifest_to_list.variant = (platform.get("variant", ""),)
manifest_to_list.os_version = (platform.get("os.version", ""),)
manifest_to_list.os_features = (platform.get("os.features", ""),)
manifests_to_list.append(manifest_to_list)

models.ManifestListManifest.objects.bulk_create(
Expand Down Expand Up @@ -1300,6 +1300,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 Expand Up @@ -1356,13 +1358,14 @@ def put(self, request, path, pk=None):
return ManifestResponse(manifest, path, request, status=201)

def _init_manifest(self, manifest_digest, media_type, raw_text_data, config_blob=None):
return models.Manifest(
manifest = models.Manifest(
digest=manifest_digest,
schema_version=2,
media_type=media_type,
config_blob=config_blob,
data=raw_text_data,
)
return manifest

def _save_manifest(self, manifest):
try:
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
Loading

0 comments on commit ec2d14e

Please sign in to comment.