|
1 | 1 | # Generated by Django 4.2.16 on 2024-10-21 19:14 |
2 | 2 |
|
| 3 | +import json |
3 | 4 | from django.db import migrations, models |
4 | 5 |
|
| 6 | +from pulp_container.constants import MEDIA_TYPE, MANIFEST_TYPE, MANIFEST_MEDIA_TYPES, COSIGN_MEDIA_TYPES, COSIGN_MEDIA_TYPES_MANIFEST_TYPE_MAPPING |
| 7 | + |
| 8 | + |
| 9 | +def migrate_manifest_nature(apps, schema_editor): |
| 10 | + """ |
| 11 | + Populate the `type` field, and also the `is_bootable` and `is_flatpak` fields even though they are deprecated. |
| 12 | + """ |
| 13 | + Manifest = apps.get_model("container", "Manifest") |
| 14 | + to_update = [] |
| 15 | + # First, update the manifests that are not manifest lists or index manifests. |
| 16 | + manifests = Manifest.objects.filter(data__isnull=False).exclude(media_type__in=[MEDIA_TYPE.MANIFEST_LIST, MEDIA_TYPE.INDEX_OCI]) |
| 17 | + for manifest in manifests.iterator(): |
| 18 | + if init_manifest_nature(manifest): |
| 19 | + to_update.append(manifest) |
| 20 | + if len(to_update) > 1000: |
| 21 | + Manifest.objects.bulk_update(to_update, ["type", "is_bootable", "is_flatpak"]) |
| 22 | + to_update.clear() |
| 23 | + if to_update: |
| 24 | + Manifest.objects.bulk_update(to_update, ["type", "is_bootable", "is_flatpak"]) |
| 25 | + to_update.clear() |
| 26 | + # Then, update the manifest lists and index manifests. |
| 27 | + manifest_lists = Manifest.objects.filter( |
| 28 | + data__isnull=False, media_type__in=[MEDIA_TYPE.MANIFEST_LIST, MEDIA_TYPE.INDEX_OCI] |
| 29 | + ).prefetch_related("listed_manifests") |
| 30 | + for manifest_list in manifest_lists.iterator(): |
| 31 | + if init_manifest_list_nature(manifest_list): |
| 32 | + to_update.append(manifest_list) |
| 33 | + if len(to_update) > 1000: |
| 34 | + Manifest.objects.bulk_update(to_update, ["type", "is_bootable", "is_flatpak"]) |
| 35 | + to_update.clear() |
| 36 | + if to_update: |
| 37 | + Manifest.objects.bulk_update(to_update, ["type", "is_bootable", "is_flatpak"]) |
| 38 | + to_update.clear() |
| 39 | + |
| 40 | +def init_image_nature(manifest): |
| 41 | + manifest.json_manifest = json.loads(manifest.data) |
| 42 | + if manifest.media_type in [MEDIA_TYPE.INDEX_OCI, MEDIA_TYPE.MANIFEST_LIST]: |
| 43 | + return manifest.init_manifest_list_nature() |
| 44 | + else: |
| 45 | + return manifest.init_manifest_nature() |
| 46 | + |
| 47 | +def init_manifest_list_nature(manifest_list): |
| 48 | + updated_type = False |
| 49 | + if not manifest_list.type: |
| 50 | + manifest_list.type = MANIFEST_TYPE.INDEX |
| 51 | + updated_type = True |
| 52 | + |
| 53 | + for manifest in manifest_list.listed_manifests.all(): |
| 54 | + # it suffices just to have a single manifest of a specific nature; |
| 55 | + # there is no case where the manifest is both bootable and flatpak-based |
| 56 | + if manifest.type == MANIFEST_TYPE.BOOTABLE: |
| 57 | + manifest_list.is_bootable = True |
| 58 | + return True |
| 59 | + elif manifest.type == MANIFEST_TYPE.FLATPAK: |
| 60 | + manifest_list.is_flatpak = True |
| 61 | + return True |
| 62 | + |
| 63 | + return updated_type |
| 64 | + |
| 65 | +def init_manifest_nature(manifest): |
| 66 | + if is_bootable_image(manifest): |
| 67 | + # DEPRECATED: is_bootable is deprecated and will be removed in a future release. |
| 68 | + manifest.is_bootable = True |
| 69 | + manifest.type = MANIFEST_TYPE.BOOTABLE |
| 70 | + return True |
| 71 | + elif is_flatpak_image(manifest): |
| 72 | + # DEPRECATED: is_flatpak is deprecated and will be removed in a future release. |
| 73 | + manifest.is_flatpak = True |
| 74 | + manifest.type = MANIFEST_TYPE.FLATPAK |
| 75 | + return True |
| 76 | + elif is_helm_chart(manifest): |
| 77 | + manifest.type = MANIFEST_TYPE.HELM |
| 78 | + return True |
| 79 | + elif media_type := is_cosign(manifest): |
| 80 | + manifest.type = get_cosign_type(media_type) |
| 81 | + return True |
| 82 | + elif is_artifact(manifest): |
| 83 | + manifest.type = MANIFEST_TYPE.ARTIFACT |
| 84 | + return True |
| 85 | + elif is_manifest_image(manifest): |
| 86 | + manifest.type = MANIFEST_TYPE.IMAGE |
| 87 | + return True |
| 88 | + |
| 89 | + return False |
| 90 | + |
| 91 | +def is_bootable_image(manifest): |
| 92 | + return ( |
| 93 | + manifest.annotations.get("containers.bootc") == "1" |
| 94 | + or manifest.labels.get("containers.bootc") == "1" |
| 95 | + ) |
| 96 | + |
| 97 | +def is_flatpak_image(manifest): |
| 98 | + return True if manifest.labels.get("org.flatpak.ref") else False |
| 99 | + |
| 100 | +def is_manifest_image(manifest): |
| 101 | + return manifest.media_type in MANIFEST_MEDIA_TYPES.IMAGE |
| 102 | + |
| 103 | +def is_cosign(manifest): |
| 104 | + try: |
| 105 | + # layers is not a mandatory field |
| 106 | + layers = manifest.json_manifest["layers"] |
| 107 | + except KeyError: |
| 108 | + return False |
| 109 | + |
| 110 | + for layer in layers: |
| 111 | + if layer["mediaType"] in COSIGN_MEDIA_TYPES: |
| 112 | + return layer["mediaType"] |
| 113 | + return False |
| 114 | + |
| 115 | +def get_cosign_type(media_type): |
| 116 | + if media_type in MEDIA_TYPE.COSIGN_SBOM: |
| 117 | + return MANIFEST_TYPE.COSIGN_SBOM |
| 118 | + return COSIGN_MEDIA_TYPES_MANIFEST_TYPE_MAPPING.get(media_type, MANIFEST_TYPE.UNKNOWN) |
| 119 | + |
| 120 | +def is_helm_chart(manifest): |
| 121 | + try: |
| 122 | + return manifest.json_manifest["config"]["mediaType"] == MEDIA_TYPE.CONFIG_BLOB_HELM |
| 123 | + except KeyError: |
| 124 | + return False |
| 125 | + |
| 126 | +def is_artifact(manifest): |
| 127 | + # artifact is valid only for OCI spec |
| 128 | + if manifest.media_type != MEDIA_TYPE.MANIFEST_OCI: |
| 129 | + return False |
| 130 | + |
| 131 | + if manifest.json_manifest.get("artifactType", None): |
| 132 | + return True |
| 133 | + |
| 134 | + manifest_config_media_type = manifest.json_manifest["config"]["mediaType"] |
| 135 | + return ( |
| 136 | + manifest_config_media_type == MEDIA_TYPE.OCI_EMPTY_JSON |
| 137 | + or manifest_config_media_type not in vars(MEDIA_TYPE).values() |
| 138 | + ) |
| 139 | + |
5 | 140 |
|
6 | 141 | class Migration(migrations.Migration): |
7 | 142 |
|
|
0 commit comments