@@ -7,6 +7,7 @@ import json
77import sys
88import uuid
99import re
10+ import os
1011
1112
1213VALID_ROLES = ["boot" , "system" ]
@@ -41,55 +42,59 @@ def pmap_version(data):
4142 sys .exit (1 )
4243
4344
44- # Top level PMAP validator
45- def validate (data ):
46- major , minor , patch = pmap_version (data )
47- # TODO
48- return major , minor , patch
49-
50-
51- # Validates a static object and returns mandatory keys
52- def chk_static (data ):
53- role = data .get ("role" )
54-
55- # role: (mandatory, string)
56- if not role :
57- sys .stderr .write ("Error: role is mandatory in a static object.\n " )
58- sys .exit (1 )
45+ def _load_validator (schema_path ):
46+ try :
47+ from jsonschema import Draft7Validator
48+ except ImportError :
49+ sys .stderr .write ("Error: jsonschema not installed.\n " )
50+ sys .exit (2 )
5951
60- if role not in VALID_ROLES :
61- sys .stderr .write (f"Error: Invalid 'role': '{ role } '. Must be one of { VALID_ROLES } .\n " )
52+ try :
53+ with open (schema_path , "r" , encoding = "utf-8" ) as f :
54+ schema = json .load (f )
55+ Draft7Validator .check_schema (schema )
56+ return Draft7Validator (schema )
57+ except Exception as e :
58+ sys .stderr .write (f"Error: failed to load schema '{ schema_path } ': { e } \n " )
6259 sys .exit (1 )
6360
64- # id: (optional, string)
65- if "id" in data :
66- id_val = data .get ("id" )
67- if not isinstance (id_val , str ):
68- sys .stderr .write ("Error: id is not a string.\n " )
69- sys .exit (1 )
7061
71- # uuid: (optional, valid UUID string); allow placeholders like <FOO>
72- if "uuid" in data :
73- uuid_val = data .get ("uuid" )
74- if not isinstance (uuid_val , str ):
75- sys .stderr .write ("Error: uuid is not a string.\n " )
76- sys .exit (1 )
77- # Skip strict validation for obvious placeholders
78- if "<" in uuid_val or ">" in uuid_val :
79- pass
62+ # Top level PMAP validator (returns parsed version on success)
63+ def validate (data , schema_path = None ):
64+ # Resolve schema path: explicit > alongside script > skip schema
65+ validator = None
66+ if schema_path is None :
67+ # Try default schema next to this script
68+ default_schema = os .path .join (os .path .dirname (os .path .abspath (__file__ )),
69+ "provisionmap.schema.json" )
70+ if os .path .isfile (default_schema ):
71+ schema_path = default_schema
72+ if schema_path :
73+ validator = _load_validator (schema_path )
74+
75+ if validator is not None :
76+ # Always validate only the provisionmap subtree
77+ if isinstance (data , list ):
78+ pmap = data
8079 else :
81- try :
82- uuid .UUID (uuid_val )
83- except ValueError :
84- if (re .match (r'^[0-9a-f]{8}$' , uuid_val , re .IGNORECASE ) or
85- re .match (r'^[0-9a-f]{4}-[0-9a-f]{4}$' , uuid_val , re .IGNORECASE )):
86- pass # Accept as valid VFAT UUID (label)
80+ pmap = get_key (data , "layout.provisionmap" )
81+ if pmap is None :
82+ sys .stderr .write ("Error: layout.provisionmap not found in JSON.\n " )
83+ sys .exit (1 )
84+ doc = {"layout" : {"provisionmap" : pmap }}
85+
86+ errors = sorted (validator .iter_errors (doc ), key = lambda e : list (e .path ))
87+ if errors :
88+ sys .stderr .write (f"Error: provisionmap schema validation failed ({ len (errors )} errors)\n " )
89+ for e in errors :
90+ path = "/" .join (str (p ) for p in e .path )
91+ if path :
92+ sys .stderr .write (f" at $.{ path } : { e .message } \n " )
8793 else :
88- sys .stderr .write (f"Error: uuid is invalid: ' { uuid_val } '. \n " )
89- sys .exit (1 )
94+ sys .stderr .write (f" at $: { e . message } \n " )
95+ sys .exit (1 )
9096
91- # Return mandatory
92- return role
97+ return pmap_version (data )
9398
9499
95100"""
@@ -144,7 +149,7 @@ def slotvars(data):
144149 static = part .get ("static" )
145150 if static is None :
146151 continue
147- role = chk_static ( static )
152+ role = static [ "role" ]
148153 idx = next_mapper_index (mname )
149154 triplets [(slot , role )] = f"mapper:{ mname } :{ idx } "
150155 continue
@@ -164,7 +169,7 @@ def slotvars(data):
164169 static = part .get ("static" )
165170 if static is None :
166171 continue
167- role = chk_static ( static )
172+ role = static [ "role" ]
168173 idx = next_mapper_index (mname )
169174 triplets [(slot , role )] = f"mapper:{ mname } :{ idx } "
170175
@@ -175,7 +180,7 @@ def slotvars(data):
175180 static = part .get ("static" )
176181 if static is None :
177182 continue
178- role = chk_static ( static )
183+ role = static [ "role" ]
179184 triplets [(slot , role )] = f"::{ physical_part_index } "
180185
181186 continue
@@ -223,25 +228,32 @@ def get_key(data, key_path, default=None):
223228
224229if __name__ == '__main__' :
225230 parser = argparse .ArgumentParser (
226- description = 'PMAP helper ' )
231+ description = 'IDP Map File Utility ' )
227232
228233 parser .add_argument ("-f" , "--file" ,
229- help = "Path to PMAP file" ,
234+ help = "Path to Provisioning Map ( PMAP) file" ,
230235 required = True )
231236
237+ parser .add_argument ("--schema" ,
238+ help = "Path to JSON schema" )
239+
232240 parser .add_argument ("-s" , "--slotvars" ,
233241 action = "store_true" ,
234- help = "Print slot.map triplets (a.boot=..., a.system=..., b.boot=..., b.system=...) " )
242+ help = "Print slot.map triplets" )
235243
236244 parser .add_argument ("--get-key" ,
237245 help = "Dot-separated key path to retrieve from PMAP JSON" )
238246
239247 args = parser .parse_args ()
240248
241- with open (args .file ) as f :
242- data = json .load (f )
249+ try :
250+ with open (args .file ) as f :
251+ data = json .load (f )
252+ except Exception as e :
253+ sys .stderr .write (f"Error: invalid JSON: { e } \n " )
254+ sys .exit (1 )
243255
244- major , minor , patch = validate (data )
256+ major , minor , patch = validate (data , args . schema )
245257
246258 if args .get_key :
247259 value = get_key (data , args .get_key )
@@ -251,8 +263,7 @@ if __name__ == '__main__':
251263 print (value )
252264 sys .exit (0 )
253265
254- major , minor , patch = validate (data )
255-
256266 if args .slotvars :
257- slotvars (data )
267+ pmap = data if isinstance (data , list ) else get_key (data , "layout.provisionmap" )
268+ slotvars (pmap )
258269 sys .exit (0 );
0 commit comments