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