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