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