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