1#!/usr/bin/env python3 2import requests 3import zipfile 4from io import BytesIO 5from packaging.version import Version, parse 6 7import os 8from collections import OrderedDict, defaultdict 9import shutil 10import json 11 12import xml.etree.ElementTree as ET 13 14VERSION = "DSP8010_2022.2" 15 16WARNING = """/**************************************************************** 17 * READ THIS WARNING FIRST 18 * This is an auto-generated header which contains definitions 19 * for Redfish DMTF defined schemas. 20 * DO NOT modify this registry outside of running the 21 * update_schemas.py script. The definitions contained within 22 * this file are owned by DMTF. Any modifications to these files 23 * should be first pushed to the relevant registry in the DMTF 24 * github organization. 25 ***************************************************************/""" 26 27# To use a new schema, add to list and rerun tool 28include_list = [ 29 "AccountService", 30 "ActionInfo", 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 "Fan", 54 "FanCollection", 55 "IPAddresses", 56 "JsonSchemaFile", 57 "JsonSchemaFileCollection", # redfish/v1/JsonSchemas 58 "LogEntry", 59 "LogEntryCollection", 60 "LogService", 61 "LogServiceCollection", 62 "Manager", 63 "ManagerAccount", 64 "ManagerAccountCollection", 65 "ManagerCollection", 66 "ManagerDiagnosticData", 67 "ManagerNetworkProtocol", 68 "Memory", 69 "MemoryCollection", 70 "Message", 71 "MessageRegistry", 72 "MessageRegistryCollection", 73 "MessageRegistryFile", 74 "MessageRegistryFileCollection", 75 "MetricDefinition", 76 "MetricDefinitionCollection", 77 "MetricReport", 78 "MetricReportCollection", 79 "MetricReportDefinition", 80 "MetricReportDefinitionCollection", 81 "OperatingConfig", 82 "OperatingConfigCollection", 83 "PCIeDevice", 84 "PCIeDeviceCollection", 85 "PCIeFunction", 86 "PCIeFunctionCollection", 87 "PhysicalContext", 88 "PCIeSlots", 89 "Power", 90 "PowerSubsystem", 91 "PowerSupply", 92 "PowerSupplyCollection", 93 "Privileges", # Used in Role 94 "Processor", 95 "ProcessorCollection", 96 "RedfishError", 97 "RedfishExtensions", 98 "Redundancy", 99 "Resource", 100 "Role", 101 "RoleCollection", 102 "Sensor", 103 "SensorCollection", 104 "ServiceRoot", 105 "Session", 106 "SessionCollection", 107 "SessionService", 108 "Settings", 109 "SoftwareInventory", 110 "SoftwareInventoryCollection", 111 "Storage", 112 "StorageCollection", 113 "StorageController", 114 "StorageControllerCollection", 115 "Task", 116 "TaskCollection", 117 "TaskService", 118 "TelemetryService", 119 "Thermal", 120 "ThermalSubsystem", 121 "Triggers", 122 "TriggersCollection", 123 "UpdateService", 124 "VLanNetworkInterfaceCollection", 125 "VLanNetworkInterface", 126 "VirtualMedia", 127 "VirtualMediaCollection", 128 "odata", 129 "odata-v4", 130 "redfish-error", 131 "redfish-payload-annotations", 132 "redfish-schema", 133 "redfish-schema-v1", 134] 135 136SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__)) 137 138proxies = {"https": os.environ.get("https_proxy", None)} 139 140r = requests.get( 141 "https://www.dmtf.org/sites/default/files/standards/documents/" 142 + VERSION 143 + ".zip", 144 proxies=proxies, 145) 146 147r.raise_for_status() 148 149 150static_path = os.path.realpath( 151 os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1") 152) 153 154 155cpp_path = os.path.realpath( 156 os.path.join(SCRIPT_DIR, "..", "redfish-core", "include") 157) 158 159 160schema_path = os.path.join(static_path, "schema") 161json_schema_path = os.path.join(static_path, "JsonSchemas") 162metadata_index_path = os.path.join(static_path, "$metadata", "index.xml") 163 164zipBytesIO = BytesIO(r.content) 165zip_ref = zipfile.ZipFile(zipBytesIO) 166 167 168def version_sort_key(key): 169 """ 170 Method that computes a sort key that zero pads all numbers, such that 171 version sorting like 172 0_2_0 173 0_10_0 174 sorts in the way humans expect. 175 it also does case insensitive comparisons. 176 """ 177 key = str.casefold(key) 178 179 # Decription of this class calls it "Version numbering for anarchists and 180 # software realists.". That seems like exactly what we need here. 181 182 if not any(char.isdigit() for char in key): 183 split_tup = os.path.splitext(key) 184 key = split_tup[0] + ".v0_0_0" + split_tup[1] 185 186 # special case some files that don't seem to follow the naming convention, 187 # and cause sort problems. These need brought up with DMTF TODO(Ed) 188 if key == "odata.4.0.0.json": 189 key = "odata.v4_0_0.json" 190 if key == "redfish-schema.1.0.0.json": 191 key = "redfish-schema.v1_0_0.json" 192 193 return parse(key) 194 195 196# Remove the old files 197 198skip_prefixes = "Oem" 199if os.path.exists(schema_path): 200 files = [ 201 os.path.join(schema_path, f) 202 for f in os.listdir(schema_path) 203 if not f.startswith(skip_prefixes) 204 ] 205 for f in files: 206 os.remove(f) 207if os.path.exists(json_schema_path): 208 files = [ 209 os.path.join(json_schema_path, f) 210 for f in os.listdir(json_schema_path) 211 if not f.startswith(skip_prefixes) 212 ] 213 for f in files: 214 if os.path.isfile(f): 215 os.remove(f) 216 else: 217 shutil.rmtree(f) 218try: 219 os.remove(metadata_index_path) 220except FileNotFoundError: 221 pass 222 223if not os.path.exists(schema_path): 224 os.makedirs(schema_path) 225if not os.path.exists(json_schema_path): 226 os.makedirs(json_schema_path) 227 228csdl_filenames = [] 229json_schema_files = defaultdict(list) 230 231for zip_filepath in zip_ref.namelist(): 232 if zip_filepath.startswith("csdl/") and (zip_filepath != "csdl/"): 233 csdl_filenames.append(os.path.basename(zip_filepath)) 234 elif zip_filepath.startswith("json-schema/"): 235 filename = os.path.basename(zip_filepath) 236 filenamesplit = filename.split(".") 237 # exclude schemas again to save flash space 238 if filenamesplit[0] not in include_list: 239 continue 240 json_schema_files[filenamesplit[0]].append(filename) 241 elif zip_filepath.startswith("openapi/"): 242 pass 243 elif zip_filepath.startswith("dictionaries/"): 244 pass 245 246# sort the json files by version 247for key, value in json_schema_files.items(): 248 value.sort(key=version_sort_key, reverse=True) 249 250# Create a dictionary ordered by schema name 251json_schema_files = OrderedDict( 252 sorted(json_schema_files.items(), key=lambda x: version_sort_key(x[0])) 253) 254 255csdl_filenames.sort(key=version_sort_key) 256with open(metadata_index_path, "w") as metadata_index: 257 258 metadata_index.write('<?xml version="1.0" encoding="UTF-8"?>\n') 259 metadata_index.write( 260 "<edmx:Edmx xmlns:edmx=" 261 '"http://docs.oasis-open.org/odata/ns/edmx"' 262 ' Version="4.0">\n' 263 ) 264 265 for filename in csdl_filenames: 266 # filename looks like Zone_v1.xml 267 filenamesplit = filename.split("_") 268 if filenamesplit[0] not in include_list: 269 print("excluding schema: " + filename) 270 continue 271 272 with open(os.path.join(schema_path, filename), "wb") as schema_out: 273 274 metadata_index.write( 275 ' <edmx:Reference Uri="/redfish/v1/schema/' 276 + filename 277 + '">\n' 278 ) 279 280 content = zip_ref.read(os.path.join("csdl", filename)) 281 content = content.replace(b"\r\n", b"\n") 282 xml_root = ET.fromstring(content) 283 edmx = "{http://docs.oasis-open.org/odata/ns/edmx}" 284 edm = "{http://docs.oasis-open.org/odata/ns/edm}" 285 for edmx_child in xml_root: 286 if edmx_child.tag == edmx + "DataServices": 287 for data_child in edmx_child: 288 if data_child.tag == edm + "Schema": 289 namespace = data_child.attrib["Namespace"] 290 if namespace.startswith("RedfishExtensions"): 291 metadata_index.write( 292 " " 293 '<edmx:Include Namespace="' 294 + namespace 295 + '" Alias="Redfish"/>\n' 296 ) 297 298 else: 299 metadata_index.write( 300 " " 301 '<edmx:Include Namespace="' 302 + namespace 303 + '"/>\n' 304 ) 305 schema_out.write(content) 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/OemAccountService_v1.xml">\n' 352 ) 353 metadata_index.write( 354 ' <edmx:Include Namespace="OemAccountService"/>\n' 355 ) 356 metadata_index.write( 357 ' <edmx:Include Namespace="OemAccountService.v1_0_0"/>\n' 358 ) 359 metadata_index.write(" </edmx:Reference>\n") 360 361 metadata_index.write( 362 ' <edmx:Reference Uri="/redfish/v1/schema/OemSession_v1.xml">\n' 363 ) 364 metadata_index.write(' <edmx:Include Namespace="OemSession"/>\n') 365 metadata_index.write( 366 ' <edmx:Include Namespace="OemSession.v1_0_0"/>\n' 367 ) 368 metadata_index.write(" </edmx:Reference>\n") 369 370 metadata_index.write("</edmx:Edmx>\n") 371 372 373for schema, version in json_schema_files.items(): 374 zip_filepath = os.path.join("json-schema", version[0]) 375 schemadir = os.path.join(json_schema_path, schema) 376 os.makedirs(schemadir) 377 378 with open(os.path.join(schemadir, schema + ".json"), "wb") as schema_file: 379 schema_file.write(zip_ref.read(zip_filepath).replace(b"\r\n", b"\n")) 380 381with open(os.path.join(cpp_path, "schemas.hpp"), "w") as hpp_file: 382 hpp_file.write( 383 "#pragma once\n" 384 "{WARNING}\n" 385 "// clang-format off\n" 386 "#include <array>\n" 387 "\n" 388 "namespace redfish\n" 389 "{{\n" 390 " constexpr std::array schemas {{\n".format(WARNING=WARNING) 391 ) 392 for schema_file in json_schema_files: 393 hpp_file.write(' "{}",\n'.format(schema_file)) 394 395 hpp_file.write(" };\n" "}\n") 396 397zip_ref.close() 398