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.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 "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 "Protocol", 105 "RedfishError", 106 "RedfishExtensions", 107 "Redundancy", 108 "Resource", 109 "Role", 110 "RoleCollection", 111 "Sensor", 112 "SensorCollection", 113 "ServiceRoot", 114 "Session", 115 "SessionCollection", 116 "SessionService", 117 "Settings", 118 "SoftwareInventory", 119 "SoftwareInventoryCollection", 120 "Storage", 121 "StorageCollection", 122 "StorageController", 123 "StorageControllerCollection", 124 "Task", 125 "TaskCollection", 126 "TaskService", 127 "TelemetryService", 128 "Thermal", 129 "ThermalMetrics", 130 "ThermalSubsystem", 131 "Triggers", 132 "TriggersCollection", 133 "UpdateService", 134 "VirtualMedia", 135 "VirtualMediaCollection", 136 "odata", 137 "odata-v4", 138 "redfish-error", 139 "redfish-payload-annotations", 140 "redfish-schema", 141 "redfish-schema-v1", 142] 143 144# OEM schemas 145oem_schema_names = [ 146 "OemManager", 147 "OemComputerSystem", 148 "OemVirtualMedia", 149 "OpenBMCAccountService", 150] 151 152SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 153 154proxies = {"https": os.environ.get("https_proxy", None)} 155 156r = requests.get( 157 "https://www.dmtf.org/sites/default/files/standards/documents/" 158 + VERSION 159 + ".zip", 160 proxies=proxies, 161) 162 163r.raise_for_status() 164 165 166static_path = os.path.realpath( 167 os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1") 168) 169 170 171cpp_path = os.path.realpath( 172 os.path.join(SCRIPT_DIR, "..", "redfish-core", "include") 173) 174 175 176schema_path = os.path.join(static_path, "schema") 177json_schema_path = os.path.join(static_path, "JsonSchemas") 178metadata_index_path = os.path.join(static_path, "$metadata", "index.xml") 179 180zipBytesIO = BytesIO(r.content) 181zip_ref = zipfile.ZipFile(zipBytesIO) 182 183 184class SchemaVersion: 185 """ 186 A Python class for sorting Redfish schema versions. Allows sorting Redfish 187 versions in the way humans expect, by comparing version strings as lists 188 (ie 0_2_0 comes before 0_10_0) in the way humans expect. It does case 189 insensitive schema name comparisons 190 """ 191 192 def __init__(self, key): 193 key = str.casefold(key) 194 195 split_tup = key.split(".") 196 self.version_pieces = [split_tup[0]] 197 if len(split_tup) < 2: 198 return 199 version = split_tup[1] 200 201 if version.startswith("v"): 202 version = version[1:] 203 if any(char.isdigit() for char in version): 204 self.version_pieces.extend([int(x) for x in version.split("_")]) 205 206 def __lt__(self, other): 207 return self.version_pieces < other.version_pieces 208 209 210# Remove the old files 211skip_prefixes = ["Oem", "OpenBMC"] 212if os.path.exists(schema_path): 213 files = [ 214 os.path.join(schema_path, f) 215 for f in os.listdir(schema_path) 216 if not any([f.startswith(prefix) for prefix in skip_prefixes]) 217 ] 218 for f in files: 219 os.remove(f) 220if os.path.exists(json_schema_path): 221 files = [ 222 os.path.join(json_schema_path, f) 223 for f in os.listdir(json_schema_path) 224 if not any([f.startswith(prefix) for prefix in skip_prefixes]) 225 ] 226 for f in files: 227 if os.path.isfile(f): 228 os.remove(f) 229 else: 230 shutil.rmtree(f) 231try: 232 os.remove(metadata_index_path) 233except FileNotFoundError: 234 pass 235 236if not os.path.exists(schema_path): 237 os.makedirs(schema_path) 238if not os.path.exists(json_schema_path): 239 os.makedirs(json_schema_path) 240 241csdl_filenames = [] 242json_schema_files = defaultdict(list) 243 244for zip_file in zip_ref.infolist(): 245 if zip_file.is_dir(): 246 continue 247 if zip_file.filename.startswith("csdl/"): 248 csdl_filenames.append(os.path.basename(zip_file.filename)) 249 elif zip_file.filename.startswith("json-schema/"): 250 filename = os.path.basename(zip_file.filename) 251 filenamesplit = filename.split(".") 252 # exclude schemas again to save flash space 253 if filenamesplit[0] not in include_list: 254 continue 255 json_schema_files[filenamesplit[0]].append(filename) 256 elif zip_file.filename.startswith("openapi/"): 257 pass 258 elif zip_file.filename.startswith("dictionaries/"): 259 pass 260 261# sort the json files by version 262for key, value in json_schema_files.items(): 263 value.sort(key=SchemaVersion, reverse=True) 264 265# Create a dictionary ordered by schema name 266json_schema_files = OrderedDict( 267 sorted(json_schema_files.items(), key=lambda x: SchemaVersion(x[0])) 268) 269 270csdl_filenames.sort(key=SchemaVersion) 271 272# Create oem filenames - from oem json names 273oem_csdl_filenames = [] 274for filename in oem_schema_names: 275 oem_csdl_filenames.append(filename + "_v1.xml") 276 277# Append Oem csdl files 278csdl_filenames += oem_csdl_filenames 279 280with open(metadata_index_path, "w") as metadata_index: 281 metadata_index.write('<?xml version="1.0" encoding="UTF-8"?>\n') 282 metadata_index.write( 283 "<edmx:Edmx xmlns:edmx=" 284 '"http://docs.oasis-open.org/odata/ns/edmx"' 285 ' Version="4.0">\n' 286 ) 287 288 for filename in csdl_filenames: 289 # filename looks like Zone_v1.xml 290 if filename in oem_csdl_filenames: 291 with open( 292 os.path.join(schema_path, filename), "rb" 293 ) as oem_csdl_in: 294 content = oem_csdl_in.read() 295 content = content.replace(b"\r\n", b"\n") 296 else: 297 with open(os.path.join(schema_path, filename), "wb") as schema_out: 298 content = zip_ref.read(os.path.join("csdl", filename)) 299 content = content.replace(b"\r\n", b"\n") 300 schema_out.write(content) 301 filenamesplit = filename.split("_") 302 if filenamesplit[0] not in include_list: 303 continue 304 305 metadata_index.write( 306 ' <edmx:Reference Uri="/redfish/v1/schema/' + filename + '">\n' 307 ) 308 309 xml_root = ET.fromstring(content) 310 edmx = "{http://docs.oasis-open.org/odata/ns/edmx}" 311 edm = "{http://docs.oasis-open.org/odata/ns/edm}" 312 for edmx_child in xml_root: 313 if edmx_child.tag == edmx + "DataServices": 314 for data_child in edmx_child: 315 if data_child.tag == edm + "Schema": 316 namespace = data_child.attrib["Namespace"] 317 if namespace.startswith("RedfishExtensions"): 318 metadata_index.write( 319 ' <edmx:Include Namespace="' 320 + namespace 321 + '" Alias="Redfish"/>\n' 322 ) 323 324 else: 325 metadata_index.write( 326 ' <edmx:Include Namespace="' 327 + namespace 328 + '"/>\n' 329 ) 330 metadata_index.write(" </edmx:Reference>\n") 331 332 metadata_index.write( 333 " <edmx:DataServices>\n" 334 " <Schema " 335 'xmlns="http://docs.oasis-open.org/odata/ns/edm" ' 336 'Namespace="Service">\n' 337 ' <EntityContainer Name="Service" ' 338 'Extends="ServiceRoot.v1_0_0.ServiceContainer"/>\n' 339 " </Schema>\n" 340 " </edmx:DataServices>\n" 341 ) 342 metadata_index.write("</edmx:Edmx>\n") 343 344 345for schema, version in json_schema_files.items(): 346 zip_filepath = os.path.join("json-schema", version[0]) 347 schemadir = os.path.join(json_schema_path, schema) 348 os.makedirs(schemadir) 349 350 with open(os.path.join(schemadir, schema + ".json"), "wb") as schema_file: 351 schema_file.write(zip_ref.read(zip_filepath).replace(b"\r\n", b"\n")) 352 353with open(os.path.join(cpp_path, "schemas.hpp"), "w") as hpp_file: 354 hpp_file.write( 355 "#pragma once\n" 356 "{WARNING}\n" 357 "// clang-format off\n" 358 "#include <array>\n" 359 "#include <string_view>\n" 360 "\n" 361 "namespace redfish\n" 362 "{{\n" 363 " constexpr std::array<std::string_view,{SIZE}> schemas {{\n".format( 364 WARNING=WARNING, 365 SIZE=len(json_schema_files) + len(oem_schema_names), 366 ) 367 ) 368 for schema_file in json_schema_files: 369 hpp_file.write(' "{}",\n'.format(schema_file)) 370 371 for schema_file in oem_schema_names: 372 hpp_file.write(' "{}",\n'.format(schema_file)) 373 374 hpp_file.write(" };\n}\n") 375 376zip_ref.close() 377 378generate_schema_enums.main() 379generate_top_collections() 380 381# Now delete the xml schema files we aren't supporting 382if os.path.exists(schema_path): 383 files = [ 384 os.path.join(schema_path, f) 385 for f in os.listdir(schema_path) 386 if not any([f.startswith(prefix) for prefix in skip_prefixes]) 387 ] 388 for filename in files: 389 # filename will include the absolute path 390 filenamesplit = filename.split("/") 391 name = filenamesplit.pop() 392 namesplit = name.split("_") 393 if namesplit[0] not in include_list: 394 print("excluding schema: " + filename) 395 os.remove(filename) 396