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