1#!/usr/bin/env python3 2import os 3import shutil 4import xml.etree.ElementTree as ET 5import zipfile 6from collections import OrderedDict, defaultdict 7from io import BytesIO 8 9import generate_schema_enums 10import requests 11from generate_schema_collections import generate_top_collections 12 13VERSION = "DSP8010_2022.3" 14 15WARNING = """/**************************************************************** 16 * READ THIS WARNING FIRST 17 * This is an auto-generated header which contains definitions 18 * for Redfish DMTF defined schemas. 19 * DO NOT modify this registry outside of running the 20 * update_schemas.py script. The definitions contained within 21 * this file are owned by DMTF. Any modifications to these files 22 * should be first pushed to the relevant registry in the DMTF 23 * github organization. 24 ***************************************************************/""" 25 26# To use a new schema, add to list and rerun tool 27include_list = [ 28 "AccountService", 29 "ActionInfo", 30 "AggregationService", 31 "AggregationSourceCollection", 32 "Assembly", 33 "AttributeRegistry", 34 "Bios", 35 "Cable", 36 "CableCollection", 37 "Certificate", 38 "CertificateCollection", 39 "CertificateLocations", 40 "CertificateService", 41 "Chassis", 42 "ChassisCollection", 43 "ComputerSystem", 44 "ComputerSystemCollection", 45 "Drive", 46 "DriveCollection", 47 "EnvironmentMetrics", 48 "EthernetInterface", 49 "EthernetInterfaceCollection", 50 "Event", 51 "EventDestination", 52 "EventDestinationCollection", 53 "EventService", 54 "FabricAdapter", 55 "FabricAdapterCollection", 56 "Fan", 57 "FanCollection", 58 "IPAddresses", 59 "JsonSchemaFile", 60 "JsonSchemaFileCollection", # redfish/v1/JsonSchemas 61 "LogEntry", 62 "LogEntryCollection", 63 "LogService", 64 "LogServiceCollection", 65 "Manager", 66 "ManagerAccount", 67 "ManagerAccountCollection", 68 "ManagerCollection", 69 "ManagerDiagnosticData", 70 "ManagerNetworkProtocol", 71 "Memory", 72 "MemoryCollection", 73 "Message", 74 "MessageRegistry", 75 "MessageRegistryCollection", 76 "MessageRegistryFile", 77 "MessageRegistryFileCollection", 78 "MetricDefinition", 79 "MetricDefinitionCollection", 80 "MetricReport", 81 "MetricReportCollection", 82 "MetricReportDefinition", 83 "MetricReportDefinitionCollection", 84 "OperatingConfig", 85 "OperatingConfigCollection", 86 "PCIeDevice", 87 "PCIeDeviceCollection", 88 "PCIeFunction", 89 "PCIeFunctionCollection", 90 "PhysicalContext", 91 "PCIeSlots", 92 "Port", 93 "PortCollection", 94 "Power", 95 "PowerSubsystem", 96 "PowerSupply", 97 "PowerSupplyCollection", 98 "Privileges", # Used in Role 99 "Processor", 100 "ProcessorCollection", 101 "RedfishError", 102 "RedfishExtensions", 103 "Redundancy", 104 "Resource", 105 "Role", 106 "RoleCollection", 107 "Sensor", 108 "SensorCollection", 109 "ServiceRoot", 110 "Session", 111 "SessionCollection", 112 "SessionService", 113 "Settings", 114 "SoftwareInventory", 115 "SoftwareInventoryCollection", 116 "Storage", 117 "StorageCollection", 118 "StorageController", 119 "StorageControllerCollection", 120 "Task", 121 "TaskCollection", 122 "TaskService", 123 "TelemetryService", 124 "Thermal", 125 "ThermalMetrics", 126 "ThermalSubsystem", 127 "Triggers", 128 "TriggersCollection", 129 "UpdateService", 130 "VLanNetworkInterfaceCollection", 131 "VLanNetworkInterface", 132 "VirtualMedia", 133 "VirtualMediaCollection", 134 "odata", 135 "odata-v4", 136 "redfish-error", 137 "redfish-payload-annotations", 138 "redfish-schema", 139 "redfish-schema-v1", 140] 141 142SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 143 144proxies = {"https": os.environ.get("https_proxy", None)} 145 146r = requests.get( 147 "https://www.dmtf.org/sites/default/files/standards/documents/" 148 + VERSION 149 + ".zip", 150 proxies=proxies, 151) 152 153r.raise_for_status() 154 155 156static_path = os.path.realpath( 157 os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1") 158) 159 160 161cpp_path = os.path.realpath( 162 os.path.join(SCRIPT_DIR, "..", "redfish-core", "include") 163) 164 165 166schema_path = os.path.join(static_path, "schema") 167json_schema_path = os.path.join(static_path, "JsonSchemas") 168metadata_index_path = os.path.join(static_path, "$metadata", "index.xml") 169 170zipBytesIO = BytesIO(r.content) 171zip_ref = zipfile.ZipFile(zipBytesIO) 172 173 174class SchemaVersion: 175 """ 176 A Python class for sorting Redfish schema versions. Allows sorting Redfish 177 versions in the way humans expect, by comparing version strings as lists 178 (ie 0_2_0 comes before 0_10_0) in the way humans expect. It does case 179 insensitive schema name comparisons 180 """ 181 182 def __init__(self, key): 183 key = str.casefold(key) 184 185 split_tup = key.split(".") 186 self.version_pieces = [split_tup[0]] 187 if len(split_tup) < 2: 188 return 189 version = split_tup[1] 190 191 if version.startswith("v"): 192 version = version[1:] 193 if any(char.isdigit() for char in version): 194 self.version_pieces.extend([int(x) for x in version.split("_")]) 195 196 def __lt__(self, other): 197 return self.version_pieces < other.version_pieces 198 199 200# Remove the old files 201skip_prefixes = ["Oem", "OpenBMC"] 202if os.path.exists(schema_path): 203 files = [ 204 os.path.join(schema_path, f) 205 for f in os.listdir(schema_path) 206 if not any([f.startswith(prefix) for prefix in skip_prefixes]) 207 ] 208 for f in files: 209 os.remove(f) 210if os.path.exists(json_schema_path): 211 files = [ 212 os.path.join(json_schema_path, f) 213 for f in os.listdir(json_schema_path) 214 if not any([f.startswith(prefix) for prefix in skip_prefixes]) 215 ] 216 for f in files: 217 if os.path.isfile(f): 218 os.remove(f) 219 else: 220 shutil.rmtree(f) 221try: 222 os.remove(metadata_index_path) 223except FileNotFoundError: 224 pass 225 226if not os.path.exists(schema_path): 227 os.makedirs(schema_path) 228if not os.path.exists(json_schema_path): 229 os.makedirs(json_schema_path) 230 231csdl_filenames = [] 232json_schema_files = defaultdict(list) 233 234for zip_file in zip_ref.infolist(): 235 if zip_file.is_dir(): 236 continue 237 if zip_file.filename.startswith("csdl/"): 238 csdl_filenames.append(os.path.basename(zip_file.filename)) 239 elif zip_file.filename.startswith("json-schema/"): 240 filename = os.path.basename(zip_file.filename) 241 filenamesplit = filename.split(".") 242 # exclude schemas again to save flash space 243 if filenamesplit[0] not in include_list: 244 continue 245 json_schema_files[filenamesplit[0]].append(filename) 246 elif zip_file.filename.startswith("openapi/"): 247 pass 248 elif zip_file.filename.startswith("dictionaries/"): 249 pass 250 251# sort the json files by version 252for key, value in json_schema_files.items(): 253 value.sort(key=SchemaVersion, reverse=True) 254 255# Create a dictionary ordered by schema name 256json_schema_files = OrderedDict( 257 sorted(json_schema_files.items(), key=lambda x: SchemaVersion(x[0])) 258) 259 260csdl_filenames.sort(key=SchemaVersion) 261with open(metadata_index_path, "w") as metadata_index: 262 metadata_index.write('<?xml version="1.0" encoding="UTF-8"?>\n') 263 metadata_index.write( 264 "<edmx:Edmx xmlns:edmx=" 265 '"http://docs.oasis-open.org/odata/ns/edmx"' 266 ' Version="4.0">\n' 267 ) 268 269 for filename in csdl_filenames: 270 # filename looks like Zone_v1.xml 271 with open(os.path.join(schema_path, filename), "wb") as schema_out: 272 content = zip_ref.read(os.path.join("csdl", filename)) 273 content = content.replace(b"\r\n", b"\n") 274 275 schema_out.write(content) 276 277 filenamesplit = filename.split("_") 278 if filenamesplit[0] not in include_list: 279 continue 280 metadata_index.write( 281 ' <edmx:Reference Uri="/redfish/v1/schema/' 282 + filename 283 + '">\n' 284 ) 285 286 xml_root = ET.fromstring(content) 287 edmx = "{http://docs.oasis-open.org/odata/ns/edmx}" 288 edm = "{http://docs.oasis-open.org/odata/ns/edm}" 289 for edmx_child in xml_root: 290 if edmx_child.tag == edmx + "DataServices": 291 for data_child in edmx_child: 292 if data_child.tag == edm + "Schema": 293 namespace = data_child.attrib["Namespace"] 294 if namespace.startswith("RedfishExtensions"): 295 metadata_index.write( 296 ' <edmx:Include Namespace="' 297 + namespace 298 + '" Alias="Redfish"/>\n' 299 ) 300 301 else: 302 metadata_index.write( 303 ' <edmx:Include Namespace="' 304 + namespace 305 + '"/>\n' 306 ) 307 metadata_index.write(" </edmx:Reference>\n") 308 309 metadata_index.write( 310 " <edmx:DataServices>\n" 311 " <Schema " 312 'xmlns="http://docs.oasis-open.org/odata/ns/edm" ' 313 'Namespace="Service">\n' 314 ' <EntityContainer Name="Service" ' 315 'Extends="ServiceRoot.v1_0_0.ServiceContainer"/>\n' 316 " </Schema>\n" 317 " </edmx:DataServices>\n" 318 ) 319 # TODO:Issue#32 There's a bug in the script that currently deletes this 320 # schema (because it's an OEM schema). Because it's the only six, and we 321 # don't update schemas very often, we just manually fix it. Need a 322 # permanent fix to the script. 323 metadata_index.write( 324 ' <edmx:Reference Uri="/redfish/v1/schema/OemManager_v1.xml">\n' 325 ) 326 metadata_index.write(' <edmx:Include Namespace="OemManager"/>\n') 327 metadata_index.write(" </edmx:Reference>\n") 328 329 metadata_index.write( 330 ' <edmx:Reference Uri="' 331 '/redfish/v1/schema/OemComputerSystem_v1.xml">\n' 332 ) 333 metadata_index.write( 334 ' <edmx:Include Namespace="OemComputerSystem"/>\n' 335 ) 336 metadata_index.write(" </edmx:Reference>\n") 337 338 metadata_index.write( 339 ' <edmx:Reference Uri="' 340 '/redfish/v1/schema/OemVirtualMedia_v1.xml">\n' 341 ) 342 metadata_index.write( 343 ' <edmx:Include Namespace="OemVirtualMedia"/>\n' 344 ) 345 metadata_index.write( 346 ' <edmx:Include Namespace="OemVirtualMedia.v1_0_0"/>\n' 347 ) 348 metadata_index.write(" </edmx:Reference>\n") 349 350 metadata_index.write( 351 ' <edmx:Reference Uri="' 352 '/redfish/v1/schema/OpenBMCAccountService_v1.xml">\n' 353 ) 354 metadata_index.write( 355 ' <edmx:Include Namespace="OpenBMCAccountService"/>\n' 356 ) 357 metadata_index.write( 358 ' <edmx:Include Namespace="OpenBMCAccountService.v1_0_0"/>\n' 359 ) 360 metadata_index.write(" </edmx:Reference>\n") 361 362 metadata_index.write("</edmx:Edmx>\n") 363 364 365for schema, version in json_schema_files.items(): 366 zip_filepath = os.path.join("json-schema", version[0]) 367 schemadir = os.path.join(json_schema_path, schema) 368 os.makedirs(schemadir) 369 370 with open(os.path.join(schemadir, schema + ".json"), "wb") as schema_file: 371 schema_file.write(zip_ref.read(zip_filepath).replace(b"\r\n", b"\n")) 372 373with open(os.path.join(cpp_path, "schemas.hpp"), "w") as hpp_file: 374 hpp_file.write( 375 "#pragma once\n" 376 "{WARNING}\n" 377 "// clang-format off\n" 378 "#include <array>\n" 379 "\n" 380 "namespace redfish\n" 381 "{{\n" 382 " constexpr std::array schemas {{\n".format(WARNING=WARNING) 383 ) 384 for schema_file in json_schema_files: 385 hpp_file.write(' "{}",\n'.format(schema_file)) 386 387 hpp_file.write(" };\n}\n") 388 389zip_ref.close() 390 391generate_schema_enums.main() 392generate_top_collections() 393 394# Now delete the xml schema files we aren't supporting 395if os.path.exists(schema_path): 396 files = [ 397 os.path.join(schema_path, f) 398 for f in os.listdir(schema_path) 399 if not any([f.startswith(prefix) for prefix in skip_prefixes]) 400 ] 401 for filename in files: 402 # filename will include the absolute path 403 filenamesplit = filename.split("/") 404 name = filenamesplit.pop() 405 namesplit = name.split("_") 406 if namesplit[0] not in include_list: 407 print("excluding schema: " + filename) 408 os.remove(filename) 409