xref: /openbmc/bmcweb/scripts/update_schemas.py (revision 079360ae)
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
11
12VERSION = "DSP8010_2022.2"
13
14WARNING = """/****************************************************************
15 *                 READ THIS WARNING FIRST
16 * This is an auto-generated header which contains definitions
17 * for Redfish DMTF defined schemas.
18 * DO NOT modify this registry outside of running the
19 * update_schemas.py script.  The definitions contained within
20 * this file are owned by DMTF.  Any modifications to these files
21 * should be first pushed to the relevant registry in the DMTF
22 * github organization.
23 ***************************************************************/"""
24
25# To use a new schema, add to list and rerun tool
26include_list = [
27    "AccountService",
28    "ActionInfo",
29    "Assembly",
30    "AttributeRegistry",
31    "Bios",
32    "Cable",
33    "CableCollection",
34    "Certificate",
35    "CertificateCollection",
36    "CertificateLocations",
37    "CertificateService",
38    "Chassis",
39    "ChassisCollection",
40    "ComputerSystem",
41    "ComputerSystemCollection",
42    "Drive",
43    "DriveCollection",
44    "EnvironmentMetrics",
45    "EthernetInterface",
46    "EthernetInterfaceCollection",
47    "Event",
48    "EventDestination",
49    "EventDestinationCollection",
50    "EventService",
51    "Fan",
52    "FanCollection",
53    "IPAddresses",
54    "JsonSchemaFile",
55    "JsonSchemaFileCollection",  # redfish/v1/JsonSchemas
56    "LogEntry",
57    "LogEntryCollection",
58    "LogService",
59    "LogServiceCollection",
60    "Manager",
61    "ManagerAccount",
62    "ManagerAccountCollection",
63    "ManagerCollection",
64    "ManagerDiagnosticData",
65    "ManagerNetworkProtocol",
66    "Memory",
67    "MemoryCollection",
68    "Message",
69    "MessageRegistry",
70    "MessageRegistryCollection",
71    "MessageRegistryFile",
72    "MessageRegistryFileCollection",
73    "MetricDefinition",
74    "MetricDefinitionCollection",
75    "MetricReport",
76    "MetricReportCollection",
77    "MetricReportDefinition",
78    "MetricReportDefinitionCollection",
79    "OperatingConfig",
80    "OperatingConfigCollection",
81    "PCIeDevice",
82    "PCIeDeviceCollection",
83    "PCIeFunction",
84    "PCIeFunctionCollection",
85    "PhysicalContext",
86    "PCIeSlots",
87    "Power",
88    "PowerSubsystem",
89    "PowerSupply",
90    "PowerSupplyCollection",
91    "Privileges",  # Used in Role
92    "Processor",
93    "ProcessorCollection",
94    "RedfishError",
95    "RedfishExtensions",
96    "Redundancy",
97    "Resource",
98    "Role",
99    "RoleCollection",
100    "Sensor",
101    "SensorCollection",
102    "ServiceRoot",
103    "Session",
104    "SessionCollection",
105    "SessionService",
106    "Settings",
107    "SoftwareInventory",
108    "SoftwareInventoryCollection",
109    "Storage",
110    "StorageCollection",
111    "StorageController",
112    "StorageControllerCollection",
113    "Task",
114    "TaskCollection",
115    "TaskService",
116    "TelemetryService",
117    "Thermal",
118    "ThermalMetrics",
119    "ThermalSubsystem",
120    "Triggers",
121    "TriggersCollection",
122    "UpdateService",
123    "VLanNetworkInterfaceCollection",
124    "VLanNetworkInterface",
125    "VirtualMedia",
126    "VirtualMediaCollection",
127    "odata",
128    "odata-v4",
129    "redfish-error",
130    "redfish-payload-annotations",
131    "redfish-schema",
132    "redfish-schema-v1",
133]
134
135SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
136
137proxies = {"https": os.environ.get("https_proxy", None)}
138
139r = requests.get(
140    "https://www.dmtf.org/sites/default/files/standards/documents/"
141    + VERSION
142    + ".zip",
143    proxies=proxies,
144)
145
146r.raise_for_status()
147
148
149static_path = os.path.realpath(
150    os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1")
151)
152
153
154cpp_path = os.path.realpath(
155    os.path.join(SCRIPT_DIR, "..", "redfish-core", "include")
156)
157
158
159schema_path = os.path.join(static_path, "schema")
160json_schema_path = os.path.join(static_path, "JsonSchemas")
161metadata_index_path = os.path.join(static_path, "$metadata", "index.xml")
162
163zipBytesIO = BytesIO(r.content)
164zip_ref = zipfile.ZipFile(zipBytesIO)
165
166
167class SchemaVersion:
168    """
169    A Python class for sorting Redfish schema versions.  Allows sorting Redfish
170    versions in the way humans expect, by comparing version strings as lists
171    (ie 0_2_0 comes before 0_10_0) in the way humans expect.  It does case
172    insensitive schema name comparisons
173    """
174
175    def __init__(self, key):
176        key = str.casefold(key)
177
178        split_tup = key.split(".")
179        self.version_pieces = [split_tup[0]]
180        if len(split_tup) < 2:
181            return
182        version = split_tup[1]
183
184        if version.startswith("v"):
185            version = version[1:]
186        if any(char.isdigit() for char in version):
187            self.version_pieces.extend([int(x) for x in version.split("_")])
188
189    def __lt__(self, other):
190        return self.version_pieces < other.version_pieces
191
192
193# Remove the old files
194skip_prefixes = "Oem"
195if os.path.exists(schema_path):
196    files = [
197        os.path.join(schema_path, f)
198        for f in os.listdir(schema_path)
199        if not f.startswith(skip_prefixes)
200    ]
201    for f in files:
202        os.remove(f)
203if os.path.exists(json_schema_path):
204    files = [
205        os.path.join(json_schema_path, f)
206        for f in os.listdir(json_schema_path)
207        if not f.startswith(skip_prefixes)
208    ]
209    for f in files:
210        if os.path.isfile(f):
211            os.remove(f)
212        else:
213            shutil.rmtree(f)
214try:
215    os.remove(metadata_index_path)
216except FileNotFoundError:
217    pass
218
219if not os.path.exists(schema_path):
220    os.makedirs(schema_path)
221if not os.path.exists(json_schema_path):
222    os.makedirs(json_schema_path)
223
224csdl_filenames = []
225json_schema_files = defaultdict(list)
226
227for zip_file in zip_ref.infolist():
228    if zip_file.is_dir():
229        continue
230    if zip_file.filename.startswith("csdl/"):
231        csdl_filenames.append(os.path.basename(zip_file.filename))
232    elif zip_file.filename.startswith("json-schema/"):
233        filename = os.path.basename(zip_file.filename)
234        filenamesplit = filename.split(".")
235        # exclude schemas again to save flash space
236        if filenamesplit[0] not in include_list:
237            continue
238        json_schema_files[filenamesplit[0]].append(filename)
239    elif zip_file.filename.startswith("openapi/"):
240        pass
241    elif zip_file.filename.startswith("dictionaries/"):
242        pass
243
244# sort the json files by version
245for key, value in json_schema_files.items():
246    value.sort(key=SchemaVersion, reverse=True)
247
248# Create a dictionary ordered by schema name
249json_schema_files = OrderedDict(
250    sorted(json_schema_files.items(), key=lambda x: SchemaVersion(x[0]))
251)
252
253csdl_filenames.sort(key=SchemaVersion)
254with open(metadata_index_path, "w") as metadata_index:
255    metadata_index.write('<?xml version="1.0" encoding="UTF-8"?>\n')
256    metadata_index.write(
257        "<edmx:Edmx xmlns:edmx="
258        '"http://docs.oasis-open.org/odata/ns/edmx"'
259        ' Version="4.0">\n'
260    )
261
262    for filename in csdl_filenames:
263        # filename looks like Zone_v1.xml
264        filenamesplit = filename.split("_")
265        if filenamesplit[0] not in include_list:
266            print("excluding schema: " + filename)
267            continue
268
269        with open(os.path.join(schema_path, filename), "wb") as schema_out:
270            metadata_index.write(
271                '    <edmx:Reference Uri="/redfish/v1/schema/'
272                + filename
273                + '">\n'
274            )
275
276            content = zip_ref.read(os.path.join("csdl", filename))
277            content = content.replace(b"\r\n", b"\n")
278            xml_root = ET.fromstring(content)
279            edmx = "{http://docs.oasis-open.org/odata/ns/edmx}"
280            edm = "{http://docs.oasis-open.org/odata/ns/edm}"
281            for edmx_child in xml_root:
282                if edmx_child.tag == edmx + "DataServices":
283                    for data_child in edmx_child:
284                        if data_child.tag == edm + "Schema":
285                            namespace = data_child.attrib["Namespace"]
286                            if namespace.startswith("RedfishExtensions"):
287                                metadata_index.write(
288                                    '        <edmx:Include Namespace="'
289                                    + namespace
290                                    + '"  Alias="Redfish"/>\n'
291                                )
292
293                            else:
294                                metadata_index.write(
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        "#include <array>\n"
381        "\n"
382        "namespace redfish\n"
383        "{{\n"
384        "    constexpr std::array schemas {{\n".format(WARNING=WARNING)
385    )
386    for schema_file in json_schema_files:
387        hpp_file.write('        "{}",\n'.format(schema_file))
388
389    hpp_file.write("    };\n}\n")
390
391zip_ref.close()
392
393generate_schema_enums.main()
394