xref: /openbmc/bmcweb/scripts/update_schemas.py (revision 720c9898)
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
26SCRIPT_DIR = os.path.dirname(os.path.realpath(__file__))
27
28proxies = {"https": os.environ.get("https_proxy", None)}
29
30r = requests.get(
31    "https://www.dmtf.org/sites/default/files/standards/documents/"
32    + VERSION
33    + ".zip",
34    proxies=proxies,
35)
36
37r.raise_for_status()
38
39
40static_path = os.path.realpath(
41    os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1")
42)
43
44
45cpp_path = os.path.realpath(
46    os.path.join(SCRIPT_DIR, "..", "redfish-core", "include")
47)
48
49
50schema_path = os.path.join(
51    SCRIPT_DIR, "..", "redfish-core", "schema", "dmtf", "csdl"
52)
53json_schema_path = os.path.join(
54    SCRIPT_DIR, "..", "redfish-core", "schema", "dmtf", "json-schema"
55)
56metadata_index_path = os.path.join(static_path, "$metadata", "index.xml")
57
58zipBytesIO = BytesIO(r.content)
59zip_ref = zipfile.ZipFile(zipBytesIO)
60
61
62class SchemaVersion:
63    """
64    A Python class for sorting Redfish schema versions.  Allows sorting Redfish
65    versions in the way humans expect, by comparing version strings as lists
66    (ie 0_2_0 comes before 0_10_0) in the way humans expect.  It does case
67    insensitive schema name comparisons
68    """
69
70    def __init__(self, key):
71        key = str.casefold(key)
72
73        split_tup = key.split(".")
74        self.version_pieces = [split_tup[0]]
75        if len(split_tup) < 2:
76            return
77        version = split_tup[1]
78
79        if version.startswith("v"):
80            version = version[1:]
81        if any(char.isdigit() for char in version):
82            self.version_pieces.extend([int(x) for x in version.split("_")])
83
84    def __lt__(self, other):
85        return self.version_pieces < other.version_pieces
86
87
88# Remove the old files
89skip_prefixes = ["Oem", "OpenBMC"]
90if os.path.exists(schema_path):
91    files = [
92        os.path.join(schema_path, f)
93        for f in os.listdir(schema_path)
94        if not any([f.startswith(prefix) for prefix in skip_prefixes])
95    ]
96    for f in files:
97        os.remove(f)
98if os.path.exists(json_schema_path):
99    files = [
100        os.path.join(json_schema_path, f)
101        for f in os.listdir(json_schema_path)
102        if not any([f.startswith(prefix) for prefix in skip_prefixes])
103    ]
104    for f in files:
105        if os.path.isfile(f):
106            os.remove(f)
107        else:
108            shutil.rmtree(f)
109try:
110    os.remove(metadata_index_path)
111except FileNotFoundError:
112    pass
113
114if not os.path.exists(schema_path):
115    os.makedirs(schema_path)
116if not os.path.exists(json_schema_path):
117    os.makedirs(json_schema_path)
118
119csdl_filenames = []
120json_schema_files = defaultdict(list)
121
122for zip_file in zip_ref.infolist():
123    if zip_file.is_dir():
124        continue
125    if zip_file.filename.startswith("csdl/"):
126        csdl_filenames.append(os.path.basename(zip_file.filename))
127    elif zip_file.filename.startswith("json-schema/"):
128        filename = os.path.basename(zip_file.filename)
129        filenamesplit = filename.split(".")
130        json_schema_files[filenamesplit[0]].append(filename)
131    elif zip_file.filename.startswith("openapi/"):
132        pass
133    elif zip_file.filename.startswith("dictionaries/"):
134        pass
135
136# sort the json files by version
137for key, value in json_schema_files.items():
138    value.sort(key=SchemaVersion, reverse=True)
139
140# Create a dictionary ordered by schema name
141json_schema_files = OrderedDict(
142    sorted(json_schema_files.items(), key=lambda x: SchemaVersion(x[0]))
143)
144for csdl_file in csdl_filenames:
145    with open(os.path.join(schema_path, csdl_file), "wb") as schema_out:
146        content = zip_ref.read(os.path.join("csdl", csdl_file))
147        content = content.replace(b"\r\n", b"\n")
148        schema_out.write(content)
149
150with open(metadata_index_path, "w") as metadata_index:
151    metadata_index.write('<?xml version="1.0" encoding="UTF-8"?>\n')
152    metadata_index.write(
153        "<edmx:Edmx xmlns:edmx="
154        '"http://docs.oasis-open.org/odata/ns/edmx"'
155        ' Version="4.0">\n'
156    )
157
158    schema_static_dir = os.path.join(
159        SCRIPT_DIR, "..", "static", "redfish", "v1", "schema"
160    )
161    for filename in sorted(os.listdir(schema_static_dir), key=SchemaVersion):
162        if not filename.endswith(".xml"):
163            continue
164
165        metadata_index.write(
166            '    <edmx:Reference Uri="/redfish/v1/schema/' + filename + '">\n'
167        )
168
169        xml_root = ET.parse(
170            os.path.join(schema_static_dir, filename)
171        ).getroot()
172        edmx = "{http://docs.oasis-open.org/odata/ns/edmx}"
173        edm = "{http://docs.oasis-open.org/odata/ns/edm}"
174        for edmx_child in xml_root:
175            if edmx_child.tag == edmx + "DataServices":
176                for data_child in edmx_child:
177                    if data_child.tag == edm + "Schema":
178                        namespace = data_child.attrib["Namespace"]
179                        if namespace.startswith("RedfishExtensions"):
180                            metadata_index.write(
181                                '        <edmx:Include Namespace="'
182                                + namespace
183                                + '"  Alias="Redfish"/>\n'
184                            )
185
186                        else:
187                            metadata_index.write(
188                                '        <edmx:Include Namespace="'
189                                + namespace
190                                + '"/>\n'
191                            )
192        metadata_index.write("    </edmx:Reference>\n")
193
194    metadata_index.write(
195        "    <edmx:DataServices>\n"
196        "        <Schema "
197        'xmlns="http://docs.oasis-open.org/odata/ns/edm" '
198        'Namespace="Service">\n'
199        '            <EntityContainer Name="Service" '
200        'Extends="ServiceRoot.v1_0_0.ServiceContainer"/>\n'
201        "        </Schema>\n"
202        "    </edmx:DataServices>\n"
203    )
204    metadata_index.write("</edmx:Edmx>\n")
205
206
207for schema, version in json_schema_files.items():
208    zip_filepath = os.path.join("json-schema", version[0])
209
210    with open(os.path.join(json_schema_path, version[0]), "wb") as schema_file:
211        schema_file.write(zip_ref.read(zip_filepath).replace(b"\r\n", b"\n"))
212
213with open(os.path.join(cpp_path, "schemas.hpp"), "w") as hpp_file:
214    schemas = []
215    for root, dirs, files in os.walk(
216        os.path.join(SCRIPT_DIR, "..", "static", "redfish", "v1", "schema")
217    ):
218        for csdl_file in sorted(files, key=SchemaVersion):
219            if csdl_file.endswith(".xml"):
220                schemas.append(csdl_file.replace("_v1.xml", ""))
221    hpp_file.write(
222        "#pragma once\n"
223        "{WARNING}\n"
224        "// clang-format off\n"
225        "#include <array>\n"
226        "#include <string_view>\n"
227        "\n"
228        "namespace redfish\n"
229        "{{\n"
230        "    constexpr std::array<std::string_view,{SIZE}> schemas {{\n".format(
231            WARNING=WARNING,
232            SIZE=len(schemas),
233        )
234    )
235    for schema in schemas:
236        hpp_file.write('        "{}",\n'.format(schema))
237
238    hpp_file.write("    };\n}\n")
239
240zip_ref.close()
241
242generate_schema_enums.main()
243generate_top_collections()
244